1 //! Structs for reading a ZIP archive
2
3 use crc32::Crc32Reader;
4 use compression::CompressionMethod;
5 use spec;
6 use result::{ZipResult, ZipError};
7 use std::io;
8 use std::io::prelude::*;
9 use std::collections::HashMap;
10 use std::borrow::Cow;
11
12 use podio::{ReadPodExt, LittleEndian};
13 use types::{ZipFileData, System};
14 use cp437::FromCp437;
15 use msdos_time::{TmMsDosExt, MsDosDateTime};
16
17 #[cfg(feature = "flate2")]
18 use flate2;
19 #[cfg(feature = "flate2")]
20 use flate2::read::DeflateDecoder;
21
22 #[cfg(feature = "bzip2")]
23 use bzip2::read::BzDecoder;
24
25 mod ffi {
26 pub const S_IFDIR: u32 = 0o0040000;
27 pub const S_IFREG: u32 = 0o0100000;
28 }
29
30 const TM_1980_01_01 : ::time::Tm = ::time::Tm {
31 tm_sec: 0,
32 tm_min: 0,
33 tm_hour: 0,
34 tm_mday: 1,
35 tm_mon: 0,
36 tm_year: 80,
37 tm_wday: 2,
38 tm_yday: 0,
39 tm_isdst: -1,
40 tm_utcoff: 0,
41 tm_nsec: 0
42 };
43
44 /// Wrapper for reading the contents of a ZIP file.
45 ///
46 /// ```
47 /// fn doit() -> zip::result::ZipResult<()>
48 /// {
49 /// use std::io::prelude::*;
50 ///
51 /// // For demonstration purposes we read from an empty buffer.
52 /// // Normally a File object would be used.
53 /// let buf: &[u8] = &[0u8; 128];
54 /// let mut reader = std::io::Cursor::new(buf);
55 ///
56 /// let mut zip = try!(zip::ZipArchive::new(reader));
57 ///
58 /// for i in 0..zip.len()
59 /// {
60 /// let mut file = zip.by_index(i).unwrap();
61 /// println!("Filename: {}", file.name());
62 /// let first_byte = try!(file.bytes().next().unwrap());
63 /// println!("{}", first_byte);
64 /// }
65 /// Ok(())
66 /// }
67 ///
68 /// println!("Result: {:?}", doit());
69 /// ```
70 #[derive(Debug)]
71 pub struct ZipArchive<R: Read + io::Seek>
72 {
73 reader: R,
74 files: Vec<ZipFileData>,
75 names_map: HashMap<String, usize>,
76 offset: u64,
77 comment: Vec<u8>,
78 }
79
80 enum ZipFileReader<'a> {
81 NoReader,
82 Stored(Crc32Reader<io::Take<&'a mut Read>>),
83 #[cfg(feature = "flate2")]
84 Deflated(Crc32Reader<flate2::read::DeflateDecoder<io::Take<&'a mut Read>>>),
85 #[cfg(feature = "bzip2")]
86 Bzip2(Crc32Reader<BzDecoder<io::Take<&'a mut Read>>>),
87 }
88
89 /// A struct for reading a zip file
90 pub struct ZipFile<'a> {
91 data: Cow<'a, ZipFileData>,
92 reader: ZipFileReader<'a>,
93 }
94
unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T>95 fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T>
96 {
97 Err(ZipError::UnsupportedArchive(detail))
98 }
99
100
make_reader<'a>( compression_method: ::compression::CompressionMethod, crc32: u32, reader: io::Take<&'a mut io::Read>) -> ZipResult<ZipFileReader<'a>>101 fn make_reader<'a>(
102 compression_method: ::compression::CompressionMethod,
103 crc32: u32,
104 reader: io::Take<&'a mut io::Read>)
105 -> ZipResult<ZipFileReader<'a>> {
106
107 match compression_method {
108 CompressionMethod::Stored =>
109 {
110 Ok(ZipFileReader::Stored(Crc32Reader::new(
111 reader,
112 crc32)))
113 },
114 #[cfg(feature = "flate2")]
115 CompressionMethod::Deflated =>
116 {
117 let deflate_reader = DeflateDecoder::new(reader);
118 Ok(ZipFileReader::Deflated(Crc32Reader::new(
119 deflate_reader,
120 crc32)))
121 },
122 #[cfg(feature = "bzip2")]
123 CompressionMethod::Bzip2 =>
124 {
125 let bzip2_reader = BzDecoder::new(reader);
126 Ok(ZipFileReader::Bzip2(Crc32Reader::new(
127 bzip2_reader,
128 crc32)))
129 },
130 _ => unsupported_zip_error("Compression method not supported"),
131 }
132 }
133
134 impl<R: Read+io::Seek> ZipArchive<R>
135 {
136 /// Get the directory start offset and number of files. This is done in a
137 /// separate function to ease the control flow design.
get_directory_counts(reader: &mut R, footer: &spec::CentralDirectoryEnd, cde_start_pos: u64) -> ZipResult<(u64, u64, usize)>138 fn get_directory_counts(reader: &mut R,
139 footer: &spec::CentralDirectoryEnd,
140 cde_start_pos: u64) -> ZipResult<(u64, u64, usize)> {
141 // See if there's a ZIP64 footer. The ZIP64 locator if present will
142 // have its signature 20 bytes in front of the standard footer. The
143 // standard footer, in turn, is 22+N bytes large, where N is the
144 // comment length. Therefore:
145 let zip64locator = if reader.seek(io::SeekFrom::End(-(20 + 22 + footer.zip_file_comment.len() as i64))).is_ok() {
146 match spec::Zip64CentralDirectoryEndLocator::parse(reader) {
147 Ok(loc) => Some(loc),
148 Err(ZipError::InvalidArchive(_)) => {
149 // No ZIP64 header; that's actually fine. We're done here.
150 None
151 },
152 Err(e) => {
153 // Yikes, a real problem
154 return Err(e);
155 }
156 }
157 }
158 else {
159 // Empty Zip files will have nothing else so this error might be fine. If
160 // not, we'll find out soon.
161 None
162 };
163
164 match zip64locator {
165 None => {
166 // Some zip files have data prepended to them, resulting in the
167 // offsets all being too small. Get the amount of error by comparing
168 // the actual file position we found the CDE at with the offset
169 // recorded in the CDE.
170 let archive_offset = cde_start_pos.checked_sub(footer.central_directory_size as u64)
171 .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
172 .ok_or(ZipError::InvalidArchive("Invalid central directory size or offset"))?;
173
174 let directory_start = footer.central_directory_offset as u64 + archive_offset;
175 let number_of_files = footer.number_of_files_on_this_disk as usize;
176 return Ok((archive_offset, directory_start, number_of_files));
177 },
178 Some(locator64) => {
179 // If we got here, this is indeed a ZIP64 file.
180
181 if footer.disk_number as u32 != locator64.disk_with_central_directory {
182 return unsupported_zip_error("Support for multi-disk files is not implemented")
183 }
184
185 // We need to reassess `archive_offset`. We know where the ZIP64
186 // central-directory-end structure *should* be, but unfortunately we
187 // don't know how to precisely relate that location to our current
188 // actual offset in the file, since there may be junk at its
189 // beginning. Therefore we need to perform another search, as in
190 // read::CentralDirectoryEnd::find_and_parse, except now we search
191 // forward.
192
193 let search_upper_bound = cde_start_pos
194 .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator
195 .ok_or(ZipError::InvalidArchive("File cannot contain ZIP64 central directory end"))?;
196 let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse(
197 reader,
198 locator64.end_of_central_directory_offset,
199 search_upper_bound)?;
200
201 if footer.disk_number != footer.disk_with_central_directory {
202 return unsupported_zip_error("Support for multi-disk files is not implemented")
203 }
204
205 let directory_start = footer.central_directory_offset + archive_offset;
206 Ok((archive_offset, directory_start, footer.number_of_files as usize))
207 },
208 }
209 }
210
211 /// Opens a Zip archive and parses the central directory
new(mut reader: R) -> ZipResult<ZipArchive<R>>212 pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
213 let (footer, cde_start_pos) = try!(spec::CentralDirectoryEnd::find_and_parse(&mut reader));
214
215 if footer.disk_number != footer.disk_with_central_directory
216 {
217 return unsupported_zip_error("Support for multi-disk files is not implemented")
218 }
219
220 let (archive_offset, directory_start, number_of_files) =
221 try!(Self::get_directory_counts(&mut reader, &footer, cde_start_pos));
222
223 let mut files = Vec::new();
224 let mut names_map = HashMap::new();
225
226 if let Err(_) = reader.seek(io::SeekFrom::Start(directory_start)) {
227 return Err(ZipError::InvalidArchive("Could not seek to start of central directory"));
228 }
229
230 for _ in 0 .. number_of_files
231 {
232 let file = try!(central_header_to_zip_file(&mut reader, archive_offset));
233 names_map.insert(file.file_name.clone(), files.len());
234 files.push(file);
235 }
236
237 Ok(ZipArchive {
238 reader: reader,
239 files: files,
240 names_map: names_map,
241 offset: archive_offset,
242 comment: footer.zip_file_comment,
243 })
244 }
245
246 /// Number of files contained in this zip.
247 ///
248 /// ```
249 /// fn iter() {
250 /// let mut zip = zip::ZipArchive::new(std::io::Cursor::new(vec![])).unwrap();
251 ///
252 /// for i in 0..zip.len() {
253 /// let mut file = zip.by_index(i).unwrap();
254 /// // Do something with file i
255 /// }
256 /// }
257 /// ```
len(&self) -> usize258 pub fn len(&self) -> usize
259 {
260 self.files.len()
261 }
262
263 /// Get the offset from the beginning of the underlying reader that this zip begins at, in bytes.
264 ///
265 /// Normally this value is zero, but if the zip has arbitrary data prepended to it, then this value will be the size
266 /// of that prepended data.
offset(&self) -> u64267 pub fn offset(&self) -> u64 {
268 self.offset
269 }
270
271 /// Search for a file entry by name
by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>>272 pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>>
273 {
274 let index = match self.names_map.get(name) {
275 Some(index) => *index,
276 None => { return Err(ZipError::FileNotFound); },
277 };
278 self.by_index(index)
279 }
280
281 /// Get a contained file by index
by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>>282 pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>>
283 {
284 if file_number >= self.files.len() { return Err(ZipError::FileNotFound); }
285 let ref data = self.files[file_number];
286 let pos = data.data_start;
287
288 if data.encrypted
289 {
290 return unsupported_zip_error("Encrypted files are not supported")
291 }
292
293 try!(self.reader.seek(io::SeekFrom::Start(pos)));
294 let limit_reader = (self.reader.by_ref() as &mut Read).take(data.compressed_size);
295
296 Ok(ZipFile { reader: try!(make_reader(data.compression_method, data.crc32, limit_reader)), data: Cow::Borrowed(data) })
297 }
298
299 /// Unwrap and return the inner reader object
300 ///
301 /// The position of the reader is undefined.
into_inner(self) -> R302 pub fn into_inner(self) -> R
303 {
304 self.reader
305 }
306 }
307
central_header_to_zip_file<R: Read+io::Seek>(reader: &mut R, archive_offset: u64) -> ZipResult<ZipFileData>308 fn central_header_to_zip_file<R: Read+io::Seek>(reader: &mut R, archive_offset: u64) -> ZipResult<ZipFileData>
309 {
310 // Parse central header
311 let signature = try!(reader.read_u32::<LittleEndian>());
312 if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE
313 {
314 return Err(ZipError::InvalidArchive("Invalid Central Directory header"))
315 }
316
317 let version_made_by = try!(reader.read_u16::<LittleEndian>());
318 let _version_to_extract = try!(reader.read_u16::<LittleEndian>());
319 let flags = try!(reader.read_u16::<LittleEndian>());
320 let encrypted = flags & 1 == 1;
321 let is_utf8 = flags & (1 << 11) != 0;
322 let compression_method = try!(reader.read_u16::<LittleEndian>());
323 let last_mod_time = try!(reader.read_u16::<LittleEndian>());
324 let last_mod_date = try!(reader.read_u16::<LittleEndian>());
325 let crc32 = try!(reader.read_u32::<LittleEndian>());
326 let compressed_size = try!(reader.read_u32::<LittleEndian>());
327 let uncompressed_size = try!(reader.read_u32::<LittleEndian>());
328 let file_name_length = try!(reader.read_u16::<LittleEndian>()) as usize;
329 let extra_field_length = try!(reader.read_u16::<LittleEndian>()) as usize;
330 let file_comment_length = try!(reader.read_u16::<LittleEndian>()) as usize;
331 let _disk_number = try!(reader.read_u16::<LittleEndian>());
332 let _internal_file_attributes = try!(reader.read_u16::<LittleEndian>());
333 let external_file_attributes = try!(reader.read_u32::<LittleEndian>());
334 let mut offset = try!(reader.read_u32::<LittleEndian>()) as u64;
335 let file_name_raw = try!(ReadPodExt::read_exact(reader, file_name_length));
336 let extra_field = try!(ReadPodExt::read_exact(reader, extra_field_length));
337 let file_comment_raw = try!(ReadPodExt::read_exact(reader, file_comment_length));
338
339 // Account for shifted zip offsets.
340 offset += archive_offset;
341
342 let file_name = match is_utf8
343 {
344 true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
345 false => file_name_raw.clone().from_cp437(),
346 };
347 let file_comment = match is_utf8
348 {
349 true => String::from_utf8_lossy(&*file_comment_raw).into_owned(),
350 false => file_comment_raw.from_cp437(),
351 };
352
353 // Construct the result
354 let mut result = ZipFileData
355 {
356 system: System::from_u8((version_made_by >> 8) as u8),
357 version_made_by: version_made_by as u8,
358 encrypted: encrypted,
359 compression_method: CompressionMethod::from_u16(compression_method),
360 last_modified_time: ::time::Tm::from_msdos(MsDosDateTime::new(last_mod_time, last_mod_date)).unwrap_or(TM_1980_01_01),
361 crc32: crc32,
362 compressed_size: compressed_size as u64,
363 uncompressed_size: uncompressed_size as u64,
364 file_name: file_name,
365 file_name_raw: file_name_raw,
366 file_comment: file_comment,
367 header_start: offset,
368 data_start: 0,
369 external_attributes: external_file_attributes,
370 };
371
372 match parse_extra_field(&mut result, &*extra_field) {
373 Ok(..) | Err(ZipError::Io(..)) => {},
374 Err(e) => try!(Err(e)),
375 }
376
377 // Remember end of central header
378 let return_position = try!(reader.seek(io::SeekFrom::Current(0)));
379
380 // Parse local header
381 try!(reader.seek(io::SeekFrom::Start(result.header_start)));
382 let signature = try!(reader.read_u32::<LittleEndian>());
383 if signature != spec::LOCAL_FILE_HEADER_SIGNATURE
384 {
385 return Err(ZipError::InvalidArchive("Invalid local file header"))
386 }
387
388 try!(reader.seek(io::SeekFrom::Current(22)));
389 let file_name_length = try!(reader.read_u16::<LittleEndian>()) as u64;
390 let extra_field_length = try!(reader.read_u16::<LittleEndian>()) as u64;
391 let magic_and_header = 4 + 22 + 2 + 2;
392 result.data_start = result.header_start + magic_and_header + file_name_length + extra_field_length;
393
394 // Go back after the central header
395 try!(reader.seek(io::SeekFrom::Start(return_position)));
396
397 Ok(result)
398 }
399
parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()>400 fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()>
401 {
402 let mut reader = io::Cursor::new(data);
403
404 while (reader.position() as usize) < data.len()
405 {
406 let kind = try!(reader.read_u16::<LittleEndian>());
407 let len = try!(reader.read_u16::<LittleEndian>());
408 match kind
409 {
410 // Zip64 extended information extra field
411 0x0001 => {
412 file.uncompressed_size = try!(reader.read_u64::<LittleEndian>());
413 file.compressed_size = try!(reader.read_u64::<LittleEndian>());
414 try!(reader.read_u64::<LittleEndian>()); // relative header offset
415 try!(reader.read_u32::<LittleEndian>()); // disk start number
416 },
417 _ => { try!(reader.seek(io::SeekFrom::Current(len as i64))); },
418 };
419 }
420 Ok(())
421 }
422
get_reader<'a>(reader: &'a mut ZipFileReader) -> &'a mut Read423 fn get_reader<'a>(reader: &'a mut ZipFileReader) -> &'a mut Read {
424 match *reader {
425 ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
426 ZipFileReader::Stored(ref mut r) => r as &mut Read,
427 #[cfg(feature = "flate2")]
428 ZipFileReader::Deflated(ref mut r) => r as &mut Read,
429 #[cfg(feature = "bzip2")]
430 ZipFileReader::Bzip2(ref mut r) => r as &mut Read,
431 }
432 }
433
434 /// Methods for retreiving information on zip files
435 impl<'a> ZipFile<'a> {
get_reader(&mut self) -> &mut Read436 fn get_reader(&mut self) -> &mut Read {
437 get_reader(&mut self.reader)
438 }
439 /// Get the version of the file
version_made_by(&self) -> (u8, u8)440 pub fn version_made_by(&self) -> (u8, u8) {
441 (self.data.version_made_by / 10, self.data.version_made_by % 10)
442 }
443 /// Get the name of the file
name(&self) -> &str444 pub fn name(&self) -> &str {
445 &*self.data.file_name
446 }
447 /// Get the name of the file, in the raw (internal) byte representation.
name_raw(&self) -> &[u8]448 pub fn name_raw(&self) -> &[u8] {
449 &*self.data.file_name_raw
450 }
451 /// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte,
452 /// removes a leading '/' and removes '..' parts.
sanitized_name(&self) -> ::std::path::PathBuf453 pub fn sanitized_name(&self) -> ::std::path::PathBuf {
454 self.data.file_name_sanitized()
455 }
456 /// Get the comment of the file
comment(&self) -> &str457 pub fn comment(&self) -> &str {
458 &*self.data.file_comment
459 }
460 /// Get the compression method used to store the file
compression(&self) -> CompressionMethod461 pub fn compression(&self) -> CompressionMethod {
462 self.data.compression_method
463 }
464 /// Get the size of the file in the archive
compressed_size(&self) -> u64465 pub fn compressed_size(&self) -> u64 {
466 self.data.compressed_size
467 }
468 /// Get the size of the file when uncompressed
size(&self) -> u64469 pub fn size(&self) -> u64 {
470 self.data.uncompressed_size
471 }
472 /// Get the time the file was last modified
last_modified(&self) -> ::time::Tm473 pub fn last_modified(&self) -> ::time::Tm {
474 self.data.last_modified_time
475 }
476 /// Get unix mode for the file
unix_mode(&self) -> Option<u32>477 pub fn unix_mode(&self) -> Option<u32> {
478 if self.data.external_attributes == 0 {
479 return None;
480 }
481
482 match self.data.system {
483 System::Unix => {
484 Some(self.data.external_attributes >> 16)
485 },
486 System::Dos => {
487 // Interpret MSDOS directory bit
488 let mut mode = if 0x10 == (self.data.external_attributes & 0x10) {
489 ffi::S_IFDIR | 0o0775
490 } else {
491 ffi::S_IFREG | 0o0664
492 };
493 if 0x01 == (self.data.external_attributes & 0x01) {
494 // Read-only bit; strip write permissions
495 mode &= 0o0555;
496 }
497 Some(mode)
498 },
499 _ => None,
500 }
501 }
502 /// Get the CRC32 hash of the original file
crc32(&self) -> u32503 pub fn crc32(&self) -> u32 {
504 self.data.crc32
505 }
506
507 /// Get the starting offset of the data of the compressed file
data_start(&self) -> u64508 pub fn data_start(&self) -> u64 {
509 self.data.data_start
510 }
511 }
512
513 impl<'a> Read for ZipFile<'a> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>514 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
515 self.get_reader().read(buf)
516 }
517 }
518
519 impl<'a> Drop for ZipFile<'a> {
drop(&mut self)520 fn drop(&mut self) {
521 // self.data is Owned, this reader is constructed by a streaming reader.
522 // In this case, we want to exhaust the reader so that the next file is accessible.
523 if let Cow::Owned(_) = self.data {
524 let mut buffer = [0; 1<<16];
525
526 // Get the inner `Take` reader so all decompression and CRC calculation is skipped.
527 let innerreader = ::std::mem::replace(&mut self.reader, ZipFileReader::NoReader);
528 let mut reader = match innerreader {
529 ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
530 ZipFileReader::Stored(crcreader) => crcreader.into_inner(),
531 #[cfg(feature = "flate2")]
532 ZipFileReader::Deflated(crcreader) => crcreader.into_inner().into_inner(),
533 #[cfg(feature = "bzip2")]
534 ZipFileReader::Bzip2(crcreader) => crcreader.into_inner().into_inner(),
535 };
536
537 loop {
538 match reader.read(&mut buffer) {
539 Ok(0) => break,
540 Ok(_) => (),
541 Err(e) => panic!("Could not consume all of the output of the current ZipFile: {:?}", e),
542 }
543 }
544 }
545 }
546 }
547
548 /// Read ZipFile structures from a non-seekable reader.
549 ///
550 /// This is an alternative method to read a zip file. If possible, use the ZipArchive functions
551 /// as some information will be missing when reading this manner.
552 ///
553 /// Reads a file header from the start of the stream. Will return `Ok(Some(..))` if a file is
554 /// present at the start of the stream. Returns `Ok(None)` if the start of the central directory
555 /// is encountered. No more files should be read after this.
556 ///
557 /// The Drop implementation of ZipFile ensures that the reader will be correctly positioned after
558 /// the structure is done.
559 ///
560 /// Missing fields are:
561 /// * `comment`: set to an empty string
562 /// * `data_start`: set to 0
563 /// * `external_attributes`: `unix_mode()`: will return None
read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult<Option<ZipFile>>564 pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult<Option<ZipFile>> {
565 let signature = try!(reader.read_u32::<LittleEndian>());
566
567 match signature {
568 spec::LOCAL_FILE_HEADER_SIGNATURE => (),
569 spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE => return Ok(None),
570 _ => return Err(ZipError::InvalidArchive("Invalid local file header")),
571 }
572
573 let version_made_by = try!(reader.read_u16::<LittleEndian>());
574 let flags = try!(reader.read_u16::<LittleEndian>());
575 let encrypted = flags & 1 == 1;
576 let is_utf8 = flags & (1 << 11) != 0;
577 let using_data_descriptor = flags & (1 << 3) != 0;
578 let compression_method = CompressionMethod::from_u16(try!(reader.read_u16::<LittleEndian>()));
579 let last_mod_time = try!(reader.read_u16::<LittleEndian>());
580 let last_mod_date = try!(reader.read_u16::<LittleEndian>());
581 let crc32 = try!(reader.read_u32::<LittleEndian>());
582 let compressed_size = try!(reader.read_u32::<LittleEndian>());
583 let uncompressed_size = try!(reader.read_u32::<LittleEndian>());
584 let file_name_length = try!(reader.read_u16::<LittleEndian>()) as usize;
585 let extra_field_length = try!(reader.read_u16::<LittleEndian>()) as usize;
586
587 let file_name_raw = try!(ReadPodExt::read_exact(reader, file_name_length));
588 let extra_field = try!(ReadPodExt::read_exact(reader, extra_field_length));
589
590 let file_name = match is_utf8
591 {
592 true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
593 false => file_name_raw.clone().from_cp437(),
594 };
595
596 let mut result = ZipFileData
597 {
598 system: System::from_u8((version_made_by >> 8) as u8),
599 version_made_by: version_made_by as u8,
600 encrypted: encrypted,
601 compression_method: compression_method,
602 last_modified_time: ::time::Tm::from_msdos(MsDosDateTime::new(last_mod_time, last_mod_date)).unwrap_or(TM_1980_01_01),
603 crc32: crc32,
604 compressed_size: compressed_size as u64,
605 uncompressed_size: uncompressed_size as u64,
606 file_name: file_name,
607 file_name_raw: file_name_raw,
608 file_comment: String::new(), // file comment is only available in the central directory
609 // header_start and data start are not available, but also don't matter, since seeking is
610 // not available.
611 header_start: 0,
612 data_start: 0,
613 // The external_attributes field is only available in the central directory.
614 // We set this to zero, which should be valid as the docs state 'If input came
615 // from standard input, this field is set to zero.'
616 external_attributes: 0,
617 };
618
619 match parse_extra_field(&mut result, &extra_field) {
620 Ok(..) | Err(ZipError::Io(..)) => {},
621 Err(e) => try!(Err(e)),
622 }
623
624 if encrypted {
625 return unsupported_zip_error("Encrypted files are not supported")
626 }
627 if using_data_descriptor {
628 return unsupported_zip_error("The file length is not available in the local header");
629 }
630
631 let limit_reader = (reader as &'a mut io::Read).take(result.compressed_size as u64);
632
633 let result_crc32 = result.crc32;
634 let result_compression_method = result.compression_method;
635 Ok(Some(ZipFile {
636 data: Cow::Owned(result),
637 reader: try!(make_reader(result_compression_method, result_crc32, limit_reader))
638 }))
639 }
640
641 #[cfg(test)]
642 mod test {
643 #[test]
invalid_offset()644 fn invalid_offset() {
645 use std::io;
646 use super::ZipArchive;
647
648 let mut v = Vec::new();
649 v.extend_from_slice(include_bytes!("../tests/data/invalid_offset.zip"));
650 let reader = ZipArchive::new(io::Cursor::new(v));
651 assert!(reader.is_err());
652 }
653
654 #[test]
zip64_with_leading_junk()655 fn zip64_with_leading_junk() {
656 use std::io;
657 use super::ZipArchive;
658
659 let mut v = Vec::new();
660 v.extend_from_slice(include_bytes!("../tests/data/zip64_demo.zip"));
661 let reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
662 assert!(reader.len() == 1);
663 }
664
665 #[test]
zip_comment()666 fn zip_comment() {
667 use std::io;
668 use super::ZipArchive;
669
670 let mut v = Vec::new();
671 v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
672 let reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
673 assert!(reader.comment == b"zip-rs");
674 }
675
676 #[test]
zip_read_streaming()677 fn zip_read_streaming() {
678 use std::io;
679 use super::read_zipfile_from_stream;
680
681 let mut v = Vec::new();
682 v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
683 let mut reader = io::Cursor::new(v);
684 loop {
685 match read_zipfile_from_stream(&mut reader).unwrap() {
686 None => break,
687 _ => (),
688 }
689 }
690 }
691 }
692