From 320f27ff8f6accbeab4457643384078bfb188fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Rodrigues=20Miguel?= Date: Wed, 24 Mar 2021 01:56:18 -0300 Subject: [PATCH] Updates README.md and Cargo.toml --- Cargo.lock | 39 +- Cargo.toml | 12 +- README.md | 13 +- third-party/zip/.gitignore | 5 - third-party/zip/CODE_OF_CONDUCT.md | 77 -- third-party/zip/Cargo.toml | 35 - third-party/zip/LICENSE | 21 - third-party/zip/README.md | 70 -- third-party/zip/benches/read_entry.rs | 43 - third-party/zip/examples/extract.rs | 63 -- third-party/zip/examples/extract_lorem.rs | 31 - third-party/zip/examples/file_info.rs | 53 - third-party/zip/examples/stdin_info.rs | 34 - third-party/zip/examples/write_dir.rs | 120 --- third-party/zip/examples/write_sample.rs | 71 -- third-party/zip/src/compression.rs | 180 ---- third-party/zip/src/cp437.rs | 203 ---- third-party/zip/src/crc32.rs | 93 -- third-party/zip/src/lib.rs | 21 - third-party/zip/src/read.rs | 1078 --------------------- third-party/zip/src/result.rs | 39 - third-party/zip/src/spec.rs | 182 ---- third-party/zip/src/types.rs | 474 --------- third-party/zip/src/write.rs | 821 ---------------- third-party/zip/src/zipcrypto.rs | 162 ---- 25 files changed, 33 insertions(+), 3907 deletions(-) delete mode 100644 third-party/zip/.gitignore delete mode 100644 third-party/zip/CODE_OF_CONDUCT.md delete mode 100644 third-party/zip/Cargo.toml delete mode 100644 third-party/zip/LICENSE delete mode 100644 third-party/zip/README.md delete mode 100644 third-party/zip/benches/read_entry.rs delete mode 100644 third-party/zip/examples/extract.rs delete mode 100644 third-party/zip/examples/extract_lorem.rs delete mode 100644 third-party/zip/examples/file_info.rs delete mode 100644 third-party/zip/examples/stdin_info.rs delete mode 100644 third-party/zip/examples/write_dir.rs delete mode 100644 third-party/zip/examples/write_sample.rs delete mode 100644 third-party/zip/src/compression.rs delete mode 100644 third-party/zip/src/cp437.rs delete mode 100644 third-party/zip/src/crc32.rs delete mode 100644 third-party/zip/src/lib.rs delete mode 100644 third-party/zip/src/read.rs delete mode 100644 third-party/zip/src/result.rs delete mode 100644 third-party/zip/src/spec.rs delete mode 100644 third-party/zip/src/types.rs delete mode 100644 third-party/zip/src/write.rs delete mode 100644 third-party/zip/src/zipcrypto.rs diff --git a/Cargo.lock b/Cargo.lock index 699d6fa..564aa3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,10 +1,10 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "adler" -version = "1.0.2" +name = "adler32" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "ansi_term" @@ -26,12 +26,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - [[package]] name = "bitflags" version = "1.2.1" @@ -81,6 +75,12 @@ version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -119,7 +119,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -128,7 +128,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "winapi", @@ -136,11 +136,11 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.20" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "crc32fast", "libc", "miniz_oxide", @@ -180,12 +180,11 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" dependencies = [ - "adler", - "autocfg", + "adler32", ] [[package]] @@ -398,7 +397,9 @@ dependencies = [ [[package]] name = "zip" -version = "0.5.10" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8264fcea9b7a036a4a5103d7153e988dbc2ebbafb34f68a3c2d404b6b82d74b6" dependencies = [ "byteorder", "bzip2 0.3.3", diff --git a/Cargo.toml b/Cargo.toml index af1f663..f72c758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,11 @@ clap = "2.33.3" tar = "0.4.33" xz2 = "0.1.6" bzip2 = "0.4.2" -flate2 = "1.0.20" +flate2 = "1.0.14" +zip = "0.5.11" -# Keeping zip locally since upstream zip is staying on an older flate2 version -# in order to not increase MSRV, which is not something I particularly care about -# for ouch -zip = { version = "0.5.10", path = "./third-party/zip" } + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 diff --git a/README.md b/README.md index 720069f..28b8fc7 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ ``` ouch 0.1.0 +Vinícius R. Miguel ouch is a unified compression & decompression utility USAGE: @@ -24,8 +25,8 @@ FLAGS: -V, --version Prints version information OPTIONS: - -i, --input ... Input files (TODO description) - -o, --output Output file (TODO description) + -i, --input ... The input files or directories. + -o, --output The output directory or compressed file. ``` ### Examples @@ -42,8 +43,8 @@ When no output file is supplied, `ouch` infers that it must decompress all of it ```bash $ ouch -i file{1..5}.tar.gz -o some-folder -info: attempting to decompress input files into single_folder -info: done! +# Decompresses file1.tar.gz, file2.tar.gz, file3.tar.gz, file4.tar.gz and file5.tar.gz to some-folder +# The folder `ouch` saves to will be created if it doesn't already exist ``` When the output file is not a compressed file, `ouch` will check if all input files are decompressible and infer that it must decompress them into the output file. @@ -52,8 +53,6 @@ When the output file is not a compressed file, `ouch` will check if all input fi ```bash $ ouch -i file{1..20} -o archive.tar -info: trying to compress input files into 'archive.tar' -info: done! ``` ### Error scenarios @@ -65,7 +64,7 @@ $ ouch -i some-file -o some-folder error: file 'some-file' is not decompressible. ``` -`ouch` might (TODO!) be able to sniff a file's compression format if it isn't supplied in the future, but that is not currently implemented. +`ouch` cannot infer `some-file`'s compression format since it lacks an extension. Likewise, `ouch` cannot infer that the output file given is a compressed file, so it shows the user an error. ## Installation diff --git a/third-party/zip/.gitignore b/third-party/zip/.gitignore deleted file mode 100644 index 0a8fc03..0000000 --- a/third-party/zip/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -Cargo.lock -target - -\.idea/ -tests/ diff --git a/third-party/zip/CODE_OF_CONDUCT.md b/third-party/zip/CODE_OF_CONDUCT.md deleted file mode 100644 index 845634e..0000000 --- a/third-party/zip/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,77 +0,0 @@ - -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to make participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies within all project spaces, and it also applies when -an individual is representing the project or its community in public spaces. -Examples of representing a project or community include using an official -project e-mail address, posting via an official social media account, or acting -as an appointed representative at an online or offline event. Representation of -a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at ryan.levick@gmail.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/third-party/zip/Cargo.toml b/third-party/zip/Cargo.toml deleted file mode 100644 index 0a8583a..0000000 --- a/third-party/zip/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "zip" -version = "0.5.10" -authors = ["Mathijs van de Nes ", "Marli Frost ", "Ryan Levick "] -license = "MIT" -repository = "https://github.com/zip-rs/zip.git" -keywords = ["zip", "archive"] -description = """ -Library to support the reading and writing of zip files. -""" -edition = "2018" - -[dependencies] - -flate2 = { version = "1.0.20", default-features = false, optional = true } -time = { version = "0.1", optional = true } -byteorder = "1.3" -bzip2 = { version = "0.3", optional = true } -crc32fast = "1.0" -thiserror = "1.0" - -[dev-dependencies] -bencher = "0.1" -rand = "0.7" -walkdir = "2" - -[features] -deflate = ["flate2/rust_backend"] -deflate-miniz = ["flate2/default"] -deflate-zlib = ["flate2/zlib"] -default = ["bzip2", "deflate", "time"] - -[[bench]] -name = "read_entry" -harness = false diff --git a/third-party/zip/LICENSE b/third-party/zip/LICENSE deleted file mode 100644 index b2d7f7b..0000000 --- a/third-party/zip/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Mathijs van de Nes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/third-party/zip/README.md b/third-party/zip/README.md deleted file mode 100644 index 79d2dcc..0000000 --- a/third-party/zip/README.md +++ /dev/null @@ -1,70 +0,0 @@ -zip-rs -====== - -[![Build Status](https://img.shields.io/github/workflow/status/zip-rs/zip/CI)](https://github.com/zip-rs/zip/actions?query=branch%3Amaster+workflow%3ACI) -[![Crates.io version](https://img.shields.io/crates/v/zip.svg)](https://crates.io/crates/zip) - -[Documentation](https://docs.rs/zip/0.5.10/zip/) - - -Info ----- - -A zip library for rust which supports reading and writing of simple ZIP files. - -Supported compression formats: - -* stored (i.e. none) -* deflate -* bzip2 - -Currently unsupported zip extensions: - -* Encryption -* Multi-disk - -Usage ------ - -With all default features: - -```toml -[dependencies] -zip = "0.5" -``` - -Without the default features: - -```toml -[dependencies] -zip = { version = "0.5", default-features = false } -``` - -The features available are: - -* `deflate`: Enables the deflate compression algorithm, which is the default for zipfiles -* `bzip2`: Enables the BZip2 compression algorithm. -* `time`: Enables features using the [time](https://github.com/rust-lang-deprecated/time) crate. - -All of these are enabled by default. - -MSRV ----- - -Our current Minimum Supported Rust Version is **1.34.0**. When adding features, -we will follow these guidelines: - -- We will always support the latest four minor Rust versions. This gives you a 6 - month window to upgrade your compiler. -- Any change to the MSRV will be accompanied with a **minor** version bump - - While the crate is pre-1.0, this will be a change to the PATCH version. - -Examples --------- - -See the [examples directory](examples) for: - * How to write a file to a zip. - * How to write a directory of files to a zip (using [walkdir](https://github.com/BurntSushi/walkdir)). - * How to extract a zip file. - * How to extract a single file from a zip. - * How to read a zip from the standard input. diff --git a/third-party/zip/benches/read_entry.rs b/third-party/zip/benches/read_entry.rs deleted file mode 100644 index 25c0b94..0000000 --- a/third-party/zip/benches/read_entry.rs +++ /dev/null @@ -1,43 +0,0 @@ -use bencher::{benchmark_group, benchmark_main}; - -use std::io::{Cursor, Read, Write}; - -use bencher::Bencher; -use rand::Rng; -use zip::{ZipArchive, ZipWriter}; - -fn generate_random_archive(size: usize) -> Vec { - let data = Vec::new(); - let mut writer = ZipWriter::new(Cursor::new(data)); - let options = - zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored); - - writer.start_file("random.dat", options).unwrap(); - let mut bytes = vec![0u8; size]; - rand::thread_rng().fill_bytes(&mut bytes); - writer.write_all(&bytes).unwrap(); - - writer.finish().unwrap().into_inner() -} - -fn read_entry(bench: &mut Bencher) { - let size = 1024 * 1024; - let bytes = generate_random_archive(size); - let mut archive = ZipArchive::new(Cursor::new(bytes.as_slice())).unwrap(); - - bench.iter(|| { - let mut file = archive.by_name("random.dat").unwrap(); - let mut buf = [0u8; 1024]; - loop { - let n = file.read(&mut buf).unwrap(); - if n == 0 { - break; - } - } - }); - - bench.bytes = size as u64; -} - -benchmark_group!(benches, read_entry); -benchmark_main!(benches); diff --git a/third-party/zip/examples/extract.rs b/third-party/zip/examples/extract.rs deleted file mode 100644 index 05c5a4a..0000000 --- a/third-party/zip/examples/extract.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::fs; -use std::io; - -fn main() { - std::process::exit(real_main()); -} - -fn real_main() -> i32 { - let args: Vec<_> = std::env::args().collect(); - if args.len() < 2 { - println!("Usage: {} ", args[0]); - return 1; - } - let fname = std::path::Path::new(&*args[1]); - let file = fs::File::open(&fname).unwrap(); - - let mut archive = zip::ZipArchive::new(file).unwrap(); - - for i in 0..archive.len() { - let mut file = archive.by_index(i).unwrap(); - let outpath = match file.enclosed_name() { - Some(path) => path.to_owned(), - None => continue, - }; - - { - let comment = file.comment(); - if !comment.is_empty() { - println!("File {} comment: {}", i, comment); - } - } - - if (&*file.name()).ends_with('/') { - println!("File {} extracted to \"{}\"", i, outpath.display()); - fs::create_dir_all(&outpath).unwrap(); - } else { - println!( - "File {} extracted to \"{}\" ({} bytes)", - i, - outpath.display(), - file.size() - ); - if let Some(p) = outpath.parent() { - if !p.exists() { - fs::create_dir_all(&p).unwrap(); - } - } - let mut outfile = fs::File::create(&outpath).unwrap(); - io::copy(&mut file, &mut outfile).unwrap(); - } - - // Get and Set permissions - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - - if let Some(mode) = file.unix_mode() { - fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap(); - } - } - } - return 0; -} diff --git a/third-party/zip/examples/extract_lorem.rs b/third-party/zip/examples/extract_lorem.rs deleted file mode 100644 index 89e33ef..0000000 --- a/third-party/zip/examples/extract_lorem.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::io::prelude::*; - -fn main() { - std::process::exit(real_main()); -} - -fn real_main() -> i32 { - let args: Vec<_> = std::env::args().collect(); - if args.len() < 2 { - println!("Usage: {} ", args[0]); - return 1; - } - let fname = std::path::Path::new(&*args[1]); - let zipfile = std::fs::File::open(&fname).unwrap(); - - let mut archive = zip::ZipArchive::new(zipfile).unwrap(); - - let mut file = match archive.by_name("test/lorem_ipsum.txt") { - Ok(file) => file, - Err(..) => { - println!("File test/lorem_ipsum.txt not found"); - return 2; - } - }; - - let mut contents = String::new(); - file.read_to_string(&mut contents).unwrap(); - println!("{}", contents); - - return 0; -} diff --git a/third-party/zip/examples/file_info.rs b/third-party/zip/examples/file_info.rs deleted file mode 100644 index 315b5c3..0000000 --- a/third-party/zip/examples/file_info.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::fs; -use std::io::BufReader; - -fn main() { - std::process::exit(real_main()); -} - -fn real_main() -> i32 { - let args: Vec<_> = std::env::args().collect(); - if args.len() < 2 { - println!("Usage: {} ", args[0]); - return 1; - } - let fname = std::path::Path::new(&*args[1]); - let file = fs::File::open(&fname).unwrap(); - let reader = BufReader::new(file); - - let mut archive = zip::ZipArchive::new(reader).unwrap(); - - for i in 0..archive.len() { - let file = archive.by_index(i).unwrap(); - let outpath = match file.enclosed_name() { - Some(path) => path, - None => { - println!("Entry {} has a suspicious path", file.name()); - continue; - } - }; - - { - let comment = file.comment(); - if !comment.is_empty() { - println!("Entry {} comment: {}", i, comment); - } - } - - if (&*file.name()).ends_with('/') { - println!( - "Entry {} is a directory with name \"{}\"", - i, - outpath.display() - ); - } else { - println!( - "Entry {} is a file with name \"{}\" ({} bytes)", - i, - outpath.display(), - file.size() - ); - } - } - return 0; -} diff --git a/third-party/zip/examples/stdin_info.rs b/third-party/zip/examples/stdin_info.rs deleted file mode 100644 index 606944c..0000000 --- a/third-party/zip/examples/stdin_info.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::io::{self, Read}; - -fn main() { - std::process::exit(real_main()); -} - -fn real_main() -> i32 { - let stdin = io::stdin(); - let mut stdin_handle = stdin.lock(); - let mut buf = [0u8; 16]; - - loop { - match zip::read::read_zipfile_from_stream(&mut stdin_handle) { - Ok(Some(mut file)) => { - println!( - "{}: {} bytes ({} bytes packed)", - file.name(), - file.size(), - file.compressed_size() - ); - 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), - }; - } - Ok(None) => break, - Err(e) => { - println!("Error encountered while reading zip: {:?}", e); - return 1; - } - } - } - return 0; -} diff --git a/third-party/zip/examples/write_dir.rs b/third-party/zip/examples/write_dir.rs deleted file mode 100644 index 793bd6b..0000000 --- a/third-party/zip/examples/write_dir.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::io::prelude::*; -use std::io::{Seek, Write}; -use std::iter::Iterator; -use zip::result::ZipError; -use zip::write::FileOptions; - -use std::fs::File; -use std::path::Path; -use walkdir::{DirEntry, WalkDir}; - -fn main() { - std::process::exit(real_main()); -} - -const METHOD_STORED: Option = Some(zip::CompressionMethod::Stored); - -#[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" -))] -const METHOD_DEFLATED: Option = Some(zip::CompressionMethod::Deflated); -#[cfg(not(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" -)))] -const METHOD_DEFLATED: Option = None; - -#[cfg(feature = "bzip2")] -const METHOD_BZIP2: Option = Some(zip::CompressionMethod::Bzip2); -#[cfg(not(feature = "bzip2"))] -const METHOD_BZIP2: Option = None; - -fn real_main() -> i32 { - let args: Vec<_> = std::env::args().collect(); - if args.len() < 3 { - println!( - "Usage: {} ", - args[0] - ); - return 1; - } - - let src_dir = &*args[1]; - let dst_file = &*args[2]; - for &method in [METHOD_STORED, METHOD_DEFLATED, METHOD_BZIP2].iter() { - if method.is_none() { - continue; - } - match doit(src_dir, dst_file, method.unwrap()) { - Ok(_) => println!("done: {} written to {}", src_dir, dst_file), - Err(e) => println!("Error: {:?}", e), - } - } - - return 0; -} - -fn zip_dir( - it: &mut dyn Iterator, - prefix: &str, - writer: T, - method: zip::CompressionMethod, -) -> zip::result::ZipResult<()> -where - T: Write + Seek, -{ - let mut zip = zip::ZipWriter::new(writer); - let options = FileOptions::default() - .compression_method(method) - .unix_permissions(0o755); - - let mut buffer = Vec::new(); - for entry in it { - let path = entry.path(); - let name = path.strip_prefix(Path::new(prefix)).unwrap(); - - // 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); - #[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)?; - buffer.clear(); - } else if name.as_os_str().len() != 0 { - // Only if not root! Avoids path spec / warning - // and mapname conversion failed error on unzip - println!("adding dir {:?} as {:?} ...", path, name); - #[allow(deprecated)] - zip.add_directory_from_path(name, options)?; - } - } - zip.finish()?; - Result::Ok(()) -} - -fn doit( - src_dir: &str, - dst_file: &str, - method: zip::CompressionMethod, -) -> zip::result::ZipResult<()> { - if !Path::new(src_dir).is_dir() { - return Err(ZipError::FileNotFound); - } - - let path = Path::new(dst_file); - let file = File::create(&path).unwrap(); - - let walkdir = WalkDir::new(src_dir.to_string()); - let it = walkdir.into_iter(); - - zip_dir(&mut it.filter_map(|e| e.ok()), src_dir, file, method)?; - - Ok(()) -} diff --git a/third-party/zip/examples/write_sample.rs b/third-party/zip/examples/write_sample.rs deleted file mode 100644 index 4ef5ce3..0000000 --- a/third-party/zip/examples/write_sample.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::io::prelude::*; -use zip::write::FileOptions; - -fn main() { - std::process::exit(real_main()); -} - -fn real_main() -> i32 { - let args: Vec<_> = std::env::args().collect(); - if args.len() < 2 { - println!("Usage: {} ", args[0]); - return 1; - } - - let filename = &*args[1]; - match doit(filename) { - Ok(_) => println!("File written to {}", filename), - Err(e) => println!("Error: {:?}", e), - } - - return 0; -} - -fn doit(filename: &str) -> zip::result::ZipResult<()> { - let path = std::path::Path::new(filename); - let file = std::fs::File::create(&path).unwrap(); - - let mut zip = zip::ZipWriter::new(file); - - zip.add_directory("test/", Default::default())?; - - let options = FileOptions::default() - .compression_method(zip::CompressionMethod::Stored) - .unix_permissions(0o755); - zip.start_file("test/☃.txt", options)?; - zip.write_all(b"Hello, World!\n")?; - - zip.start_file("test/lorem_ipsum.txt", Default::default())?; - zip.write_all(LOREM_IPSUM)?; - - zip.finish()?; - Ok(()) -} - -const LOREM_IPSUM : &'static [u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tellus elit, tristique vitae mattis egestas, ultricies vitae risus. Quisque sit amet quam ut urna aliquet -molestie. Proin blandit ornare dui, a tempor nisl accumsan in. Praesent a consequat felis. Morbi metus diam, auctor in auctor vel, feugiat id odio. Curabitur ex ex, -dictum quis auctor quis, suscipit id lorem. Aliquam vestibulum dolor nec enim vehicula, porta tristique augue tincidunt. Vivamus ut gravida est. Sed pellentesque, dolor -vitae tristique consectetur, neque lectus pulvinar dui, sed feugiat purus diam id lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per -inceptos himenaeos. Maecenas feugiat velit in ex ultrices scelerisque id id neque. - -Phasellus sed nisi in augue sodales pulvinar ut et leo. Pellentesque eget leo vitae massa bibendum sollicitudin. Curabitur erat lectus, congue quis auctor sed, aliquet -bibendum est. Ut porta ultricies turpis at maximus. Cras non lobortis justo. Duis rutrum magna sed velit facilisis, et sagittis metus laoreet. Pellentesque quam ligula, -dapibus vitae mauris quis, dapibus cursus leo. Sed sit amet condimentum eros. Nulla vestibulum enim sit amet lorem pharetra, eu fringilla nisl posuere. Sed tristique non -nibh at viverra. Vivamus sed accumsan lacus, nec pretium eros. Mauris elementum arcu eu risus fermentum, tempor ullamcorper neque aliquam. Sed tempor in erat eu -suscipit. In euismod in libero in facilisis. Donec sagittis, odio et fermentum dignissim, risus justo pretium nibh, eget vestibulum lectus metus vel lacus. - -Quisque feugiat, magna ac feugiat ullamcorper, augue justo consequat felis, ut fermentum arcu lorem vitae ligula. Quisque iaculis tempor maximus. In quis eros ac tellus -aliquam placerat quis id tellus. Donec non gravida nulla. Morbi faucibus neque sed faucibus aliquam. Sed accumsan mattis nunc, non interdum justo. Cras vitae facilisis -leo. Fusce sollicitudin ultrices sagittis. Maecenas eget massa id lorem dignissim ultrices non et ligula. Pellentesque aliquam mi ac neque tempus ornare. Morbi non enim -vulputate quam ullamcorper finibus id non neque. Quisque malesuada commodo lorem, ut ornare velit iaculis rhoncus. Mauris vel maximus ex. - -Morbi eleifend blandit diam, non vulputate ante iaculis in. Donec pellentesque augue id enim suscipit, eget suscipit lacus commodo. Ut vel ex vitae elit imperdiet -vulputate. Nunc eu mattis orci, ut pretium sem. Nam vitae purus mollis ante tempus malesuada a at magna. Integer mattis lectus non luctus lobortis. In a cursus quam, -eget faucibus sem. - -Donec vitae condimentum nisi, non efficitur massa. Praesent sed mi in massa sollicitudin iaculis. Pellentesque a libero ultrices, sodales lacus eu, ornare dui. In -laoreet est nec dolor aliquam consectetur. Integer iaculis felis venenatis libero pulvinar, ut pretium odio interdum. Donec in nisi eu dolor varius vestibulum eget vel -nunc. Morbi a venenatis quam, in vehicula justo. Nam risus dui, auctor eu accumsan at, sagittis ac lectus. Mauris iaculis dignissim interdum. Cras cursus dapibus auctor. -Donec sagittis massa vitae tortor viverra vehicula. Mauris fringilla nunc eu lorem ultrices placerat. Maecenas posuere porta quam at semper. Praesent eu bibendum eros. -Nunc congue sollicitudin ante, sollicitudin lacinia magna cursus vitae. -"; diff --git a/third-party/zip/src/compression.rs b/third-party/zip/src/compression.rs deleted file mode 100644 index 5fdde07..0000000 --- a/third-party/zip/src/compression.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! Possible ZIP compression methods. - -use std::fmt; - -#[allow(deprecated)] -/// Identifies the storage format used to compress a file within a ZIP archive. -/// -/// Each file's compression method is stored alongside it, allowing the -/// contents to be read without context. -/// -/// When creating ZIP files, you may choose the method to use with -/// [`zip::write::FileOptions::compression_method`] -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum CompressionMethod { - /// Store the file as is - Stored, - /// Compress the file using Deflate - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - Deflated, - /// Compress the file using BZIP2 - #[cfg(feature = "bzip2")] - Bzip2, - /// Unsupported compression method - #[deprecated(since = "0.5.7", note = "use the constants instead")] - Unsupported(u16), -} -#[allow(deprecated, missing_docs)] -/// All compression methods defined for the ZIP format -impl CompressionMethod { - pub const STORE: Self = CompressionMethod::Stored; - pub const SHRINK: Self = CompressionMethod::Unsupported(1); - pub const REDUCE_1: Self = CompressionMethod::Unsupported(2); - pub const REDUCE_2: Self = CompressionMethod::Unsupported(3); - pub const REDUCE_3: Self = CompressionMethod::Unsupported(4); - pub const REDUCE_4: Self = CompressionMethod::Unsupported(5); - pub const IMPLODE: Self = CompressionMethod::Unsupported(6); - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - pub const DEFLATE: Self = CompressionMethod::Deflated; - #[cfg(not(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - )))] - pub const DEFLATE: Self = CompressionMethod::Unsupported(8); - pub const DEFLATE64: Self = CompressionMethod::Unsupported(9); - pub const PKWARE_IMPLODE: Self = CompressionMethod::Unsupported(10); - #[cfg(feature = "bzip2")] - pub const BZIP2: Self = CompressionMethod::Bzip2; - #[cfg(not(feature = "bzip2"))] - pub const BZIP2: Self = CompressionMethod::Unsupported(12); - pub const LZMA: Self = CompressionMethod::Unsupported(14); - pub const IBM_ZOS_CMPSC: Self = CompressionMethod::Unsupported(16); - pub const IBM_TERSE: Self = CompressionMethod::Unsupported(18); - pub const ZSTD_DEPRECATED: Self = CompressionMethod::Unsupported(20); - pub const ZSTD: Self = CompressionMethod::Unsupported(93); - pub const MP3: Self = CompressionMethod::Unsupported(94); - pub const XZ: Self = CompressionMethod::Unsupported(95); - pub const JPEG: Self = CompressionMethod::Unsupported(96); - pub const WAVPACK: Self = CompressionMethod::Unsupported(97); - pub const PPMD: Self = CompressionMethod::Unsupported(98); -} -impl CompressionMethod { - /// Converts an u16 to its corresponding CompressionMethod - #[deprecated( - since = "0.5.7", - note = "use a constant to construct a compression method" - )] - pub fn from_u16(val: u16) -> CompressionMethod { - #[allow(deprecated)] - match val { - 0 => CompressionMethod::Stored, - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - 8 => CompressionMethod::Deflated, - #[cfg(feature = "bzip2")] - 12 => CompressionMethod::Bzip2, - - v => CompressionMethod::Unsupported(v), - } - } - - /// Converts a CompressionMethod to a u16 - #[deprecated( - since = "0.5.7", - note = "to match on other compression methods, use a constant" - )] - pub fn to_u16(self) -> u16 { - #[allow(deprecated)] - match self { - CompressionMethod::Stored => 0, - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - CompressionMethod::Deflated => 8, - #[cfg(feature = "bzip2")] - CompressionMethod::Bzip2 => 12, - CompressionMethod::Unsupported(v) => v, - } - } -} - -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) - } -} - -#[cfg(test)] -mod test { - use super::CompressionMethod; - - #[test] - fn from_eq_to() { - for v in 0..(::std::u16::MAX as u32 + 1) { - #[allow(deprecated)] - let from = CompressionMethod::from_u16(v as u16); - #[allow(deprecated)] - let to = from.to_u16() as u32; - assert_eq!(v, to); - } - } - - fn methods() -> Vec { - let mut methods = Vec::new(); - methods.push(CompressionMethod::Stored); - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - methods.push(CompressionMethod::Deflated); - #[cfg(feature = "bzip2")] - methods.push(CompressionMethod::Bzip2); - methods - } - - #[test] - fn to_eq_from() { - fn check_match(method: CompressionMethod) { - #[allow(deprecated)] - let to = method.to_u16(); - #[allow(deprecated)] - let from = CompressionMethod::from_u16(to); - #[allow(deprecated)] - let back = from.to_u16(); - assert_eq!(to, back); - } - - for method in methods() { - check_match(method); - } - } - - #[test] - fn to_display_fmt() { - fn check_match(method: CompressionMethod) { - let debug_str = format!("{:?}", method); - let display_str = format!("{}", method); - assert_eq!(debug_str, display_str); - } - - for method in methods() { - check_match(method); - } - } -} diff --git a/third-party/zip/src/cp437.rs b/third-party/zip/src/cp437.rs deleted file mode 100644 index f994814..0000000 --- a/third-party/zip/src/cp437.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Convert a string in IBM codepage 437 to UTF-8 - -/// Trait to convert IBM codepage 437 to the target type -pub trait FromCp437 { - /// Target type - type Target; - - /// Function that does the conversion from cp437. - /// Gennerally allocations will be avoided if all data falls into the ASCII range. - fn from_cp437(self) -> Self::Target; -} - -impl<'a> FromCp437 for &'a [u8] { - type Target = ::std::borrow::Cow<'a, str>; - - fn from_cp437(self) -> Self::Target { - if self.iter().all(|c| *c < 0x80) { - ::std::str::from_utf8(self).unwrap().into() - } else { - self.iter().map(|c| to_char(*c)).collect::().into() - } - } -} - -impl FromCp437 for Vec { - type Target = String; - - fn from_cp437(self) -> Self::Target { - if self.iter().all(|c| *c < 0x80) { - String::from_utf8(self).unwrap() - } else { - self.into_iter().map(to_char).collect() - } - } -} - -fn to_char(input: u8) -> char { - let output = match input { - 0x00..=0x7f => input as u32, - 0x80 => 0x00c7, - 0x81 => 0x00fc, - 0x82 => 0x00e9, - 0x83 => 0x00e2, - 0x84 => 0x00e4, - 0x85 => 0x00e0, - 0x86 => 0x00e5, - 0x87 => 0x00e7, - 0x88 => 0x00ea, - 0x89 => 0x00eb, - 0x8a => 0x00e8, - 0x8b => 0x00ef, - 0x8c => 0x00ee, - 0x8d => 0x00ec, - 0x8e => 0x00c4, - 0x8f => 0x00c5, - 0x90 => 0x00c9, - 0x91 => 0x00e6, - 0x92 => 0x00c6, - 0x93 => 0x00f4, - 0x94 => 0x00f6, - 0x95 => 0x00f2, - 0x96 => 0x00fb, - 0x97 => 0x00f9, - 0x98 => 0x00ff, - 0x99 => 0x00d6, - 0x9a => 0x00dc, - 0x9b => 0x00a2, - 0x9c => 0x00a3, - 0x9d => 0x00a5, - 0x9e => 0x20a7, - 0x9f => 0x0192, - 0xa0 => 0x00e1, - 0xa1 => 0x00ed, - 0xa2 => 0x00f3, - 0xa3 => 0x00fa, - 0xa4 => 0x00f1, - 0xa5 => 0x00d1, - 0xa6 => 0x00aa, - 0xa7 => 0x00ba, - 0xa8 => 0x00bf, - 0xa9 => 0x2310, - 0xaa => 0x00ac, - 0xab => 0x00bd, - 0xac => 0x00bc, - 0xad => 0x00a1, - 0xae => 0x00ab, - 0xaf => 0x00bb, - 0xb0 => 0x2591, - 0xb1 => 0x2592, - 0xb2 => 0x2593, - 0xb3 => 0x2502, - 0xb4 => 0x2524, - 0xb5 => 0x2561, - 0xb6 => 0x2562, - 0xb7 => 0x2556, - 0xb8 => 0x2555, - 0xb9 => 0x2563, - 0xba => 0x2551, - 0xbb => 0x2557, - 0xbc => 0x255d, - 0xbd => 0x255c, - 0xbe => 0x255b, - 0xbf => 0x2510, - 0xc0 => 0x2514, - 0xc1 => 0x2534, - 0xc2 => 0x252c, - 0xc3 => 0x251c, - 0xc4 => 0x2500, - 0xc5 => 0x253c, - 0xc6 => 0x255e, - 0xc7 => 0x255f, - 0xc8 => 0x255a, - 0xc9 => 0x2554, - 0xca => 0x2569, - 0xcb => 0x2566, - 0xcc => 0x2560, - 0xcd => 0x2550, - 0xce => 0x256c, - 0xcf => 0x2567, - 0xd0 => 0x2568, - 0xd1 => 0x2564, - 0xd2 => 0x2565, - 0xd3 => 0x2559, - 0xd4 => 0x2558, - 0xd5 => 0x2552, - 0xd6 => 0x2553, - 0xd7 => 0x256b, - 0xd8 => 0x256a, - 0xd9 => 0x2518, - 0xda => 0x250c, - 0xdb => 0x2588, - 0xdc => 0x2584, - 0xdd => 0x258c, - 0xde => 0x2590, - 0xdf => 0x2580, - 0xe0 => 0x03b1, - 0xe1 => 0x00df, - 0xe2 => 0x0393, - 0xe3 => 0x03c0, - 0xe4 => 0x03a3, - 0xe5 => 0x03c3, - 0xe6 => 0x00b5, - 0xe7 => 0x03c4, - 0xe8 => 0x03a6, - 0xe9 => 0x0398, - 0xea => 0x03a9, - 0xeb => 0x03b4, - 0xec => 0x221e, - 0xed => 0x03c6, - 0xee => 0x03b5, - 0xef => 0x2229, - 0xf0 => 0x2261, - 0xf1 => 0x00b1, - 0xf2 => 0x2265, - 0xf3 => 0x2264, - 0xf4 => 0x2320, - 0xf5 => 0x2321, - 0xf6 => 0x00f7, - 0xf7 => 0x2248, - 0xf8 => 0x00b0, - 0xf9 => 0x2219, - 0xfa => 0x00b7, - 0xfb => 0x221a, - 0xfc => 0x207f, - 0xfd => 0x00b2, - 0xfe => 0x25a0, - 0xff => 0x00a0, - }; - ::std::char::from_u32(output).unwrap() -} - -#[cfg(test)] -mod test { - #[test] - fn to_char_valid() { - for i in 0x00_u32..0x100 { - super::to_char(i as u8); - } - } - - #[test] - fn ascii() { - for i in 0x00..0x80 { - assert_eq!(super::to_char(i), i as char); - } - } - - #[test] - fn example_slice() { - use super::FromCp437; - let data = b"Cura\x87ao"; - assert!(::std::str::from_utf8(data).is_err()); - assert_eq!(data.from_cp437(), "Curaçao"); - } - - #[test] - fn example_vec() { - use super::FromCp437; - let data = vec![0xCC, 0xCD, 0xCD, 0xB9]; - assert!(String::from_utf8(data.clone()).is_err()); - assert_eq!(&data.from_cp437(), "╠══╣"); - } -} diff --git a/third-party/zip/src/crc32.rs b/third-party/zip/src/crc32.rs deleted file mode 100644 index b351aa0..0000000 --- a/third-party/zip/src/crc32.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Helper module to compute a CRC32 checksum - -use std::io; -use std::io::prelude::*; - -use crc32fast::Hasher; - -/// Reader that validates the CRC32 when it reaches the EOF. -pub struct Crc32Reader { - inner: R, - hasher: Hasher, - check: u32, -} - -impl Crc32Reader { - /// Get a new Crc32Reader which check the inner reader against checksum. - pub fn new(inner: R, checksum: u32) -> Crc32Reader { - Crc32Reader { - inner, - hasher: Hasher::new(), - check: checksum, - } - } - - fn check_matches(&self) -> bool { - self.check == self.hasher.clone().finalize() - } - - pub fn into_inner(self) -> R { - self.inner - } -} - -impl Read for Crc32Reader { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let count = match self.inner.read(buf) { - Ok(0) if !buf.is_empty() && !self.check_matches() => { - return Err(io::Error::new(io::ErrorKind::Other, "Invalid checksum")) - } - Ok(n) => n, - Err(e) => return Err(e), - }; - self.hasher.update(&buf[0..count]); - Ok(count) - } -} - -#[cfg(test)] -mod test { - use super::*; - use std::io::Read; - - #[test] - fn test_empty_reader() { - let data: &[u8] = b""; - let mut buf = [0; 1]; - - let mut reader = Crc32Reader::new(data, 0); - assert_eq!(reader.read(&mut buf).unwrap(), 0); - - let mut reader = Crc32Reader::new(data, 1); - assert!(reader - .read(&mut buf) - .unwrap_err() - .to_string() - .contains("Invalid checksum")); - } - - #[test] - fn test_byte_by_byte() { - let data: &[u8] = b"1234"; - let mut buf = [0; 1]; - - let mut reader = Crc32Reader::new(data, 0x9be3e0a3); - assert_eq!(reader.read(&mut buf).unwrap(), 1); - assert_eq!(reader.read(&mut buf).unwrap(), 1); - assert_eq!(reader.read(&mut buf).unwrap(), 1); - assert_eq!(reader.read(&mut buf).unwrap(), 1); - assert_eq!(reader.read(&mut buf).unwrap(), 0); - // Can keep reading 0 bytes after the end - assert_eq!(reader.read(&mut buf).unwrap(), 0); - } - - #[test] - fn test_zero_read() { - let data: &[u8] = b"1234"; - let mut buf = [0; 5]; - - let mut reader = Crc32Reader::new(data, 0x9be3e0a3); - assert_eq!(reader.read(&mut buf[..0]).unwrap(), 0); - assert_eq!(reader.read(&mut buf).unwrap(), 4); - } -} diff --git a/third-party/zip/src/lib.rs b/third-party/zip/src/lib.rs deleted file mode 100644 index 3b39ab4..0000000 --- a/third-party/zip/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! An ergonomic API for reading and writing ZIP files. -//! -//! The current implementation is based on [PKWARE's APPNOTE.TXT v6.3.9](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) -// TODO(#184): Decide on the crate's bias: Do we prioritise permissiveness/correctness/speed/ergonomics? - -#![warn(missing_docs)] - -pub use crate::compression::CompressionMethod; -pub use crate::read::ZipArchive; -pub use crate::types::DateTime; -pub use crate::write::ZipWriter; - -mod compression; -mod cp437; -mod crc32; -pub mod read; -pub mod result; -mod spec; -mod types; -pub mod write; -mod zipcrypto; diff --git a/third-party/zip/src/read.rs b/third-party/zip/src/read.rs deleted file mode 100644 index 3aac00f..0000000 --- a/third-party/zip/src/read.rs +++ /dev/null @@ -1,1078 +0,0 @@ -//! Types for reading ZIP archives - -use crate::compression::CompressionMethod; -use crate::crc32::Crc32Reader; -use crate::result::{InvalidPassword, ZipError, ZipResult}; -use crate::spec; -use crate::zipcrypto::ZipCryptoReader; -use crate::zipcrypto::ZipCryptoReaderValid; -use std::borrow::Cow; -use std::collections::HashMap; -use std::io::{self, prelude::*}; -use std::path::{Component, Path}; - -use crate::cp437::FromCp437; -use crate::types::{DateTime, System, ZipFileData}; -use byteorder::{LittleEndian, ReadBytesExt}; - -#[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" -))] -use flate2::read::DeflateDecoder; - -#[cfg(feature = "bzip2")] -use bzip2::read::BzDecoder; - -mod ffi { - pub const S_IFDIR: u32 = 0o0040000; - pub const S_IFREG: u32 = 0o0100000; -} - -/// ZIP archive reader -/// -/// ```no_run -/// use std::io::prelude::*; -/// fn list_zip_contents(reader: impl Read + Seek) -> zip::result::ZipResult<()> { -/// let mut zip = zip::ZipArchive::new(reader)?; -/// -/// for i in 0..zip.len() { -/// let mut file = zip.by_index(i)?; -/// println!("Filename: {}", file.name()); -/// std::io::copy(&mut file, &mut std::io::stdout()); -/// } -/// -/// Ok(()) -/// } -/// ``` -#[derive(Clone, Debug)] -pub struct ZipArchive { - reader: R, - files: Vec, - names_map: HashMap, - offset: u64, - comment: Vec, -} - -enum CryptoReader<'a> { - Plaintext(io::Take<&'a mut dyn Read>), - ZipCrypto(ZipCryptoReaderValid>), -} - -impl<'a> Read for CryptoReader<'a> { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - CryptoReader::Plaintext(r) => r.read(buf), - CryptoReader::ZipCrypto(r) => r.read(buf), - } - } -} - -impl<'a> CryptoReader<'a> { - /// Consumes this decoder, returning the underlying reader. - pub fn into_inner(self) -> io::Take<&'a mut dyn Read> { - match self { - CryptoReader::Plaintext(r) => r, - CryptoReader::ZipCrypto(r) => r.into_inner(), - } - } -} - -enum ZipFileReader<'a> { - NoReader, - Raw(io::Take<&'a mut dyn io::Read>), - Stored(Crc32Reader>), - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - Deflated(Crc32Reader>>), - #[cfg(feature = "bzip2")] - Bzip2(Crc32Reader>>), -} - -impl<'a> Read for ZipFileReader<'a> { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"), - ZipFileReader::Raw(r) => r.read(buf), - ZipFileReader::Stored(r) => r.read(buf), - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - ZipFileReader::Deflated(r) => r.read(buf), - #[cfg(feature = "bzip2")] - ZipFileReader::Bzip2(r) => r.read(buf), - } - } -} - -impl<'a> ZipFileReader<'a> { - /// Consumes this decoder, returning the underlying reader. - pub fn into_inner(self) -> io::Take<&'a mut dyn Read> { - match self { - ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"), - ZipFileReader::Raw(r) => r, - ZipFileReader::Stored(r) => r.into_inner().into_inner(), - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - ZipFileReader::Deflated(r) => r.into_inner().into_inner().into_inner(), - #[cfg(feature = "bzip2")] - ZipFileReader::Bzip2(r) => r.into_inner().into_inner().into_inner(), - } - } -} - -/// A struct for reading a zip file -pub struct ZipFile<'a> { - data: Cow<'a, ZipFileData>, - crypto_reader: Option>, - reader: ZipFileReader<'a>, -} - -fn find_content<'a>( - data: &mut ZipFileData, - reader: &'a mut (impl Read + Seek), -) -> ZipResult> { - // Parse local header - reader.seek(io::SeekFrom::Start(data.header_start))?; - let signature = reader.read_u32::()?; - if signature != spec::LOCAL_FILE_HEADER_SIGNATURE { - return Err(ZipError::InvalidArchive("Invalid local file header")); - } - - reader.seek(io::SeekFrom::Current(22))?; - let file_name_length = reader.read_u16::()? as u64; - let extra_field_length = reader.read_u16::()? as u64; - let magic_and_header = 4 + 22 + 2 + 2; - data.data_start = data.header_start + magic_and_header + file_name_length + extra_field_length; - - reader.seek(io::SeekFrom::Start(data.data_start))?; - Ok((reader as &mut dyn Read).take(data.compressed_size)) -} - -fn make_crypto_reader<'a>( - compression_method: crate::compression::CompressionMethod, - crc32: u32, - reader: io::Take<&'a mut dyn io::Read>, - password: Option<&[u8]>, -) -> ZipResult, InvalidPassword>> { - #[allow(deprecated)] - { - if let CompressionMethod::Unsupported(_) = compression_method { - return unsupported_zip_error("Compression method not supported"); - } - } - - let reader = match password { - None => CryptoReader::Plaintext(reader), - Some(password) => match ZipCryptoReader::new(reader, password).validate(crc32)? { - None => return Ok(Err(InvalidPassword)), - Some(r) => CryptoReader::ZipCrypto(r), - }, - }; - Ok(Ok(reader)) -} - -fn make_reader<'a>( - compression_method: CompressionMethod, - crc32: u32, - reader: CryptoReader<'a>, -) -> ZipFileReader<'a> { - match compression_method { - CompressionMethod::Stored => ZipFileReader::Stored(Crc32Reader::new(reader, crc32)), - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - CompressionMethod::Deflated => { - let deflate_reader = DeflateDecoder::new(reader); - ZipFileReader::Deflated(Crc32Reader::new(deflate_reader, crc32)) - } - #[cfg(feature = "bzip2")] - CompressionMethod::Bzip2 => { - let bzip2_reader = BzDecoder::new(reader); - ZipFileReader::Bzip2(Crc32Reader::new(bzip2_reader, crc32)) - } - _ => panic!("Compression method not supported"), - } -} - -impl ZipArchive { - /// Get the directory start offset and number of files. This is done in a - /// separate function to ease the control flow design. - fn get_directory_counts( - reader: &mut R, - footer: &spec::CentralDirectoryEnd, - cde_start_pos: u64, - ) -> ZipResult<(u64, u64, usize)> { - // See if there's a ZIP64 footer. The ZIP64 locator if present will - // have its signature 20 bytes in front of the standard footer. The - // standard footer, in turn, is 22+N bytes large, where N is the - // comment length. Therefore: - let zip64locator = if reader - .seek(io::SeekFrom::End( - -(20 + 22 + footer.zip_file_comment.len() as i64), - )) - .is_ok() - { - match spec::Zip64CentralDirectoryEndLocator::parse(reader) { - Ok(loc) => Some(loc), - Err(ZipError::InvalidArchive(_)) => { - // No ZIP64 header; that's actually fine. We're done here. - None - } - Err(e) => { - // Yikes, a real problem - return Err(e); - } - } - } else { - // Empty Zip files will have nothing else so this error might be fine. If - // not, we'll find out soon. - None - }; - - match zip64locator { - None => { - // Some zip files have data prepended to them, resulting in the - // offsets all being too small. Get the amount of error by comparing - // the actual file position we found the CDE at with the offset - // recorded in the CDE. - let archive_offset = cde_start_pos - .checked_sub(footer.central_directory_size as u64) - .and_then(|x| x.checked_sub(footer.central_directory_offset as u64)) - .ok_or(ZipError::InvalidArchive( - "Invalid central directory size or offset", - ))?; - - let directory_start = footer.central_directory_offset as u64 + archive_offset; - let number_of_files = footer.number_of_files_on_this_disk as usize; - Ok((archive_offset, directory_start, number_of_files)) - } - Some(locator64) => { - // If we got here, this is indeed a ZIP64 file. - - if footer.disk_number as u32 != locator64.disk_with_central_directory { - return unsupported_zip_error( - "Support for multi-disk files is not implemented", - ); - } - - // We need to reassess `archive_offset`. We know where the ZIP64 - // central-directory-end structure *should* be, but unfortunately we - // don't know how to precisely relate that location to our current - // actual offset in the file, since there may be junk at its - // beginning. Therefore we need to perform another search, as in - // read::CentralDirectoryEnd::find_and_parse, except now we search - // forward. - - let search_upper_bound = cde_start_pos - .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator - .ok_or(ZipError::InvalidArchive( - "File cannot contain ZIP64 central directory end", - ))?; - let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse( - reader, - locator64.end_of_central_directory_offset, - search_upper_bound, - )?; - - if footer.disk_number != footer.disk_with_central_directory { - return unsupported_zip_error( - "Support for multi-disk files is not implemented", - ); - } - - let directory_start = footer - .central_directory_offset - .checked_add(archive_offset) - .ok_or_else(|| { - ZipError::InvalidArchive("Invalid central directory size or offset") - })?; - - Ok(( - archive_offset, - directory_start, - footer.number_of_files as usize, - )) - } - } - } - - /// Read a ZIP archive, collecting the files it contains - /// - /// This uses the central directory record of the ZIP file, and ignores local file headers - pub fn new(mut reader: R) -> ZipResult> { - let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?; - - if 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 let Err(_) = reader.seek(io::SeekFrom::Start(directory_start)) { - return Err(ZipError::InvalidArchive( - "Could not seek to start of central directory", - )); - } - - for _ in 0..number_of_files { - let file = central_header_to_zip_file(&mut reader, archive_offset)?; - names_map.insert(file.file_name.clone(), files.len()); - files.push(file); - } - - Ok(ZipArchive { - reader, - files, - names_map, - offset: archive_offset, - comment: footer.zip_file_comment, - }) - } - /// Extract a Zip archive into a directory, overwriting files if they - /// already exist. Paths are sanitized with [`ZipFile::enclosed_name`]. - /// - /// Extraction is not atomic; If an error is encountered, some of the files - /// may be left on disk. - pub fn extract>(&mut self, directory: P) -> ZipResult<()> { - use std::fs; - - for i in 0..self.len() { - let mut file = self.by_index(i)?; - let filepath = file - .enclosed_name() - .ok_or(ZipError::InvalidArchive("Invalid file path"))?; - - let outpath = directory.as_ref().join(filepath); - - if file.name().ends_with('/') { - fs::create_dir_all(&outpath)?; - } else { - if let Some(p) = outpath.parent() { - if !p.exists() { - fs::create_dir_all(&p)?; - } - } - let mut outfile = fs::File::create(&outpath)?; - io::copy(&mut file, &mut outfile)?; - } - // Get and Set permissions - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - if let Some(mode) = file.unix_mode() { - fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?; - } - } - } - Ok(()) - } - - /// Number of files contained in this zip. - pub fn len(&self) -> usize { - self.files.len() - } - - /// Whether this zip archive contains no files - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Get the offset from the beginning of the underlying reader that this zip begins at, in bytes. - /// - /// Normally this value is zero, but if the zip has arbitrary data prepended to it, then this value will be the size - /// of that prepended data. - pub fn offset(&self) -> u64 { - self.offset - } - - /// Get the comment of the zip archive. - pub fn comment(&self) -> &[u8] { - &self.comment - } - - /// Returns an iterator over all the file and directory names in this archive. - pub fn file_names(&self) -> impl Iterator { - self.names_map.keys().map(|s| s.as_str()) - } - - /// Search for a file entry by name, decrypt with given password - pub fn by_name_decrypt<'a>( - &'a mut self, - name: &str, - password: &[u8], - ) -> ZipResult, InvalidPassword>> { - self.by_name_with_optional_password(name, Some(password)) - } - - /// Search for a file entry by name - pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult> { - Ok(self.by_name_with_optional_password(name, None)?.unwrap()) - } - - fn by_name_with_optional_password<'a>( - &'a mut self, - name: &str, - password: Option<&[u8]>, - ) -> ZipResult, InvalidPassword>> { - let index = match self.names_map.get(name) { - Some(index) => *index, - None => { - return Err(ZipError::FileNotFound); - } - }; - self.by_index_with_optional_password(index, password) - } - - /// Get a contained file by index, decrypt with given password - pub fn by_index_decrypt<'a>( - &'a mut self, - file_number: usize, - password: &[u8], - ) -> ZipResult, InvalidPassword>> { - self.by_index_with_optional_password(file_number, Some(password)) - } - - /// Get a contained file by index - pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult> { - Ok(self - .by_index_with_optional_password(file_number, None)? - .unwrap()) - } - - /// Get a contained file by index without decompressing it - pub fn by_index_raw<'a>(&'a mut self, file_number: usize) -> ZipResult> { - let reader = &mut self.reader; - self.files - .get_mut(file_number) - .ok_or(ZipError::FileNotFound) - .and_then(move |data| { - Ok(ZipFile { - crypto_reader: None, - reader: ZipFileReader::Raw(find_content(data, reader)?), - data: Cow::Borrowed(data), - }) - }) - } - - fn by_index_with_optional_password<'a>( - &'a mut self, - file_number: usize, - mut password: Option<&[u8]>, - ) -> ZipResult, InvalidPassword>> { - if file_number >= self.files.len() { - return Err(ZipError::FileNotFound); - } - let data = &mut self.files[file_number]; - - match (password, data.encrypted) { - (None, true) => { - return Err(ZipError::UnsupportedArchive( - "Password required to decrypt file", - )) - } - (Some(_), false) => password = None, //Password supplied, but none needed! Discard. - _ => {} - } - let limit_reader = find_content(data, &mut self.reader)?; - - match make_crypto_reader(data.compression_method, data.crc32, limit_reader, password) { - Ok(Ok(crypto_reader)) => Ok(Ok(ZipFile { - crypto_reader: Some(crypto_reader), - reader: ZipFileReader::NoReader, - data: Cow::Borrowed(data), - })), - Err(e) => Err(e), - Ok(Err(e)) => Ok(Err(e)), - } - } - - /// Unwrap and return the inner reader object - /// - /// The position of the reader is undefined. - pub fn into_inner(self) -> R { - self.reader - } -} - -fn unsupported_zip_error(detail: &'static str) -> ZipResult { - Err(ZipError::UnsupportedArchive(detail)) -} - -fn central_header_to_zip_file( - reader: &mut R, - archive_offset: u64, -) -> ZipResult { - let central_header_start = reader.seek(io::SeekFrom::Current(0))?; - // Parse central header - let signature = reader.read_u32::()?; - if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE { - return Err(ZipError::InvalidArchive("Invalid Central Directory header")); - } - - let version_made_by = reader.read_u16::()?; - let _version_to_extract = reader.read_u16::()?; - let flags = reader.read_u16::()?; - let encrypted = flags & 1 == 1; - let is_utf8 = flags & (1 << 11) != 0; - let compression_method = reader.read_u16::()?; - let last_mod_time = reader.read_u16::()?; - let last_mod_date = reader.read_u16::()?; - let crc32 = reader.read_u32::()?; - let compressed_size = reader.read_u32::()?; - let uncompressed_size = reader.read_u32::()?; - let file_name_length = reader.read_u16::()? as usize; - let extra_field_length = reader.read_u16::()? as usize; - let file_comment_length = reader.read_u16::()? as usize; - let _disk_number = reader.read_u16::()?; - let _internal_file_attributes = reader.read_u16::()?; - let external_file_attributes = reader.read_u32::()?; - let offset = reader.read_u32::()? as u64; - let mut file_name_raw = vec![0; file_name_length]; - reader.read_exact(&mut file_name_raw)?; - let mut extra_field = vec![0; extra_field_length]; - reader.read_exact(&mut extra_field)?; - let mut file_comment_raw = vec![0; file_comment_length]; - reader.read_exact(&mut file_comment_raw)?; - - let file_name = match is_utf8 { - 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(), - false => file_comment_raw.from_cp437(), - }; - - // Construct the result - let mut result = ZipFileData { - system: System::from_u8((version_made_by >> 8) as u8), - version_made_by: version_made_by as u8, - encrypted, - compression_method: { - #[allow(deprecated)] - CompressionMethod::from_u16(compression_method) - }, - last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time), - crc32, - compressed_size: compressed_size as u64, - uncompressed_size: uncompressed_size as u64, - file_name, - file_name_raw, - file_comment, - header_start: offset, - central_header_start, - data_start: 0, - external_attributes: external_file_attributes, - }; - - match parse_extra_field(&mut result, &*extra_field) { - Ok(..) | Err(ZipError::Io(..)) => {} - Err(e) => return Err(e), - } - - // Account for shifted zip offsets. - result.header_start += archive_offset; - - Ok(result) -} - -fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()> { - let mut reader = io::Cursor::new(data); - - while (reader.position() as usize) < data.len() { - let kind = reader.read_u16::()?; - let len = reader.read_u16::()?; - let mut len_left = len as i64; - // Zip64 extended information extra field - if kind == 0x0001 { - if file.uncompressed_size == 0xFFFFFFFF { - file.uncompressed_size = reader.read_u64::()?; - len_left -= 8; - } - if file.compressed_size == 0xFFFFFFFF { - file.compressed_size = reader.read_u64::()?; - len_left -= 8; - } - if file.header_start == 0xFFFFFFFF { - file.header_start = reader.read_u64::()?; - len_left -= 8; - } - // Unparsed fields: - // u32: disk start number - } - - // We could also check for < 0 to check for errors - if len_left > 0 { - reader.seek(io::SeekFrom::Current(len_left))?; - } - } - Ok(()) -} - -/// Methods for retrieving information on zip files -impl<'a> ZipFile<'a> { - fn get_reader(&mut self) -> &mut ZipFileReader<'a> { - if let ZipFileReader::NoReader = self.reader { - let data = &self.data; - let crypto_reader = self.crypto_reader.take().expect("Invalid reader state"); - self.reader = make_reader(data.compression_method, data.crc32, crypto_reader) - } - &mut self.reader - } - - pub(crate) fn get_raw_reader(&mut self) -> &mut dyn Read { - if let ZipFileReader::NoReader = self.reader { - let crypto_reader = self.crypto_reader.take().expect("Invalid reader state"); - self.reader = ZipFileReader::Raw(crypto_reader.into_inner()) - } - &mut self.reader - } - - /// Get the version of the file - pub fn version_made_by(&self) -> (u8, u8) { - ( - self.data.version_made_by / 10, - self.data.version_made_by % 10, - ) - } - - /// Get the name of the file - /// - /// # Warnings - /// - /// It is dangerous to use this name directly when extracting an archive. - /// It may contain an absolute path (`/etc/shadow`), or break out of the - /// current directory (`../runtime`). Carelessly writing to these paths - /// allows an attacker to craft a ZIP archive that will overwrite critical - /// files. - /// - /// You can use the [`ZipFile::enclosed_name`] method to validate the name - /// as a safe path. - pub fn name(&self) -> &str { - &self.data.file_name - } - - /// Get the name of the file, in the raw (internal) byte representation. - /// - /// The encoding of this data is currently undefined. - pub fn name_raw(&self) -> &[u8] { - &self.data.file_name_raw - } - - /// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte, - /// removes a leading '/' and removes '..' parts. - #[deprecated( - since = "0.5.7", - note = "by stripping `..`s from the path, the meaning of paths can change. - `mangled_name` can be used if this behaviour is desirable" - )] - pub fn sanitized_name(&self) -> ::std::path::PathBuf { - self.mangled_name() - } - - /// Rewrite the path, ignoring any path components with special meaning. - /// - /// - Absolute paths are made relative - /// - [`ParentDir`]s are ignored - /// - Truncates the filename at a NULL byte - /// - /// This is appropriate if you need to be able to extract *something* from - /// any archive, but will easily misrepresent trivial paths like - /// `foo/../bar` as `foo/bar` (instead of `bar`). Because of this, - /// [`ZipFile::enclosed_name`] is the better option in most scenarios. - /// - /// [`ParentDir`]: `Component::ParentDir` - pub fn mangled_name(&self) -> ::std::path::PathBuf { - self.data.file_name_sanitized() - } - - /// Ensure the file path is safe to use as a [`Path`]. - /// - /// - It can't contain NULL bytes - /// - It can't resolve to a path outside the current directory - /// > `foo/../bar` is fine, `foo/../../bar` is not. - /// - It can't be an absolute path - /// - /// This will read well-formed ZIP files correctly, and is resistant - /// to path-based exploits. It is recommended over - /// [`ZipFile::mangled_name`]. - pub fn enclosed_name(&self) -> Option<&Path> { - if self.data.file_name.contains('\0') { - return None; - } - let path = Path::new(&self.data.file_name); - let mut depth = 0usize; - for component in path.components() { - match component { - Component::Prefix(_) | Component::RootDir => return None, - Component::ParentDir => depth = depth.checked_sub(1)?, - Component::Normal(_) => depth += 1, - Component::CurDir => (), - } - } - Some(path) - } - - /// Get the comment of the file - pub fn comment(&self) -> &str { - &self.data.file_comment - } - - /// Get the compression method used to store the file - pub fn compression(&self) -> CompressionMethod { - self.data.compression_method - } - - /// Get the size of the file in the archive - pub fn compressed_size(&self) -> u64 { - self.data.compressed_size - } - - /// Get the size of the file when uncompressed - pub fn size(&self) -> u64 { - self.data.uncompressed_size - } - - /// Get the time the file was last modified - pub fn last_modified(&self) -> DateTime { - self.data.last_modified_time - } - /// Returns whether the file is actually a directory - pub fn is_dir(&self) -> bool { - self.name() - .chars() - .rev() - .next() - .map_or(false, |c| c == '/' || c == '\\') - } - - /// Returns whether the file is a regular file - pub fn is_file(&self) -> bool { - !self.is_dir() - } - - /// Get unix mode for the file - pub fn unix_mode(&self) -> Option { - if self.data.external_attributes == 0 { - return None; - } - - match self.data.system { - System::Unix => Some(self.data.external_attributes >> 16), - System::Dos => { - // Interpret MSDOS directory bit - let mut mode = if 0x10 == (self.data.external_attributes & 0x10) { - ffi::S_IFDIR | 0o0775 - } else { - ffi::S_IFREG | 0o0664 - }; - if 0x01 == (self.data.external_attributes & 0x01) { - // Read-only bit; strip write permissions - mode &= 0o0555; - } - Some(mode) - } - _ => None, - } - } - - /// Get the CRC32 hash of the original file - pub fn crc32(&self) -> u32 { - self.data.crc32 - } - - /// Get the starting offset of the data of the compressed file - pub fn data_start(&self) -> u64 { - self.data.data_start - } - - /// Get the starting offset of the zip header for this file - pub fn header_start(&self) -> u64 { - self.data.header_start - } - /// Get the starting offset of the zip header in the central directory for this file - pub fn central_header_start(&self) -> u64 { - self.data.central_header_start - } -} - -impl<'a> Read for ZipFile<'a> { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.get_reader().read(buf) - } -} - -impl<'a> Drop for ZipFile<'a> { - fn drop(&mut self) { - // self.data is Owned, this reader is constructed by a streaming reader. - // In this case, we want to exhaust the reader so that the next file is accessible. - if let Cow::Owned(_) = self.data { - let mut buffer = [0; 1 << 16]; - - // Get the inner `Take` reader so all decryption, decompression and CRC calculation is skipped. - let mut reader: std::io::Take<&mut dyn std::io::Read> = match &mut self.reader { - ZipFileReader::NoReader => { - let innerreader = ::std::mem::replace(&mut self.crypto_reader, None); - innerreader.expect("Invalid reader state").into_inner() - } - reader => { - let innerreader = ::std::mem::replace(reader, ZipFileReader::NoReader); - innerreader.into_inner() - } - }; - - loop { - match reader.read(&mut buffer) { - Ok(0) => break, - Ok(_) => (), - Err(e) => panic!( - "Could not consume all of the output of the current ZipFile: {:?}", - e - ), - } - } - } - } -} - -/// Read ZipFile structures from a non-seekable reader. -/// -/// This is an alternative method to read a zip file. If possible, use the ZipArchive functions -/// as some information will be missing when reading this manner. -/// -/// Reads a file header from the start of the stream. Will return `Ok(Some(..))` if a file is -/// present at the start of the stream. Returns `Ok(None)` if the start of the central directory -/// is encountered. No more files should be read after this. -/// -/// The Drop implementation of ZipFile ensures that the reader will be correctly positioned after -/// the structure is done. -/// -/// Missing fields are: -/// * `comment`: set to an empty string -/// * `data_start`: set to 0 -/// * `external_attributes`: `unix_mode()`: will return None -pub fn read_zipfile_from_stream<'a, R: io::Read>( - reader: &'a mut R, -) -> ZipResult>> { - let signature = reader.read_u32::()?; - - match signature { - spec::LOCAL_FILE_HEADER_SIGNATURE => (), - spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE => return Ok(None), - _ => return Err(ZipError::InvalidArchive("Invalid local file header")), - } - - let version_made_by = reader.read_u16::()?; - let flags = reader.read_u16::()?; - let encrypted = flags & 1 == 1; - let is_utf8 = flags & (1 << 11) != 0; - let using_data_descriptor = flags & (1 << 3) != 0; - #[allow(deprecated)] - let compression_method = CompressionMethod::from_u16(reader.read_u16::()?); - let last_mod_time = reader.read_u16::()?; - let last_mod_date = reader.read_u16::()?; - let crc32 = reader.read_u32::()?; - let compressed_size = reader.read_u32::()?; - let uncompressed_size = reader.read_u32::()?; - let file_name_length = reader.read_u16::()? as usize; - let extra_field_length = reader.read_u16::()? as usize; - - let mut file_name_raw = vec![0; file_name_length]; - reader.read_exact(&mut file_name_raw)?; - let mut extra_field = vec![0; extra_field_length]; - reader.read_exact(&mut extra_field)?; - - let file_name = match is_utf8 { - true => String::from_utf8_lossy(&*file_name_raw).into_owned(), - false => file_name_raw.clone().from_cp437(), - }; - - let mut result = ZipFileData { - system: System::from_u8((version_made_by >> 8) as u8), - version_made_by: version_made_by as u8, - encrypted, - compression_method, - last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time), - crc32, - compressed_size: compressed_size as u64, - uncompressed_size: uncompressed_size as u64, - file_name, - file_name_raw, - file_comment: String::new(), // file comment is only available in the central directory - // header_start and data start are not available, but also don't matter, since seeking is - // not available. - header_start: 0, - data_start: 0, - central_header_start: 0, - // The external_attributes field is only available in the central directory. - // We set this to zero, which should be valid as the docs state 'If input came - // from standard input, this field is set to zero.' - external_attributes: 0, - }; - - match parse_extra_field(&mut result, &extra_field) { - Ok(..) | Err(ZipError::Io(..)) => {} - Err(e) => return Err(e), - } - - if encrypted { - return unsupported_zip_error("Encrypted files are not supported"); - } - if using_data_descriptor { - 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 result_crc32 = result.crc32; - let result_compression_method = result.compression_method; - let crypto_reader = - make_crypto_reader(result_compression_method, result_crc32, limit_reader, None)?.unwrap(); - - Ok(Some(ZipFile { - data: Cow::Owned(result), - crypto_reader: None, - reader: make_reader(result_compression_method, result_crc32, crypto_reader), - })) -} - -#[cfg(test)] -mod test { - #[test] - fn invalid_offset() { - use super::ZipArchive; - use std::io; - - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("../tests/data/invalid_offset.zip")); - let reader = ZipArchive::new(io::Cursor::new(v)); - assert!(reader.is_err()); - } - - #[test] - fn invalid_offset2() { - use super::ZipArchive; - use std::io; - - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("../tests/data/invalid_offset2.zip")); - let reader = ZipArchive::new(io::Cursor::new(v)); - assert!(reader.is_err()); - } - - #[test] - fn zip64_with_leading_junk() { - use super::ZipArchive; - use std::io; - - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("../tests/data/zip64_demo.zip")); - let reader = ZipArchive::new(io::Cursor::new(v)).unwrap(); - assert!(reader.len() == 1); - } - - #[test] - fn zip_contents() { - use super::ZipArchive; - use std::io; - - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip")); - let mut reader = ZipArchive::new(io::Cursor::new(v)).unwrap(); - assert!(reader.comment() == b""); - assert_eq!(reader.by_index(0).unwrap().central_header_start(), 77); - } - - #[test] - fn zip_read_streaming() { - use super::read_zipfile_from_stream; - use std::io; - - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip")); - let mut reader = io::Cursor::new(v); - loop { - match read_zipfile_from_stream(&mut reader).unwrap() { - None => break, - _ => (), - } - } - } - - #[test] - fn zip_clone() { - use super::ZipArchive; - use std::io::{self, Read}; - - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip")); - let mut reader1 = ZipArchive::new(io::Cursor::new(v)).unwrap(); - let mut reader2 = reader1.clone(); - - let mut file1 = reader1.by_index(0).unwrap(); - let mut file2 = reader2.by_index(0).unwrap(); - - let t = file1.last_modified(); - assert_eq!( - ( - t.year(), - t.month(), - t.day(), - t.hour(), - t.minute(), - t.second() - ), - (1980, 1, 1, 0, 0, 0) - ); - - let mut buf1 = [0; 5]; - let mut buf2 = [0; 5]; - let mut buf3 = [0; 5]; - let mut buf4 = [0; 5]; - - file1.read(&mut buf1).unwrap(); - file2.read(&mut buf2).unwrap(); - file1.read(&mut buf3).unwrap(); - file2.read(&mut buf4).unwrap(); - - assert_eq!(buf1, buf2); - assert_eq!(buf3, buf4); - assert!(buf1 != buf3); - } - - #[test] - fn file_and_dir_predicates() { - use super::ZipArchive; - use std::io; - - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("../tests/data/files_and_dirs.zip")); - let mut zip = ZipArchive::new(io::Cursor::new(v)).unwrap(); - - for i in 0..zip.len() { - let zip_file = zip.by_index(i).unwrap(); - let full_name = zip_file.enclosed_name().unwrap(); - let file_name = full_name.file_name().unwrap().to_str().unwrap(); - assert!( - (file_name.starts_with("dir") && zip_file.is_dir()) - || (file_name.starts_with("file") && zip_file.is_file()) - ); - } - } -} diff --git a/third-party/zip/src/result.rs b/third-party/zip/src/result.rs deleted file mode 100644 index e8b7d05..0000000 --- a/third-party/zip/src/result.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Error types that can be emitted from this library - -use std::io; - -use thiserror::Error; - -/// Generic result type with ZipError as its error variant -pub type ZipResult = Result; - -/// The given password is wrong -#[derive(Error, Debug)] -#[error("invalid password for file in archive")] -pub struct InvalidPassword; - -/// Error type for Zip -#[derive(Debug, Error)] -pub enum ZipError { - /// An Error caused by I/O - #[error(transparent)] - Io(#[from] io::Error), - - /// This file is probably not a zip archive - #[error("invalid Zip archive")] - InvalidArchive(&'static str), - - /// This archive is not supported - #[error("unsupported Zip archive")] - UnsupportedArchive(&'static str), - - /// The requested file could not be found in the archive - #[error("specified file not found in archive")] - FileNotFound, -} - -impl From for io::Error { - fn from(err: ZipError) -> io::Error { - io::Error::new(io::ErrorKind::Other, err) - } -} diff --git a/third-party/zip/src/spec.rs b/third-party/zip/src/spec.rs deleted file mode 100644 index 91966b6..0000000 --- a/third-party/zip/src/spec.rs +++ /dev/null @@ -1,182 +0,0 @@ -use crate::result::{ZipError, ZipResult}; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use std::io; -use std::io::prelude::*; - -pub const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50; -pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50; -const CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06054b50; -pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50; -const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50; - -pub struct CentralDirectoryEnd { - pub disk_number: u16, - pub disk_with_central_directory: u16, - pub number_of_files_on_this_disk: u16, - pub number_of_files: u16, - pub central_directory_size: u32, - pub central_directory_offset: u32, - pub zip_file_comment: Vec, -} - -impl CentralDirectoryEnd { - pub fn parse(reader: &mut T) -> ZipResult { - let magic = reader.read_u32::()?; - if magic != CENTRAL_DIRECTORY_END_SIGNATURE { - return Err(ZipError::InvalidArchive("Invalid digital signature header")); - } - let disk_number = reader.read_u16::()?; - let disk_with_central_directory = reader.read_u16::()?; - let number_of_files_on_this_disk = reader.read_u16::()?; - let number_of_files = reader.read_u16::()?; - let central_directory_size = reader.read_u32::()?; - let central_directory_offset = reader.read_u32::()?; - let zip_file_comment_length = reader.read_u16::()? as usize; - let mut zip_file_comment = vec![0; zip_file_comment_length]; - reader.read_exact(&mut zip_file_comment)?; - - Ok(CentralDirectoryEnd { - disk_number, - disk_with_central_directory, - number_of_files_on_this_disk, - number_of_files, - central_directory_size, - central_directory_offset, - zip_file_comment, - }) - } - - pub fn find_and_parse( - reader: &mut T, - ) -> ZipResult<(CentralDirectoryEnd, u64)> { - const HEADER_SIZE: u64 = 22; - const BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE: u64 = HEADER_SIZE - 6; - let file_length = reader.seek(io::SeekFrom::End(0))?; - - let search_upper_bound = file_length.saturating_sub(HEADER_SIZE + ::std::u16::MAX as u64); - - if file_length < HEADER_SIZE { - return Err(ZipError::InvalidArchive("Invalid zip header")); - } - - let mut pos = file_length - HEADER_SIZE; - while pos >= search_upper_bound { - reader.seek(io::SeekFrom::Start(pos as u64))?; - if reader.read_u32::()? == 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))?; - return CentralDirectoryEnd::parse(reader).map(|cde| (cde, cde_start_pos)); - } - pos = match pos.checked_sub(1) { - Some(p) => p, - None => break, - }; - } - Err(ZipError::InvalidArchive( - "Could not find central directory end", - )) - } - - pub fn write(&self, writer: &mut T) -> ZipResult<()> { - writer.write_u32::(CENTRAL_DIRECTORY_END_SIGNATURE)?; - writer.write_u16::(self.disk_number)?; - writer.write_u16::(self.disk_with_central_directory)?; - writer.write_u16::(self.number_of_files_on_this_disk)?; - writer.write_u16::(self.number_of_files)?; - writer.write_u32::(self.central_directory_size)?; - writer.write_u32::(self.central_directory_offset)?; - writer.write_u16::(self.zip_file_comment.len() as u16)?; - writer.write_all(&self.zip_file_comment)?; - Ok(()) - } -} - -pub struct Zip64CentralDirectoryEndLocator { - pub disk_with_central_directory: u32, - pub end_of_central_directory_offset: u64, - pub number_of_disks: u32, -} - -impl Zip64CentralDirectoryEndLocator { - pub fn parse(reader: &mut T) -> ZipResult { - let magic = reader.read_u32::()?; - if magic != ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE { - return Err(ZipError::InvalidArchive( - "Invalid zip64 locator digital signature header", - )); - } - let disk_with_central_directory = reader.read_u32::()?; - let end_of_central_directory_offset = reader.read_u64::()?; - let number_of_disks = reader.read_u32::()?; - - Ok(Zip64CentralDirectoryEndLocator { - disk_with_central_directory, - end_of_central_directory_offset, - number_of_disks, - }) - } -} - -pub struct Zip64CentralDirectoryEnd { - pub version_made_by: u16, - pub version_needed_to_extract: u16, - pub disk_number: u32, - pub disk_with_central_directory: u32, - pub number_of_files_on_this_disk: u64, - pub number_of_files: u64, - pub central_directory_size: u64, - pub central_directory_offset: u64, - //pub extensible_data_sector: Vec, <-- We don't do anything with this at the moment. -} - -impl Zip64CentralDirectoryEnd { - pub fn find_and_parse( - reader: &mut T, - nominal_offset: u64, - search_upper_bound: u64, - ) -> ZipResult<(Zip64CentralDirectoryEnd, u64)> { - let mut pos = nominal_offset; - - while pos <= search_upper_bound { - reader.seek(io::SeekFrom::Start(pos))?; - - if reader.read_u32::()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE { - let archive_offset = pos - nominal_offset; - - let _record_size = reader.read_u64::()?; - // We would use this value if we did anything with the "zip64 extensible data sector". - - let version_made_by = reader.read_u16::()?; - let version_needed_to_extract = reader.read_u16::()?; - let disk_number = reader.read_u32::()?; - let disk_with_central_directory = reader.read_u32::()?; - let number_of_files_on_this_disk = reader.read_u64::()?; - let number_of_files = reader.read_u64::()?; - let central_directory_size = reader.read_u64::()?; - let central_directory_offset = reader.read_u64::()?; - - return Ok(( - Zip64CentralDirectoryEnd { - version_made_by, - version_needed_to_extract, - disk_number, - disk_with_central_directory, - number_of_files_on_this_disk, - number_of_files, - central_directory_size, - central_directory_offset, - }, - archive_offset, - )); - } - - pos += 1; - } - - Err(ZipError::InvalidArchive( - "Could not find ZIP64 central directory end", - )) - } -} diff --git a/third-party/zip/src/types.rs b/third-party/zip/src/types.rs deleted file mode 100644 index 154e3c2..0000000 --- a/third-party/zip/src/types.rs +++ /dev/null @@ -1,474 +0,0 @@ -//! Types that specify what is contained in a ZIP. - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum System { - Dos = 0, - Unix = 3, - Unknown, -} - -impl System { - pub fn from_u8(system: u8) -> System { - use self::System::*; - - match system { - 0 => Dos, - 3 => Unix, - _ => Unknown, - } - } -} - -/// A DateTime field to be used for storing timestamps in a zip file -/// -/// This structure does bounds checking to ensure the date is able to be stored in a zip file. -/// -/// When constructed manually from a date and time, it will also check if the input is sensible -/// (e.g. months are from [1, 12]), but when read from a zip some parts may be out of their normal -/// bounds (e.g. month 0, or hour 31). -/// -/// # Warning -/// -/// Some utilities use alternative timestamps to improve the accuracy of their -/// ZIPs, but we don't parse them yet. [We're working on this](https://github.com/zip-rs/zip/issues/156#issuecomment-652981904), -/// however this API shouldn't be considered complete. -#[derive(Debug, Clone, Copy)] -pub struct DateTime { - year: u16, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, -} - -impl ::std::default::Default for DateTime { - /// Constructs an 'default' datetime of 1980-01-01 00:00:00 - fn default() -> DateTime { - DateTime { - year: 1980, - month: 1, - day: 1, - hour: 0, - minute: 0, - second: 0, - } - } -} - -impl DateTime { - /// Converts an msdos (u16, u16) pair to a DateTime object - pub fn from_msdos(datepart: u16, timepart: u16) -> DateTime { - let seconds = (timepart & 0b0000000000011111) << 1; - let minutes = (timepart & 0b0000011111100000) >> 5; - let hours = (timepart & 0b1111100000000000) >> 11; - let days = (datepart & 0b0000000000011111) >> 0; - let months = (datepart & 0b0000000111100000) >> 5; - let years = (datepart & 0b1111111000000000) >> 9; - - DateTime { - year: (years + 1980) as u16, - month: months as u8, - day: days as u8, - hour: hours as u8, - minute: minutes as u8, - second: seconds as u8, - } - } - - /// Constructs a DateTime from a specific date and time - /// - /// The bounds are: - /// * year: [1980, 2107] - /// * month: [1, 12] - /// * day: [1, 31] - /// * hour: [0, 23] - /// * minute: [0, 59] - /// * second: [0, 60] - pub fn from_date_and_time( - year: u16, - month: u8, - day: u8, - hour: u8, - minute: u8, - second: u8, - ) -> Result { - if year >= 1980 - && year <= 2107 - && month >= 1 - && month <= 12 - && day >= 1 - && day <= 31 - && hour <= 23 - && minute <= 59 - && second <= 60 - { - Ok(DateTime { - year, - month, - day, - hour, - minute, - second, - }) - } else { - Err(()) - } - } - - #[cfg(feature = "time")] - /// Converts a ::time::Tm object to a DateTime - /// - /// Returns `Err` when this object is out of bounds - pub fn from_time(tm: ::time::Tm) -> Result { - if tm.tm_year >= 80 - && tm.tm_year <= 207 - && tm.tm_mon >= 0 - && tm.tm_mon <= 11 - && tm.tm_mday >= 1 - && tm.tm_mday <= 31 - && tm.tm_hour >= 0 - && tm.tm_hour <= 23 - && tm.tm_min >= 0 - && tm.tm_min <= 59 - && tm.tm_sec >= 0 - && tm.tm_sec <= 60 - { - Ok(DateTime { - year: (tm.tm_year + 1900) as u16, - month: (tm.tm_mon + 1) as u8, - day: tm.tm_mday as u8, - hour: tm.tm_hour as u8, - minute: tm.tm_min as u8, - second: tm.tm_sec as u8, - }) - } else { - Err(()) - } - } - - /// Gets the time portion of this datetime in the msdos representation - pub fn timepart(&self) -> u16 { - ((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11) - } - - /// Gets the date portion of this datetime in the msdos representation - pub fn datepart(&self) -> u16 { - (self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9) - } - - #[cfg(feature = "time")] - /// Converts the datetime to a Tm structure - /// - /// The fields `tm_wday`, `tm_yday`, `tm_utcoff` and `tm_nsec` are set to their defaults. - pub fn to_time(&self) -> ::time::Tm { - ::time::Tm { - tm_sec: self.second as i32, - tm_min: self.minute as i32, - tm_hour: self.hour as i32, - tm_mday: self.day as i32, - tm_mon: self.month as i32 - 1, - tm_year: self.year as i32 - 1900, - tm_isdst: -1, - ..::time::empty_tm() - } - } - - /// Get the year. There is no epoch, i.e. 2018 will be returned as 2018. - pub fn year(&self) -> u16 { - self.year - } - - /// Get the month, where 1 = january and 12 = december - pub fn month(&self) -> u8 { - self.month - } - - /// Get the day - pub fn day(&self) -> u8 { - self.day - } - - /// Get the hour - pub fn hour(&self) -> u8 { - self.hour - } - - /// Get the minute - pub fn minute(&self) -> u8 { - self.minute - } - - /// Get the second - pub fn second(&self) -> u8 { - self.second - } -} - -pub const DEFAULT_VERSION: u8 = 46; - -/// Structure representing a ZIP file. -#[derive(Debug, Clone)] -pub struct ZipFileData { - /// Compatibility of the file attribute information - pub system: System, - /// Specification version - pub version_made_by: u8, - /// True if the file is encrypted. - pub encrypted: bool, - /// Compression method used to store the file - pub compression_method: crate::compression::CompressionMethod, - /// Last modified time. This will only have a 2 second precision. - pub last_modified_time: DateTime, - /// CRC32 checksum - pub crc32: u32, - /// Size of the file in the ZIP - pub compressed_size: u64, - /// Size of the file when extracted - pub uncompressed_size: u64, - /// Name of the file - pub file_name: String, - /// Raw file name. To be used when file_name was incorrectly decoded. - pub file_name_raw: Vec, - /// File comment - pub file_comment: String, - /// Specifies where the local header of the file starts - pub header_start: u64, - /// Specifies where the central header of the file starts - /// - /// Note that when this is not known, it is set to 0 - pub central_header_start: u64, - /// Specifies where the compressed data of the file starts - pub data_start: u64, - /// External file attributes - pub external_attributes: u32, -} - -impl ZipFileData { - pub fn file_name_sanitized(&self) -> ::std::path::PathBuf { - let no_null_filename = match self.file_name.find('\0') { - Some(index) => &self.file_name[0..index], - None => &self.file_name, - } - .to_string(); - - // zip files can contain both / and \ as separators regardless of the OS - // and as we want to return a sanitized PathBuf that only supports the - // OS separator let's convert incompatible separators to compatible ones - let separator = ::std::path::MAIN_SEPARATOR; - let opposite_separator = match separator { - '/' => '\\', - _ => '/', - }; - let filename = - no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string()); - - ::std::path::Path::new(&filename) - .components() - .filter(|component| match *component { - ::std::path::Component::Normal(..) => true, - _ => false, - }) - .fold(::std::path::PathBuf::new(), |mut path, ref cur| { - path.push(cur.as_os_str()); - path - }) - } - - pub fn version_needed(&self) -> u16 { - match self.compression_method { - #[cfg(feature = "bzip2")] - crate::compression::CompressionMethod::Bzip2 => 46, - _ => 20, - } - } -} - -#[cfg(test)] -mod test { - #[test] - fn system() { - use super::System; - assert_eq!(System::Dos as u16, 0u16); - assert_eq!(System::Unix as u16, 3u16); - assert_eq!(System::from_u8(0), System::Dos); - assert_eq!(System::from_u8(3), System::Unix); - } - - #[test] - fn sanitize() { - use super::*; - let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string(); - let data = ZipFileData { - system: System::Dos, - version_made_by: 0, - encrypted: false, - compression_method: crate::compression::CompressionMethod::Stored, - last_modified_time: DateTime::default(), - crc32: 0, - compressed_size: 0, - uncompressed_size: 0, - file_name: file_name.clone(), - file_name_raw: file_name.into_bytes(), - file_comment: String::new(), - header_start: 0, - data_start: 0, - central_header_start: 0, - external_attributes: 0, - }; - assert_eq!( - data.file_name_sanitized(), - ::std::path::PathBuf::from("path/etc/passwd") - ); - } - - #[test] - fn datetime_default() { - use super::DateTime; - let dt = DateTime::default(); - assert_eq!(dt.timepart(), 0); - assert_eq!(dt.datepart(), 0b0000000_0001_00001); - } - - #[test] - fn datetime_max() { - use super::DateTime; - let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap(); - assert_eq!(dt.timepart(), 0b10111_111011_11110); - assert_eq!(dt.datepart(), 0b1111111_1100_11111); - } - - #[test] - fn datetime_bounds() { - use super::DateTime; - - assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok()); - assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err()); - assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err()); - assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err()); - - assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok()); - assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok()); - assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err()); - assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err()); - assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err()); - assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err()); - assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err()); - assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err()); - } - - #[cfg(feature = "time")] - #[test] - fn datetime_from_time_bounds() { - use super::DateTime; - - // 1979-12-31 23:59:59 - assert!(DateTime::from_time(::time::Tm { - tm_sec: 59, - tm_min: 59, - tm_hour: 23, - tm_mday: 31, - tm_mon: 11, // tm_mon has number range [0, 11] - tm_year: 79, // 1979 - 1900 = 79 - ..::time::empty_tm() - }) - .is_err()); - - // 1980-01-01 00:00:00 - assert!(DateTime::from_time(::time::Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 1, - tm_mon: 0, // tm_mon has number range [0, 11] - tm_year: 80, // 1980 - 1900 = 80 - ..::time::empty_tm() - }) - .is_ok()); - - // 2107-12-31 23:59:59 - assert!(DateTime::from_time(::time::Tm { - tm_sec: 59, - tm_min: 59, - tm_hour: 23, - tm_mday: 31, - tm_mon: 11, // tm_mon has number range [0, 11] - tm_year: 207, // 2107 - 1900 = 207 - ..::time::empty_tm() - }) - .is_ok()); - - // 2108-01-01 00:00:00 - assert!(DateTime::from_time(::time::Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 1, - tm_mon: 0, // tm_mon has number range [0, 11] - tm_year: 208, // 2108 - 1900 = 208 - ..::time::empty_tm() - }) - .is_err()); - } - - #[test] - fn time_conversion() { - use super::DateTime; - let dt = DateTime::from_msdos(0x4D71, 0x54CF); - assert_eq!(dt.year(), 2018); - assert_eq!(dt.month(), 11); - assert_eq!(dt.day(), 17); - assert_eq!(dt.hour(), 10); - assert_eq!(dt.minute(), 38); - assert_eq!(dt.second(), 30); - - #[cfg(feature = "time")] - assert_eq!( - format!("{}", dt.to_time().rfc3339()), - "2018-11-17T10:38:30Z" - ); - } - - #[test] - fn time_out_of_bounds() { - use super::DateTime; - let dt = DateTime::from_msdos(0xFFFF, 0xFFFF); - assert_eq!(dt.year(), 2107); - assert_eq!(dt.month(), 15); - assert_eq!(dt.day(), 31); - assert_eq!(dt.hour(), 31); - assert_eq!(dt.minute(), 63); - assert_eq!(dt.second(), 62); - - #[cfg(feature = "time")] - assert_eq!( - format!("{}", dt.to_time().rfc3339()), - "2107-15-31T31:63:62Z" - ); - - let dt = DateTime::from_msdos(0x0000, 0x0000); - assert_eq!(dt.year(), 1980); - assert_eq!(dt.month(), 0); - assert_eq!(dt.day(), 0); - assert_eq!(dt.hour(), 0); - assert_eq!(dt.minute(), 0); - assert_eq!(dt.second(), 0); - - #[cfg(feature = "time")] - assert_eq!( - format!("{}", dt.to_time().rfc3339()), - "1980-00-00T00:00:00Z" - ); - } - - #[cfg(feature = "time")] - #[test] - fn time_at_january() { - use super::DateTime; - - // 2020-01-01 00:00:00 - let clock = ::time::Timespec::new(1577836800, 0); - let tm = ::time::at_utc(clock); - assert!(DateTime::from_time(tm).is_ok()); - } -} diff --git a/third-party/zip/src/write.rs b/third-party/zip/src/write.rs deleted file mode 100644 index bc68817..0000000 --- a/third-party/zip/src/write.rs +++ /dev/null @@ -1,821 +0,0 @@ -//! Types for creating ZIP archives - -use crate::compression::CompressionMethod; -use crate::read::ZipFile; -use crate::result::{ZipError, ZipResult}; -use crate::spec; -use crate::types::{DateTime, System, ZipFileData, DEFAULT_VERSION}; -use byteorder::{LittleEndian, WriteBytesExt}; -use crc32fast::Hasher; -use std::default::Default; -use std::io; -use std::io::prelude::*; -use std::mem; - -#[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" -))] -use flate2::write::DeflateEncoder; - -#[cfg(feature = "bzip2")] -use bzip2::write::BzEncoder; - -enum GenericZipWriter { - Closed, - Storer(W), - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - Deflater(DeflateEncoder), - #[cfg(feature = "bzip2")] - Bzip2(BzEncoder), -} - -/// ZIP archive generator -/// -/// Handles the bookkeeping involved in building an archive, and provides an -/// API to edit its contents. -/// -/// ``` -/// # fn doit() -> zip::result::ZipResult<()> -/// # { -/// # use zip::ZipWriter; -/// use std::io::Write; -/// use zip::write::FileOptions; -/// -/// // We use a buffer here, though you'd normally use a `File` -/// let mut buf = [0; 65536]; -/// let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf[..])); -/// -/// let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored); -/// zip.start_file("hello_world.txt", options)?; -/// zip.write(b"Hello, World!")?; -/// -/// // Apply the changes you've made. -/// // Dropping the `ZipWriter` will have the same effect, but may silently fail -/// zip.finish()?; -/// -/// # Ok(()) -/// # } -/// # doit().unwrap(); -/// ``` -pub struct ZipWriter { - inner: GenericZipWriter, - files: Vec, - stats: ZipWriterStats, - writing_to_file: bool, - comment: String, - writing_raw: bool, -} - -#[derive(Default)] -struct ZipWriterStats { - hasher: Hasher, - start: u64, - bytes_written: u64, -} - -struct ZipRawValues { - crc32: u32, - compressed_size: u64, - uncompressed_size: u64, -} - -/// Metadata for a file to be written -#[derive(Copy, Clone)] -pub struct FileOptions { - compression_method: CompressionMethod, - last_modified_time: DateTime, - permissions: Option, -} - -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, - #[cfg(feature = "time")] - last_modified_time: DateTime::from_time(time::now()).unwrap_or_default(), - #[cfg(not(feature = "time"))] - last_modified_time: DateTime::default(), - permissions: None, - } - } - - /// Set the compression method for the new file - /// - /// The default is `CompressionMethod::Deflated`. If the deflate compression feature is - /// disabled, `CompressionMethod::Stored` becomes the default. - /// otherwise. - pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions { - self.compression_method = method; - self - } - - /// Set the last modified time - /// - /// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01 - /// otherwise - pub fn last_modified_time(mut self, mod_time: DateTime) -> FileOptions { - self.last_modified_time = mod_time; - self - } - - /// Set the permissions for the new file. - /// - /// 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 - pub fn unix_permissions(mut self, mode: u32) -> FileOptions { - self.permissions = Some(mode & 0o777); - self - } -} - -impl Default for FileOptions { - fn default() -> Self { - Self::default() - } -} - -impl Write for ZipWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - if !self.writing_to_file { - return Err(io::Error::new( - io::ErrorKind::Other, - "No file has been started", - )); - } - match self.inner.ref_mut() { - Some(ref mut w) => { - let write_result = w.write(buf); - if let Ok(count) = write_result { - self.stats.update(&buf[0..count]); - } - write_result - } - None => Err(io::Error::new( - io::ErrorKind::BrokenPipe, - "ZipWriter was already closed", - )), - } - } - - fn flush(&mut self) -> io::Result<()> { - match self.inner.ref_mut() { - Some(ref mut w) => w.flush(), - None => Err(io::Error::new( - io::ErrorKind::BrokenPipe, - "ZipWriter was already closed", - )), - } - } -} - -impl ZipWriterStats { - fn update(&mut self, buf: &[u8]) { - self.hasher.update(buf); - self.bytes_written += buf.len() as u64; - } -} - -impl ZipWriter { - /// Initializes the archive. - /// - /// Before writing to this object, the [`ZipWriter::start_file`] function should be called. - pub fn new(inner: W) -> ZipWriter { - ZipWriter { - inner: GenericZipWriter::Storer(inner), - files: Vec::new(), - stats: Default::default(), - writing_to_file: false, - comment: String::new(), - writing_raw: false, - } - } - - /// Set ZIP archive comment. - pub fn set_comment(&mut self, comment: S) - where - S: Into, - { - self.comment = comment.into(); - } - - /// Start a new file for with the requested options. - fn start_entry( - &mut self, - name: S, - options: FileOptions, - raw_values: Option, - ) -> ZipResult<()> - where - S: Into, - { - self.finish_file()?; - - let is_raw = raw_values.is_some(); - let raw_values = raw_values.unwrap_or_else(|| ZipRawValues { - crc32: 0, - compressed_size: 0, - uncompressed_size: 0, - }); - - { - let writer = self.inner.get_plain(); - let header_start = writer.seek(io::SeekFrom::Current(0))?; - - let permissions = options.permissions.unwrap_or(0o100644); - let mut file = ZipFileData { - system: System::Unix, - version_made_by: DEFAULT_VERSION, - encrypted: false, - compression_method: options.compression_method, - last_modified_time: options.last_modified_time, - crc32: raw_values.crc32, - compressed_size: raw_values.compressed_size, - uncompressed_size: raw_values.uncompressed_size, - file_name: name.into(), - file_name_raw: Vec::new(), // Never used for saving - file_comment: String::new(), - header_start, - data_start: 0, - central_header_start: 0, - external_attributes: permissions << 16, - }; - write_local_file_header(writer, &file)?; - - let header_end = writer.seek(io::SeekFrom::Current(0))?; - self.stats.start = header_end; - file.data_start = header_end; - - self.stats.bytes_written = 0; - self.stats.hasher = Hasher::new(); - - self.files.push(file); - } - - self.writing_raw = is_raw; - self.inner.switch_to(if is_raw { - CompressionMethod::Stored - } else { - options.compression_method - })?; - - Ok(()) - } - - fn finish_file(&mut self) -> ZipResult<()> { - self.inner.switch_to(CompressionMethod::Stored)?; - let writer = self.inner.get_plain(); - - if !self.writing_raw { - let file = match self.files.last_mut() { - None => return Ok(()), - Some(f) => f, - }; - file.crc32 = self.stats.hasher.clone().finalize(); - file.uncompressed_size = self.stats.bytes_written; - - let file_end = writer.seek(io::SeekFrom::Current(0))?; - file.compressed_size = file_end - self.stats.start; - - update_local_file_header(writer, file)?; - writer.seek(io::SeekFrom::Start(file_end))?; - } - - self.writing_to_file = false; - self.writing_raw = false; - Ok(()) - } - - /// Create a file in the archive and start writing its' contents. - /// - /// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`] - pub fn start_file(&mut self, name: S, mut options: FileOptions) -> ZipResult<()> - where - S: Into, - { - if options.permissions.is_none() { - options.permissions = Some(0o644); - } - *options.permissions.as_mut().unwrap() |= 0o100000; - self.start_entry(name, options, None)?; - self.writing_to_file = true; - Ok(()) - } - - /// Starts a file, taking a Path as argument. - /// - /// This function ensures that the '/' path seperator is used. It also ignores all non 'Normal' - /// Components, such as a starting '/' or '..' and '.'. - #[deprecated( - since = "0.5.7", - note = "by stripping `..`s from the path, the meaning of paths can change. Use `start_file` instead." - )] - pub fn start_file_from_path( - &mut self, - path: &std::path::Path, - options: FileOptions, - ) -> ZipResult<()> { - self.start_file(path_to_string(path), options) - } - - /// Add a new file using the already compressed data from a ZIP file being read and renames it, this - /// allows faster copies of the `ZipFile` since there is no need to decompress and compress it again. - /// Any `ZipFile` metadata is copied and not checked, for example the file CRC. - - /// ```no_run - /// use std::fs::File; - /// use std::io::{Read, Seek, Write}; - /// use zip::{ZipArchive, ZipWriter}; - /// - /// fn copy_rename( - /// src: &mut ZipArchive, - /// dst: &mut ZipWriter, - /// ) -> zip::result::ZipResult<()> - /// where - /// R: Read + Seek, - /// W: Write + Seek, - /// { - /// // Retrieve file entry by name - /// let file = src.by_name("src_file.txt")?; - /// - /// // Copy and rename the previously obtained file entry to the destination zip archive - /// dst.raw_copy_file_rename(file, "new_name.txt")?; - /// - /// Ok(()) - /// } - /// ``` - pub fn raw_copy_file_rename(&mut self, mut file: ZipFile, name: S) -> ZipResult<()> - where - S: Into, - { - let options = FileOptions::default() - .last_modified_time(file.last_modified()) - .compression_method(file.compression()); - if let Some(perms) = file.unix_mode() { - options.unix_permissions(perms); - } - - let raw_values = ZipRawValues { - crc32: file.crc32(), - compressed_size: file.compressed_size(), - uncompressed_size: file.size(), - }; - - self.start_entry(name, options, Some(raw_values))?; - self.writing_to_file = true; - - io::copy(file.get_raw_reader(), self)?; - - Ok(()) - } - - /// Add a new file using the already compressed data from a ZIP file being read, this allows faster - /// copies of the `ZipFile` since there is no need to decompress and compress it again. Any `ZipFile` - /// metadata is copied and not checked, for example the file CRC. - /// - /// ```no_run - /// use std::fs::File; - /// use std::io::{Read, Seek, Write}; - /// use zip::{ZipArchive, ZipWriter}; - /// - /// fn copy(src: &mut ZipArchive, dst: &mut ZipWriter) -> zip::result::ZipResult<()> - /// where - /// R: Read + Seek, - /// W: Write + Seek, - /// { - /// // Retrieve file entry by name - /// let file = src.by_name("src_file.txt")?; - /// - /// // Copy the previously obtained file entry to the destination zip archive - /// dst.raw_copy_file(file)?; - /// - /// Ok(()) - /// } - /// ``` - pub fn raw_copy_file(&mut self, file: ZipFile) -> ZipResult<()> { - let name = file.name().to_owned(); - self.raw_copy_file_rename(file, name) - } - - /// Add a directory entry. - /// - /// You can't write data to the file afterwards. - pub fn add_directory(&mut self, name: S, mut options: FileOptions) -> ZipResult<()> - where - S: Into, - { - if options.permissions.is_none() { - options.permissions = Some(0o755); - } - *options.permissions.as_mut().unwrap() |= 0o40000; - options.compression_method = CompressionMethod::Stored; - - let name_as_string = name.into(); - // Append a slash to the filename if it does not end with it. - let name_with_slash = match name_as_string.chars().last() { - Some('/') | Some('\\') => name_as_string, - _ => name_as_string + "/", - }; - - self.start_entry(name_with_slash, options, None)?; - self.writing_to_file = false; - Ok(()) - } - - /// Add a directory entry, taking a Path as argument. - /// - /// This function ensures that the '/' path seperator is used. It also ignores all non 'Normal' - /// Components, such as a starting '/' or '..' and '.'. - #[deprecated( - since = "0.5.7", - note = "by stripping `..`s from the path, the meaning of paths can change. Use `add_directory` instead." - )] - pub fn add_directory_from_path( - &mut self, - path: &std::path::Path, - options: FileOptions, - ) -> ZipResult<()> { - self.add_directory(path_to_string(path), options) - } - - /// Finish the last file and write all other zip-structures - /// - /// This will return the writer, but one should normally not append any data to the end of the file. - /// Note that the zipfile will also be finished on drop. - pub fn finish(&mut self) -> ZipResult { - self.finalize()?; - let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed); - Ok(inner.unwrap()) - } - - fn finalize(&mut self) -> ZipResult<()> { - self.finish_file()?; - - { - let writer = self.inner.get_plain(); - - let central_start = writer.seek(io::SeekFrom::Current(0))?; - for file in self.files.iter() { - write_central_directory_header(writer, file)?; - } - let central_size = writer.seek(io::SeekFrom::Current(0))? - central_start; - - let footer = spec::CentralDirectoryEnd { - disk_number: 0, - disk_with_central_directory: 0, - number_of_files_on_this_disk: self.files.len() as u16, - number_of_files: self.files.len() as u16, - central_directory_size: central_size as u32, - central_directory_offset: central_start as u32, - zip_file_comment: self.comment.as_bytes().to_vec(), - }; - - footer.write(writer)?; - } - - Ok(()) - } -} - -impl Drop for ZipWriter { - fn drop(&mut self) { - if !self.inner.is_closed() { - if let Err(e) = self.finalize() { - let _ = write!(&mut io::stderr(), "ZipWriter drop failed: {:?}", e); - } - } - } -} - -impl GenericZipWriter { - fn switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()> { - match self.current_compression() { - Some(method) if method == compression => return Ok(()), - None => { - return Err(io::Error::new( - io::ErrorKind::BrokenPipe, - "ZipWriter was already closed", - ) - .into()) - } - _ => {} - } - - let bare = match mem::replace(self, GenericZipWriter::Closed) { - GenericZipWriter::Storer(w) => w, - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - GenericZipWriter::Deflater(w) => w.finish()?, - #[cfg(feature = "bzip2")] - GenericZipWriter::Bzip2(w) => w.finish()?, - GenericZipWriter::Closed => { - return Err(io::Error::new( - io::ErrorKind::BrokenPipe, - "ZipWriter was already closed", - ) - .into()) - } - }; - - *self = { - #[allow(deprecated)] - match compression { - CompressionMethod::Stored => GenericZipWriter::Storer(bare), - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new( - bare, - flate2::Compression::default(), - )), - #[cfg(feature = "bzip2")] - CompressionMethod::Bzip2 => { - GenericZipWriter::Bzip2(BzEncoder::new(bare, bzip2::Compression::Default)) - } - CompressionMethod::Unsupported(..) => { - return Err(ZipError::UnsupportedArchive("Unsupported compression")) - } - } - }; - - Ok(()) - } - - fn ref_mut(&mut self) -> Option<&mut dyn Write> { - match *self { - GenericZipWriter::Storer(ref mut w) => Some(w as &mut dyn Write), - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - GenericZipWriter::Deflater(ref mut w) => Some(w as &mut dyn Write), - #[cfg(feature = "bzip2")] - GenericZipWriter::Bzip2(ref mut w) => Some(w as &mut dyn Write), - GenericZipWriter::Closed => None, - } - } - - fn is_closed(&self) -> bool { - match *self { - GenericZipWriter::Closed => true, - _ => false, - } - } - - fn get_plain(&mut self) -> &mut W { - match *self { - GenericZipWriter::Storer(ref mut w) => w, - _ => panic!("Should have switched to stored beforehand"), - } - } - - fn current_compression(&self) -> Option { - match *self { - GenericZipWriter::Storer(..) => Some(CompressionMethod::Stored), - #[cfg(any( - feature = "deflate", - feature = "deflate-miniz", - feature = "deflate-zlib" - ))] - GenericZipWriter::Deflater(..) => Some(CompressionMethod::Deflated), - #[cfg(feature = "bzip2")] - GenericZipWriter::Bzip2(..) => Some(CompressionMethod::Bzip2), - GenericZipWriter::Closed => None, - } - } - - fn unwrap(self) -> W { - match self { - GenericZipWriter::Storer(w) => w, - _ => panic!("Should have switched to stored beforehand"), - } - } -} - -fn write_local_file_header(writer: &mut T, file: &ZipFileData) -> ZipResult<()> { - // local file header signature - writer.write_u32::(spec::LOCAL_FILE_HEADER_SIGNATURE)?; - // version needed to extract - writer.write_u16::(file.version_needed())?; - // general purpose bit flag - let flag = if !file.file_name.is_ascii() { - 1u16 << 11 - } else { - 0 - }; - writer.write_u16::(flag)?; - // Compression method - #[allow(deprecated)] - writer.write_u16::(file.compression_method.to_u16())?; - // last mod file time and last mod file date - writer.write_u16::(file.last_modified_time.timepart())?; - writer.write_u16::(file.last_modified_time.datepart())?; - // crc-32 - writer.write_u32::(file.crc32)?; - // compressed size - writer.write_u32::(file.compressed_size as u32)?; - // uncompressed size - writer.write_u32::(file.uncompressed_size as u32)?; - // file name length - writer.write_u16::(file.file_name.as_bytes().len() as u16)?; - // extra field length - let extra_field = build_extra_field(file)?; - writer.write_u16::(extra_field.len() as u16)?; - // file name - writer.write_all(file.file_name.as_bytes())?; - // extra field - writer.write_all(&extra_field)?; - - Ok(()) -} - -fn update_local_file_header( - writer: &mut T, - file: &ZipFileData, -) -> ZipResult<()> { - const CRC32_OFFSET: u64 = 14; - writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?; - writer.write_u32::(file.crc32)?; - writer.write_u32::(file.compressed_size as u32)?; - writer.write_u32::(file.uncompressed_size as u32)?; - Ok(()) -} - -fn write_central_directory_header(writer: &mut T, file: &ZipFileData) -> ZipResult<()> { - // central file header signature - writer.write_u32::(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?; - // version made by - let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16); - writer.write_u16::(version_made_by)?; - // version needed to extract - writer.write_u16::(file.version_needed())?; - // general puprose bit flag - let flag = if !file.file_name.is_ascii() { - 1u16 << 11 - } else { - 0 - }; - writer.write_u16::(flag)?; - // compression method - #[allow(deprecated)] - writer.write_u16::(file.compression_method.to_u16())?; - // last mod file time + date - writer.write_u16::(file.last_modified_time.timepart())?; - writer.write_u16::(file.last_modified_time.datepart())?; - // crc-32 - writer.write_u32::(file.crc32)?; - // compressed size - writer.write_u32::(file.compressed_size as u32)?; - // uncompressed size - writer.write_u32::(file.uncompressed_size as u32)?; - // file name length - writer.write_u16::(file.file_name.as_bytes().len() as u16)?; - // extra field length - let extra_field = build_extra_field(file)?; - writer.write_u16::(extra_field.len() as u16)?; - // file comment length - writer.write_u16::(0)?; - // disk number start - writer.write_u16::(0)?; - // internal file attribytes - writer.write_u16::(0)?; - // external file attributes - writer.write_u32::(file.external_attributes)?; - // relative offset of local header - writer.write_u32::(file.header_start as u32)?; - // file name - writer.write_all(file.file_name.as_bytes())?; - // extra field - writer.write_all(&extra_field)?; - // file comment - // - - Ok(()) -} - -fn build_extra_field(_file: &ZipFileData) -> ZipResult> { - let writer = Vec::new(); - // Future work - Ok(writer) -} - -fn path_to_string(path: &std::path::Path) -> String { - let mut path_str = String::new(); - for component in path.components() { - if let std::path::Component::Normal(os_str) = component { - if !path_str.is_empty() { - path_str.push('/'); - } - path_str.push_str(&*os_str.to_string_lossy()); - } - } - path_str -} - -#[cfg(test)] -mod test { - use super::{FileOptions, ZipWriter}; - use crate::compression::CompressionMethod; - use crate::types::DateTime; - use std::io; - use std::io::Write; - - #[test] - fn write_empty_zip() { - let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); - writer.set_comment("ZIP"); - let result = writer.finish().unwrap(); - assert_eq!(result.get_ref().len(), 25); - assert_eq!( - *result.get_ref(), - [80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 90, 73, 80] - ); - } - - #[test] - fn write_zip_dir() { - let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); - writer - .add_directory( - "test", - 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 directory is not allowed, and will not write any data") - .is_err()); - let result = writer.finish().unwrap(); - assert_eq!(result.get_ref().len(), 108); - assert_eq!( - *result.get_ref(), - &[ - 80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 1, 2, 46, 3, 20, 0, 0, 0, 0, 0, - 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 237, 65, 0, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, - 1, 0, 51, 0, 0, 0, 35, 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 { - compression_method: CompressionMethod::Stored, - last_modified_time: DateTime::default(), - permissions: Some(33188), - }; - writer.start_file("mimetype", options).unwrap(); - writer - .write(b"application/vnd.oasis.opendocument.text") - .unwrap(); - let result = writer.finish().unwrap(); - - assert_eq!(result.get_ref().len(), 153); - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip")); - assert_eq!(result.get_ref(), &v); - } - - #[test] - fn path_to_string() { - let mut path = std::path::PathBuf::new(); - #[cfg(windows)] - path.push(r"C:\"); - #[cfg(unix)] - path.push("/"); - path.push("windows"); - path.push(".."); - path.push("."); - path.push("system32"); - let path_str = super::path_to_string(&path); - assert_eq!(path_str, "windows/system32"); - } -} diff --git a/third-party/zip/src/zipcrypto.rs b/third-party/zip/src/zipcrypto.rs deleted file mode 100644 index 32e8af8..0000000 --- a/third-party/zip/src/zipcrypto.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! Implementation of the ZipCrypto algorithm -//! -//! The following paper was used to implement the ZipCrypto algorithm: -//! [https://courses.cs.ut.ee/MTAT.07.022/2015_fall/uploads/Main/dmitri-report-f15-16.pdf](https://courses.cs.ut.ee/MTAT.07.022/2015_fall/uploads/Main/dmitri-report-f15-16.pdf) - -use std::num::Wrapping; - -/// A container to hold the current key state -struct ZipCryptoKeys { - key_0: Wrapping, - key_1: Wrapping, - key_2: Wrapping, -} - -impl ZipCryptoKeys { - fn new() -> ZipCryptoKeys { - ZipCryptoKeys { - key_0: Wrapping(0x12345678), - key_1: Wrapping(0x23456789), - key_2: Wrapping(0x34567890), - } - } - - fn update(&mut self, input: u8) { - self.key_0 = ZipCryptoKeys::crc32(self.key_0, input); - self.key_1 = - (self.key_1 + (self.key_0 & Wrapping(0xff))) * Wrapping(0x08088405) + Wrapping(1); - self.key_2 = ZipCryptoKeys::crc32(self.key_2, (self.key_1 >> 24).0 as u8); - } - - fn stream_byte(&mut self) -> u8 { - let temp: Wrapping = Wrapping(self.key_2.0 as u16) | Wrapping(3); - ((temp * (temp ^ Wrapping(1))) >> 8).0 as u8 - } - - fn decrypt_byte(&mut self, cipher_byte: u8) -> u8 { - let plain_byte: u8 = self.stream_byte() ^ cipher_byte; - self.update(plain_byte); - plain_byte - } - - #[allow(dead_code)] - fn encrypt_byte(&mut self, plain_byte: u8) -> u8 { - let cipher_byte: u8 = self.stream_byte() ^ plain_byte; - self.update(plain_byte); - cipher_byte - } - - fn crc32(crc: Wrapping, input: u8) -> Wrapping { - return (crc >> 8) ^ Wrapping(CRCTABLE[((crc & Wrapping(0xff)).0 as u8 ^ input) as usize]); - } -} - -/// A ZipCrypto reader with unverified password -pub struct ZipCryptoReader { - file: R, - keys: ZipCryptoKeys, -} - -impl ZipCryptoReader { - /// Note: The password is `&[u8]` and not `&str` because the - /// [zip specification](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT) - /// does not specify password encoding (see function `update_keys` in the specification). - /// Therefore, if `&str` was used, the password would be UTF-8 and it - /// would be impossible to decrypt files that were encrypted with a - /// password byte sequence that is unrepresentable in UTF-8. - pub fn new(file: R, password: &[u8]) -> ZipCryptoReader { - let mut result = ZipCryptoReader { - file: file, - keys: ZipCryptoKeys::new(), - }; - - // Key the cipher by updating the keys with the password. - for byte in password.iter() { - result.keys.update(*byte); - } - - result - } - - /// Read the ZipCrypto header bytes and validate the password. - pub fn validate( - mut self, - crc32_plaintext: u32, - ) -> Result>, std::io::Error> { - // ZipCrypto prefixes a file with a 12 byte header - let mut header_buf = [0u8; 12]; - self.file.read_exact(&mut header_buf)?; - for byte in header_buf.iter_mut() { - *byte = self.keys.decrypt_byte(*byte); - } - - // PKZIP before 2.0 used 2 byte CRC check. - // PKZIP 2.0+ used 1 byte CRC check. It's more secure. - // We also use 1 byte CRC. - - if (crc32_plaintext >> 24) as u8 != header_buf[11] { - return Ok(None); // Wrong password - } - Ok(Some(ZipCryptoReaderValid { reader: self })) - } -} - -/// A ZipCrypto reader with verified password -pub struct ZipCryptoReaderValid { - reader: ZipCryptoReader, -} - -impl std::io::Read for ZipCryptoReaderValid { - fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result { - // Note: There might be potential for optimization. Inspiration can be found at: - // https://github.com/kornelski/7z/blob/master/CPP/7zip/Crypto/ZipCrypto.cpp - - let result = self.reader.file.read(&mut buf); - for byte in buf.iter_mut() { - *byte = self.reader.keys.decrypt_byte(*byte); - } - result - } -} - -impl ZipCryptoReaderValid { - /// Consumes this decoder, returning the underlying reader. - pub fn into_inner(self) -> R { - self.reader.file - } -} - -static CRCTABLE: [u32; 256] = [ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, -];