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