Coverage Report

Created: 2026-02-14 06:51

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