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