1 //! Types for reading ZIP archives
2 
3 use crate::compression::CompressionMethod;
4 use crate::crc32::Crc32Reader;
5 use crate::result::{InvalidPassword, ZipError, ZipResult};
6 use crate::spec;
7 use crate::zipcrypto::ZipCryptoReader;
8 use crate::zipcrypto::ZipCryptoReaderValid;
9 use std::borrow::Cow;
10 use std::collections::HashMap;
11 use std::io::{self, prelude::*};
12 
13 use crate::cp437::FromCp437;
14 use crate::types::{DateTime, System, ZipFileData};
15 use byteorder::{LittleEndian, ReadBytesExt};
16 
17 #[cfg(any(
18     feature = "deflate",
19     feature = "deflate-miniz",
20     feature = "deflate-zlib"
21 ))]
22 use flate2::read::DeflateDecoder;
23 
24 #[cfg(feature = "bzip2")]
25 use bzip2::read::BzDecoder;
26 
27 mod ffi {
28     pub const S_IFDIR: u32 = 0o0040000;
29     pub const S_IFREG: u32 = 0o0100000;
30 }
31 
32 /// ZIP archive reader
33 ///
34 /// ```no_run
35 /// use std::io::prelude::*;
36 /// fn list_zip_contents(reader: impl Read + Seek) -> zip::result::ZipResult<()> {
37 ///     let mut zip = zip::ZipArchive::new(reader)?;
38 ///
39 ///     for i in 0..zip.len() {
40 ///         let mut file = zip.by_index(i)?;
41 ///         println!("Filename: {}", file.name());
42 ///         std::io::copy(&mut file, &mut std::io::stdout());
43 ///     }
44 ///
45 ///     Ok(())
46 /// }
47 /// ```
48 #[derive(Clone, Debug)]
49 pub struct ZipArchive<R: Read + io::Seek> {
50     reader: R,
51     files: Vec<ZipFileData>,
52     names_map: HashMap<String, usize>,
53     offset: u64,
54     comment: Vec<u8>,
55 }
56 
57 enum CryptoReader<'a> {
58     Plaintext(io::Take<&'a mut dyn Read>),
59     ZipCrypto(ZipCryptoReaderValid<io::Take<&'a mut dyn Read>>),
60 }
61 
62 impl<'a> Read for CryptoReader<'a> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>63     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
64         match self {
65             CryptoReader::Plaintext(r) => r.read(buf),
66             CryptoReader::ZipCrypto(r) => r.read(buf),
67         }
68     }
69 }
70 
71 impl<'a> CryptoReader<'a> {
72     /// Consumes this decoder, returning the underlying reader.
into_inner(self) -> io::Take<&'a mut dyn Read>73     pub fn into_inner(self) -> io::Take<&'a mut dyn Read> {
74         match self {
75             CryptoReader::Plaintext(r) => r,
76             CryptoReader::ZipCrypto(r) => r.into_inner(),
77         }
78     }
79 }
80 
81 enum ZipFileReader<'a> {
82     NoReader,
83     Stored(Crc32Reader<CryptoReader<'a>>),
84     #[cfg(any(
85         feature = "deflate",
86         feature = "deflate-miniz",
87         feature = "deflate-zlib"
88     ))]
89     Deflated(Crc32Reader<flate2::read::DeflateDecoder<CryptoReader<'a>>>),
90     #[cfg(feature = "bzip2")]
91     Bzip2(Crc32Reader<BzDecoder<CryptoReader<'a>>>),
92 }
93 
94 impl<'a> Read for ZipFileReader<'a> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>95     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
96         match self {
97             ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
98             ZipFileReader::Stored(r) => r.read(buf),
99             #[cfg(any(
100                 feature = "deflate",
101                 feature = "deflate-miniz",
102                 feature = "deflate-zlib"
103             ))]
104             ZipFileReader::Deflated(r) => r.read(buf),
105             #[cfg(feature = "bzip2")]
106             ZipFileReader::Bzip2(r) => r.read(buf),
107         }
108     }
109 }
110 
111 impl<'a> ZipFileReader<'a> {
112     /// Consumes this decoder, returning the underlying reader.
into_inner(self) -> io::Take<&'a mut dyn Read>113     pub fn into_inner(self) -> io::Take<&'a mut dyn Read> {
114         match self {
115             ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
116             ZipFileReader::Stored(r) => r.into_inner().into_inner(),
117             #[cfg(any(
118                 feature = "deflate",
119                 feature = "deflate-miniz",
120                 feature = "deflate-zlib"
121             ))]
122             ZipFileReader::Deflated(r) => r.into_inner().into_inner().into_inner(),
123             #[cfg(feature = "bzip2")]
124             ZipFileReader::Bzip2(r) => r.into_inner().into_inner().into_inner(),
125         }
126     }
127 }
128 
129 /// A struct for reading a zip file
130 pub struct ZipFile<'a> {
131     data: Cow<'a, ZipFileData>,
132     reader: ZipFileReader<'a>,
133 }
134 
make_reader<'a>( compression_method: crate::compression::CompressionMethod, crc32: u32, reader: io::Take<&'a mut dyn io::Read>, password: Option<&[u8]>, ) -> ZipResult<Result<ZipFileReader<'a>, InvalidPassword>>135 fn make_reader<'a>(
136     compression_method: crate::compression::CompressionMethod,
137     crc32: u32,
138     reader: io::Take<&'a mut dyn io::Read>,
139     password: Option<&[u8]>,
140 ) -> ZipResult<Result<ZipFileReader<'a>, InvalidPassword>> {
141     let reader = match password {
142         None => CryptoReader::Plaintext(reader),
143         Some(password) => match ZipCryptoReader::new(reader, password).validate(crc32)? {
144             None => return Ok(Err(InvalidPassword)),
145             Some(r) => CryptoReader::ZipCrypto(r),
146         },
147     };
148 
149     match compression_method {
150         CompressionMethod::Stored => Ok(Ok(ZipFileReader::Stored(Crc32Reader::new(reader, crc32)))),
151         #[cfg(any(
152             feature = "deflate",
153             feature = "deflate-miniz",
154             feature = "deflate-zlib"
155         ))]
156         CompressionMethod::Deflated => {
157             let deflate_reader = DeflateDecoder::new(reader);
158             Ok(Ok(ZipFileReader::Deflated(Crc32Reader::new(
159                 deflate_reader,
160                 crc32,
161             ))))
162         }
163         #[cfg(feature = "bzip2")]
164         CompressionMethod::Bzip2 => {
165             let bzip2_reader = BzDecoder::new(reader);
166             Ok(Ok(ZipFileReader::Bzip2(Crc32Reader::new(
167                 bzip2_reader,
168                 crc32,
169             ))))
170         }
171         _ => unsupported_zip_error("Compression method not supported"),
172     }
173 }
174 
175 impl<R: Read + io::Seek> ZipArchive<R> {
176     /// Get the directory start offset and number of files. This is done in a
177     /// 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)>178     fn get_directory_counts(
179         reader: &mut R,
180         footer: &spec::CentralDirectoryEnd,
181         cde_start_pos: u64,
182     ) -> ZipResult<(u64, u64, usize)> {
183         // See if there's a ZIP64 footer. The ZIP64 locator if present will
184         // have its signature 20 bytes in front of the standard footer. The
185         // standard footer, in turn, is 22+N bytes large, where N is the
186         // comment length. Therefore:
187         let zip64locator = if reader
188             .seek(io::SeekFrom::End(
189                 -(20 + 22 + footer.zip_file_comment.len() as i64),
190             ))
191             .is_ok()
192         {
193             match spec::Zip64CentralDirectoryEndLocator::parse(reader) {
194                 Ok(loc) => Some(loc),
195                 Err(ZipError::InvalidArchive(_)) => {
196                     // No ZIP64 header; that's actually fine. We're done here.
197                     None
198                 }
199                 Err(e) => {
200                     // Yikes, a real problem
201                     return Err(e);
202                 }
203             }
204         } else {
205             // Empty Zip files will have nothing else so this error might be fine. If
206             // not, we'll find out soon.
207             None
208         };
209 
210         match zip64locator {
211             None => {
212                 // Some zip files have data prepended to them, resulting in the
213                 // offsets all being too small. Get the amount of error by comparing
214                 // the actual file position we found the CDE at with the offset
215                 // recorded in the CDE.
216                 let archive_offset = cde_start_pos
217                     .checked_sub(footer.central_directory_size as u64)
218                     .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
219                     .ok_or(ZipError::InvalidArchive(
220                         "Invalid central directory size or offset",
221                     ))?;
222 
223                 let directory_start = footer.central_directory_offset as u64 + archive_offset;
224                 let number_of_files = footer.number_of_files_on_this_disk as usize;
225                 Ok((archive_offset, directory_start, number_of_files))
226             }
227             Some(locator64) => {
228                 // If we got here, this is indeed a ZIP64 file.
229 
230                 if footer.disk_number as u32 != locator64.disk_with_central_directory {
231                     return unsupported_zip_error(
232                         "Support for multi-disk files is not implemented",
233                     );
234                 }
235 
236                 // We need to reassess `archive_offset`. We know where the ZIP64
237                 // central-directory-end structure *should* be, but unfortunately we
238                 // don't know how to precisely relate that location to our current
239                 // actual offset in the file, since there may be junk at its
240                 // beginning. Therefore we need to perform another search, as in
241                 // read::CentralDirectoryEnd::find_and_parse, except now we search
242                 // forward.
243 
244                 let search_upper_bound = cde_start_pos
245                     .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator
246                     .ok_or(ZipError::InvalidArchive(
247                         "File cannot contain ZIP64 central directory end",
248                     ))?;
249                 let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse(
250                     reader,
251                     locator64.end_of_central_directory_offset,
252                     search_upper_bound,
253                 )?;
254 
255                 if footer.disk_number != footer.disk_with_central_directory {
256                     return unsupported_zip_error(
257                         "Support for multi-disk files is not implemented",
258                     );
259                 }
260 
261                 let directory_start = footer
262                     .central_directory_offset
263                     .checked_add(archive_offset)
264                     .ok_or_else(|| {
265                         ZipError::InvalidArchive("Invalid central directory size or offset")
266                     })?;
267 
268                 Ok((
269                     archive_offset,
270                     directory_start,
271                     footer.number_of_files as usize,
272                 ))
273             }
274         }
275     }
276 
277     /// Read a ZIP archive, collecting the files it contains
278     ///
279     /// This uses the central directory record of the ZIP file, and ignores local file headers
new(mut reader: R) -> ZipResult<ZipArchive<R>>280     pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
281         let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
282 
283         if footer.disk_number != footer.disk_with_central_directory {
284             return unsupported_zip_error("Support for multi-disk files is not implemented");
285         }
286 
287         let (archive_offset, directory_start, number_of_files) =
288             Self::get_directory_counts(&mut reader, &footer, cde_start_pos)?;
289 
290         let mut files = Vec::new();
291         let mut names_map = HashMap::new();
292 
293         if let Err(_) = reader.seek(io::SeekFrom::Start(directory_start)) {
294             return Err(ZipError::InvalidArchive(
295                 "Could not seek to start of central directory",
296             ));
297         }
298 
299         for _ in 0..number_of_files {
300             let file = central_header_to_zip_file(&mut reader, archive_offset)?;
301             names_map.insert(file.file_name.clone(), files.len());
302             files.push(file);
303         }
304 
305         Ok(ZipArchive {
306             reader,
307             files,
308             names_map,
309             offset: archive_offset,
310             comment: footer.zip_file_comment,
311         })
312     }
313 
314     /// Number of files contained in this zip.
len(&self) -> usize315     pub fn len(&self) -> usize {
316         self.files.len()
317     }
318 
319     /// Whether this zip archive contains no files
is_empty(&self) -> bool320     pub fn is_empty(&self) -> bool {
321         self.len() == 0
322     }
323 
324     /// Get the offset from the beginning of the underlying reader that this zip begins at, in bytes.
325     ///
326     /// Normally this value is zero, but if the zip has arbitrary data prepended to it, then this value will be the size
327     /// of that prepended data.
offset(&self) -> u64328     pub fn offset(&self) -> u64 {
329         self.offset
330     }
331 
332     /// Get the comment of the zip archive.
comment(&self) -> &[u8]333     pub fn comment(&self) -> &[u8] {
334         &self.comment
335     }
336 
337     /// Returns an iterator over all the file and directory names in this archive.
file_names(&self) -> impl Iterator<Item = &str>338     pub fn file_names(&self) -> impl Iterator<Item = &str> {
339         self.names_map.keys().map(|s| s.as_str())
340     }
341 
342     /// Search for a file entry by name, decrypt with given password
by_name_decrypt<'a>( &'a mut self, name: &str, password: &[u8], ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>>343     pub fn by_name_decrypt<'a>(
344         &'a mut self,
345         name: &str,
346         password: &[u8],
347     ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
348         self.by_name_with_optional_password(name, Some(password))
349     }
350 
351     /// Search for a file entry by name
by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>>352     pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
353         Ok(self.by_name_with_optional_password(name, None)?.unwrap())
354     }
355 
by_name_with_optional_password<'a>( &'a mut self, name: &str, password: Option<&[u8]>, ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>>356     fn by_name_with_optional_password<'a>(
357         &'a mut self,
358         name: &str,
359         password: Option<&[u8]>,
360     ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
361         let index = match self.names_map.get(name) {
362             Some(index) => *index,
363             None => {
364                 return Err(ZipError::FileNotFound);
365             }
366         };
367         self.by_index_with_optional_password(index, password)
368     }
369 
370     /// Get a contained file by index, decrypt with given password
by_index_decrypt<'a>( &'a mut self, file_number: usize, password: &[u8], ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>>371     pub fn by_index_decrypt<'a>(
372         &'a mut self,
373         file_number: usize,
374         password: &[u8],
375     ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
376         self.by_index_with_optional_password(file_number, Some(password))
377     }
378 
379     /// Get a contained file by index
by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>>380     pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>> {
381         Ok(self
382             .by_index_with_optional_password(file_number, None)?
383             .unwrap())
384     }
385 
by_index_with_optional_password<'a>( &'a mut self, file_number: usize, mut password: Option<&[u8]>, ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>>386     fn by_index_with_optional_password<'a>(
387         &'a mut self,
388         file_number: usize,
389         mut password: Option<&[u8]>,
390     ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
391         if file_number >= self.files.len() {
392             return Err(ZipError::FileNotFound);
393         }
394         let data = &mut self.files[file_number];
395 
396         match (password, data.encrypted) {
397             (None, true) => {
398                 return Err(ZipError::UnsupportedArchive(
399                     "Password required to decrypt file",
400                 ))
401             }
402             (Some(_), false) => password = None, //Password supplied, but none needed! Discard.
403             _ => {}
404         }
405 
406         // Parse local header
407         self.reader.seek(io::SeekFrom::Start(data.header_start))?;
408         let signature = self.reader.read_u32::<LittleEndian>()?;
409         if signature != spec::LOCAL_FILE_HEADER_SIGNATURE {
410             return Err(ZipError::InvalidArchive("Invalid local file header"));
411         }
412 
413         self.reader.seek(io::SeekFrom::Current(22))?;
414         let file_name_length = self.reader.read_u16::<LittleEndian>()? as u64;
415         let extra_field_length = self.reader.read_u16::<LittleEndian>()? as u64;
416         let magic_and_header = 4 + 22 + 2 + 2;
417         data.data_start =
418             data.header_start + magic_and_header + file_name_length + extra_field_length;
419 
420         self.reader.seek(io::SeekFrom::Start(data.data_start))?;
421         let limit_reader = (self.reader.by_ref() as &mut dyn Read).take(data.compressed_size);
422 
423         match make_reader(data.compression_method, data.crc32, limit_reader, password) {
424             Ok(Ok(reader)) => Ok(Ok(ZipFile {
425                 reader,
426                 data: Cow::Borrowed(data),
427             })),
428             Err(e) => Err(e),
429             Ok(Err(e)) => Ok(Err(e)),
430         }
431     }
432 
433     /// Unwrap and return the inner reader object
434     ///
435     /// The position of the reader is undefined.
into_inner(self) -> R436     pub fn into_inner(self) -> R {
437         self.reader
438     }
439 }
440 
unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T>441 fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
442     Err(ZipError::UnsupportedArchive(detail))
443 }
444 
central_header_to_zip_file<R: Read + io::Seek>( reader: &mut R, archive_offset: u64, ) -> ZipResult<ZipFileData>445 fn central_header_to_zip_file<R: Read + io::Seek>(
446     reader: &mut R,
447     archive_offset: u64,
448 ) -> ZipResult<ZipFileData> {
449     let central_header_start = reader.seek(io::SeekFrom::Current(0))?;
450     // Parse central header
451     let signature = reader.read_u32::<LittleEndian>()?;
452     if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
453         return Err(ZipError::InvalidArchive("Invalid Central Directory header"));
454     }
455 
456     let version_made_by = reader.read_u16::<LittleEndian>()?;
457     let _version_to_extract = reader.read_u16::<LittleEndian>()?;
458     let flags = reader.read_u16::<LittleEndian>()?;
459     let encrypted = flags & 1 == 1;
460     let is_utf8 = flags & (1 << 11) != 0;
461     let compression_method = reader.read_u16::<LittleEndian>()?;
462     let last_mod_time = reader.read_u16::<LittleEndian>()?;
463     let last_mod_date = reader.read_u16::<LittleEndian>()?;
464     let crc32 = reader.read_u32::<LittleEndian>()?;
465     let compressed_size = reader.read_u32::<LittleEndian>()?;
466     let uncompressed_size = reader.read_u32::<LittleEndian>()?;
467     let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
468     let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
469     let file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
470     let _disk_number = reader.read_u16::<LittleEndian>()?;
471     let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
472     let external_file_attributes = reader.read_u32::<LittleEndian>()?;
473     let offset = reader.read_u32::<LittleEndian>()? as u64;
474     let mut file_name_raw = vec![0; file_name_length];
475     reader.read_exact(&mut file_name_raw)?;
476     let mut extra_field = vec![0; extra_field_length];
477     reader.read_exact(&mut extra_field)?;
478     let mut file_comment_raw = vec![0; file_comment_length];
479     reader.read_exact(&mut file_comment_raw)?;
480 
481     let file_name = match is_utf8 {
482         true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
483         false => file_name_raw.clone().from_cp437(),
484     };
485     let file_comment = match is_utf8 {
486         true => String::from_utf8_lossy(&*file_comment_raw).into_owned(),
487         false => file_comment_raw.from_cp437(),
488     };
489 
490     // Construct the result
491     let mut result = ZipFileData {
492         system: System::from_u8((version_made_by >> 8) as u8),
493         version_made_by: version_made_by as u8,
494         encrypted,
495         compression_method: {
496             #[allow(deprecated)]
497             CompressionMethod::from_u16(compression_method)
498         },
499         last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
500         crc32,
501         compressed_size: compressed_size as u64,
502         uncompressed_size: uncompressed_size as u64,
503         file_name,
504         file_name_raw,
505         file_comment,
506         header_start: offset,
507         central_header_start,
508         data_start: 0,
509         external_attributes: external_file_attributes,
510     };
511 
512     match parse_extra_field(&mut result, &*extra_field) {
513         Ok(..) | Err(ZipError::Io(..)) => {}
514         Err(e) => return Err(e),
515     }
516 
517     // Account for shifted zip offsets.
518     result.header_start += archive_offset;
519 
520     Ok(result)
521 }
522 
parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()>523 fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()> {
524     let mut reader = io::Cursor::new(data);
525 
526     while (reader.position() as usize) < data.len() {
527         let kind = reader.read_u16::<LittleEndian>()?;
528         let len = reader.read_u16::<LittleEndian>()?;
529         let mut len_left = len as i64;
530         // Zip64 extended information extra field
531         if kind == 0x0001 {
532             if file.uncompressed_size == 0xFFFFFFFF {
533                 file.uncompressed_size = reader.read_u64::<LittleEndian>()?;
534                 len_left -= 8;
535             }
536             if file.compressed_size == 0xFFFFFFFF {
537                 file.compressed_size = reader.read_u64::<LittleEndian>()?;
538                 len_left -= 8;
539             }
540             if file.header_start == 0xFFFFFFFF {
541                 file.header_start = reader.read_u64::<LittleEndian>()?;
542                 len_left -= 8;
543             }
544             // Unparsed fields:
545             // u32: disk start number
546         }
547 
548         // We could also check for < 0 to check for errors
549         if len_left > 0 {
550             reader.seek(io::SeekFrom::Current(len_left))?;
551         }
552     }
553     Ok(())
554 }
555 
556 /// Methods for retrieving information on zip files
557 impl<'a> ZipFile<'a> {
558     /// Get the version of the file
version_made_by(&self) -> (u8, u8)559     pub fn version_made_by(&self) -> (u8, u8) {
560         (
561             self.data.version_made_by / 10,
562             self.data.version_made_by % 10,
563         )
564     }
565 
566     /// Get the name of the file
name(&self) -> &str567     pub fn name(&self) -> &str {
568         &self.data.file_name
569     }
570 
571     /// Get the name of the file, in the raw (internal) byte representation.
name_raw(&self) -> &[u8]572     pub fn name_raw(&self) -> &[u8] {
573         &self.data.file_name_raw
574     }
575 
576     /// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte,
577     /// removes a leading '/' and removes '..' parts.
578     #[deprecated(
579         since = "0.5.7",
580         note = "by stripping `..`s from the path, the meaning of paths can change.
581                 You must use a sanitization strategy that's appropriate for your input"
582     )]
sanitized_name(&self) -> ::std::path::PathBuf583     pub fn sanitized_name(&self) -> ::std::path::PathBuf {
584         self.data.file_name_sanitized()
585     }
586 
587     /// Get the comment of the file
comment(&self) -> &str588     pub fn comment(&self) -> &str {
589         &self.data.file_comment
590     }
591 
592     /// Get the compression method used to store the file
compression(&self) -> CompressionMethod593     pub fn compression(&self) -> CompressionMethod {
594         self.data.compression_method
595     }
596 
597     /// Get the size of the file in the archive
compressed_size(&self) -> u64598     pub fn compressed_size(&self) -> u64 {
599         self.data.compressed_size
600     }
601 
602     /// Get the size of the file when uncompressed
size(&self) -> u64603     pub fn size(&self) -> u64 {
604         self.data.uncompressed_size
605     }
606 
607     /// Get the time the file was last modified
last_modified(&self) -> DateTime608     pub fn last_modified(&self) -> DateTime {
609         self.data.last_modified_time
610     }
611     /// Returns whether the file is actually a directory
is_dir(&self) -> bool612     pub fn is_dir(&self) -> bool {
613         self.name()
614             .chars()
615             .rev()
616             .next()
617             .map_or(false, |c| c == '/' || c == '\\')
618     }
619 
620     /// Returns whether the file is a regular file
is_file(&self) -> bool621     pub fn is_file(&self) -> bool {
622         !self.is_dir()
623     }
624 
625     /// Get unix mode for the file
unix_mode(&self) -> Option<u32>626     pub fn unix_mode(&self) -> Option<u32> {
627         if self.data.external_attributes == 0 {
628             return None;
629         }
630 
631         match self.data.system {
632             System::Unix => Some(self.data.external_attributes >> 16),
633             System::Dos => {
634                 // Interpret MSDOS directory bit
635                 let mut mode = if 0x10 == (self.data.external_attributes & 0x10) {
636                     ffi::S_IFDIR | 0o0775
637                 } else {
638                     ffi::S_IFREG | 0o0664
639                 };
640                 if 0x01 == (self.data.external_attributes & 0x01) {
641                     // Read-only bit; strip write permissions
642                     mode &= 0o0555;
643                 }
644                 Some(mode)
645             }
646             _ => None,
647         }
648     }
649 
650     /// Get the CRC32 hash of the original file
crc32(&self) -> u32651     pub fn crc32(&self) -> u32 {
652         self.data.crc32
653     }
654 
655     /// Get the starting offset of the data of the compressed file
data_start(&self) -> u64656     pub fn data_start(&self) -> u64 {
657         self.data.data_start
658     }
659 
660     /// Get the starting offset of the zip header for this file
header_start(&self) -> u64661     pub fn header_start(&self) -> u64 {
662         self.data.header_start
663     }
664     /// Get the starting offset of the zip header in the central directory for this file
central_header_start(&self) -> u64665     pub fn central_header_start(&self) -> u64 {
666         self.data.central_header_start
667     }
668 }
669 
670 impl<'a> Read for ZipFile<'a> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>671     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
672         self.reader.read(buf)
673     }
674 }
675 
676 impl<'a> Drop for ZipFile<'a> {
drop(&mut self)677     fn drop(&mut self) {
678         // self.data is Owned, this reader is constructed by a streaming reader.
679         // In this case, we want to exhaust the reader so that the next file is accessible.
680         if let Cow::Owned(_) = self.data {
681             let mut buffer = [0; 1 << 16];
682 
683             // Get the inner `Take` reader so all decryption, decompression and CRC calculation is skipped.
684             let innerreader = ::std::mem::replace(&mut self.reader, ZipFileReader::NoReader);
685             let mut reader: std::io::Take<&mut dyn std::io::Read> = innerreader.into_inner();
686 
687             loop {
688                 match reader.read(&mut buffer) {
689                     Ok(0) => break,
690                     Ok(_) => (),
691                     Err(e) => panic!(
692                         "Could not consume all of the output of the current ZipFile: {:?}",
693                         e
694                     ),
695                 }
696             }
697         }
698     }
699 }
700 
701 /// Read ZipFile structures from a non-seekable reader.
702 ///
703 /// This is an alternative method to read a zip file. If possible, use the ZipArchive functions
704 /// as some information will be missing when reading this manner.
705 ///
706 /// Reads a file header from the start of the stream. Will return `Ok(Some(..))` if a file is
707 /// present at the start of the stream. Returns `Ok(None)` if the start of the central directory
708 /// is encountered. No more files should be read after this.
709 ///
710 /// The Drop implementation of ZipFile ensures that the reader will be correctly positioned after
711 /// the structure is done.
712 ///
713 /// Missing fields are:
714 /// * `comment`: set to an empty string
715 /// * `data_start`: set to 0
716 /// * `external_attributes`: `unix_mode()`: will return None
read_zipfile_from_stream<'a, R: io::Read>( reader: &'a mut R, ) -> ZipResult<Option<ZipFile<'_>>>717 pub fn read_zipfile_from_stream<'a, R: io::Read>(
718     reader: &'a mut R,
719 ) -> ZipResult<Option<ZipFile<'_>>> {
720     let signature = reader.read_u32::<LittleEndian>()?;
721 
722     match signature {
723         spec::LOCAL_FILE_HEADER_SIGNATURE => (),
724         spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE => return Ok(None),
725         _ => return Err(ZipError::InvalidArchive("Invalid local file header")),
726     }
727 
728     let version_made_by = reader.read_u16::<LittleEndian>()?;
729     let flags = reader.read_u16::<LittleEndian>()?;
730     let encrypted = flags & 1 == 1;
731     let is_utf8 = flags & (1 << 11) != 0;
732     let using_data_descriptor = flags & (1 << 3) != 0;
733     #[allow(deprecated)]
734     let compression_method = CompressionMethod::from_u16(reader.read_u16::<LittleEndian>()?);
735     let last_mod_time = reader.read_u16::<LittleEndian>()?;
736     let last_mod_date = reader.read_u16::<LittleEndian>()?;
737     let crc32 = reader.read_u32::<LittleEndian>()?;
738     let compressed_size = reader.read_u32::<LittleEndian>()?;
739     let uncompressed_size = reader.read_u32::<LittleEndian>()?;
740     let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
741     let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
742 
743     let mut file_name_raw = vec![0; file_name_length];
744     reader.read_exact(&mut file_name_raw)?;
745     let mut extra_field = vec![0; extra_field_length];
746     reader.read_exact(&mut extra_field)?;
747 
748     let file_name = match is_utf8 {
749         true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
750         false => file_name_raw.clone().from_cp437(),
751     };
752 
753     let mut result = ZipFileData {
754         system: System::from_u8((version_made_by >> 8) as u8),
755         version_made_by: version_made_by as u8,
756         encrypted,
757         compression_method,
758         last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
759         crc32,
760         compressed_size: compressed_size as u64,
761         uncompressed_size: uncompressed_size as u64,
762         file_name,
763         file_name_raw,
764         file_comment: String::new(), // file comment is only available in the central directory
765         // header_start and data start are not available, but also don't matter, since seeking is
766         // not available.
767         header_start: 0,
768         data_start: 0,
769         central_header_start: 0,
770         // The external_attributes field is only available in the central directory.
771         // We set this to zero, which should be valid as the docs state 'If input came
772         // from standard input, this field is set to zero.'
773         external_attributes: 0,
774     };
775 
776     match parse_extra_field(&mut result, &extra_field) {
777         Ok(..) | Err(ZipError::Io(..)) => {}
778         Err(e) => return Err(e),
779     }
780 
781     if encrypted {
782         return unsupported_zip_error("Encrypted files are not supported");
783     }
784     if using_data_descriptor {
785         return unsupported_zip_error("The file length is not available in the local header");
786     }
787 
788     let limit_reader = (reader as &'a mut dyn io::Read).take(result.compressed_size as u64);
789 
790     let result_crc32 = result.crc32;
791     let result_compression_method = result.compression_method;
792     Ok(Some(ZipFile {
793         data: Cow::Owned(result),
794         reader: make_reader(result_compression_method, result_crc32, limit_reader, None)?.unwrap(),
795     }))
796 }
797 
798 #[cfg(test)]
799 mod test {
800     #[test]
invalid_offset()801     fn invalid_offset() {
802         use super::ZipArchive;
803         use std::io;
804 
805         let mut v = Vec::new();
806         v.extend_from_slice(include_bytes!("../tests/data/invalid_offset.zip"));
807         let reader = ZipArchive::new(io::Cursor::new(v));
808         assert!(reader.is_err());
809     }
810 
811     #[test]
invalid_offset2()812     fn invalid_offset2() {
813         use super::ZipArchive;
814         use std::io;
815 
816         let mut v = Vec::new();
817         v.extend_from_slice(include_bytes!("../tests/data/invalid_offset2.zip"));
818         let reader = ZipArchive::new(io::Cursor::new(v));
819         assert!(reader.is_err());
820     }
821 
822     #[test]
zip64_with_leading_junk()823     fn zip64_with_leading_junk() {
824         use super::ZipArchive;
825         use std::io;
826 
827         let mut v = Vec::new();
828         v.extend_from_slice(include_bytes!("../tests/data/zip64_demo.zip"));
829         let reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
830         assert!(reader.len() == 1);
831     }
832 
833     #[test]
zip_contents()834     fn zip_contents() {
835         use super::ZipArchive;
836         use std::io;
837 
838         let mut v = Vec::new();
839         v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
840         let mut reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
841         assert!(reader.comment() == b"");
842         assert_eq!(reader.by_index(0).unwrap().central_header_start(), 77);
843     }
844 
845     #[test]
zip_read_streaming()846     fn zip_read_streaming() {
847         use super::read_zipfile_from_stream;
848         use std::io;
849 
850         let mut v = Vec::new();
851         v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
852         let mut reader = io::Cursor::new(v);
853         loop {
854             match read_zipfile_from_stream(&mut reader).unwrap() {
855                 None => break,
856                 _ => (),
857             }
858         }
859     }
860 
861     #[test]
zip_clone()862     fn zip_clone() {
863         use super::ZipArchive;
864         use std::io::{self, Read};
865 
866         let mut v = Vec::new();
867         v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
868         let mut reader1 = ZipArchive::new(io::Cursor::new(v)).unwrap();
869         let mut reader2 = reader1.clone();
870 
871         let mut file1 = reader1.by_index(0).unwrap();
872         let mut file2 = reader2.by_index(0).unwrap();
873 
874         let t = file1.last_modified();
875         assert_eq!(
876             (
877                 t.year(),
878                 t.month(),
879                 t.day(),
880                 t.hour(),
881                 t.minute(),
882                 t.second()
883             ),
884             (1980, 1, 1, 0, 0, 0)
885         );
886 
887         let mut buf1 = [0; 5];
888         let mut buf2 = [0; 5];
889         let mut buf3 = [0; 5];
890         let mut buf4 = [0; 5];
891 
892         file1.read(&mut buf1).unwrap();
893         file2.read(&mut buf2).unwrap();
894         file1.read(&mut buf3).unwrap();
895         file2.read(&mut buf4).unwrap();
896 
897         assert_eq!(buf1, buf2);
898         assert_eq!(buf3, buf4);
899         assert!(buf1 != buf3);
900     }
901 
902     #[test]
file_and_dir_predicates()903     fn file_and_dir_predicates() {
904         use super::ZipArchive;
905         use std::io;
906 
907         let mut v = Vec::new();
908         v.extend_from_slice(include_bytes!("../tests/data/files_and_dirs.zip"));
909         let mut zip = ZipArchive::new(io::Cursor::new(v)).unwrap();
910 
911         for i in 0..zip.len() {
912             let zip_file = zip.by_index(i).unwrap();
913             #[allow(deprecated)]
914             let full_name = zip_file.sanitized_name();
915             let file_name = full_name.file_name().unwrap().to_str().unwrap();
916             assert!(
917                 (file_name.starts_with("dir") && zip_file.is_dir())
918                     || (file_name.starts_with("file") && zip_file.is_file())
919             );
920         }
921     }
922 }
923