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