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