Coverage Report

Created: 2025-11-09 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/zip/src/read.rs
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
137k
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
87
137k
        match self {
88
137k
            CryptoReader::Plaintext(r) => r.read(buf),
89
0
            CryptoReader::ZipCrypto(r) => r.read(buf),
90
            #[cfg(feature = "aes-crypto")]
91
0
            CryptoReader::Aes { reader: r, .. } => r.read(buf),
92
        }
93
137k
    }
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
102k
    pub fn is_ae2_encrypted(&self) -> bool {
109
        #[cfg(feature = "aes-crypto")]
110
102k
        return matches!(
111
0
            self,
112
            CryptoReader::Aes {
113
                vendor_version: AesVendorVersion::Ae2,
114
                ..
115
            }
116
        );
117
        #[cfg(not(feature = "aes-crypto"))]
118
        false
119
102k
    }
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
199k
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
140
199k
        match self {
141
0
            ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
142
0
            ZipFileReader::Raw(r) => r.read(buf),
143
24.0k
            ZipFileReader::Stored(r) => r.read(buf),
144
            #[cfg(any(
145
                feature = "deflate",
146
                feature = "deflate-miniz",
147
                feature = "deflate-zlib"
148
            ))]
149
95.2k
            ZipFileReader::Deflated(r) => r.read(buf),
150
            #[cfg(feature = "bzip2")]
151
3.43k
            ZipFileReader::Bzip2(r) => r.read(buf),
152
            #[cfg(feature = "zstd")]
153
76.9k
            ZipFileReader::Zstd(r) => r.read(buf),
154
        }
155
199k
    }
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
102k
fn find_content<'a>(
187
102k
    data: &ZipFileData,
188
102k
    reader: &'a mut (impl Read + Seek),
189
102k
) -> ZipResult<io::Take<&'a mut dyn Read>> {
190
    // Parse local header
191
102k
    reader.seek(io::SeekFrom::Start(data.header_start))?;
192
102k
    let signature = reader.read_u32::<LittleEndian>()?;
193
102k
    if signature != spec::LOCAL_FILE_HEADER_SIGNATURE {
194
3
        return Err(ZipError::InvalidArchive("Invalid local file header"));
195
102k
    }
196
197
102k
    reader.seek(io::SeekFrom::Current(22))?;
198
102k
    let file_name_length = reader.read_u16::<LittleEndian>()? as u64;
199
102k
    let extra_field_length = reader.read_u16::<LittleEndian>()? as u64;
200
102k
    let magic_and_header = 4 + 22 + 2 + 2;
201
102k
    let data_start = data.header_start + magic_and_header + file_name_length + extra_field_length;
202
102k
    data.data_start.store(data_start);
203
204
102k
    reader.seek(io::SeekFrom::Start(data_start))?;
205
102k
    Ok((reader as &mut dyn Read).take(data.compressed_size))
206
102k
}
zip::read::find_content::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
186
62.4k
fn find_content<'a>(
187
62.4k
    data: &ZipFileData,
188
62.4k
    reader: &'a mut (impl Read + Seek),
189
62.4k
) -> ZipResult<io::Take<&'a mut dyn Read>> {
190
    // Parse local header
191
62.4k
    reader.seek(io::SeekFrom::Start(data.header_start))?;
192
62.4k
    let signature = reader.read_u32::<LittleEndian>()?;
193
62.4k
    if signature != spec::LOCAL_FILE_HEADER_SIGNATURE {
194
3
        return Err(ZipError::InvalidArchive("Invalid local file header"));
195
62.3k
    }
196
197
62.3k
    reader.seek(io::SeekFrom::Current(22))?;
198
62.3k
    let file_name_length = reader.read_u16::<LittleEndian>()? as u64;
199
62.3k
    let extra_field_length = reader.read_u16::<LittleEndian>()? as u64;
200
62.3k
    let magic_and_header = 4 + 22 + 2 + 2;
201
62.3k
    let data_start = data.header_start + magic_and_header + file_name_length + extra_field_length;
202
62.3k
    data.data_start.store(data_start);
203
204
62.3k
    reader.seek(io::SeekFrom::Start(data_start))?;
205
62.3k
    Ok((reader as &mut dyn Read).take(data.compressed_size))
206
62.4k
}
zip::read::find_content::<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>
Line
Count
Source
186
39.7k
fn find_content<'a>(
187
39.7k
    data: &ZipFileData,
188
39.7k
    reader: &'a mut (impl Read + Seek),
189
39.7k
) -> ZipResult<io::Take<&'a mut dyn Read>> {
190
    // Parse local header
191
39.7k
    reader.seek(io::SeekFrom::Start(data.header_start))?;
192
39.7k
    let signature = reader.read_u32::<LittleEndian>()?;
193
39.7k
    if signature != spec::LOCAL_FILE_HEADER_SIGNATURE {
194
0
        return Err(ZipError::InvalidArchive("Invalid local file header"));
195
39.7k
    }
196
197
39.7k
    reader.seek(io::SeekFrom::Current(22))?;
198
39.7k
    let file_name_length = reader.read_u16::<LittleEndian>()? as u64;
199
39.7k
    let extra_field_length = reader.read_u16::<LittleEndian>()? as u64;
200
39.7k
    let magic_and_header = 4 + 22 + 2 + 2;
201
39.7k
    let data_start = data.header_start + magic_and_header + file_name_length + extra_field_length;
202
39.7k
    data.data_start.store(data_start);
203
204
39.7k
    reader.seek(io::SeekFrom::Start(data_start))?;
205
39.7k
    Ok((reader as &mut dyn Read).take(data.compressed_size))
206
39.7k
}
207
208
#[allow(clippy::too_many_arguments)]
209
102k
fn make_crypto_reader<'a>(
210
102k
    compression_method: crate::compression::CompressionMethod,
211
102k
    crc32: u32,
212
102k
    last_modified_time: DateTime,
213
102k
    using_data_descriptor: bool,
214
102k
    reader: io::Take<&'a mut dyn io::Read>,
215
102k
    password: Option<&[u8]>,
216
102k
    aes_info: Option<(AesMode, AesVendorVersion)>,
217
102k
    #[cfg(feature = "aes-crypto")] compressed_size: u64,
218
102k
) -> ZipResult<Result<CryptoReader<'a>, InvalidPassword>> {
219
    #[allow(deprecated)]
220
    {
221
102k
        if let CompressionMethod::Unsupported(_) = compression_method {
222
0
            return unsupported_zip_error("Compression method not supported");
223
102k
        }
224
    }
225
226
102k
    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
0
        (Some(password), Some((aes_mode, vendor_version))) => {
235
0
            match AesReader::new(reader, aes_mode, compressed_size).validate(password)? {
236
0
                None => return Ok(Err(InvalidPassword)),
237
0
                Some(r) => CryptoReader::Aes {
238
0
                    reader: r,
239
0
                    vendor_version,
240
0
                },
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
102k
        (None, None) => CryptoReader::Plaintext(reader),
256
    };
257
102k
    Ok(Ok(reader))
258
102k
}
259
260
102k
fn make_reader(
261
102k
    compression_method: CompressionMethod,
262
102k
    crc32: u32,
263
102k
    reader: CryptoReader,
264
102k
) -> ZipFileReader {
265
102k
    let ae2_encrypted = reader.is_ae2_encrypted();
266
267
102k
    match compression_method {
268
        CompressionMethod::Stored => {
269
19.9k
            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
38.7k
            let deflate_reader = DeflateDecoder::new(reader);
278
38.7k
            ZipFileReader::Deflated(Crc32Reader::new(deflate_reader, crc32, ae2_encrypted))
279
        }
280
        #[cfg(feature = "bzip2")]
281
        CompressionMethod::Bzip2 => {
282
1.67k
            let bzip2_reader = BzDecoder::new(reader);
283
1.67k
            ZipFileReader::Bzip2(Crc32Reader::new(bzip2_reader, crc32, ae2_encrypted))
284
        }
285
        #[cfg(feature = "zstd")]
286
        CompressionMethod::Zstd => {
287
41.7k
            let zstd_reader = ZstdDecoder::new(reader).unwrap();
288
41.7k
            ZipFileReader::Zstd(Crc32Reader::new(zstd_reader, crc32, ae2_encrypted))
289
        }
290
0
        _ => panic!("Compression method not supported"),
291
    }
292
102k
}
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.47k
    pub(crate) fn get_directory_counts(
298
3.47k
        reader: &mut R,
299
3.47k
        footer: &spec::CentralDirectoryEnd,
300
3.47k
        cde_start_pos: u64,
301
3.47k
    ) -> 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.47k
        let zip64locator = if reader
307
3.47k
            .seek(io::SeekFrom::End(
308
3.47k
                -(20 + 22 + footer.zip_file_comment.len() as i64),
309
3.47k
            ))
310
3.47k
            .is_ok()
311
        {
312
3.10k
            match spec::Zip64CentralDirectoryEndLocator::parse(reader) {
313
707
                Ok(loc) => Some(loc),
314
                Err(ZipError::InvalidArchive(_)) => {
315
                    // No ZIP64 header; that's actually fine. We're done here.
316
2.40k
                    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
371
            None
327
        };
328
329
3.47k
        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.77k
                let archive_offset = cde_start_pos
336
2.77k
                    .checked_sub(footer.central_directory_size as u64)
337
2.77k
                    .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.48k
                    .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
415
                    .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
749
                    .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
338
2.77k
                    .ok_or(ZipError::InvalidArchive(
339
2.77k
                        "Invalid central directory size or offset",
340
2.77k
                    ))?;
341
342
2.60k
                let directory_start = footer.central_directory_offset as u64 + archive_offset;
343
2.60k
                let number_of_files = footer.number_of_files_on_this_disk as usize;
344
2.60k
                Ok((archive_offset, directory_start, number_of_files))
345
            }
346
707
            Some(locator64) => {
347
                // If we got here, this is indeed a ZIP64 file.
348
349
707
                if !footer.record_too_small()
350
508
                    && footer.disk_number as u32 != locator64.disk_with_central_directory
351
                {
352
81
                    return unsupported_zip_error(
353
                        "Support for multi-disk files is not implemented",
354
                    );
355
626
                }
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
626
                let search_upper_bound = cde_start_pos
366
626
                    .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator
367
626
                    .ok_or(ZipError::InvalidArchive(
368
626
                        "File cannot contain ZIP64 central directory end",
369
626
                    ))?;
370
583
                let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse(
371
583
                    reader,
372
583
                    locator64.end_of_central_directory_offset,
373
583
                    search_upper_bound,
374
152
                )?;
375
376
431
                if footer.disk_number != footer.disk_with_central_directory {
377
78
                    return unsupported_zip_error(
378
                        "Support for multi-disk files is not implemented",
379
                    );
380
353
                }
381
382
353
                let directory_start = footer
383
353
                    .central_directory_offset
384
353
                    .checked_add(archive_offset)
385
353
                    .ok_or({
386
353
                        ZipError::InvalidArchive("Invalid central directory size or offset")
387
1
                    })?;
388
389
352
                Ok((
390
352
                    archive_offset,
391
352
                    directory_start,
392
352
                    footer.number_of_files as usize,
393
352
                ))
394
            }
395
        }
396
3.47k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::get_directory_counts
Line
Count
Source
297
2.30k
    pub(crate) fn get_directory_counts(
298
2.30k
        reader: &mut R,
299
2.30k
        footer: &spec::CentralDirectoryEnd,
300
2.30k
        cde_start_pos: u64,
301
2.30k
    ) -> 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.30k
        let zip64locator = if reader
307
2.30k
            .seek(io::SeekFrom::End(
308
2.30k
                -(20 + 22 + footer.zip_file_comment.len() as i64),
309
2.30k
            ))
310
2.30k
            .is_ok()
311
        {
312
1.92k
            match spec::Zip64CentralDirectoryEndLocator::parse(reader) {
313
696
                Ok(loc) => Some(loc),
314
                Err(ZipError::InvalidArchive(_)) => {
315
                    // No ZIP64 header; that's actually fine. We're done here.
316
1.23k
                    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
371
            None
327
        };
328
329
2.30k
        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.60k
                let archive_offset = cde_start_pos
336
1.60k
                    .checked_sub(footer.central_directory_size as u64)
337
1.60k
                    .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
338
1.60k
                    .ok_or(ZipError::InvalidArchive(
339
1.60k
                        "Invalid central directory size or offset",
340
1.60k
                    ))?;
341
342
1.44k
                let directory_start = footer.central_directory_offset as u64 + archive_offset;
343
1.44k
                let number_of_files = footer.number_of_files_on_this_disk as usize;
344
1.44k
                Ok((archive_offset, directory_start, number_of_files))
345
            }
346
696
            Some(locator64) => {
347
                // If we got here, this is indeed a ZIP64 file.
348
349
696
                if !footer.record_too_small()
350
508
                    && footer.disk_number as u32 != locator64.disk_with_central_directory
351
                {
352
81
                    return unsupported_zip_error(
353
                        "Support for multi-disk files is not implemented",
354
                    );
355
615
                }
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
615
                let search_upper_bound = cde_start_pos
366
615
                    .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator
367
615
                    .ok_or(ZipError::InvalidArchive(
368
615
                        "File cannot contain ZIP64 central directory end",
369
615
                    ))?;
370
572
                let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse(
371
572
                    reader,
372
572
                    locator64.end_of_central_directory_offset,
373
572
                    search_upper_bound,
374
151
                )?;
375
376
421
                if footer.disk_number != footer.disk_with_central_directory {
377
71
                    return unsupported_zip_error(
378
                        "Support for multi-disk files is not implemented",
379
                    );
380
350
                }
381
382
350
                let directory_start = footer
383
350
                    .central_directory_offset
384
350
                    .checked_add(archive_offset)
385
350
                    .ok_or({
386
350
                        ZipError::InvalidArchive("Invalid central directory size or offset")
387
1
                    })?;
388
389
349
                Ok((
390
349
                    archive_offset,
391
349
                    directory_start,
392
349
                    footer.number_of_files as usize,
393
349
                ))
394
            }
395
        }
396
2.30k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::get_directory_counts
Line
Count
Source
297
429
    pub(crate) fn get_directory_counts(
298
429
        reader: &mut R,
299
429
        footer: &spec::CentralDirectoryEnd,
300
429
        cde_start_pos: u64,
301
429
    ) -> 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
429
        let zip64locator = if reader
307
429
            .seek(io::SeekFrom::End(
308
429
                -(20 + 22 + footer.zip_file_comment.len() as i64),
309
429
            ))
310
429
            .is_ok()
311
        {
312
429
            match spec::Zip64CentralDirectoryEndLocator::parse(reader) {
313
11
                Ok(loc) => Some(loc),
314
                Err(ZipError::InvalidArchive(_)) => {
315
                    // No ZIP64 header; that's actually fine. We're done here.
316
418
                    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
429
        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
418
                let archive_offset = cde_start_pos
336
418
                    .checked_sub(footer.central_directory_size as u64)
337
418
                    .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
338
418
                    .ok_or(ZipError::InvalidArchive(
339
418
                        "Invalid central directory size or offset",
340
418
                    ))?;
341
342
412
                let directory_start = footer.central_directory_offset as u64 + archive_offset;
343
412
                let number_of_files = footer.number_of_files_on_this_disk as usize;
344
412
                Ok((archive_offset, directory_start, number_of_files))
345
            }
346
11
            Some(locator64) => {
347
                // If we got here, this is indeed a ZIP64 file.
348
349
11
                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
11
                }
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
11
                let search_upper_bound = cde_start_pos
366
11
                    .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator
367
11
                    .ok_or(ZipError::InvalidArchive(
368
11
                        "File cannot contain ZIP64 central directory end",
369
11
                    ))?;
370
11
                let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse(
371
11
                    reader,
372
11
                    locator64.end_of_central_directory_offset,
373
11
                    search_upper_bound,
374
1
                )?;
375
376
10
                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
3
                }
381
382
3
                let directory_start = footer
383
3
                    .central_directory_offset
384
3
                    .checked_add(archive_offset)
385
3
                    .ok_or({
386
3
                        ZipError::InvalidArchive("Invalid central directory size or offset")
387
0
                    })?;
388
389
3
                Ok((
390
3
                    archive_offset,
391
3
                    directory_start,
392
3
                    footer.number_of_files as usize,
393
3
                ))
394
            }
395
        }
396
429
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::get_directory_counts
Line
Count
Source
297
749
    pub(crate) fn get_directory_counts(
298
749
        reader: &mut R,
299
749
        footer: &spec::CentralDirectoryEnd,
300
749
        cde_start_pos: u64,
301
749
    ) -> 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
749
        let zip64locator = if reader
307
749
            .seek(io::SeekFrom::End(
308
749
                -(20 + 22 + footer.zip_file_comment.len() as i64),
309
749
            ))
310
749
            .is_ok()
311
        {
312
749
            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
749
                    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
749
        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
749
                let archive_offset = cde_start_pos
336
749
                    .checked_sub(footer.central_directory_size as u64)
337
749
                    .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
338
749
                    .ok_or(ZipError::InvalidArchive(
339
749
                        "Invalid central directory size or offset",
340
749
                    ))?;
341
342
749
                let directory_start = footer.central_directory_offset as u64 + archive_offset;
343
749
                let number_of_files = footer.number_of_files_on_this_disk as usize;
344
749
                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
749
    }
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.7k
    pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
402
14.7k
        let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
403
404
3.56k
        if !footer.record_too_small() && footer.disk_number != footer.disk_with_central_directory {
405
83
            return unsupported_zip_error("Support for multi-disk files is not implemented");
406
3.47k
        }
407
408
2.95k
        let (archive_offset, directory_start, number_of_files) =
409
3.47k
            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
2.95k
        let file_capacity = if number_of_files > cde_start_pos as usize {
414
1.54k
            0
415
        } else {
416
1.40k
            number_of_files
417
        };
418
419
2.95k
        let mut files = Vec::with_capacity(file_capacity);
420
2.95k
        let mut names_map = HashMap::with_capacity(file_capacity);
421
422
2.95k
        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
2.95k
        }
427
428
2.95k
        for _ in 0..number_of_files {
429
292k
            let file = central_header_to_zip_file(&mut reader, archive_offset)?;
430
290k
            names_map.insert(file.file_name.clone(), files.len());
431
290k
            files.push(file);
432
        }
433
434
1.26k
        let shared = Arc::new(zip_archive::Shared {
435
1.26k
            files,
436
1.26k
            names_map,
437
1.26k
            offset: archive_offset,
438
1.26k
            comment: footer.zip_file_comment,
439
1.26k
        });
440
441
1.26k
        Ok(ZipArchive { reader, shared })
442
14.7k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::new
Line
Count
Source
401
2.50k
    pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
402
2.50k
        let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
403
404
2.38k
        if !footer.record_too_small() && footer.disk_number != footer.disk_with_central_directory {
405
81
            return unsupported_zip_error("Support for multi-disk files is not implemented");
406
2.30k
        }
407
408
1.79k
        let (archive_offset, directory_start, number_of_files) =
409
2.30k
            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.79k
        let file_capacity = if number_of_files > cde_start_pos as usize {
414
1.49k
            0
415
        } else {
416
293
            number_of_files
417
        };
418
419
1.79k
        let mut files = Vec::with_capacity(file_capacity);
420
1.79k
        let mut names_map = HashMap::with_capacity(file_capacity);
421
422
1.79k
        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.79k
        }
427
428
1.79k
        for _ in 0..number_of_files {
429
241k
            let file = central_header_to_zip_file(&mut reader, archive_offset)?;
430
239k
            names_map.insert(file.file_name.clone(), files.len());
431
239k
            files.push(file);
432
        }
433
434
166
        let shared = Arc::new(zip_archive::Shared {
435
166
            files,
436
166
            names_map,
437
166
            offset: archive_offset,
438
166
            comment: footer.zip_file_comment,
439
166
        });
440
441
166
        Ok(ZipArchive { reader, shared })
442
2.50k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::new
Line
Count
Source
401
518
    pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
402
518
        let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
403
404
431
        if !footer.record_too_small() && footer.disk_number != footer.disk_with_central_directory {
405
2
            return unsupported_zip_error("Support for multi-disk files is not implemented");
406
429
        }
407
408
415
        let (archive_offset, directory_start, number_of_files) =
409
429
            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
415
        let file_capacity = if number_of_files > cde_start_pos as usize {
414
50
            0
415
        } else {
416
365
            number_of_files
417
        };
418
419
415
        let mut files = Vec::with_capacity(file_capacity);
420
415
        let mut names_map = HashMap::with_capacity(file_capacity);
421
422
415
        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
415
        }
427
428
415
        for _ in 0..number_of_files {
429
11.0k
            let file = central_header_to_zip_file(&mut reader, archive_offset)?;
430
11.0k
            names_map.insert(file.file_name.clone(), files.len());
431
11.0k
            files.push(file);
432
        }
433
434
346
        let shared = Arc::new(zip_archive::Shared {
435
346
            files,
436
346
            names_map,
437
346
            offset: archive_offset,
438
346
            comment: footer.zip_file_comment,
439
346
        });
440
441
346
        Ok(ZipArchive { reader, shared })
442
518
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new
Line
Count
Source
401
11.7k
    pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
402
11.7k
        let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
403
404
749
        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
749
        }
407
408
749
        let (archive_offset, directory_start, number_of_files) =
409
749
            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
749
        let file_capacity = if number_of_files > cde_start_pos as usize {
414
0
            0
415
        } else {
416
749
            number_of_files
417
        };
418
419
749
        let mut files = Vec::with_capacity(file_capacity);
420
749
        let mut names_map = HashMap::with_capacity(file_capacity);
421
422
749
        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
749
        }
427
428
749
        for _ in 0..number_of_files {
429
39.7k
            let file = central_header_to_zip_file(&mut reader, archive_offset)?;
430
39.7k
            names_map.insert(file.file_name.clone(), files.len());
431
39.7k
            files.push(file);
432
        }
433
434
749
        let shared = Arc::new(zip_archive::Shared {
435
749
            files,
436
749
            names_map,
437
749
            offset: archive_offset,
438
749
            comment: footer.zip_file_comment,
439
749
        });
440
441
749
        Ok(ZipArchive { reader, shared })
442
11.7k
    }
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
12.3k
    pub fn len(&self) -> usize {
484
12.3k
        self.shared.files.len()
485
12.3k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::len
Line
Count
Source
483
166
    pub fn len(&self) -> usize {
484
166
        self.shared.files.len()
485
166
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::len
Line
Count
Source
483
11.4k
    pub fn len(&self) -> usize {
484
11.4k
        self.shared.files.len()
485
11.4k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::len
Line
Count
Source
483
749
    pub fn len(&self) -> usize {
484
749
        self.shared.files.len()
485
749
    }
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
72.3k
    pub fn comment(&self) -> &[u8] {
502
72.3k
        &self.shared.comment
503
72.3k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::comment
Line
Count
Source
501
72.1k
    pub fn comment(&self) -> &[u8] {
502
72.1k
        &self.shared.comment
503
72.1k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::comment
Line
Count
Source
501
209
    pub fn comment(&self) -> &[u8] {
502
209
        &self.shared.comment
503
209
    }
504
505
    /// Returns an iterator over all the file and directory names in this archive.
506
24.4k
    pub fn file_names(&self) -> impl Iterator<Item = &str> {
507
32.8k
        self.shared.names_map.keys().map(|s| s.as_str())
508
24.4k
    }
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
30
    pub fn by_name_decrypt<'a>(
524
30
        &'a mut self,
525
30
        name: &str,
526
30
        password: &[u8],
527
30
    ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
528
30
        self.by_name_with_optional_password(name, Some(password))
529
30
    }
530
531
    /// Search for a file entry by name
532
32.0k
    pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
533
32.0k
        Ok(self.by_name_with_optional_password(name, None)?.unwrap())
534
32.0k
    }
535
536
32.0k
    fn by_name_with_optional_password<'a>(
537
32.0k
        &'a mut self,
538
32.0k
        name: &str,
539
32.0k
        password: Option<&[u8]>,
540
32.0k
    ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
541
32.0k
        let index = match self.shared.names_map.get(name) {
542
31.9k
            Some(index) => *index,
543
            None => {
544
117
                return Err(ZipError::FileNotFound);
545
            }
546
        };
547
31.9k
        self.by_index_with_optional_password(index, password)
548
32.0k
    }
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
26
    pub fn by_index_decrypt<'a>(
564
26
        &'a mut self,
565
26
        file_number: usize,
566
26
        password: &[u8],
567
26
    ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
568
26
        self.by_index_with_optional_password(file_number, Some(password))
569
26
    }
570
571
    /// Get a contained file by index
572
70.2k
    pub fn by_index(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
573
70.2k
        Ok(self
574
70.2k
            .by_index_with_optional_password(file_number, None)?
575
70.1k
            .unwrap())
576
70.2k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::by_index
Line
Count
Source
572
30.4k
    pub fn by_index(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
573
30.4k
        Ok(self
574
30.4k
            .by_index_with_optional_password(file_number, None)?
575
30.4k
            .unwrap())
576
30.4k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::by_index
Line
Count
Source
572
39.7k
    pub fn by_index(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
573
39.7k
        Ok(self
574
39.7k
            .by_index_with_optional_password(file_number, None)?
575
39.7k
            .unwrap())
576
39.7k
    }
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
102k
    fn by_index_with_optional_password<'a>(
595
102k
        &'a mut self,
596
102k
        file_number: usize,
597
102k
        mut password: Option<&[u8]>,
598
102k
    ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
599
102k
        let data = self
600
102k
            .shared
601
102k
            .files
602
102k
            .get(file_number)
603
102k
            .ok_or(ZipError::FileNotFound)?;
604
605
102k
        match (password, data.encrypted) {
606
7
            (None, true) => return Err(ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED)),
607
17
            (Some(_), false) => password = None, //Password supplied, but none needed! Discard.
608
102k
            _ => {}
609
        }
610
102k
        let limit_reader = find_content(data, &mut self.reader)?;
611
612
102k
        match make_crypto_reader(
613
102k
            data.compression_method,
614
102k
            data.crc32,
615
102k
            data.last_modified_time,
616
102k
            data.using_data_descriptor,
617
102k
            limit_reader,
618
102k
            password,
619
102k
            data.aes_mode,
620
            #[cfg(feature = "aes-crypto")]
621
102k
            data.compressed_size,
622
        ) {
623
102k
            Ok(Ok(crypto_reader)) => Ok(Ok(ZipFile {
624
102k
                crypto_reader: Some(crypto_reader),
625
102k
                reader: ZipFileReader::NoReader,
626
102k
                data: Cow::Borrowed(data),
627
102k
            })),
628
0
            Err(e) => Err(e),
629
0
            Ok(Err(e)) => Ok(Err(e)),
630
        }
631
102k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>::by_index_with_optional_password
Line
Count
Source
594
62.4k
    fn by_index_with_optional_password<'a>(
595
62.4k
        &'a mut self,
596
62.4k
        file_number: usize,
597
62.4k
        mut password: Option<&[u8]>,
598
62.4k
    ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
599
62.4k
        let data = self
600
62.4k
            .shared
601
62.4k
            .files
602
62.4k
            .get(file_number)
603
62.4k
            .ok_or(ZipError::FileNotFound)?;
604
605
62.4k
        match (password, data.encrypted) {
606
7
            (None, true) => return Err(ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED)),
607
17
            (Some(_), false) => password = None, //Password supplied, but none needed! Discard.
608
62.3k
            _ => {}
609
        }
610
62.4k
        let limit_reader = find_content(data, &mut self.reader)?;
611
612
62.3k
        match make_crypto_reader(
613
62.3k
            data.compression_method,
614
62.3k
            data.crc32,
615
62.3k
            data.last_modified_time,
616
62.3k
            data.using_data_descriptor,
617
62.3k
            limit_reader,
618
62.3k
            password,
619
62.3k
            data.aes_mode,
620
            #[cfg(feature = "aes-crypto")]
621
62.3k
            data.compressed_size,
622
        ) {
623
62.3k
            Ok(Ok(crypto_reader)) => Ok(Ok(ZipFile {
624
62.3k
                crypto_reader: Some(crypto_reader),
625
62.3k
                reader: ZipFileReader::NoReader,
626
62.3k
                data: Cow::Borrowed(data),
627
62.3k
            })),
628
0
            Err(e) => Err(e),
629
0
            Ok(Err(e)) => Ok(Err(e)),
630
        }
631
62.4k
    }
<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::by_index_with_optional_password
Line
Count
Source
594
39.7k
    fn by_index_with_optional_password<'a>(
595
39.7k
        &'a mut self,
596
39.7k
        file_number: usize,
597
39.7k
        mut password: Option<&[u8]>,
598
39.7k
    ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
599
39.7k
        let data = self
600
39.7k
            .shared
601
39.7k
            .files
602
39.7k
            .get(file_number)
603
39.7k
            .ok_or(ZipError::FileNotFound)?;
604
605
39.7k
        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
39.7k
            _ => {}
609
        }
610
39.7k
        let limit_reader = find_content(data, &mut self.reader)?;
611
612
39.7k
        match make_crypto_reader(
613
39.7k
            data.compression_method,
614
39.7k
            data.crc32,
615
39.7k
            data.last_modified_time,
616
39.7k
            data.using_data_descriptor,
617
39.7k
            limit_reader,
618
39.7k
            password,
619
39.7k
            data.aes_mode,
620
            #[cfg(feature = "aes-crypto")]
621
39.7k
            data.compressed_size,
622
        ) {
623
39.7k
            Ok(Ok(crypto_reader)) => Ok(Ok(ZipFile {
624
39.7k
                crypto_reader: Some(crypto_reader),
625
39.7k
                reader: ZipFileReader::NoReader,
626
39.7k
                data: Cow::Borrowed(data),
627
39.7k
            })),
628
0
            Err(e) => Err(e),
629
0
            Ok(Err(e)) => Ok(Err(e)),
630
        }
631
39.7k
    }
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
242
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
642
242
    Err(ZipError::UnsupportedArchive(detail))
643
242
}
zip::read::unsupported_zip_error::<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>
Line
Count
Source
641
81
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
642
81
    Err(ZipError::UnsupportedArchive(detail))
643
81
}
zip::read::unsupported_zip_error::<(u64, u64, usize)>
Line
Count
Source
641
152
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
642
152
    Err(ZipError::UnsupportedArchive(detail))
643
152
}
Unexecuted instantiation: zip::read::unsupported_zip_error::<core::result::Result<zip::read::CryptoReader, zip::result::InvalidPassword>>
zip::read::unsupported_zip_error::<zip::read::zip_archive::ZipArchive<std::io::cursor::Cursor<&[u8]>>>
Line
Count
Source
641
2
fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
642
2
    Err(ZipError::UnsupportedArchive(detail))
643
2
}
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
292k
pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
647
292k
    reader: &mut R,
648
292k
    archive_offset: u64,
649
292k
) -> ZipResult<ZipFileData> {
650
292k
    let central_header_start = reader.stream_position()?;
651
652
    // Parse central header
653
292k
    let signature = reader.read_u32::<LittleEndian>()?;
654
291k
    if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
655
465
        Err(ZipError::InvalidArchive("Invalid Central Directory header"))
656
    } else {
657
290k
        central_header_to_zip_file_inner(reader, archive_offset, central_header_start)
658
    }
659
292k
}
zip::read::central_header_to_zip_file::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
646
241k
pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
647
241k
    reader: &mut R,
648
241k
    archive_offset: u64,
649
241k
) -> ZipResult<ZipFileData> {
650
241k
    let central_header_start = reader.stream_position()?;
651
652
    // Parse central header
653
241k
    let signature = reader.read_u32::<LittleEndian>()?;
654
240k
    if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
655
425
        Err(ZipError::InvalidArchive("Invalid Central Directory header"))
656
    } else {
657
240k
        central_header_to_zip_file_inner(reader, archive_offset, central_header_start)
658
    }
659
241k
}
zip::read::central_header_to_zip_file::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
646
11.0k
pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
647
11.0k
    reader: &mut R,
648
11.0k
    archive_offset: u64,
649
11.0k
) -> ZipResult<ZipFileData> {
650
11.0k
    let central_header_start = reader.stream_position()?;
651
652
    // Parse central header
653
11.0k
    let signature = reader.read_u32::<LittleEndian>()?;
654
11.0k
    if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
655
40
        Err(ZipError::InvalidArchive("Invalid Central Directory header"))
656
    } else {
657
11.0k
        central_header_to_zip_file_inner(reader, archive_offset, central_header_start)
658
    }
659
11.0k
}
zip::read::central_header_to_zip_file::<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>
Line
Count
Source
646
39.7k
pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
647
39.7k
    reader: &mut R,
648
39.7k
    archive_offset: u64,
649
39.7k
) -> ZipResult<ZipFileData> {
650
39.7k
    let central_header_start = reader.stream_position()?;
651
652
    // Parse central header
653
39.7k
    let signature = reader.read_u32::<LittleEndian>()?;
654
39.7k
    if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
655
0
        Err(ZipError::InvalidArchive("Invalid Central Directory header"))
656
    } else {
657
39.7k
        central_header_to_zip_file_inner(reader, archive_offset, central_header_start)
658
    }
659
39.7k
}
660
661
/// Parse a central directory entry to collect the information for the file.
662
290k
fn central_header_to_zip_file_inner<R: Read>(
663
290k
    reader: &mut R,
664
290k
    archive_offset: u64,
665
290k
    central_header_start: u64,
666
290k
) -> ZipResult<ZipFileData> {
667
290k
    let version_made_by = reader.read_u16::<LittleEndian>()?;
668
290k
    let _version_to_extract = reader.read_u16::<LittleEndian>()?;
669
290k
    let flags = reader.read_u16::<LittleEndian>()?;
670
290k
    let encrypted = flags & 1 == 1;
671
290k
    let is_utf8 = flags & (1 << 11) != 0;
672
290k
    let using_data_descriptor = flags & (1 << 3) != 0;
673
290k
    let compression_method = reader.read_u16::<LittleEndian>()?;
674
290k
    let last_mod_time = reader.read_u16::<LittleEndian>()?;
675
290k
    let last_mod_date = reader.read_u16::<LittleEndian>()?;
676
290k
    let crc32 = reader.read_u32::<LittleEndian>()?;
677
290k
    let compressed_size = reader.read_u32::<LittleEndian>()?;
678
290k
    let uncompressed_size = reader.read_u32::<LittleEndian>()?;
679
290k
    let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
680
290k
    let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
681
290k
    let file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
682
290k
    let _disk_number = reader.read_u16::<LittleEndian>()?;
683
290k
    let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
684
290k
    let external_file_attributes = reader.read_u32::<LittleEndian>()?;
685
290k
    let offset = reader.read_u32::<LittleEndian>()? as u64;
686
290k
    let mut file_name_raw = vec![0; file_name_length];
687
290k
    reader.read_exact(&mut file_name_raw)?;
688
290k
    let mut extra_field = vec![0; extra_field_length];
689
290k
    reader.read_exact(&mut extra_field)?;
690
290k
    let mut file_comment_raw = vec![0; file_comment_length];
691
290k
    reader.read_exact(&mut file_comment_raw)?;
692
693
290k
    let file_name = match is_utf8 {
694
81.3k
        true => String::from_utf8_lossy(&file_name_raw).into_owned(),
695
209k
        false => file_name_raw.clone().from_cp437(),
696
    };
697
290k
    let file_comment = match is_utf8 {
698
81.3k
        true => String::from_utf8_lossy(&file_comment_raw).into_owned(),
699
209k
        false => file_comment_raw.from_cp437(),
700
    };
701
702
    // Construct the result
703
290k
    let mut result = ZipFileData {
704
290k
        system: System::from_u8((version_made_by >> 8) as u8),
705
290k
        version_made_by: version_made_by as u8,
706
290k
        encrypted,
707
290k
        using_data_descriptor,
708
290k
        compression_method: {
709
290k
            #[allow(deprecated)]
710
290k
            CompressionMethod::from_u16(compression_method)
711
290k
        },
712
290k
        compression_level: None,
713
290k
        last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
714
290k
        crc32,
715
290k
        compressed_size: compressed_size as u64,
716
290k
        uncompressed_size: uncompressed_size as u64,
717
290k
        file_name,
718
290k
        file_name_raw,
719
290k
        extra_field,
720
290k
        file_comment,
721
290k
        header_start: offset,
722
290k
        central_header_start,
723
290k
        data_start: AtomicU64::new(0),
724
290k
        external_attributes: external_file_attributes,
725
290k
        large_file: false,
726
290k
        aes_mode: None,
727
290k
    };
728
729
290k
    match parse_extra_field(&mut result) {
730
290k
        Ok(..) | Err(ZipError::Io(..)) => {}
731
54
        Err(e) => return Err(e),
732
    }
733
734
290k
    let aes_enabled = result.compression_method == CompressionMethod::AES;
735
290k
    if aes_enabled && result.aes_mode.is_none() {
736
33
        return Err(ZipError::InvalidArchive(
737
33
            "AES encryption without AES extra data field",
738
33
        ));
739
290k
    }
740
741
    // Account for shifted zip offsets.
742
290k
    result.header_start = result
743
290k
        .header_start
744
290k
        .checked_add(archive_offset)
745
290k
        .ok_or(ZipError::InvalidArchive("Archive header is too large"))?;
746
747
290k
    Ok(result)
748
290k
}
zip::read::central_header_to_zip_file_inner::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
662
240k
fn central_header_to_zip_file_inner<R: Read>(
663
240k
    reader: &mut R,
664
240k
    archive_offset: u64,
665
240k
    central_header_start: u64,
666
240k
) -> ZipResult<ZipFileData> {
667
240k
    let version_made_by = reader.read_u16::<LittleEndian>()?;
668
240k
    let _version_to_extract = reader.read_u16::<LittleEndian>()?;
669
240k
    let flags = reader.read_u16::<LittleEndian>()?;
670
240k
    let encrypted = flags & 1 == 1;
671
240k
    let is_utf8 = flags & (1 << 11) != 0;
672
240k
    let using_data_descriptor = flags & (1 << 3) != 0;
673
240k
    let compression_method = reader.read_u16::<LittleEndian>()?;
674
240k
    let last_mod_time = reader.read_u16::<LittleEndian>()?;
675
240k
    let last_mod_date = reader.read_u16::<LittleEndian>()?;
676
240k
    let crc32 = reader.read_u32::<LittleEndian>()?;
677
240k
    let compressed_size = reader.read_u32::<LittleEndian>()?;
678
240k
    let uncompressed_size = reader.read_u32::<LittleEndian>()?;
679
240k
    let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
680
240k
    let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
681
240k
    let file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
682
240k
    let _disk_number = reader.read_u16::<LittleEndian>()?;
683
240k
    let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
684
240k
    let external_file_attributes = reader.read_u32::<LittleEndian>()?;
685
240k
    let offset = reader.read_u32::<LittleEndian>()? as u64;
686
240k
    let mut file_name_raw = vec![0; file_name_length];
687
240k
    reader.read_exact(&mut file_name_raw)?;
688
240k
    let mut extra_field = vec![0; extra_field_length];
689
240k
    reader.read_exact(&mut extra_field)?;
690
240k
    let mut file_comment_raw = vec![0; file_comment_length];
691
240k
    reader.read_exact(&mut file_comment_raw)?;
692
693
240k
    let file_name = match is_utf8 {
694
75.9k
        true => String::from_utf8_lossy(&file_name_raw).into_owned(),
695
164k
        false => file_name_raw.clone().from_cp437(),
696
    };
697
240k
    let file_comment = match is_utf8 {
698
75.9k
        true => String::from_utf8_lossy(&file_comment_raw).into_owned(),
699
164k
        false => file_comment_raw.from_cp437(),
700
    };
701
702
    // Construct the result
703
240k
    let mut result = ZipFileData {
704
240k
        system: System::from_u8((version_made_by >> 8) as u8),
705
240k
        version_made_by: version_made_by as u8,
706
240k
        encrypted,
707
240k
        using_data_descriptor,
708
240k
        compression_method: {
709
240k
            #[allow(deprecated)]
710
240k
            CompressionMethod::from_u16(compression_method)
711
240k
        },
712
240k
        compression_level: None,
713
240k
        last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
714
240k
        crc32,
715
240k
        compressed_size: compressed_size as u64,
716
240k
        uncompressed_size: uncompressed_size as u64,
717
240k
        file_name,
718
240k
        file_name_raw,
719
240k
        extra_field,
720
240k
        file_comment,
721
240k
        header_start: offset,
722
240k
        central_header_start,
723
240k
        data_start: AtomicU64::new(0),
724
240k
        external_attributes: external_file_attributes,
725
240k
        large_file: false,
726
240k
        aes_mode: None,
727
240k
    };
728
729
240k
    match parse_extra_field(&mut result) {
730
239k
        Ok(..) | Err(ZipError::Io(..)) => {}
731
54
        Err(e) => return Err(e),
732
    }
733
734
239k
    let aes_enabled = result.compression_method == CompressionMethod::AES;
735
239k
    if aes_enabled && result.aes_mode.is_none() {
736
33
        return Err(ZipError::InvalidArchive(
737
33
            "AES encryption without AES extra data field",
738
33
        ));
739
239k
    }
740
741
    // Account for shifted zip offsets.
742
239k
    result.header_start = result
743
239k
        .header_start
744
239k
        .checked_add(archive_offset)
745
239k
        .ok_or(ZipError::InvalidArchive("Archive header is too large"))?;
746
747
239k
    Ok(result)
748
240k
}
zip::read::central_header_to_zip_file_inner::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
662
11.0k
fn central_header_to_zip_file_inner<R: Read>(
663
11.0k
    reader: &mut R,
664
11.0k
    archive_offset: u64,
665
11.0k
    central_header_start: u64,
666
11.0k
) -> ZipResult<ZipFileData> {
667
11.0k
    let version_made_by = reader.read_u16::<LittleEndian>()?;
668
11.0k
    let _version_to_extract = reader.read_u16::<LittleEndian>()?;
669
11.0k
    let flags = reader.read_u16::<LittleEndian>()?;
670
11.0k
    let encrypted = flags & 1 == 1;
671
11.0k
    let is_utf8 = flags & (1 << 11) != 0;
672
11.0k
    let using_data_descriptor = flags & (1 << 3) != 0;
673
11.0k
    let compression_method = reader.read_u16::<LittleEndian>()?;
674
11.0k
    let last_mod_time = reader.read_u16::<LittleEndian>()?;
675
11.0k
    let last_mod_date = reader.read_u16::<LittleEndian>()?;
676
11.0k
    let crc32 = reader.read_u32::<LittleEndian>()?;
677
11.0k
    let compressed_size = reader.read_u32::<LittleEndian>()?;
678
11.0k
    let uncompressed_size = reader.read_u32::<LittleEndian>()?;
679
11.0k
    let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
680
11.0k
    let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
681
11.0k
    let file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
682
11.0k
    let _disk_number = reader.read_u16::<LittleEndian>()?;
683
11.0k
    let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
684
11.0k
    let external_file_attributes = reader.read_u32::<LittleEndian>()?;
685
11.0k
    let offset = reader.read_u32::<LittleEndian>()? as u64;
686
11.0k
    let mut file_name_raw = vec![0; file_name_length];
687
11.0k
    reader.read_exact(&mut file_name_raw)?;
688
11.0k
    let mut extra_field = vec![0; extra_field_length];
689
11.0k
    reader.read_exact(&mut extra_field)?;
690
11.0k
    let mut file_comment_raw = vec![0; file_comment_length];
691
11.0k
    reader.read_exact(&mut file_comment_raw)?;
692
693
11.0k
    let file_name = match is_utf8 {
694
5.38k
        true => String::from_utf8_lossy(&file_name_raw).into_owned(),
695
5.62k
        false => file_name_raw.clone().from_cp437(),
696
    };
697
11.0k
    let file_comment = match is_utf8 {
698
5.38k
        true => String::from_utf8_lossy(&file_comment_raw).into_owned(),
699
5.62k
        false => file_comment_raw.from_cp437(),
700
    };
701
702
    // Construct the result
703
11.0k
    let mut result = ZipFileData {
704
11.0k
        system: System::from_u8((version_made_by >> 8) as u8),
705
11.0k
        version_made_by: version_made_by as u8,
706
11.0k
        encrypted,
707
11.0k
        using_data_descriptor,
708
11.0k
        compression_method: {
709
11.0k
            #[allow(deprecated)]
710
11.0k
            CompressionMethod::from_u16(compression_method)
711
11.0k
        },
712
11.0k
        compression_level: None,
713
11.0k
        last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
714
11.0k
        crc32,
715
11.0k
        compressed_size: compressed_size as u64,
716
11.0k
        uncompressed_size: uncompressed_size as u64,
717
11.0k
        file_name,
718
11.0k
        file_name_raw,
719
11.0k
        extra_field,
720
11.0k
        file_comment,
721
11.0k
        header_start: offset,
722
11.0k
        central_header_start,
723
11.0k
        data_start: AtomicU64::new(0),
724
11.0k
        external_attributes: external_file_attributes,
725
11.0k
        large_file: false,
726
11.0k
        aes_mode: None,
727
11.0k
    };
728
729
11.0k
    match parse_extra_field(&mut result) {
730
11.0k
        Ok(..) | Err(ZipError::Io(..)) => {}
731
0
        Err(e) => return Err(e),
732
    }
733
734
11.0k
    let aes_enabled = result.compression_method == CompressionMethod::AES;
735
11.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
11.0k
    }
740
741
    // Account for shifted zip offsets.
742
11.0k
    result.header_start = result
743
11.0k
        .header_start
744
11.0k
        .checked_add(archive_offset)
745
11.0k
        .ok_or(ZipError::InvalidArchive("Archive header is too large"))?;
746
747
11.0k
    Ok(result)
748
11.0k
}
zip::read::central_header_to_zip_file_inner::<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>
Line
Count
Source
662
39.7k
fn central_header_to_zip_file_inner<R: Read>(
663
39.7k
    reader: &mut R,
664
39.7k
    archive_offset: u64,
665
39.7k
    central_header_start: u64,
666
39.7k
) -> ZipResult<ZipFileData> {
667
39.7k
    let version_made_by = reader.read_u16::<LittleEndian>()?;
668
39.7k
    let _version_to_extract = reader.read_u16::<LittleEndian>()?;
669
39.7k
    let flags = reader.read_u16::<LittleEndian>()?;
670
39.7k
    let encrypted = flags & 1 == 1;
671
39.7k
    let is_utf8 = flags & (1 << 11) != 0;
672
39.7k
    let using_data_descriptor = flags & (1 << 3) != 0;
673
39.7k
    let compression_method = reader.read_u16::<LittleEndian>()?;
674
39.7k
    let last_mod_time = reader.read_u16::<LittleEndian>()?;
675
39.7k
    let last_mod_date = reader.read_u16::<LittleEndian>()?;
676
39.7k
    let crc32 = reader.read_u32::<LittleEndian>()?;
677
39.7k
    let compressed_size = reader.read_u32::<LittleEndian>()?;
678
39.7k
    let uncompressed_size = reader.read_u32::<LittleEndian>()?;
679
39.7k
    let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
680
39.7k
    let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
681
39.7k
    let file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
682
39.7k
    let _disk_number = reader.read_u16::<LittleEndian>()?;
683
39.7k
    let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
684
39.7k
    let external_file_attributes = reader.read_u32::<LittleEndian>()?;
685
39.7k
    let offset = reader.read_u32::<LittleEndian>()? as u64;
686
39.7k
    let mut file_name_raw = vec![0; file_name_length];
687
39.7k
    reader.read_exact(&mut file_name_raw)?;
688
39.7k
    let mut extra_field = vec![0; extra_field_length];
689
39.7k
    reader.read_exact(&mut extra_field)?;
690
39.7k
    let mut file_comment_raw = vec![0; file_comment_length];
691
39.7k
    reader.read_exact(&mut file_comment_raw)?;
692
693
39.7k
    let file_name = match is_utf8 {
694
0
        true => String::from_utf8_lossy(&file_name_raw).into_owned(),
695
39.7k
        false => file_name_raw.clone().from_cp437(),
696
    };
697
39.7k
    let file_comment = match is_utf8 {
698
0
        true => String::from_utf8_lossy(&file_comment_raw).into_owned(),
699
39.7k
        false => file_comment_raw.from_cp437(),
700
    };
701
702
    // Construct the result
703
39.7k
    let mut result = ZipFileData {
704
39.7k
        system: System::from_u8((version_made_by >> 8) as u8),
705
39.7k
        version_made_by: version_made_by as u8,
706
39.7k
        encrypted,
707
39.7k
        using_data_descriptor,
708
39.7k
        compression_method: {
709
39.7k
            #[allow(deprecated)]
710
39.7k
            CompressionMethod::from_u16(compression_method)
711
39.7k
        },
712
39.7k
        compression_level: None,
713
39.7k
        last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
714
39.7k
        crc32,
715
39.7k
        compressed_size: compressed_size as u64,
716
39.7k
        uncompressed_size: uncompressed_size as u64,
717
39.7k
        file_name,
718
39.7k
        file_name_raw,
719
39.7k
        extra_field,
720
39.7k
        file_comment,
721
39.7k
        header_start: offset,
722
39.7k
        central_header_start,
723
39.7k
        data_start: AtomicU64::new(0),
724
39.7k
        external_attributes: external_file_attributes,
725
39.7k
        large_file: false,
726
39.7k
        aes_mode: None,
727
39.7k
    };
728
729
39.7k
    match parse_extra_field(&mut result) {
730
39.7k
        Ok(..) | Err(ZipError::Io(..)) => {}
731
0
        Err(e) => return Err(e),
732
    }
733
734
39.7k
    let aes_enabled = result.compression_method == CompressionMethod::AES;
735
39.7k
    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
39.7k
    }
740
741
    // Account for shifted zip offsets.
742
39.7k
    result.header_start = result
743
39.7k
        .header_start
744
39.7k
        .checked_add(archive_offset)
745
39.7k
        .ok_or(ZipError::InvalidArchive("Archive header is too large"))?;
746
747
39.7k
    Ok(result)
748
39.7k
}
749
750
290k
fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
751
290k
    let mut reader = io::Cursor::new(&file.extra_field);
752
753
325k
    while (reader.position() as usize) < file.extra_field.len() {
754
49.2k
        let kind = reader.read_u16::<LittleEndian>()?;
755
36.6k
        let len = reader.read_u16::<LittleEndian>()?;
756
36.1k
        let mut len_left = len as i64;
757
36.1k
        match kind {
758
            // Zip64 extended information extra field
759
            0x0001 => {
760
3.34k
                if file.uncompressed_size == spec::ZIP64_BYTES_THR {
761
973
                    file.large_file = true;
762
973
                    file.uncompressed_size = reader.read_u64::<LittleEndian>()?;
763
670
                    len_left -= 8;
764
2.36k
                }
765
3.03k
                if file.compressed_size == spec::ZIP64_BYTES_THR {
766
846
                    file.large_file = true;
767
846
                    file.compressed_size = reader.read_u64::<LittleEndian>()?;
768
545
                    len_left -= 8;
769
2.19k
                }
770
2.73k
                if file.header_start == spec::ZIP64_BYTES_THR {
771
932
                    file.header_start = reader.read_u64::<LittleEndian>()?;
772
497
                    len_left -= 8;
773
1.80k
                }
774
            }
775
            0x9901 => {
776
                // AES
777
2.59k
                if len != 7 {
778
18
                    return Err(ZipError::UnsupportedArchive(
779
18
                        "AES extra data field has an unsupported length",
780
18
                    ));
781
2.57k
                }
782
2.57k
                let vendor_version = reader.read_u16::<LittleEndian>()?;
783
2.37k
                let vendor_id = reader.read_u16::<LittleEndian>()?;
784
2.18k
                let aes_mode = reader.read_u8()?;
785
1.96k
                let compression_method = reader.read_u16::<LittleEndian>()?;
786
787
1.76k
                if vendor_id != 0x4541 {
788
26
                    return Err(ZipError::InvalidArchive("Invalid AES vendor"));
789
1.74k
                }
790
1.74k
                let vendor_version = match vendor_version {
791
992
                    0x0001 => AesVendorVersion::Ae1,
792
748
                    0x0002 => AesVendorVersion::Ae2,
793
1
                    _ => return Err(ZipError::InvalidArchive("Invalid AES vendor version")),
794
                };
795
1.74k
                match aes_mode {
796
1.27k
                    0x01 => file.aes_mode = Some((AesMode::Aes128, vendor_version)),
797
10
                    0x02 => file.aes_mode = Some((AesMode::Aes192, vendor_version)),
798
442
                    0x03 => file.aes_mode = Some((AesMode::Aes256, vendor_version)),
799
9
                    _ => return Err(ZipError::InvalidArchive("Invalid AES encryption strength")),
800
                };
801
1.73k
                file.compression_method = {
802
1.73k
                    #[allow(deprecated)]
803
1.73k
                    CompressionMethod::from_u16(compression_method)
804
1.73k
                };
805
            }
806
30.2k
            _ => {
807
30.2k
                // Other fields are ignored
808
30.2k
            }
809
        }
810
811
        // We could also check for < 0 to check for errors
812
34.2k
        if len_left > 0 {
813
24.8k
            reader.seek(io::SeekFrom::Current(len_left))?;
814
9.44k
        }
815
    }
816
275k
    Ok(())
817
290k
}
818
819
/// Methods for retrieving information on zip files
820
impl<'a> ZipFile<'a> {
821
199k
    fn get_reader(&mut self) -> &mut ZipFileReader<'a> {
822
199k
        if let ZipFileReader::NoReader = self.reader {
823
102k
            let data = &self.data;
824
102k
            let crypto_reader = self.crypto_reader.take().expect("Invalid reader state");
825
102k
            self.reader = make_reader(data.compression_method, data.crc32, crypto_reader)
826
97.5k
        }
827
199k
        &mut self.reader
828
199k
    }
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
187k
    pub fn name(&self) -> &str {
859
187k
        &self.data.file_name
860
187k
    }
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
62.3k
    pub fn mangled_name(&self) -> ::std::path::PathBuf {
893
62.3k
        self.data.file_name_sanitized()
894
62.3k
    }
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
62.3k
    pub fn enclosed_name(&self) -> Option<&Path> {
907
62.3k
        self.data.enclosed_name()
908
62.3k
    }
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
62.3k
    pub fn compression(&self) -> CompressionMethod {
917
62.3k
        self.data.compression_method
918
62.3k
    }
919
920
    /// Get the size of the file, in bytes, in the archive
921
62.3k
    pub fn compressed_size(&self) -> u64 {
922
62.3k
        self.data.compressed_size
923
62.3k
    }
924
925
    /// Get the size of the file, in bytes, when uncompressed
926
62.3k
    pub fn size(&self) -> u64 {
927
62.3k
        self.data.uncompressed_size
928
62.3k
    }
929
930
    /// Get the time the file was last modified
931
62.3k
    pub fn last_modified(&self) -> DateTime {
932
62.3k
        self.data.last_modified_time
933
62.3k
    }
934
    /// Returns whether the file is actually a directory
935
124k
    pub fn is_dir(&self) -> bool {
936
124k
        self.name()
937
124k
            .chars()
938
124k
            .rev()
939
124k
            .next()
940
124k
            .map_or(false, |c| c == '/' || c == '\\')
941
124k
    }
942
943
    /// Returns whether the file is a regular file
944
62.3k
    pub fn is_file(&self) -> bool {
945
62.3k
        !self.is_dir()
946
62.3k
    }
947
948
    /// Get unix mode for the file
949
62.3k
    pub fn unix_mode(&self) -> Option<u32> {
950
62.3k
        self.data.unix_mode()
951
62.3k
    }
952
953
    /// Get the CRC32 hash of the original file
954
62.3k
    pub fn crc32(&self) -> u32 {
955
62.3k
        self.data.crc32
956
62.3k
    }
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
62.3k
    pub fn data_start(&self) -> u64 {
965
62.3k
        self.data.data_start.load()
966
62.3k
    }
967
968
    /// Get the starting offset of the zip header for this file
969
62.3k
    pub fn header_start(&self) -> u64 {
970
62.3k
        self.data.header_start
971
62.3k
    }
972
    /// Get the starting offset of the zip header in the central directory for this file
973
62.3k
    pub fn central_header_start(&self) -> u64 {
974
62.3k
        self.data.central_header_start
975
62.3k
    }
976
}
977
978
impl<'a> Read for ZipFile<'a> {
979
199k
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
980
199k
        self.get_reader().read(buf)
981
199k
    }
982
}
983
984
impl<'a> Drop for ZipFile<'a> {
985
102k
    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
102k
        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
102k
        }
1013
102k
    }
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
}