fix some clippy warnings
fix another clippy complaint ad support for extended timestamp support missing timestamps in the extended timestamps field handle inconsistencies between flags and len handle len_left add getter Update README.md to state that the crate has moved ad support for extended timestamp handle inconsistencies between flags and len handle len_left add getter Update README.md to state that the crate has moved
This commit is contained in:
parent
3e88fe66c9
commit
0321c05557
9 changed files with 152 additions and 100 deletions
97
README.md
97
README.md
|
@ -1,95 +1,4 @@
|
||||||
zip-rs
|
zip has moved
|
||||||
======
|
=============
|
||||||
|
|
||||||
[](https://github.com/zip-rs/zip/actions?query=branch%3Amaster+workflow%3ACI)
|
This repository was formerly the source of the [zip](https://crates.io/crates/zip) Rust crate for compressing and decompressing ZIP files, but that has moved to https://github.com/Pr0methean/zip. Please submit all issues and pull requests there, and close any existing copies here. Once the existing ones are closed, this repository will be archived.
|
||||||
[](https://crates.io/crates/zip)
|
|
||||||
[](https://discord.gg/rQ7H9cSsF4)
|
|
||||||
|
|
||||||
[Documentation](https://docs.rs/zip/0.6.3/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
|
|
||||||
* zstd
|
|
||||||
|
|
||||||
Currently unsupported zip extensions:
|
|
||||||
|
|
||||||
* Encryption
|
|
||||||
* Multi-disk
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
With all default features:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
zip = "0.6"
|
|
||||||
```
|
|
||||||
|
|
||||||
Without the default features:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
zip = { version = "0.6.6", default-features = false }
|
|
||||||
```
|
|
||||||
|
|
||||||
The features available are:
|
|
||||||
|
|
||||||
* `aes-crypto`: Enables decryption of files which were encrypted with AES. Supports AE-1 and AE-2 methods.
|
|
||||||
* `deflate`: Enables the deflate compression algorithm, which is the default for zip files.
|
|
||||||
* `bzip2`: Enables the BZip2 compression algorithm.
|
|
||||||
* `time`: Enables features using the [time](https://github.com/rust-lang-deprecated/time) crate.
|
|
||||||
* `zstd`: Enables the Zstandard compression algorithm.
|
|
||||||
|
|
||||||
All of these are enabled by default.
|
|
||||||
|
|
||||||
MSRV
|
|
||||||
----
|
|
||||||
|
|
||||||
Our current Minimum Supported Rust Version is **1.59.0**. When adding features,
|
|
||||||
we will follow these guidelines:
|
|
||||||
|
|
||||||
- We will always support the latest four minor Rust versions. This gives you a 6
|
|
||||||
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.
|
|
||||||
|
|
||||||
Fuzzing
|
|
||||||
-------
|
|
||||||
|
|
||||||
Fuzzing support is through [cargo fuzz](https://github.com/rust-fuzz/cargo-fuzz). To install cargo fuzz:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install cargo-fuzz
|
|
||||||
```
|
|
||||||
|
|
||||||
To list fuzz targets:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo +nightly fuzz list
|
|
||||||
```
|
|
||||||
|
|
||||||
To start fuzzing zip extraction:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo +nightly fuzz run fuzz_read
|
|
||||||
```
|
|
||||||
|
|
|
@ -187,6 +187,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[allow(invalid_from_utf8)]
|
||||||
fn example_slice() {
|
fn example_slice() {
|
||||||
use super::FromCp437;
|
use super::FromCp437;
|
||||||
let data = b"Cura\x87ao";
|
let data = b"Cura\x87ao";
|
||||||
|
|
88
src/extra_fields/extended_timestamp.rs
Normal file
88
src/extra_fields/extended_timestamp.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use byteorder::LittleEndian;
|
||||||
|
use byteorder::ReadBytesExt;
|
||||||
|
|
||||||
|
use crate::result::{ZipError, ZipResult};
|
||||||
|
|
||||||
|
/// extended timestamp, as described in <https://libzip.org/specifications/extrafld.txt>
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ExtendedTimestamp {
|
||||||
|
mod_time: Option<u32>,
|
||||||
|
ac_time: Option<u32>,
|
||||||
|
cr_time: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtendedTimestamp {
|
||||||
|
/// creates an extended timestamp struct by reading the required bytes from the reader.
|
||||||
|
///
|
||||||
|
/// This method assumes that the length has already been read, therefore
|
||||||
|
/// it must be passed as an argument
|
||||||
|
pub fn try_from_reader<R>(reader: &mut R, len: u16) -> ZipResult<Self>
|
||||||
|
where
|
||||||
|
R: Read,
|
||||||
|
{
|
||||||
|
let flags = reader.read_u8()?;
|
||||||
|
|
||||||
|
// the `flags` field refers to the local headers and might not correspond
|
||||||
|
// to the len field. If the length field is 1+4, we assume that only
|
||||||
|
// the modification time has been set
|
||||||
|
|
||||||
|
// > Those times that are present will appear in the order indicated, but
|
||||||
|
// > any combination of times may be omitted. (Creation time may be
|
||||||
|
// > present without access time, for example.) TSize should equal
|
||||||
|
// > (1 + 4*(number of set bits in Flags)), as the block is currently
|
||||||
|
// > defined.
|
||||||
|
if len != 5 && len as u32 != 1 + 4 * flags.count_ones() {
|
||||||
|
//panic!("found len {len} and flags {flags:08b}");
|
||||||
|
return Err(ZipError::UnsupportedArchive(
|
||||||
|
"flags and len don't match in extended timestamp field",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags & 0b11111000 != 0 {
|
||||||
|
return Err(ZipError::UnsupportedArchive(
|
||||||
|
"found unsupported timestamps in the extended timestamp header",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mod_time = if (flags & 0b00000001u8 == 0b00000001u8) || len == 5 {
|
||||||
|
Some(reader.read_u32::<LittleEndian>()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let ac_time = if flags & 0b00000010u8 == 0b00000010u8 && len > 5 {
|
||||||
|
Some(reader.read_u32::<LittleEndian>()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let cr_time = if flags & 0b00000100u8 == 0b00000100u8 && len > 5 {
|
||||||
|
Some(reader.read_u32::<LittleEndian>()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
mod_time,
|
||||||
|
ac_time,
|
||||||
|
cr_time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns the last modification timestamp
|
||||||
|
pub fn mod_time(&self) -> Option<&u32> {
|
||||||
|
self.mod_time.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns the last access timestamp
|
||||||
|
pub fn ac_time(&self) -> Option<&u32> {
|
||||||
|
self.ac_time.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns the creation timestamp
|
||||||
|
pub fn cr_time(&self) -> Option<&u32> {
|
||||||
|
self.cr_time.as_ref()
|
||||||
|
}
|
||||||
|
}
|
29
src/extra_fields/mod.rs
Normal file
29
src/extra_fields/mod.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
//! types for extra fields
|
||||||
|
|
||||||
|
/// marker trait to denote the place where this extra field has been stored
|
||||||
|
pub trait ExtraFieldVersion {}
|
||||||
|
|
||||||
|
/// use this to mark extra fields specified in a local header
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LocalHeaderVersion;
|
||||||
|
|
||||||
|
/// use this to mark extra fields specified in the central header
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CentralHeaderVersion;
|
||||||
|
|
||||||
|
impl ExtraFieldVersion for LocalHeaderVersion {}
|
||||||
|
impl ExtraFieldVersion for CentralHeaderVersion {}
|
||||||
|
|
||||||
|
mod extended_timestamp;
|
||||||
|
|
||||||
|
pub use extended_timestamp::*;
|
||||||
|
|
||||||
|
/// contains one extra field
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ExtraField {
|
||||||
|
|
||||||
|
/// extended timestamp, as described in <https://libzip.org/specifications/extrafld.txt>
|
||||||
|
ExtendedTimestamp(ExtendedTimestamp)
|
||||||
|
}
|
|
@ -42,6 +42,8 @@ mod spec;
|
||||||
mod types;
|
mod types;
|
||||||
pub mod write;
|
pub mod write;
|
||||||
mod zipcrypto;
|
mod zipcrypto;
|
||||||
|
pub mod extra_fields;
|
||||||
|
pub use extra_fields::ExtraField;
|
||||||
|
|
||||||
/// Unstable APIs
|
/// Unstable APIs
|
||||||
///
|
///
|
||||||
|
|
24
src/read.rs
24
src/read.rs
|
@ -5,6 +5,7 @@ use crate::aes::{AesReader, AesReaderValid};
|
||||||
use crate::compression::CompressionMethod;
|
use crate::compression::CompressionMethod;
|
||||||
use crate::cp437::FromCp437;
|
use crate::cp437::FromCp437;
|
||||||
use crate::crc32::Crc32Reader;
|
use crate::crc32::Crc32Reader;
|
||||||
|
use crate::extra_fields::{ExtendedTimestamp, ExtraField};
|
||||||
use crate::result::{InvalidPassword, ZipError, ZipResult};
|
use crate::result::{InvalidPassword, ZipError, ZipResult};
|
||||||
use crate::spec;
|
use crate::spec;
|
||||||
use crate::types::{AesMode, AesVendorVersion, AtomicU64, DateTime, System, ZipFileData};
|
use crate::types::{AesMode, AesVendorVersion, AtomicU64, DateTime, System, ZipFileData};
|
||||||
|
@ -724,6 +725,7 @@ fn central_header_to_zip_file_inner<R: Read>(
|
||||||
external_attributes: external_file_attributes,
|
external_attributes: external_file_attributes,
|
||||||
large_file: false,
|
large_file: false,
|
||||||
aes_mode: None,
|
aes_mode: None,
|
||||||
|
extra_fields: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match parse_extra_field(&mut result) {
|
match parse_extra_field(&mut result) {
|
||||||
|
@ -803,6 +805,17 @@ fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
|
||||||
CompressionMethod::from_u16(compression_method)
|
CompressionMethod::from_u16(compression_method)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
0x5455 => {
|
||||||
|
// extended timestamp
|
||||||
|
// https://libzip.org/specifications/extrafld.txt
|
||||||
|
|
||||||
|
file.extra_fields.push(ExtraField::ExtendedTimestamp(
|
||||||
|
ExtendedTimestamp::try_from_reader(&mut reader, len)?,
|
||||||
|
));
|
||||||
|
|
||||||
|
// the reader for ExtendedTimestamp consumes `len` bytes
|
||||||
|
len_left = 0;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Other fields are ignored
|
// Other fields are ignored
|
||||||
}
|
}
|
||||||
|
@ -935,8 +948,7 @@ impl<'a> ZipFile<'a> {
|
||||||
pub fn is_dir(&self) -> bool {
|
pub fn is_dir(&self) -> bool {
|
||||||
self.name()
|
self.name()
|
||||||
.chars()
|
.chars()
|
||||||
.rev()
|
.next_back()
|
||||||
.next()
|
|
||||||
.map_or(false, |c| c == '/' || c == '\\')
|
.map_or(false, |c| c == '/' || c == '\\')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -973,6 +985,11 @@ impl<'a> ZipFile<'a> {
|
||||||
pub fn central_header_start(&self) -> u64 {
|
pub fn central_header_start(&self) -> u64 {
|
||||||
self.data.central_header_start
|
self.data.central_header_start
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// iterate through all extra fields
|
||||||
|
pub fn extra_data_fields(&self) -> impl Iterator<Item=&ExtraField> {
|
||||||
|
self.data.extra_fields.iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Read for ZipFile<'a> {
|
impl<'a> Read for ZipFile<'a> {
|
||||||
|
@ -991,7 +1008,7 @@ impl<'a> Drop for ZipFile<'a> {
|
||||||
// Get the inner `Take` reader so all decryption, decompression and CRC calculation is skipped.
|
// Get the inner `Take` reader so all decryption, decompression and CRC calculation is skipped.
|
||||||
let mut reader: std::io::Take<&mut dyn std::io::Read> = match &mut self.reader {
|
let mut reader: std::io::Take<&mut dyn std::io::Read> = match &mut self.reader {
|
||||||
ZipFileReader::NoReader => {
|
ZipFileReader::NoReader => {
|
||||||
let innerreader = ::std::mem::replace(&mut self.crypto_reader, None);
|
let innerreader = self.crypto_reader.take();
|
||||||
innerreader.expect("Invalid reader state").into_inner()
|
innerreader.expect("Invalid reader state").into_inner()
|
||||||
}
|
}
|
||||||
reader => {
|
reader => {
|
||||||
|
@ -1091,6 +1108,7 @@ pub fn read_zipfile_from_stream<'a, R: io::Read>(
|
||||||
external_attributes: 0,
|
external_attributes: 0,
|
||||||
large_file: false,
|
large_file: false,
|
||||||
aes_mode: None,
|
aes_mode: None,
|
||||||
|
extra_fields: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match parse_extra_field(&mut result) {
|
match parse_extra_field(&mut result) {
|
||||||
|
|
|
@ -184,8 +184,7 @@ impl ZipStreamFileMetadata {
|
||||||
pub fn is_dir(&self) -> bool {
|
pub fn is_dir(&self) -> bool {
|
||||||
self.name()
|
self.name()
|
||||||
.chars()
|
.chars()
|
||||||
.rev()
|
.next_back()
|
||||||
.next()
|
|
||||||
.map_or(false, |c| c == '/' || c == '\\')
|
.map_or(false, |c| c == '/' || c == '\\')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ mod atomic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::extra_fields::ExtraField;
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
use crate::result::DateTimeRangeError;
|
use crate::result::DateTimeRangeError;
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
|
@ -350,6 +351,9 @@ pub struct ZipFileData {
|
||||||
pub large_file: bool,
|
pub large_file: bool,
|
||||||
/// AES mode if applicable
|
/// AES mode if applicable
|
||||||
pub aes_mode: Option<(AesMode, AesVendorVersion)>,
|
pub aes_mode: Option<(AesMode, AesVendorVersion)>,
|
||||||
|
|
||||||
|
/// extra fields, see <https://libzip.org/specifications/extrafld.txt>
|
||||||
|
pub extra_fields: Vec<ExtraField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZipFileData {
|
impl ZipFileData {
|
||||||
|
@ -508,6 +512,7 @@ mod test {
|
||||||
external_attributes: 0,
|
external_attributes: 0,
|
||||||
large_file: false,
|
large_file: false,
|
||||||
aes_mode: None,
|
aes_mode: None,
|
||||||
|
extra_fields: Vec::new(),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
data.file_name_sanitized(),
|
data.file_name_sanitized(),
|
||||||
|
|
|
@ -397,6 +397,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
external_attributes: permissions << 16,
|
external_attributes: permissions << 16,
|
||||||
large_file: options.large_file,
|
large_file: options.large_file,
|
||||||
aes_mode: None,
|
aes_mode: None,
|
||||||
|
extra_fields: Vec::new(),
|
||||||
};
|
};
|
||||||
write_local_file_header(writer, &file)?;
|
write_local_file_header(writer, &file)?;
|
||||||
|
|
||||||
|
@ -411,7 +412,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
|
||||||
}
|
}
|
||||||
if let Some(keys) = options.encrypt_with {
|
if let Some(keys) = options.encrypt_with {
|
||||||
let mut zipwriter = crate::zipcrypto::ZipCryptoWriter { writer: core::mem::replace(&mut self.inner, GenericZipWriter::Closed).unwrap(), buffer: vec![], keys };
|
let mut zipwriter = crate::zipcrypto::ZipCryptoWriter { writer: core::mem::replace(&mut self.inner, GenericZipWriter::Closed).unwrap(), buffer: vec![], keys };
|
||||||
let mut crypto_header = [0u8; 12];
|
let crypto_header = [0u8; 12];
|
||||||
|
|
||||||
zipwriter.write_all(&crypto_header)?;
|
zipwriter.write_all(&crypto_header)?;
|
||||||
self.inner = GenericZipWriter::Storer(MaybeEncrypted::Encrypted(zipwriter));
|
self.inner = GenericZipWriter::Storer(MaybeEncrypted::Encrypted(zipwriter));
|
||||||
|
|
Loading…
Add table
Reference in a new issue