aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--.github/workflows/ci.yaml18
-rw-r--r--Android.bp5
-rw-r--r--CHANGELOG.md9
-rw-r--r--Cargo.toml57
-rw-r--r--Cargo.toml.orig15
-rw-r--r--METADATA14
-rw-r--r--README.md29
-rw-r--r--TEST_MAPPING26
-rw-r--r--benches/read_metadata.rs38
-rw-r--r--examples/extract.rs6
-rw-r--r--examples/extract_lorem.rs4
-rw-r--r--examples/file_info.rs4
-rw-r--r--examples/stdin_info.rs4
-rw-r--r--examples/write_dir.rs12
-rw-r--r--examples/write_sample.rs6
-rw-r--r--patches/Android.bp.diff4
-rw-r--r--src/compression.rs6
-rw-r--r--src/read.rs68
-rw-r--r--src/result.rs21
-rw-r--r--src/spec.rs16
-rw-r--r--src/types.rs91
-rw-r--r--src/write.rs180
-rw-r--r--tests/data/invalid_cde_number_of_files_allocation_greater_offset.zipbin0 -> 124 bytes
-rw-r--r--tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zipbin0 -> 212 bytes
-rw-r--r--tests/end_to_end.rs2
-rw-r--r--tests/zip64_large.rs2
27 files changed, 488 insertions, 151 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index b50dc02..535489c 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
{
"git": {
- "sha1": "4f7609cec700765525a537747c8f340dd1090aa0"
+ "sha1": "f7dcc666b75256e766295589a5ac5dc5a9617c39"
},
"path_in_vcs": ""
} \ No newline at end of file
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 6f0e4b9..35d4a6e 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
- rust: [stable, 1.54.0]
+ rust: [stable, 1.59.0]
steps:
- uses: actions/checkout@master
@@ -74,3 +74,19 @@ jobs:
- name: Docs
run: cargo doc
+
+ fuzz:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: nightly
+ override: true
+
+ - run: cargo install cargo-fuzz
+ - name: compile fuzz
+ run: |
+ cargo fuzz build fuzz_read
diff --git a/Android.bp b/Android.bp
index c0b2349..8ccfd4a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -20,10 +20,11 @@ license {
rust_library {
name: "libzip",
+ // has rustc warnings
host_supported: true,
crate_name: "zip",
cargo_env_compat: true,
- cargo_pkg_version: "0.6.2",
+ cargo_pkg_version: "0.6.4",
srcs: ["src/lib.rs"],
edition: "2018",
features: [
@@ -39,6 +40,8 @@ rust_library {
"//apex_available:platform",
"com.android.virt",
],
+ product_available: true,
+ vendor_available: true,
arch: {
arm: {
rustlibs: ["libcrossbeam_utils"],
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..cd79e39
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+## [0.6.4]
+
+### Changed
+
+ - [#333](https://github.com/zip-rs/zip/pull/333): disabled the default features of the `time` dependency, and also `formatting` and `macros`, as they were enabled by mistake.
+ - Deprecated [`DateTime::from_time`](https://docs.rs/zip/0.6/zip/struct.DateTime.html#method.from_time) in favor of [`DateTime::try_from`](https://docs.rs/zip/0.6/zip/struct.DateTime.html#impl-TryFrom-for-DateTime)
+ \ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index fe1a031..7a1b656 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,16 +12,31 @@
[package]
edition = "2018"
name = "zip"
-version = "0.6.2"
-authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>", "Marli Frost <marli@frost.red>", "Ryan Levick <ryan.levick@gmail.com>"]
-description = "Library to support the reading and writing of zip files.\n"
-keywords = ["zip", "archive"]
+version = "0.6.4"
+authors = [
+ "Mathijs van de Nes <git@mathijs.vd-nes.nl>",
+ "Marli Frost <marli@frost.red>",
+ "Ryan Levick <ryan.levick@gmail.com>",
+]
+description = """
+Library to support the reading and writing of zip files.
+"""
+readme = "README.md"
+keywords = [
+ "zip",
+ "archive",
+]
license = "MIT"
repository = "https://github.com/zip-rs/zip.git"
[[bench]]
name = "read_entry"
harness = false
+
+[[bench]]
+name = "read_metadata"
+harness = false
+
[dependencies.aes]
version = "0.7.5"
optional = true
@@ -41,7 +56,7 @@ optional = true
version = "1.3.2"
[dependencies.flate2]
-version = "1.0.22"
+version = "1.0.23"
optional = true
default-features = false
@@ -51,7 +66,7 @@ features = ["reset"]
optional = true
[dependencies.pbkdf2]
-version = "0.10.1"
+version = "0.11.0"
optional = true
[dependencies.sha1]
@@ -60,27 +75,49 @@ optional = true
[dependencies.time]
version = "0.3.7"
-features = ["formatting", "macros"]
+features = ["std"]
optional = true
+default-features = false
[dependencies.zstd]
-version = "0.10.0"
+version = "0.11.2"
optional = true
+
[dev-dependencies.bencher]
version = "0.1.5"
[dev-dependencies.getrandom]
version = "0.2.5"
+[dev-dependencies.time]
+version = "0.3.7"
+features = [
+ "formatting",
+ "macros",
+]
+
[dev-dependencies.walkdir]
version = "2.3.2"
[features]
-aes-crypto = ["aes", "constant_time_eq", "hmac", "pbkdf2", "sha1"]
-default = ["aes-crypto", "bzip2", "deflate", "time", "zstd"]
+aes-crypto = [
+ "aes",
+ "constant_time_eq",
+ "hmac",
+ "pbkdf2",
+ "sha1",
+]
+default = [
+ "aes-crypto",
+ "bzip2",
+ "deflate",
+ "time",
+ "zstd",
+]
deflate = ["flate2/rust_backend"]
deflate-miniz = ["flate2/default"]
deflate-zlib = ["flate2/zlib"]
unreserved = []
+
[target."cfg(any(all(target_arch = \"arm\", target_pointer_width = \"32\"), target_arch = \"mips\", target_arch = \"powerpc\"))".dependencies.crossbeam-utils]
version = "0.8.8"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index cc87821..caf6a07 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "zip"
-version = "0.6.2"
+version = "0.6.4"
authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>", "Marli Frost <marli@frost.red>", "Ryan Levick <ryan.levick@gmail.com>"]
license = "MIT"
repository = "https://github.com/zip-rs/zip.git"
@@ -16,12 +16,12 @@ byteorder = "1.4.3"
bzip2 = { version = "0.4.3", optional = true }
constant_time_eq = { version = "0.1.5", optional = true }
crc32fast = "1.3.2"
-flate2 = { version = "1.0.22", default-features = false, optional = true }
+flate2 = { version = "1.0.23", default-features = false, optional = true }
hmac = { version = "0.12.1", optional = true, features = ["reset"] }
-pbkdf2 = {version = "0.10.1", optional = true }
+pbkdf2 = {version = "0.11.0", optional = true }
sha1 = {version = "0.10.1", optional = true }
-time = { version = "0.3.7", features = ["formatting", "macros" ], optional = true }
-zstd = { version = "0.10.0", optional = true }
+time = { version = "0.3.7", optional = true, default-features = false, features = ["std"] }
+zstd = { version = "0.11.2", optional = true }
[target.'cfg(any(all(target_arch = "arm", target_pointer_width = "32"), target_arch = "mips", target_arch = "powerpc"))'.dependencies]
crossbeam-utils = "0.8.8"
@@ -30,6 +30,7 @@ crossbeam-utils = "0.8.8"
bencher = "0.1.5"
getrandom = "0.2.5"
walkdir = "2.3.2"
+time = { version = "0.3.7", features = ["formatting", "macros"] }
[features]
aes-crypto = [ "aes", "constant_time_eq", "hmac", "pbkdf2", "sha1" ]
@@ -42,3 +43,7 @@ default = ["aes-crypto", "bzip2", "deflate", "time", "zstd"]
[[bench]]
name = "read_entry"
harness = false
+
+[[bench]]
+name = "read_metadata"
+harness = false
diff --git a/METADATA b/METADATA
index c6e83c0..a547795 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/zip
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
name: "zip"
description: "Library to support the reading and writing of zip files."
third_party {
@@ -7,13 +11,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/zip/zip-0.6.2.crate"
+ value: "https://static.crates.io/crates/zip/zip-0.6.4.crate"
}
- version: "0.6.2"
+ version: "0.6.4"
license_type: NOTICE
last_upgrade_date {
- year: 2022
- month: 4
- day: 13
+ year: 2023
+ month: 2
+ day: 17
}
}
diff --git a/README.md b/README.md
index 7db31d4..3754a7c 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ zip-rs
[![Crates.io version](https://img.shields.io/crates/v/zip.svg)](https://crates.io/crates/zip)
[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/rQ7H9cSsF4)
-[Documentation](https://docs.rs/zip/0.6.2/zip/)
+[Documentation](https://docs.rs/zip/0.6.3/zip/)
> PSA: This version of the ZIP crate will not gain any new features,
> and will only be updated if major security issues are found.
@@ -35,14 +35,14 @@ With all default features:
```toml
[dependencies]
-zip = "0.6.2"
+zip = "0.6.4"
```
Without the default features:
```toml
[dependencies]
-zip = { version = "0.6.2", default-features = false }
+zip = { version = "0.6.4", default-features = false }
```
The features available are:
@@ -58,7 +58,7 @@ All of these are enabled by default.
MSRV
----
-Our current Minimum Supported Rust Version is **1.54.0**. When adding features,
+Our current Minimum Supported Rust Version is **1.59.0**. When adding features,
we will follow these guidelines:
- We will always support the latest four minor Rust versions. This gives you a 6
@@ -75,3 +75,24 @@ See the [examples directory](examples) for:
* How to extract a zip file.
* How to extract a single file from a zip.
* How to read a zip from the standard input.
+
+Fuzzing
+-------
+
+Fuzzing support is through [cargo fuzz](https://github.com/rust-fuzz/cargo-fuzz). To install cargo fuzz:
+
+```bash
+cargo install cargo-fuzz
+```
+
+To list fuzz targets:
+
+```bash
+cargo +nightly fuzz list
+```
+
+To start fuzzing zip extraction:
+
+```bash
+cargo +nightly fuzz run fuzz_read
+```
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 7325ef4..bbc8a7f 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,37 +1,29 @@
// Generated by update_crate_tests.py for tests that depend on this crate.
{
- "presubmit": [
+ "imports": [
{
- "name": "ZipFuseTest"
+ "path": "packages/modules/Virtualization/apkdmverity"
},
{
- "name": "libapkverify.integration_test"
+ "path": "packages/modules/Virtualization/avmd"
},
{
- "name": "libapkverify.test"
+ "path": "packages/modules/Virtualization/libs/apexutil"
},
{
- "name": "microdroid_manager_test"
+ "path": "packages/modules/Virtualization/libs/apkverify"
},
{
- "name": "virtualizationservice_device_test"
- }
- ],
- "presubmit-rust": [
- {
- "name": "ZipFuseTest"
- },
- {
- "name": "libapkverify.integration_test"
+ "path": "packages/modules/Virtualization/microdroid_manager"
},
{
- "name": "libapkverify.test"
+ "path": "packages/modules/Virtualization/virtualizationmanager"
},
{
- "name": "microdroid_manager_test"
+ "path": "packages/modules/Virtualization/vm"
},
{
- "name": "virtualizationservice_device_test"
+ "path": "packages/modules/Virtualization/zipfuse"
}
]
}
diff --git a/benches/read_metadata.rs b/benches/read_metadata.rs
new file mode 100644
index 0000000..95334b1
--- /dev/null
+++ b/benches/read_metadata.rs
@@ -0,0 +1,38 @@
+use bencher::{benchmark_group, benchmark_main};
+
+use std::io::{Cursor, Write};
+
+use bencher::Bencher;
+use zip::{ZipArchive, ZipWriter};
+
+const FILE_COUNT: usize = 15_000;
+const FILE_SIZE: usize = 1024;
+
+fn generate_random_archive(count_files: usize, file_size: usize) -> Vec<u8> {
+ let data = Vec::new();
+ let mut writer = ZipWriter::new(Cursor::new(data));
+ let options =
+ zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
+
+ let bytes = vec![0u8; file_size];
+
+ for i in 0..count_files {
+ let name = format!("file_deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_{i}.dat");
+ writer.start_file(name, options).unwrap();
+ writer.write_all(&bytes).unwrap();
+ }
+
+ writer.finish().unwrap().into_inner()
+}
+
+fn read_metadata(bench: &mut Bencher) {
+ let bytes = generate_random_archive(FILE_COUNT, FILE_SIZE);
+
+ bench.iter(|| {
+ let archive = ZipArchive::new(Cursor::new(bytes.as_slice())).unwrap();
+ archive.len()
+ });
+}
+
+benchmark_group!(benches, read_metadata);
+benchmark_main!(benches);
diff --git a/examples/extract.rs b/examples/extract.rs
index 7b8860c..3080716 100644
--- a/examples/extract.rs
+++ b/examples/extract.rs
@@ -12,7 +12,7 @@ fn real_main() -> i32 {
return 1;
}
let fname = std::path::Path::new(&*args[1]);
- let file = fs::File::open(&fname).unwrap();
+ let file = fs::File::open(fname).unwrap();
let mut archive = zip::ZipArchive::new(file).unwrap();
@@ -26,7 +26,7 @@ fn real_main() -> i32 {
{
let comment = file.comment();
if !comment.is_empty() {
- println!("File {} comment: {}", i, comment);
+ println!("File {i} comment: {comment}");
}
}
@@ -42,7 +42,7 @@ fn real_main() -> i32 {
);
if let Some(p) = outpath.parent() {
if !p.exists() {
- fs::create_dir_all(&p).unwrap();
+ fs::create_dir_all(p).unwrap();
}
}
let mut outfile = fs::File::create(&outpath).unwrap();
diff --git a/examples/extract_lorem.rs b/examples/extract_lorem.rs
index a34a04f..bc50abe 100644
--- a/examples/extract_lorem.rs
+++ b/examples/extract_lorem.rs
@@ -11,7 +11,7 @@ fn real_main() -> i32 {
return 1;
}
let fname = std::path::Path::new(&*args[1]);
- let zipfile = std::fs::File::open(&fname).unwrap();
+ let zipfile = std::fs::File::open(fname).unwrap();
let mut archive = zip::ZipArchive::new(zipfile).unwrap();
@@ -25,7 +25,7 @@ fn real_main() -> i32 {
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
- println!("{}", contents);
+ println!("{contents}");
0
}
diff --git a/examples/file_info.rs b/examples/file_info.rs
index 64969b6..6a2adc5 100644
--- a/examples/file_info.rs
+++ b/examples/file_info.rs
@@ -12,7 +12,7 @@ fn real_main() -> i32 {
return 1;
}
let fname = std::path::Path::new(&*args[1]);
- let file = fs::File::open(&fname).unwrap();
+ let file = fs::File::open(fname).unwrap();
let reader = BufReader::new(file);
let mut archive = zip::ZipArchive::new(reader).unwrap();
@@ -30,7 +30,7 @@ fn real_main() -> i32 {
{
let comment = file.comment();
if !comment.is_empty() {
- println!("Entry {} comment: {}", i, comment);
+ println!("Entry {i} comment: {comment}");
}
}
diff --git a/examples/stdin_info.rs b/examples/stdin_info.rs
index 10d7aa8..a609916 100644
--- a/examples/stdin_info.rs
+++ b/examples/stdin_info.rs
@@ -20,12 +20,12 @@ fn real_main() -> i32 {
);
match file.read(&mut buf) {
Ok(n) => println!("The first {} bytes are: {:?}", n, &buf[0..n]),
- Err(e) => println!("Could not read the file: {:?}", e),
+ Err(e) => println!("Could not read the file: {e:?}"),
};
}
Ok(None) => break,
Err(e) => {
- println!("Error encountered while reading zip: {:?}", e);
+ println!("Error encountered while reading zip: {e:?}");
return 1;
}
}
diff --git a/examples/write_dir.rs b/examples/write_dir.rs
index 8cc561f..3b04352 100644
--- a/examples/write_dir.rs
+++ b/examples/write_dir.rs
@@ -54,8 +54,8 @@ fn real_main() -> i32 {
continue;
}
match doit(src_dir, dst_file, method.unwrap()) {
- Ok(_) => println!("done: {} written to {}", src_dir, dst_file),
- Err(e) => println!("Error: {:?}", e),
+ Ok(_) => println!("done: {src_dir} written to {dst_file}"),
+ Err(e) => println!("Error: {e:?}"),
}
}
@@ -84,18 +84,18 @@ where
// Write file or directory explicitly
// Some unzip tools unzip files with directory paths correctly, some do not!
if path.is_file() {
- println!("adding file {:?} as {:?} ...", path, name);
+ println!("adding file {path:?} as {name:?} ...");
#[allow(deprecated)]
zip.start_file_from_path(name, options)?;
let mut f = File::open(path)?;
f.read_to_end(&mut buffer)?;
- zip.write_all(&*buffer)?;
+ zip.write_all(&buffer)?;
buffer.clear();
} else if !name.as_os_str().is_empty() {
// Only if not root! Avoids path spec / warning
// and mapname conversion failed error on unzip
- println!("adding dir {:?} as {:?} ...", path, name);
+ println!("adding dir {path:?} as {name:?} ...");
#[allow(deprecated)]
zip.add_directory_from_path(name, options)?;
}
@@ -114,7 +114,7 @@ fn doit(
}
let path = Path::new(dst_file);
- let file = File::create(&path).unwrap();
+ let file = File::create(path).unwrap();
let walkdir = WalkDir::new(src_dir);
let it = walkdir.into_iter();
diff --git a/examples/write_sample.rs b/examples/write_sample.rs
index b574950..2e45cb1 100644
--- a/examples/write_sample.rs
+++ b/examples/write_sample.rs
@@ -14,8 +14,8 @@ fn real_main() -> i32 {
let filename = &*args[1];
match doit(filename) {
- Ok(_) => println!("File written to {}", filename),
- Err(e) => println!("Error: {:?}", e),
+ Ok(_) => println!("File written to {filename}"),
+ Err(e) => println!("Error: {e:?}"),
}
0
@@ -23,7 +23,7 @@ fn real_main() -> i32 {
fn doit(filename: &str) -> zip::result::ZipResult<()> {
let path = std::path::Path::new(filename);
- let file = std::fs::File::create(&path).unwrap();
+ let file = std::fs::File::create(path).unwrap();
let mut zip = zip::ZipWriter::new(file);
diff --git a/patches/Android.bp.diff b/patches/Android.bp.diff
index c0037cc..dfeb034 100644
--- a/patches/Android.bp.diff
+++ b/patches/Android.bp.diff
@@ -2,10 +2,12 @@ diff --git a/Android.bp b/Android.bp
index 2373253..c0b2349 100644
--- a/Android.bp
+++ b/Android.bp
-@@ -39,4 +39,9 @@ rust_library {
+@@ -39,6 +39,11 @@ rust_library {
"//apex_available:platform",
"com.android.virt",
],
+ product_available: true,
+ vendor_available: true,
+ arch: {
+ arm: {
+ rustlibs: ["libcrossbeam_utils"],
diff --git a/src/compression.rs b/src/compression.rs
index abd8b53..baec939 100644
--- a/src/compression.rs
+++ b/src/compression.rs
@@ -141,7 +141,7 @@ impl CompressionMethod {
impl fmt::Display for CompressionMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Just duplicate what the Debug format looks like, i.e, the enum key:
- write!(f, "{:?}", self)
+ write!(f, "{self:?}")
}
}
@@ -195,8 +195,8 @@ mod test {
#[test]
fn to_display_fmt() {
fn check_match(method: CompressionMethod) {
- let debug_str = format!("{:?}", method);
- let display_str = format!("{}", method);
+ let debug_str = format!("{method:?}");
+ let display_str = format!("{method}");
assert_eq!(debug_str, display_str);
}
diff --git a/src/read.rs b/src/read.rs
index c619f24..dad20c2 100644
--- a/src/read.rs
+++ b/src/read.rs
@@ -348,7 +348,9 @@ impl<R: Read + io::Seek> ZipArchive<R> {
Some(locator64) => {
// If we got here, this is indeed a ZIP64 file.
- if footer.disk_number as u32 != locator64.disk_with_central_directory {
+ if !footer.record_too_small()
+ && footer.disk_number as u32 != locator64.disk_with_central_directory
+ {
return unsupported_zip_error(
"Support for multi-disk files is not implemented",
);
@@ -401,15 +403,23 @@ impl<R: Read + io::Seek> ZipArchive<R> {
pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
- if footer.disk_number != footer.disk_with_central_directory {
+ if !footer.record_too_small() && footer.disk_number != footer.disk_with_central_directory {
return unsupported_zip_error("Support for multi-disk files is not implemented");
}
let (archive_offset, directory_start, number_of_files) =
Self::get_directory_counts(&mut reader, &footer, cde_start_pos)?;
- let mut files = Vec::new();
- let mut names_map = HashMap::new();
+ // If the parsed number of files is greater than the offset then
+ // something fishy is going on and we shouldn't trust number_of_files.
+ let file_capacity = if number_of_files > cde_start_pos as usize {
+ 0
+ } else {
+ number_of_files
+ };
+
+ let mut files = Vec::with_capacity(file_capacity);
+ let mut names_map = HashMap::with_capacity(file_capacity);
if reader.seek(io::SeekFrom::Start(directory_start)).is_err() {
return Err(ZipError::InvalidArchive(
@@ -453,7 +463,7 @@ impl<R: Read + io::Seek> ZipArchive<R> {
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
- fs::create_dir_all(&p)?;
+ fs::create_dir_all(p)?;
}
}
let mut outfile = fs::File::create(&outpath)?;
@@ -639,7 +649,7 @@ pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
reader: &mut R,
archive_offset: u64,
) -> ZipResult<ZipFileData> {
- let central_header_start = reader.seek(io::SeekFrom::Current(0))?;
+ let central_header_start = reader.stream_position()?;
// Parse central header
let signature = reader.read_u32::<LittleEndian>()?;
if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
@@ -673,11 +683,11 @@ pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
reader.read_exact(&mut file_comment_raw)?;
let file_name = match is_utf8 {
- true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
+ true => String::from_utf8_lossy(&file_name_raw).into_owned(),
false => file_name_raw.clone().from_cp437(),
};
let file_comment = match is_utf8 {
- true => String::from_utf8_lossy(&*file_comment_raw).into_owned(),
+ true => String::from_utf8_lossy(&file_comment_raw).into_owned(),
false => file_comment_raw.from_cp437(),
};
@@ -912,12 +922,12 @@ impl<'a> ZipFile<'a> {
self.data.compression_method
}
- /// Get the size of the file in the archive
+ /// Get the size of the file, in bytes, in the archive
pub fn compressed_size(&self) -> u64 {
self.data.compressed_size
}
- /// Get the size of the file when uncompressed
+ /// Get the size of the file, in bytes, when uncompressed
pub fn size(&self) -> u64 {
self.data.uncompressed_size
}
@@ -949,7 +959,7 @@ impl<'a> ZipFile<'a> {
match self.data.system {
System::Unix => Some(self.data.external_attributes >> 16),
System::Dos => {
- // Interpret MSDOS directory bit
+ // Interpret MS-DOS directory bit
let mut mode = if 0x10 == (self.data.external_attributes & 0x10) {
ffi::S_IFDIR | 0o0775
} else {
@@ -1077,7 +1087,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(
reader.read_exact(&mut extra_field)?;
let file_name = match is_utf8 {
- true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
+ true => String::from_utf8_lossy(&file_name_raw).into_owned(),
false => file_name_raw.clone().from_cp437(),
};
@@ -1121,7 +1131,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(
return unsupported_zip_error("The file length is not available in the local header");
}
- let limit_reader = (reader as &'a mut dyn io::Read).take(result.compressed_size as u64);
+ let limit_reader = (reader as &'a mut dyn io::Read).take(result.compressed_size);
let result_crc32 = result.crc32;
let result_compression_method = result.compression_method;
@@ -1267,4 +1277,36 @@ mod test {
);
}
}
+
+ /// test case to ensure we don't preemptively over allocate based on the
+ /// declared number of files in the CDE of an invalid zip when the number of
+ /// files declared is more than the alleged offset in the CDE
+ #[test]
+ fn invalid_cde_number_of_files_allocation_smaller_offset() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!(
+ "../tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip"
+ ));
+ let reader = ZipArchive::new(io::Cursor::new(v));
+ assert!(reader.is_err());
+ }
+
+ /// test case to ensure we don't preemptively over allocate based on the
+ /// declared number of files in the CDE of an invalid zip when the number of
+ /// files declared is less than the alleged offset in the CDE
+ #[test]
+ fn invalid_cde_number_of_files_allocation_greater_offset() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!(
+ "../tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip"
+ ));
+ let reader = ZipArchive::new(io::Cursor::new(v));
+ assert!(reader.is_err());
+ }
}
diff --git a/src/result.rs b/src/result.rs
index 72a30e4..00d558c 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -44,9 +44,9 @@ impl From<io::Error> for ZipError {
impl fmt::Display for ZipError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
- ZipError::Io(err) => write!(fmt, "{}", err),
- ZipError::InvalidArchive(err) => write!(fmt, "invalid Zip archive: {}", err),
- ZipError::UnsupportedArchive(err) => write!(fmt, "unsupported Zip archive: {}", err),
+ ZipError::Io(err) => write!(fmt, "{err}"),
+ ZipError::InvalidArchive(err) => write!(fmt, "invalid Zip archive: {err}"),
+ ZipError::UnsupportedArchive(err) => write!(fmt, "unsupported Zip archive: {err}"),
ZipError::FileNotFound => write!(fmt, "specified file not found in archive"),
}
}
@@ -81,3 +81,18 @@ impl From<ZipError> for io::Error {
io::Error::new(io::ErrorKind::Other, err)
}
}
+
+/// Error type for time parsing
+#[derive(Debug)]
+pub struct DateTimeRangeError;
+
+impl fmt::Display for DateTimeRangeError {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ fmt,
+ "a date could not be represented within the bounds the MS-DOS date range (1980-2107)"
+ )
+ }
+}
+
+impl Error for DateTimeRangeError {}
diff --git a/src/spec.rs b/src/spec.rs
index 3ffcf73..1d8cb0a 100644
--- a/src/spec.rs
+++ b/src/spec.rs
@@ -23,6 +23,18 @@ pub struct CentralDirectoryEnd {
}
impl CentralDirectoryEnd {
+ // Per spec 4.4.1.4 - a CentralDirectoryEnd field might be insufficient to hold the
+ // required data. In this case the file SHOULD contain a ZIP64 format record
+ // and the field of this record will be set to -1
+ pub(crate) fn record_too_small(&self) -> bool {
+ self.disk_number == 0xFFFF
+ || self.disk_with_central_directory == 0xFFFF
+ || self.number_of_files_on_this_disk == 0xFFFF
+ || self.number_of_files == 0xFFFF
+ || self.central_directory_size == 0xFFFFFFFF
+ || self.central_directory_offset == 0xFFFFFFFF
+ }
+
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> {
let magic = reader.read_u32::<LittleEndian>()?;
if magic != CENTRAL_DIRECTORY_END_SIGNATURE {
@@ -64,12 +76,12 @@ impl CentralDirectoryEnd {
let mut pos = file_length - HEADER_SIZE;
while pos >= search_upper_bound {
- reader.seek(io::SeekFrom::Start(pos as u64))?;
+ reader.seek(io::SeekFrom::Start(pos))?;
if reader.read_u32::<LittleEndian>()? == CENTRAL_DIRECTORY_END_SIGNATURE {
reader.seek(io::SeekFrom::Current(
BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64,
))?;
- let cde_start_pos = reader.seek(io::SeekFrom::Start(pos as u64))?;
+ let cde_start_pos = reader.seek(io::SeekFrom::Start(pos))?;
return CentralDirectoryEnd::parse(reader).map(|cde| (cde, cde_start_pos));
}
pos = match pos.checked_sub(1) {
diff --git a/src/types.rs b/src/types.rs
index b65fad4..ad3a570 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -1,13 +1,16 @@
//! Types that specify what is contained in a ZIP.
-#[cfg(doc)]
-use {crate::read::ZipFile, crate::write::FileOptions};
-
+#[cfg(feature = "time")]
+use std::convert::{TryFrom, TryInto};
#[cfg(not(any(
all(target_arch = "arm", target_pointer_width = "32"),
target_arch = "mips",
target_arch = "powerpc"
)))]
use std::sync::atomic;
+#[cfg(not(feature = "time"))]
+use std::time::SystemTime;
+#[cfg(doc)]
+use {crate::read::ZipFile, crate::write::FileOptions};
#[cfg(any(
all(target_arch = "arm", target_pointer_width = "32"),
@@ -42,9 +45,11 @@ mod atomic {
}
#[cfg(feature = "time")]
+use crate::result::DateTimeRangeError;
+#[cfg(feature = "time")]
use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum System {
Dos = 0,
Unix = 3,
@@ -115,7 +120,7 @@ impl DateTime {
let years = (datepart & 0b1111111000000000) >> 9;
DateTime {
- year: (years + 1980) as u16,
+ year: years + 1980,
month: months as u8,
day: days as u8,
hour: hours as u8,
@@ -143,10 +148,8 @@ impl DateTime {
second: u8,
) -> Result<DateTime, ()> {
if (1980..=2107).contains(&year)
- && month >= 1
- && month <= 12
- && day >= 1
- && day <= 31
+ && (1..=12).contains(&month)
+ && (1..=31).contains(&day)
&& hour <= 23
&& minute <= 59
&& second <= 60
@@ -169,19 +172,9 @@ impl DateTime {
///
/// Returns `Err` when this object is out of bounds
#[allow(clippy::result_unit_err)]
+ #[deprecated(note = "use `DateTime::try_from()`")]
pub fn from_time(dt: OffsetDateTime) -> Result<DateTime, ()> {
- if dt.year() >= 1980 && dt.year() <= 2107 {
- Ok(DateTime {
- year: (dt.year()) as u16,
- month: (dt.month()) as u8,
- day: dt.day() as u8,
- hour: dt.hour() as u8,
- minute: dt.minute() as u8,
- second: dt.second() as u8,
- })
- } else {
- Err(())
- }
+ dt.try_into().map_err(|_err| ())
}
/// Gets the time portion of this datetime in the msdos representation
@@ -197,8 +190,6 @@ impl DateTime {
#[cfg(feature = "time")]
/// Converts the DateTime to a OffsetDateTime structure
pub fn to_time(&self) -> Result<OffsetDateTime, ComponentRange> {
- use std::convert::TryFrom;
-
let date =
Date::from_calendar_date(self.year as i32, Month::try_from(self.month)?, self.day)?;
let time = Time::from_hms(self.hour, self.minute, self.second)?;
@@ -256,6 +247,26 @@ impl DateTime {
}
}
+#[cfg(feature = "time")]
+impl TryFrom<OffsetDateTime> for DateTime {
+ type Error = DateTimeRangeError;
+
+ fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
+ if dt.year() >= 1980 && dt.year() <= 2107 {
+ Ok(DateTime {
+ year: (dt.year()) as u16,
+ month: (dt.month()) as u8,
+ day: dt.day(),
+ hour: dt.hour(),
+ minute: dt.minute(),
+ second: dt.second(),
+ })
+ } else {
+ Err(DateTimeRangeError)
+ }
+ }
+}
+
pub const DEFAULT_VERSION: u8 = 46;
/// A type like `AtomicU64` except it implements `Clone` and has predefined
@@ -500,20 +511,43 @@ mod test {
#[cfg(feature = "time")]
#[test]
fn datetime_from_time_bounds() {
+ use std::convert::TryFrom;
+
+ use super::DateTime;
+ use time::macros::datetime;
+
+ // 1979-12-31 23:59:59
+ assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err());
+
+ // 1980-01-01 00:00:00
+ assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
+
+ // 2107-12-31 23:59:59
+ assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
+
+ // 2108-01-01 00:00:00
+ assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err());
+ }
+
+ #[cfg(feature = "time")]
+ #[test]
+ fn datetime_try_from_bounds() {
+ use std::convert::TryFrom;
+
use super::DateTime;
use time::macros::datetime;
// 1979-12-31 23:59:59
- assert!(DateTime::from_time(datetime!(1979-12-31 23:59:59 UTC)).is_err());
+ assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err());
// 1980-01-01 00:00:00
- assert!(DateTime::from_time(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
+ assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
// 2107-12-31 23:59:59
- assert!(DateTime::from_time(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
+ assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
// 2108-01-01 00:00:00
- assert!(DateTime::from_time(datetime!(2108-01-01 00:00:00 UTC)).is_err());
+ assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err());
}
#[test]
@@ -564,10 +598,11 @@ mod test {
#[test]
fn time_at_january() {
use super::DateTime;
+ use std::convert::TryFrom;
// 2020-01-01 00:00:00
let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
- assert!(DateTime::from_time(clock).is_ok());
+ assert!(DateTime::try_from(clock).is_ok());
}
}
diff --git a/src/write.rs b/src/write.rs
index 551b4e3..14252b4 100644
--- a/src/write.rs
+++ b/src/write.rs
@@ -7,6 +7,7 @@ use crate::spec;
use crate::types::{AtomicU64, DateTime, System, ZipFileData, DEFAULT_VERSION};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crc32fast::Hasher;
+use std::convert::TryInto;
use std::default::Default;
use std::io;
use std::io::prelude::*;
@@ -110,31 +111,6 @@ pub struct FileOptions {
}
impl FileOptions {
- /// Construct a new FileOptions object
- pub fn default() -> FileOptions {
- FileOptions {
- #[cfg(any(
- feature = "deflate",
- feature = "deflate-miniz",
- feature = "deflate-zlib"
- ))]
- compression_method: CompressionMethod::Deflated,
- #[cfg(not(any(
- feature = "deflate",
- feature = "deflate-miniz",
- feature = "deflate-zlib"
- )))]
- compression_method: CompressionMethod::Stored,
- compression_level: None,
- #[cfg(feature = "time")]
- last_modified_time: DateTime::from_time(OffsetDateTime::now_utc()).unwrap_or_default(),
- #[cfg(not(feature = "time"))]
- last_modified_time: DateTime::default(),
- permissions: None,
- large_file: false,
- }
- }
-
/// Set the compression method for the new file
///
/// The default is `CompressionMethod::Deflated`. If the deflate compression feature is
@@ -174,7 +150,11 @@ impl FileOptions {
///
/// The format is represented with unix-style permissions.
/// The default is `0o644`, which represents `rw-r--r--` for files,
- /// and `0o755`, which represents `rwxr-xr-x` for directories
+ /// and `0o755`, which represents `rwxr-xr-x` for directories.
+ ///
+ /// This method only preserves the file permissions bits (via a `& 0o777`) and discards
+ /// higher file mode bits. So it cannot be used to denote an entry as a directory,
+ /// symlink, or other special file type.
#[must_use]
pub fn unix_permissions(mut self, mode: u32) -> FileOptions {
self.permissions = Some(mode & 0o777);
@@ -194,8 +174,29 @@ impl FileOptions {
}
impl Default for FileOptions {
+ /// Construct a new FileOptions object
fn default() -> Self {
- Self::default()
+ Self {
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ compression_method: CompressionMethod::Deflated,
+ #[cfg(not(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ )))]
+ compression_method: CompressionMethod::Stored,
+ compression_level: None,
+ #[cfg(feature = "time")]
+ last_modified_time: OffsetDateTime::now_utc().try_into().unwrap_or_default(),
+ #[cfg(not(feature = "time"))]
+ last_modified_time: DateTime::default(),
+ permissions: None,
+ large_file: false,
+ }
}
}
@@ -348,7 +349,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
{
let writer = self.inner.get_plain();
- let header_start = writer.seek(io::SeekFrom::Current(0))?;
+ let header_start = writer.stream_position()?;
let permissions = options.permissions.unwrap_or(0o100644);
let mut file = ZipFileData {
@@ -375,7 +376,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
};
write_local_file_header(writer, &file)?;
- let header_end = writer.seek(io::SeekFrom::Current(0))?;
+ let header_end = writer.stream_position()?;
self.stats.start = header_end;
*file.data_start.get_mut() = header_end;
@@ -404,7 +405,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
file.crc32 = self.stats.hasher.clone().finalize();
file.uncompressed_size = self.stats.bytes_written;
- let file_end = writer.seek(io::SeekFrom::Current(0))?;
+ let file_end = writer.stream_position()?;
file.compressed_size = file_end - self.stats.start;
update_local_file_header(writer, file)?;
@@ -723,7 +724,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
/// Add a directory entry, taking a Path as argument.
///
- /// This function ensures that the '/' path seperator is used. It also ignores all non 'Normal'
+ /// This function ensures that the '/' path separator is used. It also ignores all non 'Normal'
/// Components, such as a starting '/' or '..' and '.'.
#[deprecated(
since = "0.5.7",
@@ -747,17 +748,55 @@ impl<W: Write + io::Seek> ZipWriter<W> {
Ok(inner.unwrap())
}
+ /// Add a symlink entry.
+ ///
+ /// The zip archive will contain an entry for path `name` which is a symlink to `target`.
+ ///
+ /// No validation or normalization of the paths is performed. For best results,
+ /// callers should normalize `\` to `/` and ensure symlinks are relative to other
+ /// paths within the zip archive.
+ ///
+ /// WARNING: not all zip implementations preserve symlinks on extract. Some zip
+ /// implementations may materialize a symlink as a regular file, possibly with the
+ /// content incorrectly set to the symlink target. For maximum portability, consider
+ /// storing a regular file instead.
+ pub fn add_symlink<N, T>(
+ &mut self,
+ name: N,
+ target: T,
+ mut options: FileOptions,
+ ) -> ZipResult<()>
+ where
+ N: Into<String>,
+ T: Into<String>,
+ {
+ if options.permissions.is_none() {
+ options.permissions = Some(0o777);
+ }
+ *options.permissions.as_mut().unwrap() |= 0o120000;
+ // The symlink target is stored as file content. And compressing the target path
+ // likely wastes space. So always store.
+ options.compression_method = CompressionMethod::Stored;
+
+ self.start_entry(name, options, None)?;
+ self.writing_to_file = true;
+ self.write_all(target.into().as_bytes())?;
+ self.writing_to_file = false;
+
+ Ok(())
+ }
+
fn finalize(&mut self) -> ZipResult<()> {
self.finish_file()?;
{
let writer = self.inner.get_plain();
- let central_start = writer.seek(io::SeekFrom::Current(0))?;
+ let central_start = writer.stream_position()?;
for file in self.files.iter() {
write_central_directory_header(writer, file)?;
}
- let central_size = writer.seek(io::SeekFrom::Current(0))? - central_start;
+ let central_size = writer.stream_position()? - central_start;
if self.files.len() > spec::ZIP64_ENTRY_THR
|| central_size.max(central_start) > spec::ZIP64_BYTES_THR
@@ -806,7 +845,7 @@ impl<W: Write + io::Seek> Drop for ZipWriter<W> {
fn drop(&mut self) {
if !self.inner.is_closed() {
if let Err(e) = self.finalize() {
- let _ = write!(io::stderr(), "ZipWriter drop failed: {:?}", e);
+ let _ = write!(io::stderr(), "ZipWriter drop failed: {e:?}");
}
}
}
@@ -1169,8 +1208,7 @@ fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> {
return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other,
format!(
- "Extra data header ID {:#06} requires crate feature \"unreserved\"",
- kind,
+ "Extra data header ID {kind:#06} requires crate feature \"unreserved\"",
),
)));
}
@@ -1259,7 +1297,7 @@ fn path_to_string(path: &std::path::Path) -> String {
if !path_str.is_empty() {
path_str.push('/');
}
- path_str.push_str(&*os_str.to_string_lossy());
+ path_str.push_str(&os_str.to_string_lossy());
}
}
path_str
@@ -1286,6 +1324,13 @@ mod test {
}
#[test]
+ fn unix_permissions_bitmask() {
+ // unix_permissions() throws away upper bits.
+ let options = FileOptions::default().unix_permissions(0o120777);
+ assert_eq!(options.permissions, Some(0o777));
+ }
+
+ #[test]
fn write_zip_dir() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer
@@ -1314,6 +1359,67 @@ mod test {
}
#[test]
+ fn write_symlink_simple() {
+ let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
+ writer
+ .add_symlink(
+ "name",
+ "target",
+ FileOptions::default().last_modified_time(
+ DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
+ ),
+ )
+ .unwrap();
+ assert!(writer
+ .write(b"writing to a symlink is not allowed and will not write any data")
+ .is_err());
+ let result = writer.finish().unwrap();
+ assert_eq!(result.get_ref().len(), 112);
+ assert_eq!(
+ *result.get_ref(),
+ &[
+ 80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0,
+ 6, 0, 0, 0, 4, 0, 0, 0, 110, 97, 109, 101, 116, 97, 114, 103, 101, 116, 80, 75, 1,
+ 2, 46, 3, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0, 6, 0,
+ 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 161, 0, 0, 0, 0, 110, 97, 109, 101,
+ 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 50, 0, 0, 0, 40, 0, 0, 0, 0, 0
+ ] as &[u8],
+ );
+ }
+
+ #[test]
+ fn write_symlink_wonky_paths() {
+ let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
+ writer
+ .add_symlink(
+ "directory\\link",
+ "/absolute/symlink\\with\\mixed/slashes",
+ FileOptions::default().last_modified_time(
+ DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
+ ),
+ )
+ .unwrap();
+ assert!(writer
+ .write(b"writing to a symlink is not allowed and will not write any data")
+ .is_err());
+ let result = writer.finish().unwrap();
+ assert_eq!(result.get_ref().len(), 162);
+ assert_eq!(
+ *result.get_ref(),
+ &[
+ 80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95, 41, 81, 245, 36, 0, 0, 0,
+ 36, 0, 0, 0, 14, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105,
+ 110, 107, 47, 97, 98, 115, 111, 108, 117, 116, 101, 47, 115, 121, 109, 108, 105,
+ 110, 107, 92, 119, 105, 116, 104, 92, 109, 105, 120, 101, 100, 47, 115, 108, 97,
+ 115, 104, 101, 115, 80, 75, 1, 2, 46, 3, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95,
+ 41, 81, 245, 36, 0, 0, 0, 36, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
+ 161, 0, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105, 110,
+ 107, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 60, 0, 0, 0, 80, 0, 0, 0, 0, 0
+ ] as &[u8],
+ );
+ }
+
+ #[test]
fn write_mimetype_zip() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
let options = FileOptions {
diff --git a/tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip b/tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip
new file mode 100644
index 0000000..a428ca7
--- /dev/null
+++ b/tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip
Binary files differ
diff --git a/tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip b/tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip
new file mode 100644
index 0000000..2cc9007
--- /dev/null
+++ b/tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip
Binary files differ
diff --git a/tests/end_to_end.rs b/tests/end_to_end.rs
index 25d0c54..09e7ce4 100644
--- a/tests/end_to_end.rs
+++ b/tests/end_to_end.rs
@@ -13,7 +13,7 @@ fn end_to_end() {
for &method in SUPPORTED_COMPRESSION_METHODS {
let file = &mut Cursor::new(Vec::new());
- println!("Writing file with {} compression", method);
+ println!("Writing file with {method} compression");
write_test_archive(file, method).expect("Couldn't write test zip archive");
println!("Checking file contents");
diff --git a/tests/zip64_large.rs b/tests/zip64_large.rs
index 3d10a31..468ef19 100644
--- a/tests/zip64_large.rs
+++ b/tests/zip64_large.rs
@@ -205,7 +205,7 @@ fn zip64_large() {
match file.read_exact(&mut buf) {
Ok(()) => println!("The first {} bytes are: {:?}", buf.len(), buf),
- Err(e) => println!("Could not read the file: {:?}", e),
+ Err(e) => println!("Could not read the file: {e:?}"),
};
}
}