Turns out LZMA decompression is not working

This commit is contained in:
Vinícius Rodrigues Miguel 2021-03-23 23:17:48 -03:00
parent 989f928320
commit f8ca0e3c56
33 changed files with 4013 additions and 239 deletions

168
Cargo.lock generated
View File

@ -1,26 +1,11 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "addr2line"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "ansi_term"
version = "0.11.0"
@ -47,30 +32,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
dependencies = [
"addr2line",
"cfg-if 1.0.0",
"libc",
"miniz_oxide 0.4.4",
"object",
"rustc-demangle",
]
[[package]]
name = "bgzip"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adff113e9fe73a6d0c4efd0f143857bd6bdad40743542f3f57464398c532234f"
dependencies = [
"failure",
"flate2",
]
[[package]]
name = "bitflags"
version = "1.2.1"
@ -120,12 +81,6 @@ 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"
@ -164,38 +119,7 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "enum_primitive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
dependencies = [
"num-traits 0.1.43",
]
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
"cfg-if",
]
[[package]]
@ -204,7 +128,7 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"redox_syscall",
"winapi",
@ -212,22 +136,16 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.14"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
dependencies = [
"cfg-if 0.1.10",
"cfg-if",
"crc32fast",
"libc",
"miniz_oxide 0.3.7",
"miniz_oxide",
]
[[package]]
name = "gimli"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
[[package]]
name = "hermit-abi"
version = "0.1.18"
@ -260,15 +178,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
"adler32",
]
[[package]]
name = "miniz_oxide"
version = "0.4.4"
@ -279,54 +188,17 @@ dependencies = [
"autocfg",
]
[[package]]
name = "niffler"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea40fb13399dd0e9780ea3d82cb6cf27f489f63d820aa7dc9ec967750dc6d58"
dependencies = [
"bgzip",
"bzip2 0.4.2",
"cfg-if 1.0.0",
"enum_primitive",
"flate2",
"thiserror",
"xz2",
]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
dependencies = [
"num-traits 0.2.14",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
[[package]]
name = "ouch"
version = "0.1.0"
dependencies = [
"bzip2 0.4.2",
"clap",
"colored",
"niffler",
"flate2",
"tar",
"walkdir",
"xz2",
"zip",
]
@ -363,12 +235,6 @@ dependencies = [
"bitflags",
]
[[package]]
name = "rustc-demangle"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
[[package]]
name = "same-file"
version = "1.0.6"
@ -395,18 +261,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "synstructure"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "tar"
version = "0.4.33"
@ -544,9 +398,7 @@ dependencies = [
[[package]]
name = "zip"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8264fcea9b7a036a4a5103d7153e988dbc2ebbafb34f68a3c2d404b6b82d74b6"
version = "0.5.10"
dependencies = [
"byteorder",
"bzip2 0.3.3",

View File

@ -8,8 +8,14 @@ edition = "2018"
[dependencies]
colored = "2.0.0"
niffler = "2.3.1"
walkdir = "2.3.2"
clap = "2.33.3"
zip = "0.5.11"
tar = "0.4.33"
tar = "0.4.33"
xz2 = "0.1"
bzip2 = "0.4.2"
flate2 = "1.0.20"
# 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 = { path = "./third-party/zip" }

View File

@ -3,10 +3,10 @@
`ouch` is the Obvious Unified Compression (and decompression) Helper.
| Supported formats | .tar | .zip | .tar.{.lz, .lzma, .gz, .bz} | .zip.{.lz, .lzma, .gz, .bz} | .bz | .gz | .lz, .lzma |
| Supported formats | .tar | .zip | .tar.{.gz, .bz} | .zip.{.gz, .bz, .bz2} | .bz | .gz | .lz, .lzma |
|-------------------|------|------|------------------------------|------------------------------|-----|-----|------------|
| Decompression | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Compression | ✓ | | ✗ | ✗ | ✗ | ✗ | ✗ |
| Decompression | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Compression | ✓ | | ✗ | ✗ | ✗ | ✗ | ✗ |
## How does it work?

View File

@ -1,5 +1,6 @@
mod tar;
mod zip;
mod unified;
mod compressor;
pub use compressor::Compressor;

View File

View File

@ -31,7 +31,6 @@ impl ZipCompressor {
let options =
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
// let ok = Box::from(inner_file_path.to_string_lossy());
writer.start_file(inner_file_path, options)?;
let input_bytes = match input.contents_in_memory {

View File

@ -1,10 +1,12 @@
mod decompressor;
mod tar;
mod zip;
mod niffler;
mod unified;
pub use decompressor::Decompressor;
pub use decompressor::DecompressionResult;
pub use self::tar::TarDecompressor;
pub use self::zip::ZipDecompressor;
pub use self::niffler::NifflerDecompressor;
pub use self::unified::GzipDecompressor;
pub use self::unified::BzipDecompressor;
pub use self::unified::LzmaDecompressor;

View File

@ -1,54 +0,0 @@
use std::{io::Read, path::Path};
use colored::Colorize;
use niffler;
use crate::file::File;
use crate::{
error::{self, OuchResult},
utils,
};
use super::decompressor::Decompressor;
use super::decompressor::DecompressionResult;
pub struct NifflerDecompressor {}
impl NifflerDecompressor {
fn unpack_file(from: &Path) -> OuchResult<Vec<u8>> {
// println!("{}: trying to decompress {:?}", "info".yellow(), from);
let file = std::fs::read(from)?;
let (mut reader, compression) = niffler::get_reader(Box::new(&file[..]))?;
match compression {
niffler::Format::No => {
return Err(error::Error::InvalidInput);
},
other => {
println!("{}: {:?} detected.", "info".yellow(), other);
}
}
let mut buffer = Vec::new();
let bytes_read = reader.read_to_end(&mut buffer)?;
println!("{}: {:?} extracted into memory ({} bytes).", "info".yellow(), from, bytes_read);
Ok(buffer)
}
}
impl Decompressor for NifflerDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?;
let bytes = Self::unpack_file(&from.path)?;
Ok(DecompressionResult::FileInMemory(bytes))
}
}

View File

@ -0,0 +1,81 @@
use std::{
io::{self, Read},
path::{Path, PathBuf},
};
use bzip2::Compress;
use colored::Colorize;
// use niffler;
use crate::{extension::CompressionFormat, file::File};
use crate::{
error::{self, OuchResult},
utils,
};
use super::decompressor::DecompressionResult;
use super::decompressor::Decompressor;
pub struct UnifiedDecompressor {}
pub struct LzmaDecompressor {}
pub struct GzipDecompressor {}
pub struct BzipDecompressor {}
fn get_decoder<'a>(format: CompressionFormat, buffer: Box<dyn io::Read + Send + 'a>) -> Box<dyn io::Read + Send + 'a> {
match format {
CompressionFormat::Lzma => Box::new(xz2::read::XzDecoder::new(buffer)),
CompressionFormat::Bzip => Box::new(bzip2::read::BzDecoder::new(buffer)),
CompressionFormat::Gzip => Box::new(flate2::read::MultiGzDecoder::new(buffer)),
other => unreachable!()
}
}
impl UnifiedDecompressor {
fn unpack_file(from: &Path, format: CompressionFormat) -> OuchResult<Vec<u8>> {
// println!("{}: trying to decompress {:?}", "info".yellow(), from);
let file = std::fs::read(from)?;
let mut reader = get_decoder(format, Box::new(&file[..]));
let mut buffer = Vec::new();
let bytes_read = reader.read_to_end(&mut buffer)?;
println!(
"{}: {:?} extracted into memory ({} bytes).",
"info".yellow(),
from,
bytes_read
);
Ok(buffer)
}
fn decompress(from: File, format: CompressionFormat, into: &Option<File>) -> OuchResult<DecompressionResult> {
let destination_path = utils::get_destination_path(into);
utils::create_path_if_non_existent(destination_path)?;
let bytes = Self::unpack_file(&from.path, format)?;
Ok(DecompressionResult::FileInMemory(bytes))
}
}
impl Decompressor for LzmaDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
UnifiedDecompressor::decompress(from, CompressionFormat::Lzma, into)
}
}
impl Decompressor for GzipDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
UnifiedDecompressor::decompress(from, CompressionFormat::Gzip, into)
}
}
impl Decompressor for BzipDecompressor {
fn decompress(&self, from: File, into: &Option<File>) -> OuchResult<DecompressionResult> {
UnifiedDecompressor::decompress(from, CompressionFormat::Bzip, into)
}
}

View File

@ -1,4 +1,4 @@
use std::fmt;
use std::{fmt, path::PathBuf};
use colored::Colorize;
@ -13,6 +13,7 @@ pub enum Error {
FileNotFound,
AlreadyExists,
InvalidZipArchive(&'static str),
UnsupportedArchive(PathBuf),
PermissionDenied,
UnsupportedZipArchive(&'static str),
FileTooShort,
@ -39,6 +40,9 @@ impl fmt::Display for Error {
Error::FileNotFound => {
write!(f, "file not found!")
}
Error::UnsupportedArchive(path) => {
write!(f, "ouch is currently uncapable of decompressing {:?}", path)
}
err => {
// TODO
write!(f, "todo: missing description for error {:?}", err)
@ -73,20 +77,20 @@ impl From<zip::result::ZipError> for Error {
}
}
impl From<niffler::error::Error> for Error {
fn from(err: niffler::error::Error) -> Self {
use niffler::error::Error as NifErr;
match err {
NifErr::FeatureDisabled => {
// Ouch is using Niffler with all its features so
// this should be unreachable.
unreachable!();
},
NifErr::FileTooShort => Self::FileTooShort,
NifErr::IOError(io_err) => Self::from(io_err)
}
}
}
// impl From<niffler::error::Error> for Error {
// fn from(err: niffler::error::Error) -> Self {
// use niffler::error::Error as NifErr;
// match err {
// NifErr::FeatureDisabled => {
// // Ouch is using Niffler with all its features so
// // this should be unreachable.
// unreachable!();
// },
// NifErr::FileTooShort => Self::FileTooShort,
// NifErr::IOError(io_err) => Self::from(io_err)
// }
// }
// }
impl From<walkdir::Error> for Error {
fn from(err: walkdir::Error) -> Self {

View File

@ -13,7 +13,8 @@ use crate::decompressors::{
Decompressor,
TarDecompressor,
ZipDecompressor,
NifflerDecompressor,
GzipDecompressor,
BzipDecompressor,
DecompressionResult
};
@ -94,8 +95,14 @@ impl Evaluator {
CompressionFormat::Zip => Box::new(ZipDecompressor {}),
CompressionFormat::Gzip | CompressionFormat::Lzma | CompressionFormat::Bzip => {
Box::new(NifflerDecompressor {})
CompressionFormat::Gzip => Box::new(GzipDecompressor {}),
CompressionFormat::Lzma => {
todo!()
}
CompressionFormat::Bzip => {
Box::new(BzipDecompressor {})
}
};

5
third-party/zip/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
Cargo.lock
target
\.idea/
tests/

77
third-party/zip/CODE_OF_CONDUCT.md vendored Normal file
View File

@ -0,0 +1,77 @@
# 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 Normal file
View File

@ -0,0 +1,35 @@
[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 Normal file
View File

@ -0,0 +1,21 @@
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 Normal file
View File

@ -0,0 +1,70 @@
zip-rs
======
[![Build Status](https://img.shields.io/github/workflow/status/zip-rs/zip/CI)](https://github.com/zip-rs/zip/actions?query=branch%3Amaster+workflow%3ACI)
[![Crates.io version](https://img.shields.io/crates/v/zip.svg)](https://crates.io/crates/zip)
[Documentation](https://docs.rs/zip/0.5.10/zip/)
Info
----
A zip library for rust which supports reading and writing of simple ZIP files.
Supported compression formats:
* stored (i.e. none)
* deflate
* bzip2
Currently unsupported zip extensions:
* Encryption
* Multi-disk
Usage
-----
With all default features:
```toml
[dependencies]
zip = "0.5"
```
Without the default features:
```toml
[dependencies]
zip = { version = "0.5", default-features = false }
```
The features available are:
* `deflate`: Enables the deflate compression algorithm, which is the default for zipfiles
* `bzip2`: Enables the BZip2 compression algorithm.
* `time`: Enables features using the [time](https://github.com/rust-lang-deprecated/time) crate.
All of these are enabled by default.
MSRV
----
Our current Minimum Supported Rust Version is **1.34.0**. When adding features,
we will follow these guidelines:
- We will always support the latest four minor Rust versions. This gives you a 6
month window to upgrade your compiler.
- Any change to the MSRV will be accompanied with a **minor** version bump
- While the crate is pre-1.0, this will be a change to the PATCH version.
Examples
--------
See the [examples directory](examples) for:
* How to write a file to a zip.
* How to write a directory of files to a zip (using [walkdir](https://github.com/BurntSushi/walkdir)).
* How to extract a zip file.
* How to extract a single file from a zip.
* How to read a zip from the standard input.

43
third-party/zip/benches/read_entry.rs vendored Normal file
View File

@ -0,0 +1,43 @@
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 Normal file
View File

@ -0,0 +1,63 @@
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;
}

View File

@ -0,0 +1,31 @@
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 Normal file
View File

@ -0,0 +1,53 @@
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 Normal file
View File

@ -0,0 +1,34 @@
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 Normal file
View File

@ -0,0 +1,120 @@
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(())
}

View File

@ -0,0 +1,71 @@
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 Normal file
View File

@ -0,0 +1,180 @@
//! 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 Normal file
View File

@ -0,0 +1,203 @@
//! 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 Normal file
View File

@ -0,0 +1,93 @@
//! 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 Normal file
View File

@ -0,0 +1,21 @@
//! 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 Normal file

File diff suppressed because it is too large Load Diff

39
third-party/zip/src/result.rs vendored Normal file
View File

@ -0,0 +1,39 @@
//! 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 Normal file
View File

@ -0,0 +1,182 @@
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 Normal file
View File

@ -0,0 +1,474 @@
//! 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 Normal file
View File

@ -0,0 +1,821 @@
//! 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 Normal file
View File

@ -0,0 +1,162 @@
//! 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,
];