1 //! Types for reading ZIP archives
2 
3 #[cfg(feature = "aes-crypto")]
4 use crate::aes::{AesReader, AesReaderValid};
5 use crate::compression::CompressionMethod;
6 use crate::cp437::FromCp437;
7 use crate::crc32::Crc32Reader;
8 use crate::result::{InvalidPassword, ZipError, ZipResult};
9 use crate::spec;
10 use crate::types::{AesMode, AesVendorVersion, AtomicU64, DateTime, System, ZipFileData};
11 use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator};
12 use byteorder::{LittleEndian, ReadBytesExt};
13 use std::borrow::Cow;
14 use std::collections::HashMap;
15 use std::io::{self, prelude::*};
16 use std::path::Path;
17 use std::sync::Arc;
18 
19 #[cfg(any(
20     feature = "deflate",
21     feature = "deflate-miniz",
22     feature = "deflate-zlib"
23 ))]
24 use flate2::read::DeflateDecoder;
25 
26 #[cfg(feature = "bzip2")]
27 use bzip2::read::BzDecoder;
28 
29 #[cfg(feature = "zstd")]
30 use zstd::stream::read::Decoder as ZstdDecoder;
31 
32 /// Provides high level API for reading from a stream.
33 pub(crate) mod stream;
34 
35 // Put the struct declaration in a private module to convince rustdoc to display ZipArchive nicely
36 pub(crate) mod zip_archive {
37     /// Extract immutable data from `ZipArchive` to make it cheap to clone
38     #[derive(Debug)]
39     pub(crate) struct Shared {
40         pub(super) files: Vec<super::ZipFileData>,
41         pub(super) names_map: super::HashMap<String, usize>,
42         pub(super) offset: u64,
43         pub(super) comment: Vec<u8>,
44     }
45 
46     /// ZIP archive reader
47     ///
48     /// At the moment, this type is cheap to clone if this is the case for the
49     /// reader it uses. However, this is not guaranteed by this crate and it may
50     /// change in the future.
51     ///
52     /// ```no_run
53     /// use std::io::prelude::*;
54     /// fn list_zip_contents(reader: impl Read + Seek) -> zip::result::ZipResult<()> {
55     ///     let mut zip = zip::ZipArchive::new(reader)?;
56     ///
57     ///     for i in 0..zip.len() {
58     ///         let mut file = zip.by_index(i)?;
59     ///         println!("Filename: {}", file.name());
60     ///         std::io::copy(&mut file, &mut std::io::stdout());
61     ///     }
62     ///
63     ///     Ok(())
64     /// }
65     /// ```
66     #[derive(Clone, Debug)]
67     pub struct ZipArchive<R> {
68         pub(super) reader: R,
69         pub(super) shared: super::Arc<Shared>,
70     }
71 }
72 
73 pub use zip_archive::ZipArchive;
74 #[allow(clippy::large_enum_variant)]
75 enum CryptoReader<'a> {
76     Plaintext(io::Take<&'a mut dyn Read>),
77     ZipCrypto(ZipCryptoReaderValid<io::Take<&'a mut dyn Read>>),
78     #[cfg(feature = "aes-crypto")]
79     Aes {
80         reader: AesReaderValid<io::Take<&'a mut dyn Read>>,
81         vendor_version: AesVendorVersion,
82     },
83 }
84 
85 impl<'a> Read for CryptoReader<'a> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>86     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
87         match self {
88             CryptoReader::Plaintext(r) => r.read(buf),
89             CryptoReader::ZipCrypto(r) => r.read(buf),
90             #[cfg(feature = "aes-crypto")]
91             CryptoReader::Aes { reader: r, .. } => r.read(buf),
92         }
93     }
94 }
95 
96 impl<'a> CryptoReader<'a> {
97     /// Consumes this decoder, returning the underlying reader.
into_inner(self) -> io::Take<&'a mut dyn Read>98     pub fn into_inner(self) -> io::Take<&'a mut dyn Read> {
99         match self {
100             CryptoReader::Plaintext(r) => r,
101             CryptoReader::ZipCrypto(r) => r.into_inner(),
102             #[cfg(feature = "aes-crypto")]
103             CryptoReader::Aes { reader: r, .. } => r.into_inner(),
104         }
105     }
106 
107     /// Returns `true` if the data is encrypted using AE2.
is_ae2_encrypted(&self) -> bool108     pub fn is_ae2_encrypted(&self) -> bool {
109         #[cfg(feature = "aes-crypto")]
110         return matches!(
111             self,
112             CryptoReader::Aes {
113                 vendor_version: AesVendorVersion::Ae2,
114                 ..
115             }
116         );
117         #[cfg(not(feature = "aes-crypto"))]
118         false
119     }
120 }
121 
122 enum ZipFileReader<'a> {
123     NoReader,
124     Raw(io::Take<&'a mut dyn io::Read>),
125     Stored(Crc32Reader<CryptoReader<'a>>),
126     #[cfg(any(
127         feature = "deflate",
128         feature = "deflate-miniz",
129         feature = "deflate-zlib"
130     ))]
131     Deflated(Crc32Reader<flate2::read::DeflateDecoder<CryptoReader<'a>>>),
132     #[cfg(feature = "bzip2")]
133     Bzip2(Crc32Reader<BzDecoder<CryptoReader<'a>>>),
134     #[cfg(feature = "zstd")]
135     Zstd(Crc32Reader<ZstdDecoder<'a, io::BufReader<CryptoReader<'a>>>>),
136 }
137 
138 impl<'a> Read for ZipFileReader<'a> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>139     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
140         match self {
141             ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
142             ZipFileReader::Raw(r) => r.read(buf),
143             ZipFileReader::Stored(r) => r.read(buf),
144             #[cfg(any(
145                 feature = "deflate",
146                 feature = "deflate-miniz",
147                 feature = "deflate-zlib"
148             ))]
149             ZipFileReader::Deflated(r) => r.read(buf),
150             #[cfg(feature = "bzip2")]
151             ZipFileReader::Bzip2(r) => r.read(buf),
152             #[cfg(feature = "zstd")]
153             ZipFileReader::Zstd(r) => r.read(buf),
154         }
155     }
156 }
157 
158 impl<'a> ZipFileReader<'a> {
159     /// Consumes this decoder, returning the underlying reader.
into_inner(self) -> io::Take<&'a mut dyn Read>160     pub fn into_inner(self) -> io::Take<&'a mut dyn Read> {
161         match self {
162             ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
163             ZipFileReader::Raw(r) => r,
164             ZipFileReader::Stored(r) => r.into_inner().into_inner(),
165             #[cfg(any(
166                 feature = "deflate",
167                 feature = "deflate-miniz",
168                 feature = "deflate-zlib"
169             ))]
170             ZipFileReader::Deflated(r) => r.into_inner().into_inner().into_inner(),
171             #[cfg(feature = "bzip2")]
172             ZipFileReader::Bzip2(r) => r.into_inner().into_inner().into_inner(),
173             #[cfg(feature = "zstd")]
174             ZipFileReader::Zstd(r) => r.into_inner().finish().into_inner().into_inner(),
175         }
176     }
177 }
178 
179 /// A struct for reading a zip file
180 pub struct ZipFile<'a> {
181     data: Cow<'a, ZipFileData>,
182     crypto_reader: Option<CryptoReader<'a>>,
183     reader: ZipFileReader<'a>,
184 }
185 
find_content<'a>( data: &ZipFileData, reader: &'a mut (impl Read + Seek), ) -> ZipResult<io::Take<&'a mut dyn Read>>186 fn find_content<'a>(
187     data: &ZipFileData,
188     reader: &'a mut (impl Read + Seek),
189 ) -> ZipResult<io::Take<&'a mut dyn Read>> {
190     // Parse local header
191     reader.seek(io::SeekFrom::Start(data.header_start))?;
192     let signature = reader.read_u32::<LittleEndian>()?;
193     if signature != spec::LOCAL_FILE_HEADER_SIGNATURE {
194         return Err(ZipError::InvalidArchive("Invalid local file header"));
195     }
196 
197     reader.seek(io::SeekFrom::Current(22))?;
198     let file_name_length = reader.read_u16::<LittleEndian>()? as u64;
199     let extra_field_length = reader.read_u16::<LittleEndian>()? as u64;
200     let magic_and_header = 4 + 22 + 2 + 2;
201     let data_start = data.header_start + magic_and_header + file_name_length + extra_field_length;
202     data.data_start.store(data_start);
203 
204     reader.seek(io::SeekFrom::Start(data_start))?;
205     Ok((reader as &mut dyn Read).take(data.compressed_size))
206 }
207 
208 #[allow(clippy::too_many_arguments)]
make_crypto_reader<'a>( compression_method: crate::compression::CompressionMethod, crc32: u32, last_modified_time: DateTime, using_data_descriptor: bool, reader: io::Take<&'a mut dyn io::Read>, password: Option<&[u8]>, aes_info: Option<(AesMode, AesVendorVersion)>, #[cfg(feature = "aes-crypto")] compressed_size: u64, ) -> ZipResult<Result<CryptoReader<'a>, InvalidPassword>>209 fn make_crypto_reader<'a>(
210     compression_method: crate::compression::CompressionMethod,
211     crc32: u32,
212     last_modified_time: DateTime,
213     using_data_descriptor: bool,
214     reader: io::Take<&'a mut dyn io::Read>,
215     password: Option<&[u8]>,
216     aes_info: Option<(AesMode, AesVendorVersion)>,
217     #[cfg(feature = "aes-crypto")] compressed_size: u64,
218 ) -> ZipResult<Result<CryptoReader<'a>, InvalidPassword>> {
219     #[allow(deprecated)]
220     {
221         if let CompressionMethod::Unsupported(_) = compression_method {
222             return unsupported_zip_error("Compression method not supported");
223         }
224     }
225 
226     let reader = match (password, aes_info) {
227         #[cfg(not(feature = "aes-crypto"))]
228         (Some(_), Some(_)) => {
229             return Err(ZipError::UnsupportedArchive(
230                 "AES encrypted files cannot be decrypted without the aes-crypto feature.",
231             ))
232         }
233         #[cfg(feature = "aes-crypto")]
234         (Some(password), Some((aes_mode, vendor_version))) => {
235             match AesReader::new(reader, aes_mode, compressed_size).validate(password)? {
236                 None => return Ok(Err(InvalidPassword)),
237                 Some(r) => CryptoReader::Aes {
238                     reader: r,
239                     vendor_version,
240                 },
241             }
242         }
243         (Some(password), None) => {
244             let validator = if using_data_descriptor {
245                 ZipCryptoValidator::InfoZipMsdosTime(last_modified_time.timepart())
246             } else {
247                 ZipCryptoValidator::PkzipCrc32(crc32)
248             };
249             match ZipCryptoReader::new(reader, password).validate(validator)? {
250                 None => return Ok(Err(InvalidPassword)),
251                 Some(r) => CryptoReader::ZipCrypto(r),
252             }
253         }
254         (None, Some(_)) => return Ok(Err(InvalidPassword)),
255         (None, None) => CryptoReader::Plaintext(reader),
256     };
257     Ok(Ok(reader))
258 }
259 
make_reader( compression_method: CompressionMethod, crc32: u32, reader: CryptoReader, ) -> ZipFileReader260 fn make_reader(
261     compression_method: CompressionMethod,
262     crc32: u32,
263     reader: CryptoReader,
264 ) -> ZipFileReader {
265     let ae2_encrypted = reader.is_ae2_encrypted();
266 
267     match compression_method {
268         CompressionMethod::Stored => {
269             ZipFileReader::Stored(Crc32Reader::new(reader, crc32, ae2_encrypted))
270         }
271         #[cfg(any(
272             feature = "deflate",
273             feature = "deflate-miniz",
274             feature = "deflate-zlib"
275         ))]
276         CompressionMethod::Deflated => {
277             let deflate_reader = DeflateDecoder::new(reader);
278             ZipFileReader::Deflated(Crc32Reader::new(deflate_reader, crc32, ae2_encrypted))
279         }
280         #[cfg(feature = "bzip2")]
281         CompressionMethod::Bzip2 => {
282             let bzip2_reader = BzDecoder::new(reader);
283             ZipFileReader::Bzip2(Crc32Reader::new(bzip2_reader, crc32, ae2_encrypted))
284         }
285         #[cfg(feature = "zstd")]
286         CompressionMethod::Zstd => {
287             let zstd_reader = ZstdDecoder::new(reader).unwrap();
288             ZipFileReader::Zstd(Crc32Reader::new(zstd_reader, crc32, ae2_encrypted))
289         }
290         _ => panic!("Compression method not supported"),
291     }
292 }
293 
294 impl<R: Read + io::Seek> ZipArchive<R> {
295     /// Get the directory start offset and number of files. This is done in a
296     /// 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)>297     pub(crate) fn get_directory_counts(
298         reader: &mut R,
299         footer: &spec::CentralDirectoryEnd,
300         cde_start_pos: u64,
301     ) -> ZipResult<(u64, u64, usize)> {
302         // See if there's a ZIP64 footer. The ZIP64 locator if present will
303         // have its signature 20 bytes in front of the standard footer. The
304         // standard footer, in turn, is 22+N bytes large, where N is the
305         // comment length. Therefore:
306         let zip64locator = if reader
307             .seek(io::SeekFrom::End(
308                 -(20 + 22 + footer.zip_file_comment.len() as i64),
309             ))
310             .is_ok()
311         {
312             match spec::Zip64CentralDirectoryEndLocator::parse(reader) {
313                 Ok(loc) => Some(loc),
314                 Err(ZipError::InvalidArchive(_)) => {
315                     // No ZIP64 header; that's actually fine. We're done here.
316                     None
317                 }
318                 Err(e) => {
319                     // Yikes, a real problem
320                     return Err(e);
321                 }
322             }
323         } else {
324             // Empty Zip files will have nothing else so this error might be fine. If
325             // not, we'll find out soon.
326             None
327         };
328 
329         match zip64locator {
330             None => {
331                 // Some zip files have data prepended to them, resulting in the
332                 // offsets all being too small. Get the amount of error by comparing
333                 // the actual file position we found the CDE at with the offset
334                 // recorded in the CDE.
335                 let archive_offset = cde_start_pos
336                     .checked_sub(footer.central_directory_size as u64)
337                     .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
338                     .ok_or(ZipError::InvalidArchive(
339                         "Invalid central directory size or offset",
340                     ))?;
341 
342                 let directory_start = footer.central_directory_offset as u64 + archive_offset;
343                 let number_of_files = footer.number_of_files_on_this_disk as usize;
344                 Ok((archive_offset, directory_start, number_of_files))
345             }
346             Some(locator64) => {
347                 // If we got here, this is indeed a ZIP64 file.
348 
349                 if !footer.record_too_small()
350                     && footer.disk_number as u32 != locator64.disk_with_central_directory
351                 {
352                     return unsupported_zip_error(
353                         "Support for multi-disk files is not implemented",
354                     );
355                 }
356 
357                 // We need to reassess `archive_offset`. We know where the ZIP64
358                 // central-directory-end structure *should* be, but unfortunately we
359                 // don't know how to precisely relate that location to our current
360                 // actual offset in the file, since there may be junk at its
361                 // beginning. Therefore we need to perform another search, as in
362                 // read::CentralDirectoryEnd::find_and_parse, except now we search
363                 // forward.
364 
365                 let search_upper_bound = cde_start_pos
366                     .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator
367                     .ok_or(ZipError::InvalidArchive(
368                         "File cannot contain ZIP64 central directory end",
369                     ))?;
370                 let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse(
371                     reader,
372                     locator64.end_of_central_directory_offset,
373                     search_upper_bound,
374                 )?;
375 
376                 if footer.disk_number != footer.disk_with_central_directory {
377                     return unsupported_zip_error(
378                         "Support for multi-disk files is not implemented",
379                     );
380                 }
381 
382                 let directory_start = footer
383                     .central_directory_offset
384                     .checked_add(archive_offset)
385                     .ok_or({
386                         ZipError::InvalidArchive("Invalid central directory size or offset")
387                     })?;
388 
389                 Ok((
390                     archive_offset,
391                     directory_start,
392                     footer.number_of_files as usize,
393                 ))
394             }
395         }
396     }
397 
398     /// Read a ZIP archive, collecting the files it contains
399     ///
400     /// This uses the central directory record of the ZIP file, and ignores local file headers
new(mut reader: R) -> ZipResult<ZipArchive<R>>401     pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
402         let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
403 
404         if !footer.record_too_small() && footer.disk_number != footer.disk_with_central_directory {
405             return unsupported_zip_error("Support for multi-disk files is not implemented");
406         }
407 
408         let (archive_offset, directory_start, number_of_files) =
409             Self::get_directory_counts(&mut reader, &footer, cde_start_pos)?;
410 
411         // If the parsed number of files is greater than the offset then
412         // something fishy is going on and we shouldn't trust number_of_files.
413         let file_capacity = if number_of_files > cde_start_pos as usize {
414             0
415         } else {
416             number_of_files
417         };
418 
419         let mut files = Vec::with_capacity(file_capacity);
420         let mut names_map = HashMap::with_capacity(file_capacity);
421 
422         if reader.seek(io::SeekFrom::Start(directory_start)).is_err() {
423             return Err(ZipError::InvalidArchive(
424                 "Could not seek to start of central directory",
425             ));
426         }
427 
428         for _ in 0..number_of_files {
429             let file = central_header_to_zip_file(&mut reader, archive_offset)?;
430             names_map.insert(file.file_name.clone(), files.len());
431             files.push(file);
432         }
433 
434         let shared = Arc::new(zip_archive::Shared {
435             files,
436             names_map,
437             offset: archive_offset,
438             comment: footer.zip_file_comment,
439         });
440 
441         Ok(ZipArchive { reader, shared })
442     }
443     /// Extract a Zip archive into a directory, overwriting files if they
444     /// already exist. Paths are sanitized with [`ZipFile::enclosed_name`].
445     ///
446     /// Extraction is not atomic; If an error is encountered, some of the files
447     /// may be left on disk.
extract<P: AsRef<Path>>(&mut self, directory: P) -> ZipResult<()>448     pub fn extract<P: AsRef<Path>>(&mut self, directory: P) -> ZipResult<()> {
449         use std::fs;
450 
451         for i in 0..self.len() {
452             let mut file = self.by_index(i)?;
453             let filepath = file
454                 .enclosed_name()
455                 .ok_or(ZipError::InvalidArchive("Invalid file path"))?;
456 
457             let outpath = directory.as_ref().join(filepath);
458 
459             if file.name().ends_with('/') {
460                 fs::create_dir_all(&outpath)?;
461             } else {
462                 if let Some(p) = outpath.parent() {
463                     if !p.exists() {
464                         fs::create_dir_all(p)?;
465                     }
466                 }
467                 let mut outfile = fs::File::create(&outpath)?;
468                 io::copy(&mut file, &mut outfile)?;
469             }
470             // Get and Set permissions
471             #[cfg(unix)]
472             {
473                 use std::os::unix::fs::PermissionsExt;
474                 if let Some(mode) = file.unix_mode() {
475                     fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
476                 }
477             }
478         }
479         Ok(())
480     }
481 
482     /// Number of files contained in this zip.
len(&self) -> usize483     pub fn len(&self) -> usize {
484         self.shared.files.len()
485     }
486 
487     /// Whether this zip archive contains no files
is_empty(&self) -> bool488     pub fn is_empty(&self) -> bool {
489         self.len() == 0
490     }
491 
492     /// Get the offset from the beginning of the underlying reader that this zip begins at, in bytes.
493     ///
494     /// Normally this value is zero, but if the zip has arbitrary data prepended to it, then this value will be the size
495     /// of that prepended data.
offset(&self) -> u64496     pub fn offset(&self) -> u64 {
497         self.shared.offset
498     }
499 
500     /// Get the comment of the zip archive.
comment(&self) -> &[u8]501     pub fn comment(&self) -> &[u8] {
502         &self.shared.comment
503     }
504 
505     /// Returns an iterator over all the file and directory names in this archive.
file_names(&self) -> impl Iterator<Item = &str>506     pub fn file_names(&self) -> impl Iterator<Item = &str> {
507         self.shared.names_map.keys().map(|s| s.as_str())
508     }
509 
510     /// Search for a file entry by name, decrypt with given password
511     ///
512     /// # Warning
513     ///
514     /// The implementation of the cryptographic algorithms has not
515     /// gone through a correctness review, and you should assume it is insecure:
516     /// passwords used with this API may be compromised.
517     ///
518     /// This function sometimes accepts wrong password. This is because the ZIP spec only allows us
519     /// to check for a 1/256 chance that the password is correct.
520     /// There are many passwords out there that will also pass the validity checks
521     /// we are able to perform. This is a weakness of the ZipCrypto algorithm,
522     /// due to its fairly primitive approach to cryptography.
by_name_decrypt<'a>( &'a mut self, name: &str, password: &[u8], ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>>523     pub fn by_name_decrypt<'a>(
524         &'a mut self,
525         name: &str,
526         password: &[u8],
527     ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
528         self.by_name_with_optional_password(name, Some(password))
529     }
530 
531     /// Search for a file entry by name
by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>>532     pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
533         Ok(self.by_name_with_optional_password(name, None)?.unwrap())
534     }
535 
by_name_with_optional_password<'a>( &'a mut self, name: &str, password: Option<&[u8]>, ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>>536     fn by_name_with_optional_password<'a>(
537         &'a mut self,
538         name: &str,
539         password: Option<&[u8]>,
540     ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
541         let index = match self.shared.names_map.get(name) {
542             Some(index) => *index,
543             None => {
544                 return Err(ZipError::FileNotFound);
545             }
546         };
547         self.by_index_with_optional_password(index, password)
548     }
549 
550     /// Get a contained file by index, decrypt with given password
551     ///
552     /// # Warning
553     ///
554     /// The implementation of the cryptographic algorithms has not
555     /// gone through a correctness review, and you should assume it is insecure:
556     /// passwords used with this API may be compromised.
557     ///
558     /// This function sometimes accepts wrong password. This is because the ZIP spec only allows us
559     /// to check for a 1/256 chance that the password is correct.
560     /// There are many passwords out there that will also pass the validity checks
561     /// we are able to perform. This is a weakness of the ZipCrypto algorithm,
562     /// due to its fairly primitive approach to cryptography.
by_index_decrypt<'a>( &'a mut self, file_number: usize, password: &[u8], ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>>563     pub fn by_index_decrypt<'a>(
564         &'a mut self,
565         file_number: usize,
566         password: &[u8],
567     ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
568         self.by_index_with_optional_password(file_number, Some(password))
569     }
570 
571     /// Get a contained file by index
by_index(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>>572     pub fn by_index(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
573         Ok(self
574             .by_index_with_optional_password(file_number, None)?
575             .unwrap())
576     }
577 
578     /// Get a contained file by index without decompressing it
by_index_raw(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>>579     pub fn by_index_raw(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
580         let reader = &mut self.reader;
581         self.shared
582             .files
583             .get(file_number)
584             .ok_or(ZipError::FileNotFound)
585             .and_then(move |data| {
586                 Ok(ZipFile {
587                     crypto_reader: None,
588                     reader: ZipFileReader::Raw(find_content(data, reader)?),
589                     data: Cow::Borrowed(data),
590                 })
591             })
592     }
593 
by_index_with_optional_password<'a>( &'a mut self, file_number: usize, mut password: Option<&[u8]>, ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>>594     fn by_index_with_optional_password<'a>(
595         &'a mut self,
596         file_number: usize,
597         mut password: Option<&[u8]>,
598     ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
599         let data = self
600             .shared
601             .files
602             .get(file_number)
603             .ok_or(ZipError::FileNotFound)?;
604 
605         match (password, data.encrypted) {
606             (None, true) => return Err(ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED)),
607             (Some(_), false) => password = None, //Password supplied, but none needed! Discard.
608             _ => {}
609         }
610         let limit_reader = find_content(data, &mut self.reader)?;
611 
612         match make_crypto_reader(
613             data.compression_method,
614             data.crc32,
615             data.last_modified_time,
616             data.using_data_descriptor,
617             limit_reader,
618             password,
619             data.aes_mode,
620             #[cfg(feature = "aes-crypto")]
621             data.compressed_size,
622         ) {
623             Ok(Ok(crypto_reader)) => Ok(Ok(ZipFile {
624                 crypto_reader: Some(crypto_reader),
625                 reader: ZipFileReader::NoReader,
626                 data: Cow::Borrowed(data),
627             })),
628             Err(e) => Err(e),
629             Ok(Err(e)) => Ok(Err(e)),
630         }
631     }
632 
633     /// Unwrap and return the inner reader object
634     ///
635     /// The position of the reader is undefined.
into_inner(self) -> R636     pub fn into_inner(self) -> R {
637         self.reader
638     }
639 }
640 
unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T>641 fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
642     Err(ZipError::UnsupportedArchive(detail))
643 }
644 
645 /// Parse a central directory entry to collect the information for the file.
central_header_to_zip_file<R: Read + io::Seek>( reader: &mut R, archive_offset: u64, ) -> ZipResult<ZipFileData>646 pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
647     reader: &mut R,
648     archive_offset: u64,
649 ) -> ZipResult<ZipFileData> {
650     let central_header_start = reader.stream_position()?;
651 
652     // Parse central header
653     let signature = reader.read_u32::<LittleEndian>()?;
654     if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
655         Err(ZipError::InvalidArchive("Invalid Central Directory header"))
656     } else {
657         central_header_to_zip_file_inner(reader, archive_offset, central_header_start)
658     }
659 }
660 
661 /// Parse a central directory entry to collect the information for the file.
central_header_to_zip_file_inner<R: Read>( reader: &mut R, archive_offset: u64, central_header_start: u64, ) -> ZipResult<ZipFileData>662 fn central_header_to_zip_file_inner<R: Read>(
663     reader: &mut R,
664     archive_offset: u64,
665     central_header_start: u64,
666 ) -> ZipResult<ZipFileData> {
667     let version_made_by = reader.read_u16::<LittleEndian>()?;
668     let _version_to_extract = reader.read_u16::<LittleEndian>()?;
669     let flags = reader.read_u16::<LittleEndian>()?;
670     let encrypted = flags & 1 == 1;
671     let is_utf8 = flags & (1 << 11) != 0;
672     let using_data_descriptor = flags & (1 << 3) != 0;
673     let compression_method = reader.read_u16::<LittleEndian>()?;
674     let last_mod_time = reader.read_u16::<LittleEndian>()?;
675     let last_mod_date = reader.read_u16::<LittleEndian>()?;
676     let crc32 = reader.read_u32::<LittleEndian>()?;
677     let compressed_size = reader.read_u32::<LittleEndian>()?;
678     let uncompressed_size = reader.read_u32::<LittleEndian>()?;
679     let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
680     let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
681     let file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
682     let _disk_number = reader.read_u16::<LittleEndian>()?;
683     let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
684     let external_file_attributes = reader.read_u32::<LittleEndian>()?;
685     let offset = reader.read_u32::<LittleEndian>()? as u64;
686     let mut file_name_raw = vec![0; file_name_length];
687     reader.read_exact(&mut file_name_raw)?;
688     let mut extra_field = vec![0; extra_field_length];
689     reader.read_exact(&mut extra_field)?;
690     let mut file_comment_raw = vec![0; file_comment_length];
691     reader.read_exact(&mut file_comment_raw)?;
692 
693     let file_name = match is_utf8 {
694         true => String::from_utf8_lossy(&file_name_raw).into_owned(),
695         false => file_name_raw.clone().from_cp437(),
696     };
697     let file_comment = match is_utf8 {
698         true => String::from_utf8_lossy(&file_comment_raw).into_owned(),
699         false => file_comment_raw.from_cp437(),
700     };
701 
702     // Construct the result
703     let mut result = ZipFileData {
704         system: System::from_u8((version_made_by >> 8) as u8),
705         version_made_by: version_made_by as u8,
706         encrypted,
707         using_data_descriptor,
708         compression_method: {
709             #[allow(deprecated)]
710             CompressionMethod::from_u16(compression_method)
711         },
712         compression_level: None,
713         last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
714         crc32,
715         compressed_size: compressed_size as u64,
716         uncompressed_size: uncompressed_size as u64,
717         file_name,
718         file_name_raw,
719         extra_field,
720         file_comment,
721         header_start: offset,
722         central_header_start,
723         data_start: AtomicU64::new(0),
724         external_attributes: external_file_attributes,
725         large_file: false,
726         aes_mode: None,
727     };
728 
729     match parse_extra_field(&mut result) {
730         Ok(..) | Err(ZipError::Io(..)) => {}
731         Err(e) => return Err(e),
732     }
733 
734     let aes_enabled = result.compression_method == CompressionMethod::AES;
735     if aes_enabled && result.aes_mode.is_none() {
736         return Err(ZipError::InvalidArchive(
737             "AES encryption without AES extra data field",
738         ));
739     }
740 
741     // Account for shifted zip offsets.
742     result.header_start = result
743         .header_start
744         .checked_add(archive_offset)
745         .ok_or(ZipError::InvalidArchive("Archive header is too large"))?;
746 
747     Ok(result)
748 }
749 
parse_extra_field(file: &mut ZipFileData) -> ZipResult<()>750 fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
751     let mut reader = io::Cursor::new(&file.extra_field);
752 
753     while (reader.position() as usize) < file.extra_field.len() {
754         let kind = reader.read_u16::<LittleEndian>()?;
755         let len = reader.read_u16::<LittleEndian>()?;
756         let mut len_left = len as i64;
757         match kind {
758             // Zip64 extended information extra field
759             0x0001 => {
760                 if file.uncompressed_size == spec::ZIP64_BYTES_THR {
761                     file.large_file = true;
762                     file.uncompressed_size = reader.read_u64::<LittleEndian>()?;
763                     len_left -= 8;
764                 }
765                 if file.compressed_size == spec::ZIP64_BYTES_THR {
766                     file.large_file = true;
767                     file.compressed_size = reader.read_u64::<LittleEndian>()?;
768                     len_left -= 8;
769                 }
770                 if file.header_start == spec::ZIP64_BYTES_THR {
771                     file.header_start = reader.read_u64::<LittleEndian>()?;
772                     len_left -= 8;
773                 }
774             }
775             0x9901 => {
776                 // AES
777                 if len != 7 {
778                     return Err(ZipError::UnsupportedArchive(
779                         "AES extra data field has an unsupported length",
780                     ));
781                 }
782                 let vendor_version = reader.read_u16::<LittleEndian>()?;
783                 let vendor_id = reader.read_u16::<LittleEndian>()?;
784                 let aes_mode = reader.read_u8()?;
785                 let compression_method = reader.read_u16::<LittleEndian>()?;
786 
787                 if vendor_id != 0x4541 {
788                     return Err(ZipError::InvalidArchive("Invalid AES vendor"));
789                 }
790                 let vendor_version = match vendor_version {
791                     0x0001 => AesVendorVersion::Ae1,
792                     0x0002 => AesVendorVersion::Ae2,
793                     _ => return Err(ZipError::InvalidArchive("Invalid AES vendor version")),
794                 };
795                 match aes_mode {
796                     0x01 => file.aes_mode = Some((AesMode::Aes128, vendor_version)),
797                     0x02 => file.aes_mode = Some((AesMode::Aes192, vendor_version)),
798                     0x03 => file.aes_mode = Some((AesMode::Aes256, vendor_version)),
799                     _ => return Err(ZipError::InvalidArchive("Invalid AES encryption strength")),
800                 };
801                 file.compression_method = {
802                     #[allow(deprecated)]
803                     CompressionMethod::from_u16(compression_method)
804                 };
805             }
806             _ => {
807                 // Other fields are ignored
808             }
809         }
810 
811         // We could also check for < 0 to check for errors
812         if len_left > 0 {
813             reader.seek(io::SeekFrom::Current(len_left))?;
814         }
815     }
816     Ok(())
817 }
818 
819 /// Methods for retrieving information on zip files
820 impl<'a> ZipFile<'a> {
get_reader(&mut self) -> &mut ZipFileReader<'a>821     fn get_reader(&mut self) -> &mut ZipFileReader<'a> {
822         if let ZipFileReader::NoReader = self.reader {
823             let data = &self.data;
824             let crypto_reader = self.crypto_reader.take().expect("Invalid reader state");
825             self.reader = make_reader(data.compression_method, data.crc32, crypto_reader)
826         }
827         &mut self.reader
828     }
829 
get_raw_reader(&mut self) -> &mut dyn Read830     pub(crate) fn get_raw_reader(&mut self) -> &mut dyn Read {
831         if let ZipFileReader::NoReader = self.reader {
832             let crypto_reader = self.crypto_reader.take().expect("Invalid reader state");
833             self.reader = ZipFileReader::Raw(crypto_reader.into_inner())
834         }
835         &mut self.reader
836     }
837 
838     /// Get the version of the file
version_made_by(&self) -> (u8, u8)839     pub fn version_made_by(&self) -> (u8, u8) {
840         (
841             self.data.version_made_by / 10,
842             self.data.version_made_by % 10,
843         )
844     }
845 
846     /// Get the name of the file
847     ///
848     /// # Warnings
849     ///
850     /// It is dangerous to use this name directly when extracting an archive.
851     /// It may contain an absolute path (`/etc/shadow`), or break out of the
852     /// current directory (`../runtime`). Carelessly writing to these paths
853     /// allows an attacker to craft a ZIP archive that will overwrite critical
854     /// files.
855     ///
856     /// You can use the [`ZipFile::enclosed_name`] method to validate the name
857     /// as a safe path.
name(&self) -> &str858     pub fn name(&self) -> &str {
859         &self.data.file_name
860     }
861 
862     /// Get the name of the file, in the raw (internal) byte representation.
863     ///
864     /// The encoding of this data is currently undefined.
name_raw(&self) -> &[u8]865     pub fn name_raw(&self) -> &[u8] {
866         &self.data.file_name_raw
867     }
868 
869     /// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte,
870     /// removes a leading '/' and removes '..' parts.
871     #[deprecated(
872         since = "0.5.7",
873         note = "by stripping `..`s from the path, the meaning of paths can change.
874                 `mangled_name` can be used if this behaviour is desirable"
875     )]
sanitized_name(&self) -> ::std::path::PathBuf876     pub fn sanitized_name(&self) -> ::std::path::PathBuf {
877         self.mangled_name()
878     }
879 
880     /// Rewrite the path, ignoring any path components with special meaning.
881     ///
882     /// - Absolute paths are made relative
883     /// - [`ParentDir`]s are ignored
884     /// - Truncates the filename at a NULL byte
885     ///
886     /// This is appropriate if you need to be able to extract *something* from
887     /// any archive, but will easily misrepresent trivial paths like
888     /// `foo/../bar` as `foo/bar` (instead of `bar`). Because of this,
889     /// [`ZipFile::enclosed_name`] is the better option in most scenarios.
890     ///
891     /// [`ParentDir`]: `Component::ParentDir`
mangled_name(&self) -> ::std::path::PathBuf892     pub fn mangled_name(&self) -> ::std::path::PathBuf {
893         self.data.file_name_sanitized()
894     }
895 
896     /// Ensure the file path is safe to use as a [`Path`].
897     ///
898     /// - It can't contain NULL bytes
899     /// - It can't resolve to a path outside the current directory
900     ///   > `foo/../bar` is fine, `foo/../../bar` is not.
901     /// - It can't be an absolute path
902     ///
903     /// This will read well-formed ZIP files correctly, and is resistant
904     /// to path-based exploits. It is recommended over
905     /// [`ZipFile::mangled_name`].
enclosed_name(&self) -> Option<&Path>906     pub fn enclosed_name(&self) -> Option<&Path> {
907         self.data.enclosed_name()
908     }
909 
910     /// Get the comment of the file
comment(&self) -> &str911     pub fn comment(&self) -> &str {
912         &self.data.file_comment
913     }
914 
915     /// Get the compression method used to store the file
compression(&self) -> CompressionMethod916     pub fn compression(&self) -> CompressionMethod {
917         self.data.compression_method
918     }
919 
920     /// Get the size of the file, in bytes, in the archive
compressed_size(&self) -> u64921     pub fn compressed_size(&self) -> u64 {
922         self.data.compressed_size
923     }
924 
925     /// Get the size of the file, in bytes, when uncompressed
size(&self) -> u64926     pub fn size(&self) -> u64 {
927         self.data.uncompressed_size
928     }
929 
930     /// Get the time the file was last modified
last_modified(&self) -> DateTime931     pub fn last_modified(&self) -> DateTime {
932         self.data.last_modified_time
933     }
934     /// Returns whether the file is actually a directory
is_dir(&self) -> bool935     pub fn is_dir(&self) -> bool {
936         self.name()
937             .chars()
938             .rev()
939             .next()
940             .map_or(false, |c| c == '/' || c == '\\')
941     }
942 
943     /// Returns whether the file is a regular file
is_file(&self) -> bool944     pub fn is_file(&self) -> bool {
945         !self.is_dir()
946     }
947 
948     /// Get unix mode for the file
unix_mode(&self) -> Option<u32>949     pub fn unix_mode(&self) -> Option<u32> {
950         self.data.unix_mode()
951     }
952 
953     /// Get the CRC32 hash of the original file
crc32(&self) -> u32954     pub fn crc32(&self) -> u32 {
955         self.data.crc32
956     }
957 
958     /// Get the extra data of the zip header for this file
extra_data(&self) -> &[u8]959     pub fn extra_data(&self) -> &[u8] {
960         &self.data.extra_field
961     }
962 
963     /// Get the starting offset of the data of the compressed file
data_start(&self) -> u64964     pub fn data_start(&self) -> u64 {
965         self.data.data_start.load()
966     }
967 
968     /// Get the starting offset of the zip header for this file
header_start(&self) -> u64969     pub fn header_start(&self) -> u64 {
970         self.data.header_start
971     }
972     /// Get the starting offset of the zip header in the central directory for this file
central_header_start(&self) -> u64973     pub fn central_header_start(&self) -> u64 {
974         self.data.central_header_start
975     }
976 }
977 
978 impl<'a> Read for ZipFile<'a> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>979     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
980         self.get_reader().read(buf)
981     }
982 }
983 
984 impl<'a> Drop for ZipFile<'a> {
drop(&mut self)985     fn drop(&mut self) {
986         // self.data is Owned, this reader is constructed by a streaming reader.
987         // In this case, we want to exhaust the reader so that the next file is accessible.
988         if let Cow::Owned(_) = self.data {
989             let mut buffer = [0; 1 << 16];
990 
991             // Get the inner `Take` reader so all decryption, decompression and CRC calculation is skipped.
992             let mut reader: std::io::Take<&mut dyn std::io::Read> = match &mut self.reader {
993                 ZipFileReader::NoReader => {
994                     let innerreader = ::std::mem::replace(&mut self.crypto_reader, None);
995                     innerreader.expect("Invalid reader state").into_inner()
996                 }
997                 reader => {
998                     let innerreader = ::std::mem::replace(reader, ZipFileReader::NoReader);
999                     innerreader.into_inner()
1000                 }
1001             };
1002 
1003             loop {
1004                 match reader.read(&mut buffer) {
1005                     Ok(0) => break,
1006                     Ok(_) => (),
1007                     Err(e) => {
1008                         panic!("Could not consume all of the output of the current ZipFile: {e:?}")
1009                     }
1010                 }
1011             }
1012         }
1013     }
1014 }
1015 
1016 /// Read ZipFile structures from a non-seekable reader.
1017 ///
1018 /// This is an alternative method to read a zip file. If possible, use the ZipArchive functions
1019 /// as some information will be missing when reading this manner.
1020 ///
1021 /// Reads a file header from the start of the stream. Will return `Ok(Some(..))` if a file is
1022 /// present at the start of the stream. Returns `Ok(None)` if the start of the central directory
1023 /// is encountered. No more files should be read after this.
1024 ///
1025 /// The Drop implementation of ZipFile ensures that the reader will be correctly positioned after
1026 /// the structure is done.
1027 ///
1028 /// Missing fields are:
1029 /// * `comment`: set to an empty string
1030 /// * `data_start`: set to 0
1031 /// * `external_attributes`: `unix_mode()`: will return None
read_zipfile_from_stream<'a, R: io::Read>( reader: &'a mut R, ) -> ZipResult<Option<ZipFile<'_>>>1032 pub fn read_zipfile_from_stream<'a, R: io::Read>(
1033     reader: &'a mut R,
1034 ) -> ZipResult<Option<ZipFile<'_>>> {
1035     let signature = reader.read_u32::<LittleEndian>()?;
1036 
1037     match signature {
1038         spec::LOCAL_FILE_HEADER_SIGNATURE => (),
1039         spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE => return Ok(None),
1040         _ => return Err(ZipError::InvalidArchive("Invalid local file header")),
1041     }
1042 
1043     let version_made_by = reader.read_u16::<LittleEndian>()?;
1044     let flags = reader.read_u16::<LittleEndian>()?;
1045     let encrypted = flags & 1 == 1;
1046     let is_utf8 = flags & (1 << 11) != 0;
1047     let using_data_descriptor = flags & (1 << 3) != 0;
1048     #[allow(deprecated)]
1049     let compression_method = CompressionMethod::from_u16(reader.read_u16::<LittleEndian>()?);
1050     let last_mod_time = reader.read_u16::<LittleEndian>()?;
1051     let last_mod_date = reader.read_u16::<LittleEndian>()?;
1052     let crc32 = reader.read_u32::<LittleEndian>()?;
1053     let compressed_size = reader.read_u32::<LittleEndian>()?;
1054     let uncompressed_size = reader.read_u32::<LittleEndian>()?;
1055     let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
1056     let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
1057 
1058     let mut file_name_raw = vec![0; file_name_length];
1059     reader.read_exact(&mut file_name_raw)?;
1060     let mut extra_field = vec![0; extra_field_length];
1061     reader.read_exact(&mut extra_field)?;
1062 
1063     let file_name = match is_utf8 {
1064         true => String::from_utf8_lossy(&file_name_raw).into_owned(),
1065         false => file_name_raw.clone().from_cp437(),
1066     };
1067 
1068     let mut result = ZipFileData {
1069         system: System::from_u8((version_made_by >> 8) as u8),
1070         version_made_by: version_made_by as u8,
1071         encrypted,
1072         using_data_descriptor,
1073         compression_method,
1074         compression_level: None,
1075         last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
1076         crc32,
1077         compressed_size: compressed_size as u64,
1078         uncompressed_size: uncompressed_size as u64,
1079         file_name,
1080         file_name_raw,
1081         extra_field,
1082         file_comment: String::new(), // file comment is only available in the central directory
1083         // header_start and data start are not available, but also don't matter, since seeking is
1084         // not available.
1085         header_start: 0,
1086         data_start: AtomicU64::new(0),
1087         central_header_start: 0,
1088         // The external_attributes field is only available in the central directory.
1089         // We set this to zero, which should be valid as the docs state 'If input came
1090         // from standard input, this field is set to zero.'
1091         external_attributes: 0,
1092         large_file: false,
1093         aes_mode: None,
1094     };
1095 
1096     match parse_extra_field(&mut result) {
1097         Ok(..) | Err(ZipError::Io(..)) => {}
1098         Err(e) => return Err(e),
1099     }
1100 
1101     if encrypted {
1102         return unsupported_zip_error("Encrypted files are not supported");
1103     }
1104     if using_data_descriptor {
1105         return unsupported_zip_error("The file length is not available in the local header");
1106     }
1107 
1108     let limit_reader = (reader as &'a mut dyn io::Read).take(result.compressed_size);
1109 
1110     let result_crc32 = result.crc32;
1111     let result_compression_method = result.compression_method;
1112     let crypto_reader = make_crypto_reader(
1113         result_compression_method,
1114         result_crc32,
1115         result.last_modified_time,
1116         result.using_data_descriptor,
1117         limit_reader,
1118         None,
1119         None,
1120         #[cfg(feature = "aes-crypto")]
1121         result.compressed_size,
1122     )?
1123     .unwrap();
1124 
1125     Ok(Some(ZipFile {
1126         data: Cow::Owned(result),
1127         crypto_reader: None,
1128         reader: make_reader(result_compression_method, result_crc32, crypto_reader),
1129     }))
1130 }
1131 
1132 #[cfg(test)]
1133 mod test {
1134     #[test]
invalid_offset()1135     fn invalid_offset() {
1136         use super::ZipArchive;
1137         use std::io;
1138 
1139         let mut v = Vec::new();
1140         v.extend_from_slice(include_bytes!("../tests/data/invalid_offset.zip"));
1141         let reader = ZipArchive::new(io::Cursor::new(v));
1142         assert!(reader.is_err());
1143     }
1144 
1145     #[test]
invalid_offset2()1146     fn invalid_offset2() {
1147         use super::ZipArchive;
1148         use std::io;
1149 
1150         let mut v = Vec::new();
1151         v.extend_from_slice(include_bytes!("../tests/data/invalid_offset2.zip"));
1152         let reader = ZipArchive::new(io::Cursor::new(v));
1153         assert!(reader.is_err());
1154     }
1155 
1156     #[test]
zip64_with_leading_junk()1157     fn zip64_with_leading_junk() {
1158         use super::ZipArchive;
1159         use std::io;
1160 
1161         let mut v = Vec::new();
1162         v.extend_from_slice(include_bytes!("../tests/data/zip64_demo.zip"));
1163         let reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
1164         assert_eq!(reader.len(), 1);
1165     }
1166 
1167     #[test]
zip_contents()1168     fn zip_contents() {
1169         use super::ZipArchive;
1170         use std::io;
1171 
1172         let mut v = Vec::new();
1173         v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
1174         let mut reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
1175         assert_eq!(reader.comment(), b"");
1176         assert_eq!(reader.by_index(0).unwrap().central_header_start(), 77);
1177     }
1178 
1179     #[test]
zip_read_streaming()1180     fn zip_read_streaming() {
1181         use super::read_zipfile_from_stream;
1182         use std::io;
1183 
1184         let mut v = Vec::new();
1185         v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
1186         let mut reader = io::Cursor::new(v);
1187         loop {
1188             if read_zipfile_from_stream(&mut reader).unwrap().is_none() {
1189                 break;
1190             }
1191         }
1192     }
1193 
1194     #[test]
zip_clone()1195     fn zip_clone() {
1196         use super::ZipArchive;
1197         use std::io::{self, Read};
1198 
1199         let mut v = Vec::new();
1200         v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
1201         let mut reader1 = ZipArchive::new(io::Cursor::new(v)).unwrap();
1202         let mut reader2 = reader1.clone();
1203 
1204         let mut file1 = reader1.by_index(0).unwrap();
1205         let mut file2 = reader2.by_index(0).unwrap();
1206 
1207         let t = file1.last_modified();
1208         assert_eq!(
1209             (
1210                 t.year(),
1211                 t.month(),
1212                 t.day(),
1213                 t.hour(),
1214                 t.minute(),
1215                 t.second()
1216             ),
1217             (1980, 1, 1, 0, 0, 0)
1218         );
1219 
1220         let mut buf1 = [0; 5];
1221         let mut buf2 = [0; 5];
1222         let mut buf3 = [0; 5];
1223         let mut buf4 = [0; 5];
1224 
1225         file1.read_exact(&mut buf1).unwrap();
1226         file2.read_exact(&mut buf2).unwrap();
1227         file1.read_exact(&mut buf3).unwrap();
1228         file2.read_exact(&mut buf4).unwrap();
1229 
1230         assert_eq!(buf1, buf2);
1231         assert_eq!(buf3, buf4);
1232         assert_ne!(buf1, buf3);
1233     }
1234 
1235     #[test]
file_and_dir_predicates()1236     fn file_and_dir_predicates() {
1237         use super::ZipArchive;
1238         use std::io;
1239 
1240         let mut v = Vec::new();
1241         v.extend_from_slice(include_bytes!("../tests/data/files_and_dirs.zip"));
1242         let mut zip = ZipArchive::new(io::Cursor::new(v)).unwrap();
1243 
1244         for i in 0..zip.len() {
1245             let zip_file = zip.by_index(i).unwrap();
1246             let full_name = zip_file.enclosed_name().unwrap();
1247             let file_name = full_name.file_name().unwrap().to_str().unwrap();
1248             assert!(
1249                 (file_name.starts_with("dir") && zip_file.is_dir())
1250                     || (file_name.starts_with("file") && zip_file.is_file())
1251             );
1252         }
1253     }
1254 
1255     /// test case to ensure we don't preemptively over allocate based on the
1256     /// declared number of files in the CDE of an invalid zip when the number of
1257     /// files declared is more than the alleged offset in the CDE
1258     #[test]
invalid_cde_number_of_files_allocation_smaller_offset()1259     fn invalid_cde_number_of_files_allocation_smaller_offset() {
1260         use super::ZipArchive;
1261         use std::io;
1262 
1263         let mut v = Vec::new();
1264         v.extend_from_slice(include_bytes!(
1265             "../tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip"
1266         ));
1267         let reader = ZipArchive::new(io::Cursor::new(v));
1268         assert!(reader.is_err());
1269     }
1270 
1271     /// test case to ensure we don't preemptively over allocate based on the
1272     /// declared number of files in the CDE of an invalid zip when the number of
1273     /// files declared is less than the alleged offset in the CDE
1274     #[test]
invalid_cde_number_of_files_allocation_greater_offset()1275     fn invalid_cde_number_of_files_allocation_greater_offset() {
1276         use super::ZipArchive;
1277         use std::io;
1278 
1279         let mut v = Vec::new();
1280         v.extend_from_slice(include_bytes!(
1281             "../tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip"
1282         ));
1283         let reader = ZipArchive::new(io::Cursor::new(v));
1284         assert!(reader.is_err());
1285     }
1286 }
1287