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