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