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