mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-05 02:55:31 +00:00
Updates README.md and Cargo.toml
This commit is contained in:
parent
729dda819e
commit
320f27ff8f
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -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",
|
||||
|
12
Cargo.toml
12
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
|
||||
|
13
README.md
13
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>... Input files (TODO description)
|
||||
-o, --output <output> Output file (TODO description)
|
||||
-i, --input <input>... The input files or directories.
|
||||
-o, --output <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
|
||||
|
||||
|
5
third-party/zip/.gitignore
vendored
5
third-party/zip/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
Cargo.lock
|
||||
target
|
||||
|
||||
\.idea/
|
||||
tests/
|
77
third-party/zip/CODE_OF_CONDUCT.md
vendored
77
third-party/zip/CODE_OF_CONDUCT.md
vendored
@ -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
|
35
third-party/zip/Cargo.toml
vendored
35
third-party/zip/Cargo.toml
vendored
@ -1,35 +0,0 @@
|
||||
[package]
|
||||
name = "zip"
|
||||
version = "0.5.10"
|
||||
authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>", "Marli Frost <marli@frost.red>", "Ryan Levick <ryan.levick@gmail.com>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/zip-rs/zip.git"
|
||||
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
|
21
third-party/zip/LICENSE
vendored
21
third-party/zip/LICENSE
vendored
@ -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.
|
70
third-party/zip/README.md
vendored
70
third-party/zip/README.md
vendored
@ -1,70 +0,0 @@
|
||||
zip-rs
|
||||
======
|
||||
|
||||
[](https://github.com/zip-rs/zip/actions?query=branch%3Amaster+workflow%3ACI)
|
||||
[](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.
|
43
third-party/zip/benches/read_entry.rs
vendored
43
third-party/zip/benches/read_entry.rs
vendored
@ -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<u8> {
|
||||
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);
|
63
third-party/zip/examples/extract.rs
vendored
63
third-party/zip/examples/extract.rs
vendored
@ -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: {} <filename>", 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;
|
||||
}
|
31
third-party/zip/examples/extract_lorem.rs
vendored
31
third-party/zip/examples/extract_lorem.rs
vendored
@ -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: {} <filename>", 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;
|
||||
}
|
53
third-party/zip/examples/file_info.rs
vendored
53
third-party/zip/examples/file_info.rs
vendored
@ -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: {} <filename>", 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;
|
||||
}
|
34
third-party/zip/examples/stdin_info.rs
vendored
34
third-party/zip/examples/stdin_info.rs
vendored
@ -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;
|
||||
}
|
120
third-party/zip/examples/write_dir.rs
vendored
120
third-party/zip/examples/write_dir.rs
vendored
@ -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<zip::CompressionMethod> = Some(zip::CompressionMethod::Stored);
|
||||
|
||||
#[cfg(any(
|
||||
feature = "deflate",
|
||||
feature = "deflate-miniz",
|
||||
feature = "deflate-zlib"
|
||||
))]
|
||||
const METHOD_DEFLATED: Option<zip::CompressionMethod> = Some(zip::CompressionMethod::Deflated);
|
||||
#[cfg(not(any(
|
||||
feature = "deflate",
|
||||
feature = "deflate-miniz",
|
||||
feature = "deflate-zlib"
|
||||
)))]
|
||||
const METHOD_DEFLATED: Option<zip::CompressionMethod> = None;
|
||||
|
||||
#[cfg(feature = "bzip2")]
|
||||
const METHOD_BZIP2: Option<zip::CompressionMethod> = Some(zip::CompressionMethod::Bzip2);
|
||||
#[cfg(not(feature = "bzip2"))]
|
||||
const METHOD_BZIP2: Option<zip::CompressionMethod> = None;
|
||||
|
||||
fn real_main() -> i32 {
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
if args.len() < 3 {
|
||||
println!(
|
||||
"Usage: {} <source_directory> <destination_zipfile>",
|
||||
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<T>(
|
||||
it: &mut dyn Iterator<Item = DirEntry>,
|
||||
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(())
|
||||
}
|
71
third-party/zip/examples/write_sample.rs
vendored
71
third-party/zip/examples/write_sample.rs
vendored
@ -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: {} <filename>", 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.
|
||||
";
|
180
third-party/zip/src/compression.rs
vendored
180
third-party/zip/src/compression.rs
vendored
@ -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<CompressionMethod> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
203
third-party/zip/src/cp437.rs
vendored
203
third-party/zip/src/cp437.rs
vendored
@ -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::<String>().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromCp437 for Vec<u8> {
|
||||
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(), "╠══╣");
|
||||
}
|
||||
}
|
93
third-party/zip/src/crc32.rs
vendored
93
third-party/zip/src/crc32.rs
vendored
@ -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<R> {
|
||||
inner: R,
|
||||
hasher: Hasher,
|
||||
check: u32,
|
||||
}
|
||||
|
||||
impl<R> Crc32Reader<R> {
|
||||
/// Get a new Crc32Reader which check the inner reader against checksum.
|
||||
pub fn new(inner: R, checksum: u32) -> Crc32Reader<R> {
|
||||
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<R: Read> Read for Crc32Reader<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
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);
|
||||
}
|
||||
}
|
21
third-party/zip/src/lib.rs
vendored
21
third-party/zip/src/lib.rs
vendored
@ -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;
|
1078
third-party/zip/src/read.rs
vendored
1078
third-party/zip/src/read.rs
vendored
File diff suppressed because it is too large
Load Diff
39
third-party/zip/src/result.rs
vendored
39
third-party/zip/src/result.rs
vendored
@ -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<T> = Result<T, ZipError>;
|
||||
|
||||
/// 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<ZipError> for io::Error {
|
||||
fn from(err: ZipError) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, err)
|
||||
}
|
||||
}
|
182
third-party/zip/src/spec.rs
vendored
182
third-party/zip/src/spec.rs
vendored
@ -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<u8>,
|
||||
}
|
||||
|
||||
impl CentralDirectoryEnd {
|
||||
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> {
|
||||
let magic = reader.read_u32::<LittleEndian>()?;
|
||||
if magic != CENTRAL_DIRECTORY_END_SIGNATURE {
|
||||
return Err(ZipError::InvalidArchive("Invalid digital signature header"));
|
||||
}
|
||||
let disk_number = reader.read_u16::<LittleEndian>()?;
|
||||
let disk_with_central_directory = reader.read_u16::<LittleEndian>()?;
|
||||
let number_of_files_on_this_disk = reader.read_u16::<LittleEndian>()?;
|
||||
let number_of_files = reader.read_u16::<LittleEndian>()?;
|
||||
let central_directory_size = reader.read_u32::<LittleEndian>()?;
|
||||
let central_directory_offset = reader.read_u32::<LittleEndian>()?;
|
||||
let zip_file_comment_length = reader.read_u16::<LittleEndian>()? 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<T: Read + io::Seek>(
|
||||
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::<LittleEndian>()? == CENTRAL_DIRECTORY_END_SIGNATURE {
|
||||
reader.seek(io::SeekFrom::Current(
|
||||
BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64,
|
||||
))?;
|
||||
let cde_start_pos = reader.seek(io::SeekFrom::Start(pos as u64))?;
|
||||
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<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
|
||||
writer.write_u32::<LittleEndian>(CENTRAL_DIRECTORY_END_SIGNATURE)?;
|
||||
writer.write_u16::<LittleEndian>(self.disk_number)?;
|
||||
writer.write_u16::<LittleEndian>(self.disk_with_central_directory)?;
|
||||
writer.write_u16::<LittleEndian>(self.number_of_files_on_this_disk)?;
|
||||
writer.write_u16::<LittleEndian>(self.number_of_files)?;
|
||||
writer.write_u32::<LittleEndian>(self.central_directory_size)?;
|
||||
writer.write_u32::<LittleEndian>(self.central_directory_offset)?;
|
||||
writer.write_u16::<LittleEndian>(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<T: Read>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator> {
|
||||
let magic = reader.read_u32::<LittleEndian>()?;
|
||||
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::<LittleEndian>()?;
|
||||
let end_of_central_directory_offset = reader.read_u64::<LittleEndian>()?;
|
||||
let number_of_disks = reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
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<u8>, <-- We don't do anything with this at the moment.
|
||||
}
|
||||
|
||||
impl Zip64CentralDirectoryEnd {
|
||||
pub fn find_and_parse<T: Read + io::Seek>(
|
||||
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::<LittleEndian>()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE {
|
||||
let archive_offset = pos - nominal_offset;
|
||||
|
||||
let _record_size = reader.read_u64::<LittleEndian>()?;
|
||||
// We would use this value if we did anything with the "zip64 extensible data sector".
|
||||
|
||||
let version_made_by = reader.read_u16::<LittleEndian>()?;
|
||||
let version_needed_to_extract = reader.read_u16::<LittleEndian>()?;
|
||||
let disk_number = reader.read_u32::<LittleEndian>()?;
|
||||
let disk_with_central_directory = reader.read_u32::<LittleEndian>()?;
|
||||
let number_of_files_on_this_disk = reader.read_u64::<LittleEndian>()?;
|
||||
let number_of_files = reader.read_u64::<LittleEndian>()?;
|
||||
let central_directory_size = reader.read_u64::<LittleEndian>()?;
|
||||
let central_directory_offset = reader.read_u64::<LittleEndian>()?;
|
||||
|
||||
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",
|
||||
))
|
||||
}
|
||||
}
|
474
third-party/zip/src/types.rs
vendored
474
third-party/zip/src/types.rs
vendored
@ -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<DateTime, ()> {
|
||||
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<DateTime, ()> {
|
||||
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<u8>,
|
||||
/// 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());
|
||||
}
|
||||
}
|
821
third-party/zip/src/write.rs
vendored
821
third-party/zip/src/write.rs
vendored
@ -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<W: Write + io::Seek> {
|
||||
Closed,
|
||||
Storer(W),
|
||||
#[cfg(any(
|
||||
feature = "deflate",
|
||||
feature = "deflate-miniz",
|
||||
feature = "deflate-zlib"
|
||||
))]
|
||||
Deflater(DeflateEncoder<W>),
|
||||
#[cfg(feature = "bzip2")]
|
||||
Bzip2(BzEncoder<W>),
|
||||
}
|
||||
|
||||
/// 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<W: Write + io::Seek> {
|
||||
inner: GenericZipWriter<W>,
|
||||
files: Vec<ZipFileData>,
|
||||
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<u32>,
|
||||
}
|
||||
|
||||
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<W: Write + io::Seek> Write for ZipWriter<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
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<W: Write + io::Seek> ZipWriter<W> {
|
||||
/// Initializes the archive.
|
||||
///
|
||||
/// Before writing to this object, the [`ZipWriter::start_file`] function should be called.
|
||||
pub fn new(inner: W) -> ZipWriter<W> {
|
||||
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<S>(&mut self, comment: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.comment = comment.into();
|
||||
}
|
||||
|
||||
/// Start a new file for with the requested options.
|
||||
fn start_entry<S>(
|
||||
&mut self,
|
||||
name: S,
|
||||
options: FileOptions,
|
||||
raw_values: Option<ZipRawValues>,
|
||||
) -> ZipResult<()>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
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<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
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<R, W>(
|
||||
/// src: &mut ZipArchive<R>,
|
||||
/// dst: &mut ZipWriter<W>,
|
||||
/// ) -> 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<S>(&mut self, mut file: ZipFile, name: S) -> ZipResult<()>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
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<R, W>(src: &mut ZipArchive<R>, dst: &mut ZipWriter<W>) -> 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<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
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<W> {
|
||||
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<W: Write + io::Seek> Drop for ZipWriter<W> {
|
||||
fn drop(&mut self) {
|
||||
if !self.inner.is_closed() {
|
||||
if let Err(e) = self.finalize() {
|
||||
let _ = write!(&mut io::stderr(), "ZipWriter drop failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write + io::Seek> GenericZipWriter<W> {
|
||||
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<CompressionMethod> {
|
||||
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<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
||||
// local file header signature
|
||||
writer.write_u32::<LittleEndian>(spec::LOCAL_FILE_HEADER_SIGNATURE)?;
|
||||
// version needed to extract
|
||||
writer.write_u16::<LittleEndian>(file.version_needed())?;
|
||||
// general purpose bit flag
|
||||
let flag = if !file.file_name.is_ascii() {
|
||||
1u16 << 11
|
||||
} else {
|
||||
0
|
||||
};
|
||||
writer.write_u16::<LittleEndian>(flag)?;
|
||||
// Compression method
|
||||
#[allow(deprecated)]
|
||||
writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
|
||||
// last mod file time and last mod file date
|
||||
writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
|
||||
writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
|
||||
// crc-32
|
||||
writer.write_u32::<LittleEndian>(file.crc32)?;
|
||||
// compressed size
|
||||
writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
|
||||
// uncompressed size
|
||||
writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
|
||||
// file name length
|
||||
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
|
||||
// extra field length
|
||||
let extra_field = build_extra_field(file)?;
|
||||
writer.write_u16::<LittleEndian>(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<T: Write + io::Seek>(
|
||||
writer: &mut T,
|
||||
file: &ZipFileData,
|
||||
) -> ZipResult<()> {
|
||||
const CRC32_OFFSET: u64 = 14;
|
||||
writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
|
||||
writer.write_u32::<LittleEndian>(file.crc32)?;
|
||||
writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
|
||||
writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
||||
// central file header signature
|
||||
writer.write_u32::<LittleEndian>(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::<LittleEndian>(version_made_by)?;
|
||||
// version needed to extract
|
||||
writer.write_u16::<LittleEndian>(file.version_needed())?;
|
||||
// general puprose bit flag
|
||||
let flag = if !file.file_name.is_ascii() {
|
||||
1u16 << 11
|
||||
} else {
|
||||
0
|
||||
};
|
||||
writer.write_u16::<LittleEndian>(flag)?;
|
||||
// compression method
|
||||
#[allow(deprecated)]
|
||||
writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
|
||||
// last mod file time + date
|
||||
writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
|
||||
writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
|
||||
// crc-32
|
||||
writer.write_u32::<LittleEndian>(file.crc32)?;
|
||||
// compressed size
|
||||
writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
|
||||
// uncompressed size
|
||||
writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
|
||||
// file name length
|
||||
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
|
||||
// extra field length
|
||||
let extra_field = build_extra_field(file)?;
|
||||
writer.write_u16::<LittleEndian>(extra_field.len() as u16)?;
|
||||
// file comment length
|
||||
writer.write_u16::<LittleEndian>(0)?;
|
||||
// disk number start
|
||||
writer.write_u16::<LittleEndian>(0)?;
|
||||
// internal file attribytes
|
||||
writer.write_u16::<LittleEndian>(0)?;
|
||||
// external file attributes
|
||||
writer.write_u32::<LittleEndian>(file.external_attributes)?;
|
||||
// relative offset of local header
|
||||
writer.write_u32::<LittleEndian>(file.header_start as u32)?;
|
||||
// file name
|
||||
writer.write_all(file.file_name.as_bytes())?;
|
||||
// extra field
|
||||
writer.write_all(&extra_field)?;
|
||||
// file comment
|
||||
// <none>
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>> {
|
||||
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");
|
||||
}
|
||||
}
|
162
third-party/zip/src/zipcrypto.rs
vendored
162
third-party/zip/src/zipcrypto.rs
vendored
@ -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<u32>,
|
||||
key_1: Wrapping<u32>,
|
||||
key_2: Wrapping<u32>,
|
||||
}
|
||||
|
||||
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<u16> = 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<u32>, input: u8) -> Wrapping<u32> {
|
||||
return (crc >> 8) ^ Wrapping(CRCTABLE[((crc & Wrapping(0xff)).0 as u8 ^ input) as usize]);
|
||||
}
|
||||
}
|
||||
|
||||
/// A ZipCrypto reader with unverified password
|
||||
pub struct ZipCryptoReader<R> {
|
||||
file: R,
|
||||
keys: ZipCryptoKeys,
|
||||
}
|
||||
|
||||
impl<R: std::io::Read> ZipCryptoReader<R> {
|
||||
/// 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<R> {
|
||||
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<Option<ZipCryptoReaderValid<R>>, 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<R> {
|
||||
reader: ZipCryptoReader<R>,
|
||||
}
|
||||
|
||||
impl<R: std::io::Read> std::io::Read for ZipCryptoReaderValid<R> {
|
||||
fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
// 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<R: std::io::Read> ZipCryptoReaderValid<R> {
|
||||
/// 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,
|
||||
];
|
Loading…
x
Reference in New Issue
Block a user