Skip to content

Commit 61afe4d

Browse files
committed
Added ExtendedFileOptions
1 parent 16e9485 commit 61afe4d

File tree

12 files changed

+344
-202
lines changed

12 files changed

+344
-202
lines changed

Cargo.toml

+24-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
[package]
22
name = "zip_next"
33
version = "1.0.1"
4-
authors = ["Mathijs van de Nes <[email protected]>", "Marli Frost <[email protected]>", "Ryan Levick <[email protected]>",
5-
"Chris Hennick <[email protected]>"]
4+
authors = [
5+
"Mathijs van de Nes <[email protected]>",
6+
"Marli Frost <[email protected]>",
7+
"Ryan Levick <[email protected]>",
8+
"Chris Hennick <[email protected]>",
9+
]
610
license = "MIT"
711
repository = "https://github.com/Pr0methean/zip-next.git"
812
keywords = ["zip", "archive"]
@@ -22,9 +26,11 @@ constant_time_eq = { version = "0.3.0", optional = true }
2226
crc32fast = "1.4.0"
2327
flate2 = { version = "1.0.28", default-features = false, optional = true }
2428
hmac = { version = "0.12.1", optional = true, features = ["reset"] }
25-
pbkdf2 = {version = "0.12.2", optional = true }
26-
sha1 = {version = "0.10.6", optional = true }
27-
time = { version = "0.3.34", optional = true, default-features = false, features = ["std"] }
29+
pbkdf2 = { version = "0.12.2", optional = true }
30+
sha1 = { version = "0.10.6", optional = true }
31+
time = { version = "0.3.34", optional = true, default-features = false, features = [
32+
"std",
33+
] }
2834
zstd = { version = "0.13.1", optional = true, default-features = false }
2935
zopfli = { version = "0.8.0", optional = true }
3036
deflate64 = { version = "0.1.8", optional = true }
@@ -41,9 +47,9 @@ bencher = "0.1.5"
4147
getrandom = { version = "0.2.14", features = ["js"] }
4248
walkdir = "2.5.0"
4349
time = { version = "0.3.34", features = ["formatting", "macros"] }
44-
50+
anyhow = "1"
4551
[features]
46-
aes-crypto = [ "aes", "constant_time_eq", "hmac", "pbkdf2", "sha1" ]
52+
aes-crypto = ["aes", "constant_time_eq", "hmac", "pbkdf2", "sha1"]
4753
chrono = ["chrono/default"]
4854
deflate = ["flate2/rust_backend"]
4955
deflate-miniz = ["flate2/default"]
@@ -52,7 +58,17 @@ deflate-zlib-ng = ["flate2/zlib-ng"]
5258
deflate-zopfli = ["zopfli"]
5359
lzma = ["lzma-rs/stream"]
5460
unreserved = []
55-
default = ["aes-crypto", "bzip2", "deflate", "deflate64", "deflate-zlib-ng", "deflate-zopfli", "lzma", "time", "zstd"]
61+
default = [
62+
"aes-crypto",
63+
"bzip2",
64+
"deflate",
65+
"deflate64",
66+
"deflate-zlib-ng",
67+
"deflate-zopfli",
68+
"lzma",
69+
"time",
70+
"zstd",
71+
]
5672

5773
[[bench]]
5874
name = "read_entry"

benches/read_entry.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ use std::io::{Cursor, Read, Write};
44

55
use bencher::Bencher;
66
use getrandom::getrandom;
7-
use zip_next::{ZipArchive, ZipWriter};
7+
use zip_next::{write::SimpleFileOptions, ZipArchive, ZipWriter};
88

99
fn generate_random_archive(size: usize) -> Vec<u8> {
1010
let data = Vec::new();
1111
let mut writer = ZipWriter::new(Cursor::new(data));
12-
let options = zip_next::write::FileOptions::default()
13-
.compression_method(zip_next::CompressionMethod::Stored);
12+
let options =
13+
SimpleFileOptions::default().compression_method(zip_next::CompressionMethod::Stored);
1414

1515
writer.start_file("random.dat", options).unwrap();
1616
let mut bytes = vec![0u8; size];

benches/read_metadata.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bencher::{benchmark_group, benchmark_main};
33
use std::io::{Cursor, Write};
44

55
use bencher::Bencher;
6-
use zip_next::write::FileOptions;
6+
use zip_next::write::SimpleFileOptions;
77
use zip_next::{CompressionMethod, ZipArchive, ZipWriter};
88

99
const FILE_COUNT: usize = 15_000;
@@ -12,13 +12,13 @@ const FILE_SIZE: usize = 1024;
1212
fn generate_random_archive(count_files: usize, file_size: usize) -> Vec<u8> {
1313
let data = Vec::new();
1414
let mut writer = ZipWriter::new(Cursor::new(data));
15-
let options = FileOptions::default().compression_method(CompressionMethod::Stored);
15+
let options = SimpleFileOptions::default().compression_method(CompressionMethod::Stored);
1616

1717
let bytes = vec![0u8; file_size];
1818

1919
for i in 0..count_files {
2020
let name = format!("file_deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_{i}.dat");
21-
writer.start_file(name, options.clone()).unwrap();
21+
writer.start_file(name, options).unwrap();
2222
writer.write_all(&bytes).unwrap();
2323
}
2424

examples/write_dir.rs

+16-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
use anyhow::Context;
12
use std::io::prelude::*;
2-
use zip_next::result::ZipError;
3-
use zip_next::write::FileOptions;
3+
use zip_next::{result::ZipError, write::SimpleFileOptions};
44

55
use std::fs::File;
66
use std::path::Path;
@@ -58,7 +58,7 @@ fn real_main() -> i32 {
5858
}
5959
match doit(src_dir, dst_file, method.unwrap()) {
6060
Ok(_) => println!("done: {src_dir} written to {dst_file}"),
61-
Err(e) => println!("Error: {e:?}"),
61+
Err(e) => eprintln!("Error: {e:?}"),
6262
}
6363
}
6464

@@ -70,26 +70,30 @@ fn zip_dir<T>(
7070
prefix: &str,
7171
writer: T,
7272
method: zip_next::CompressionMethod,
73-
) -> zip_next::result::ZipResult<()>
73+
) -> anyhow::Result<()>
7474
where
7575
T: Write + Seek,
7676
{
7777
let mut zip = zip_next::ZipWriter::new(writer);
78-
let options = FileOptions::default()
78+
let options = SimpleFileOptions::default()
7979
.compression_method(method)
8080
.unix_permissions(0o755);
8181

82+
let prefix = Path::new(prefix);
8283
let mut buffer = Vec::new();
8384
for entry in it {
8485
let path = entry.path();
85-
let name = path.strip_prefix(Path::new(prefix)).unwrap();
86+
let name = path.strip_prefix(prefix).unwrap();
87+
let path_as_string = name
88+
.to_str()
89+
.map(str::to_owned)
90+
.with_context(|| format!("{name:?} Is a Non UTF-8 Path"))?;
8691

8792
// Write file or directory explicitly
8893
// Some unzip tools unzip files with directory paths correctly, some do not!
8994
if path.is_file() {
9095
println!("adding file {path:?} as {name:?} ...");
91-
#[allow(deprecated)]
92-
zip.start_file_from_path(name, options.clone())?;
96+
zip.start_file(path_as_string, options)?;
9397
let mut f = File::open(path)?;
9498

9599
f.read_to_end(&mut buffer)?;
@@ -98,22 +102,17 @@ where
98102
} else if !name.as_os_str().is_empty() {
99103
// Only if not root! Avoids path spec / warning
100104
// and mapname conversion failed error on unzip
101-
println!("adding dir {path:?} as {name:?} ...");
102-
#[allow(deprecated)]
103-
zip.add_directory_from_path(name, options.clone())?;
105+
println!("adding dir {path_as_string:?} as {name:?} ...");
106+
zip.add_directory(path_as_string, options)?;
104107
}
105108
}
106109
zip.finish()?;
107110
Ok(())
108111
}
109112

110-
fn doit(
111-
src_dir: &str,
112-
dst_file: &str,
113-
method: zip_next::CompressionMethod,
114-
) -> zip_next::result::ZipResult<()> {
113+
fn doit(src_dir: &str, dst_file: &str, method: zip_next::CompressionMethod) -> anyhow::Result<()> {
115114
if !Path::new(src_dir).is_dir() {
116-
return Err(ZipError::FileNotFound);
115+
return Err(ZipError::FileNotFound.into());
117116
}
118117

119118
let path = Path::new(dst_file);

examples/write_sample.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::io::prelude::*;
2-
use zip_next::write::FileOptions;
2+
use zip_next::write::SimpleFileOptions;
33

44
fn main() {
55
std::process::exit(real_main());
@@ -27,15 +27,15 @@ fn doit(filename: &str) -> zip_next::result::ZipResult<()> {
2727

2828
let mut zip = zip_next::ZipWriter::new(file);
2929

30-
zip.add_directory("test/", Default::default())?;
30+
zip.add_directory("test/", SimpleFileOptions::default())?;
3131

32-
let options = FileOptions::default()
32+
let options = SimpleFileOptions::default()
3333
.compression_method(zip_next::CompressionMethod::Stored)
3434
.unix_permissions(0o755);
3535
zip.start_file("test/☃.txt", options)?;
3636
zip.write_all(b"Hello, World!\n")?;
3737

38-
zip.start_file("test/lorem_ipsum.txt", Default::default())?;
38+
zip.start_file("test/lorem_ipsum.txt", options)?;
3939
zip.write_all(LOREM_IPSUM)?;
4040

4141
zip.finish()?;

fuzz/fuzz_targets/fuzz_write.rs

+39-21
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
11
#![no_main]
22

3-
use std::cell::RefCell;
4-
use libfuzzer_sys::fuzz_target;
53
use arbitrary::Arbitrary;
4+
use libfuzzer_sys::fuzz_target;
5+
use std::cell::RefCell;
66
use std::io::{Cursor, Read, Seek, Write};
7-
use std::path::{PathBuf};
7+
use std::path::PathBuf;
88

9-
#[derive(Arbitrary,Clone,Debug)]
9+
#[derive(Arbitrary, Clone, Debug)]
1010
pub enum BasicFileOperation {
1111
WriteNormalFile {
1212
contents: Vec<Vec<u8>>,
13-
options: zip_next::write::FileOptions,
13+
options: zip_next::write::FullFileOptions,
1414
},
15-
WriteDirectory(zip_next::write::FileOptions),
15+
WriteDirectory(zip_next::write::FullFileOptions),
1616
WriteSymlinkWithTarget {
1717
target: Box<PathBuf>,
18-
options: zip_next::write::FileOptions,
18+
options: zip_next::write::FullFileOptions,
1919
},
2020
ShallowCopy(Box<FileOperation>),
2121
DeepCopy(Box<FileOperation>),
2222
}
2323

24-
#[derive(Arbitrary,Clone,Debug)]
24+
#[derive(Arbitrary, Clone, Debug)]
2525
pub struct FileOperation {
2626
basic: BasicFileOperation,
2727
name: String,
2828
reopen: bool,
2929
// 'abort' flag is separate, to prevent trying to copy an aborted file
3030
}
3131

32-
#[derive(Arbitrary,Clone,Debug)]
32+
#[derive(Arbitrary, Clone, Debug)]
3333
pub struct FuzzTestCase {
3434
comment: Vec<u8>,
3535
operations: Vec<(FileOperation, bool)>,
@@ -47,14 +47,25 @@ impl FileOperation {
4747
}
4848
}
4949

50-
fn do_operation<T>(writer: &mut RefCell<zip_next::ZipWriter<T>>,
51-
operation: FileOperation,
52-
abort: bool, flush_on_finish_file: bool) -> Result<(), Box<dyn std::error::Error>>
53-
where T: Read + Write + Seek {
54-
writer.borrow_mut().set_flush_on_finish_file(flush_on_finish_file);
50+
fn do_operation<T>(
51+
writer: &mut RefCell<zip_next::ZipWriter<T>>,
52+
operation: FileOperation,
53+
abort: bool,
54+
flush_on_finish_file: bool,
55+
) -> Result<(), Box<dyn std::error::Error>>
56+
where
57+
T: Read + Write + Seek,
58+
{
59+
writer
60+
.borrow_mut()
61+
.set_flush_on_finish_file(flush_on_finish_file);
5562
let name = operation.name;
5663
match operation.basic {
57-
BasicFileOperation::WriteNormalFile {contents, mut options, ..} => {
64+
BasicFileOperation::WriteNormalFile {
65+
contents,
66+
mut options,
67+
..
68+
} => {
5869
let uncompressed_size = contents.iter().map(Vec::len).sum::<usize>();
5970
if uncompressed_size >= u32::MAX as usize {
6071
options = options.large_file(true);
@@ -67,8 +78,10 @@ fn do_operation<T>(writer: &mut RefCell<zip_next::ZipWriter<T>>,
6778
BasicFileOperation::WriteDirectory(options) => {
6879
writer.borrow_mut().add_directory(name, options)?;
6980
}
70-
BasicFileOperation::WriteSymlinkWithTarget {target, options} => {
71-
writer.borrow_mut().add_symlink(name, target.to_string_lossy(), options)?;
81+
BasicFileOperation::WriteSymlinkWithTarget { target, options } => {
82+
writer
83+
.borrow_mut()
84+
.add_symlink(name, target.to_string_lossy(), options)?;
7285
}
7386
BasicFileOperation::ShallowCopy(base) => {
7487
let base_name = base.referenceable_name();
@@ -86,8 +99,8 @@ fn do_operation<T>(writer: &mut RefCell<zip_next::ZipWriter<T>>,
8699
}
87100
if operation.reopen {
88101
let old_comment = writer.borrow().get_raw_comment().to_owned();
89-
let new_writer = zip_next::ZipWriter::new_append(
90-
writer.borrow_mut().finish().unwrap()).unwrap();
102+
let new_writer =
103+
zip_next::ZipWriter::new_append(writer.borrow_mut().finish().unwrap()).unwrap();
91104
assert_eq!(&old_comment, new_writer.get_raw_comment());
92105
*writer = new_writer.into();
93106
}
@@ -98,7 +111,12 @@ fuzz_target!(|test_case: FuzzTestCase| {
98111
let mut writer = RefCell::new(zip_next::ZipWriter::new(Cursor::new(Vec::new())));
99112
writer.borrow_mut().set_raw_comment(test_case.comment);
100113
for (operation, abort) in test_case.operations {
101-
let _ = do_operation(&mut writer, operation, abort, test_case.flush_on_finish_file);
114+
let _ = do_operation(
115+
&mut writer,
116+
operation,
117+
abort,
118+
test_case.flush_on_finish_file,
119+
);
102120
}
103121
let _ = zip_next::ZipArchive::new(writer.borrow_mut().finish().unwrap());
104-
});
122+
});

src/read.rs

+13-9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use byteorder::{LittleEndian, ReadBytesExt};
1414
use std::borrow::{Borrow, Cow};
1515
use std::collections::HashMap;
1616
use std::io::{self, prelude::*};
17+
use std::ops::Deref;
1718
use std::path::{Path, PathBuf};
1819
use std::sync::{Arc, OnceLock};
1920

@@ -828,8 +829,8 @@ fn central_header_to_zip_file_inner<R: Read>(
828829
uncompressed_size: uncompressed_size as u64,
829830
file_name,
830831
file_name_raw: file_name_raw.into(),
831-
extra_field: Arc::new(extra_field),
832-
central_extra_field: Arc::new(vec![]),
832+
extra_field: Some(Arc::new(extra_field)),
833+
central_extra_field: None,
833834
file_comment,
834835
header_start: offset,
835836
central_header_start,
@@ -861,9 +862,12 @@ fn central_header_to_zip_file_inner<R: Read>(
861862
}
862863

863864
fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
864-
let mut reader = io::Cursor::new(file.extra_field.as_ref());
865+
let Some(extra_field) = &file.extra_field else {
866+
return Ok(());
867+
};
868+
let mut reader = io::Cursor::new(extra_field.as_ref());
865869

866-
while (reader.position() as usize) < file.extra_field.len() {
870+
while (reader.position() as usize) < extra_field.len() {
867871
let kind = reader.read_u16::<LittleEndian>()?;
868872
let len = reader.read_u16::<LittleEndian>()?;
869873
let mut len_left = len as i64;
@@ -1068,8 +1072,8 @@ impl<'a> ZipFile<'a> {
10681072
}
10691073

10701074
/// Get the extra data of the zip header for this file
1071-
pub fn extra_data(&self) -> &[u8] {
1072-
&self.data.extra_field
1075+
pub fn extra_data(&self) -> Option<&[u8]> {
1076+
self.data.extra_field.as_ref().map(|v| v.deref().deref())
10731077
}
10741078

10751079
/// Get the starting offset of the data of the compressed file
@@ -1115,7 +1119,7 @@ impl<'a> Drop for ZipFile<'a> {
11151119
loop {
11161120
match reader.read(&mut buffer) {
11171121
Ok(0) => break,
1118-
Ok(_) => (),
1122+
Ok(_read) => (),
11191123
Err(e) => {
11201124
panic!("Could not consume all of the output of the current ZipFile: {e:?}")
11211125
}
@@ -1188,8 +1192,8 @@ pub fn read_zipfile_from_stream<'a, R: Read>(reader: &'a mut R) -> ZipResult<Opt
11881192
uncompressed_size: uncompressed_size as u64,
11891193
file_name,
11901194
file_name_raw: file_name_raw.into(),
1191-
extra_field: Arc::new(extra_field),
1192-
central_extra_field: Arc::new(vec![]),
1195+
extra_field: Some(Arc::new(extra_field)),
1196+
central_extra_field: None,
11931197
file_comment: String::with_capacity(0).into_boxed_str(), // file comment is only available in the central directory
11941198
// header_start and data start are not available, but also don't matter, since seeking is
11951199
// not available.

0 commit comments

Comments
 (0)