Merge remote-tracking branch 'zip-rs/zip/master'

This commit is contained in:
Lireer 2022-01-25 17:48:45 +01:00
commit d7f0a182b6
21 changed files with 265 additions and 171 deletions

View file

@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
rust: [stable, 1.42.0]
rust: [stable, 1.54.0]
steps:
- uses: actions/checkout@master
@ -39,6 +39,25 @@ jobs:
command: test
args: --all
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: clippy
- name: clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --all-features -- -D warnings
check_fmt_and_docs:
name: Checking fmt and docs
runs-on: ubuntu-latest

View file

@ -1,4 +1,3 @@
# Contributor Covenant Code of Conduct
## Our Pledge

View file

@ -15,17 +15,17 @@ aes = { version = "0.6.0", optional = true }
byteorder = "1.3"
bzip2 = { version = "0.4", optional = true }
constant_time_eq = { version = "0.1.5", optional = true }
crc32fast = "1.0"
crc32fast = "1.1.1"
flate2 = { version = "1.0.0", default-features = false, optional = true }
hmac = {version = "0.10.1", optional = true }
pbkdf2 = {version = "0.6.0", optional = true }
sha-1 = {version = "0.9.2", optional = true }
thiserror = "1.0"
time = { version = "0.1", optional = true }
time = { version = "0.3", features = ["formatting", "macros" ], optional = true }
zstd = { version = "0.10", optional = true }
[dev-dependencies]
bencher = "0.1"
rand = "0.7"
getrandom = "0.2"
walkdir = "2"
[features]
@ -34,7 +34,7 @@ deflate = ["flate2/rust_backend"]
deflate-miniz = ["flate2/default"]
deflate-zlib = ["flate2/zlib"]
unreserved = []
default = ["aes-crypto", "bzip2", "deflate", "time"]
default = ["aes-crypto", "bzip2", "deflate", "time", "zstd"]
[[bench]]
name = "read_entry"

View file

@ -3,6 +3,7 @@ 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)
[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/rQ7H9cSsF4)
[Documentation](https://docs.rs/zip/0.5.13/zip/)
@ -17,6 +18,7 @@ Supported compression formats:
* stored (i.e. none)
* deflate
* bzip2
* zstd
Currently unsupported zip extensions:
@ -43,16 +45,17 @@ zip = { version = "0.5", 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.
* `deflate`: Enables the deflate compression algorithm, which is the default for zipfiles
* `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.42.0**. When adding features,
Our current Minimum Supported Rust Version is **1.54.0**. When adding features,
we will follow these guidelines:
- We will always support the latest four minor Rust versions. This gives you a 6

View file

@ -3,7 +3,7 @@ use bencher::{benchmark_group, benchmark_main};
use std::io::{Cursor, Read, Write};
use bencher::Bencher;
use rand::RngCore;
use getrandom::getrandom;
use zip::{ZipArchive, ZipWriter};
fn generate_random_archive(size: usize) -> Vec<u8> {
@ -14,7 +14,7 @@ fn generate_random_archive(size: usize) -> Vec<u8> {
writer.start_file("random.dat", options).unwrap();
let mut bytes = vec![0u8; size];
rand::thread_rng().fill_bytes(&mut bytes);
getrandom(&mut bytes).unwrap();
writer.write_all(&bytes).unwrap();
writer.finish().unwrap().into_inner()

View file

@ -59,5 +59,6 @@ fn real_main() -> i32 {
}
}
}
return 0;
0
}

View file

@ -27,5 +27,5 @@ fn real_main() -> i32 {
file.read_to_string(&mut contents).unwrap();
println!("{}", contents);
return 0;
0
}

View file

@ -49,5 +49,6 @@ fn real_main() -> i32 {
);
}
}
return 0;
0
}

View file

@ -30,5 +30,6 @@ fn real_main() -> i32 {
}
}
}
return 0;
0
}

View file

@ -32,6 +32,11 @@ const METHOD_BZIP2: Option<zip::CompressionMethod> = Some(zip::CompressionMethod
#[cfg(not(feature = "bzip2"))]
const METHOD_BZIP2: Option<zip::CompressionMethod> = None;
#[cfg(feature = "zstd")]
const METHOD_ZSTD: Option<zip::CompressionMethod> = Some(zip::CompressionMethod::Zstd);
#[cfg(not(feature = "zstd"))]
const METHOD_ZSTD: Option<zip::CompressionMethod> = None;
fn real_main() -> i32 {
let args: Vec<_> = std::env::args().collect();
if args.len() < 3 {
@ -44,7 +49,7 @@ fn real_main() -> i32 {
let src_dir = &*args[1];
let dst_file = &*args[2];
for &method in [METHOD_STORED, METHOD_DEFLATED, METHOD_BZIP2].iter() {
for &method in [METHOD_STORED, METHOD_DEFLATED, METHOD_BZIP2, METHOD_ZSTD].iter() {
if method.is_none() {
continue;
}
@ -54,7 +59,7 @@ fn real_main() -> i32 {
}
}
return 0;
0
}
fn zip_dir<T>(
@ -87,7 +92,7 @@ where
f.read_to_end(&mut buffer)?;
zip.write_all(&*buffer)?;
buffer.clear();
} else if name.as_os_str().len() != 0 {
} else if !name.as_os_str().is_empty() {
// Only if not root! Avoids path spec / warning
// and mapname conversion failed error on unzip
println!("adding dir {:?} as {:?} ...", path, name);
@ -111,7 +116,7 @@ fn doit(
let path = Path::new(dst_file);
let file = File::create(&path).unwrap();
let walkdir = WalkDir::new(src_dir.to_string());
let walkdir = WalkDir::new(src_dir);
let it = walkdir.into_iter();
zip_dir(&mut it.filter_map(|e| e.ok()), src_dir, file, method)?;

View file

@ -18,7 +18,7 @@ fn real_main() -> i32 {
Err(e) => println!("Error: {:?}", e),
}
return 0;
0
}
fn doit(filename: &str) -> zip::result::ZipResult<()> {
@ -42,7 +42,7 @@ fn doit(filename: &str) -> zip::result::ZipResult<()> {
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
const LOREM_IPSUM : &[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

View file

@ -1,7 +1,7 @@
//! Implementation of the AES decryption for zip files.
//!
//! This was implemented according to the [WinZip specification](https://www.winzip.com/win/en/aes_info.html).
//! Note that using CRC with AES depends on the specific encryption specification used, AE-1 or AE-2.
//! Note that using CRC with AES depends on the used encryption specification, AE-1 or AE-2.
//! If the file is marked as encrypted with AE-2 the CRC field is ignored, even if it isn't set to 0.
use crate::aes_ctr;

View file

@ -9,7 +9,7 @@ use std::fmt;
/// contents to be read without context.
///
/// When creating ZIP files, you may choose the method to use with
/// [`zip::write::FileOptions::compression_method`]
/// [`crate::write::FileOptions::compression_method`]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum CompressionMethod {
/// Store the file as is
@ -25,9 +25,13 @@ pub enum CompressionMethod {
#[cfg(feature = "bzip2")]
Bzip2,
/// Encrypted using AES.
///
/// The actual compression method has to be taken from the AES extra data field
/// or from `ZipFileData`.
Aes,
/// Compress the file using ZStandard
#[cfg(feature = "zstd")]
Zstd,
/// Unsupported compression method
#[deprecated(since = "0.5.7", note = "use the constants instead")]
Unsupported(u16),
@ -64,6 +68,9 @@ impl CompressionMethod {
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);
#[cfg(feature = "zstd")]
pub const ZSTD: Self = CompressionMethod::Zstd;
#[cfg(not(feature = "zstd"))]
pub const ZSTD: Self = CompressionMethod::Unsupported(93);
pub const MP3: Self = CompressionMethod::Unsupported(94);
pub const XZ: Self = CompressionMethod::Unsupported(95);
@ -90,6 +97,9 @@ impl CompressionMethod {
#[cfg(feature = "bzip2")]
12 => CompressionMethod::Bzip2,
99 => CompressionMethod::Aes,
#[cfg(feature = "zstd")]
93 => CompressionMethod::Zstd,
v => CompressionMethod::Unsupported(v),
}
}
@ -112,6 +122,9 @@ impl CompressionMethod {
#[cfg(feature = "bzip2")]
CompressionMethod::Bzip2 => 12,
CompressionMethod::Aes => 99,
#[cfg(feature = "zstd")]
CompressionMethod::Zstd => 93,
CompressionMethod::Unsupported(v) => v,
}
}
@ -130,7 +143,7 @@ mod test {
#[test]
fn from_eq_to() {
for v in 0..(::std::u16::MAX as u32 + 1) {
for v in 0..(u16::MAX as u32 + 1) {
#[allow(deprecated)]
let from = CompressionMethod::from_u16(v as u16);
#[allow(deprecated)]
@ -140,17 +153,19 @@ mod test {
}
fn methods() -> Vec<CompressionMethod> {
let mut methods = Vec::new();
methods.push(CompressionMethod::Stored);
vec![
CompressionMethod::Stored,
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib"
))]
methods.push(CompressionMethod::Deflated);
CompressionMethod::Deflated,
#[cfg(feature = "bzip2")]
methods.push(CompressionMethod::Bzip2);
methods
CompressionMethod::Bzip2,
#[cfg(feature = "zstd")]
CompressionMethod::Zstd,
]
}
#[test]

View file

@ -6,7 +6,8 @@ pub trait FromCp437 {
type Target;
/// Function that does the conversion from cp437.
/// Gennerally allocations will be avoided if all data falls into the ASCII range.
/// Generally allocations will be avoided if all data falls into the ASCII range.
#[allow(clippy::wrong_self_convention)]
fn from_cp437(self) -> Self::Target;
}

View file

@ -25,6 +25,9 @@ use flate2::read::DeflateDecoder;
#[cfg(feature = "bzip2")]
use bzip2::read::BzDecoder;
#[cfg(feature = "zstd")]
use zstd::stream::read::Decoder as ZstdDecoder;
mod ffi {
pub const S_IFDIR: u32 = 0o0040000;
pub const S_IFREG: u32 = 0o0100000;
@ -109,6 +112,8 @@ enum ZipFileReader<'a> {
Deflated(Crc32Reader<flate2::read::DeflateDecoder<CryptoReader<'a>>>),
#[cfg(feature = "bzip2")]
Bzip2(Crc32Reader<BzDecoder<CryptoReader<'a>>>),
#[cfg(feature = "zstd")]
Zstd(Crc32Reader<ZstdDecoder<'a, io::BufReader<CryptoReader<'a>>>>),
}
impl<'a> Read for ZipFileReader<'a> {
@ -125,6 +130,8 @@ impl<'a> Read for ZipFileReader<'a> {
ZipFileReader::Deflated(r) => r.read(buf),
#[cfg(feature = "bzip2")]
ZipFileReader::Bzip2(r) => r.read(buf),
#[cfg(feature = "zstd")]
ZipFileReader::Zstd(r) => r.read(buf),
}
}
}
@ -144,6 +151,8 @@ impl<'a> ZipFileReader<'a> {
ZipFileReader::Deflated(r) => r.into_inner().into_inner().into_inner(),
#[cfg(feature = "bzip2")]
ZipFileReader::Bzip2(r) => r.into_inner().into_inner().into_inner(),
#[cfg(feature = "zstd")]
ZipFileReader::Zstd(r) => r.into_inner().finish().into_inner().into_inner(),
}
}
}
@ -227,11 +236,11 @@ fn make_crypto_reader<'a>(
Ok(Ok(reader))
}
fn make_reader<'a>(
fn make_reader(
compression_method: CompressionMethod,
crc32: u32,
reader: CryptoReader<'a>,
) -> ZipFileReader<'a> {
reader: CryptoReader,
) -> ZipFileReader {
let ae2_encrypted = reader.is_ae2_encrypted();
match compression_method {
@ -252,6 +261,11 @@ fn make_reader<'a>(
let bzip2_reader = BzDecoder::new(reader);
ZipFileReader::Bzip2(Crc32Reader::new(bzip2_reader, crc32, ae2_encrypted))
}
#[cfg(feature = "zstd")]
CompressionMethod::Zstd => {
let zstd_reader = ZstdDecoder::new(reader).unwrap();
ZipFileReader::Zstd(Crc32Reader::new(zstd_reader, crc32, ae2_encrypted))
}
_ => panic!("Compression method not supported"),
}
}
@ -345,7 +359,7 @@ impl<R: Read + io::Seek> ZipArchive<R> {
let directory_start = footer
.central_directory_offset
.checked_add(archive_offset)
.ok_or_else(|| {
.ok_or({
ZipError::InvalidArchive("Invalid central directory size or offset")
})?;
@ -499,14 +513,14 @@ impl<R: Read + io::Seek> ZipArchive<R> {
}
/// Get a contained file by index
pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>> {
pub fn by_index(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
Ok(self
.by_index_with_optional_password(file_number, None)?
.unwrap())
}
/// Get a contained file by index without decompressing it
pub fn by_index_raw<'a>(&'a mut self, file_number: usize) -> ZipResult<ZipFile<'a>> {
pub fn by_index_raw(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
let reader = &mut self.reader;
self.files
.get_mut(file_number)
@ -656,7 +670,10 @@ pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
}
// Account for shifted zip offsets.
result.header_start += archive_offset;
result.header_start = result
.header_start
.checked_add(archive_offset)
.ok_or(ZipError::InvalidArchive("Archive header is too large"))?;
Ok(result)
}
@ -1108,7 +1125,7 @@ mod test {
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/zip64_demo.zip"));
let reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
assert!(reader.len() == 1);
assert_eq!(reader.len(), 1);
}
#[test]
@ -1119,7 +1136,7 @@ mod test {
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
let mut reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
assert!(reader.comment() == b"");
assert_eq!(reader.comment(), b"");
assert_eq!(reader.by_index(0).unwrap().central_header_start(), 77);
}
@ -1169,14 +1186,14 @@ mod test {
let mut buf3 = [0; 5];
let mut buf4 = [0; 5];
file1.read(&mut buf1).unwrap();
file2.read(&mut buf2).unwrap();
file1.read(&mut buf3).unwrap();
file2.read(&mut buf4).unwrap();
file1.read_exact(&mut buf1).unwrap();
file2.read_exact(&mut buf2).unwrap();
file1.read_exact(&mut buf3).unwrap();
file2.read_exact(&mut buf4).unwrap();
assert_eq!(buf1, buf2);
assert_eq!(buf3, buf4);
assert!(buf1 != buf3);
assert_ne!(buf1, buf3);
}
#[test]

View file

@ -1,37 +1,66 @@
//! Error types that can be emitted from this library
use std::error::Error;
use std::fmt;
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")]
#[derive(Debug)]
pub struct InvalidPassword;
impl fmt::Display for InvalidPassword {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "invalid password for file in archive")
}
}
impl Error for InvalidPassword {}
/// Error type for Zip
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum ZipError {
/// An Error caused by I/O
#[error(transparent)]
Io(#[from] io::Error),
Io(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<io::Error> for ZipError {
fn from(err: io::Error) -> ZipError {
ZipError::Io(err)
}
}
impl fmt::Display for ZipError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
ZipError::Io(err) => write!(fmt, "{}", err),
ZipError::InvalidArchive(err) => write!(fmt, "invalid Zip archive: {}", err),
ZipError::UnsupportedArchive(err) => write!(fmt, "unsupported Zip archive: {}", err),
ZipError::FileNotFound => write!(fmt, "specified file not found in archive"),
}
}
}
impl Error for ZipError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ZipError::Io(err) => Some(err),
_ => None,
}
}
}
impl ZipError {
/// The text used as an error when a password is required and not supplied
///

View file

@ -1,5 +1,8 @@
//! Types that specify what is contained in a ZIP.
#[cfg(feature = "time")]
use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum System {
Dos = 0,
@ -62,7 +65,7 @@ impl DateTime {
let seconds = (timepart & 0b0000000000011111) << 1;
let minutes = (timepart & 0b0000011111100000) >> 5;
let hours = (timepart & 0b1111100000000000) >> 11;
let days = (datepart & 0b0000000000011111) >> 0;
let days = datepart & 0b0000000000011111;
let months = (datepart & 0b0000000111100000) >> 5;
let years = (datepart & 0b1111111000000000) >> 9;
@ -85,6 +88,7 @@ impl DateTime {
/// * hour: [0, 23]
/// * minute: [0, 59]
/// * second: [0, 60]
#[allow(clippy::result_unit_err)]
pub fn from_date_and_time(
year: u16,
month: u8,
@ -93,8 +97,7 @@ impl DateTime {
minute: u8,
second: u8,
) -> Result<DateTime, ()> {
if year >= 1980
&& year <= 2107
if (1980..=2107).contains(&year)
&& month >= 1
&& month <= 12
&& day >= 1
@ -117,30 +120,19 @@ impl DateTime {
}
#[cfg(feature = "time")]
/// Converts a ::time::Tm object to a DateTime
/// Converts a OffsetDateTime 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
{
#[allow(clippy::result_unit_err)]
pub fn from_time(dt: OffsetDateTime) -> Result<DateTime, ()> {
if dt.year() >= 1980 && dt.year() <= 2107 {
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,
year: (dt.year()) as u16,
month: (dt.month()) as u8,
day: dt.day() as u8,
hour: dt.hour() as u8,
minute: dt.minute() as u8,
second: dt.second() as u8,
})
} else {
Err(())
@ -158,20 +150,14 @@ impl DateTime {
}
#[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()
}
/// Converts the DateTime to a OffsetDateTime structure
pub fn to_time(&self) -> Result<OffsetDateTime, ComponentRange> {
use std::convert::TryFrom;
let date =
Date::from_calendar_date(self.year as i32, Month::try_from(self.month)?, self.day)?;
let time = Time::from_hms(self.hour, self.minute, self.second)?;
Ok(PrimitiveDateTime::new(date, time).assume_utc())
}
/// Get the year. There is no epoch, i.e. 2018 will be returned as 2018.
@ -373,6 +359,7 @@ mod test {
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn datetime_default() {
use super::DateTime;
let dt = DateTime::default();
@ -381,6 +368,7 @@ mod test {
}
#[test]
#[allow(clippy::unusual_byte_groupings)]
fn datetime_max() {
use super::DateTime;
let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap();
@ -407,58 +395,26 @@ mod test {
assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err());
}
#[cfg(feature = "time")]
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
#[cfg(feature = "time")]
#[test]
fn datetime_from_time_bounds() {
use super::DateTime;
use time::macros::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());
assert!(DateTime::from_time(datetime!(1979-12-31 23:59:59 UTC)).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());
assert!(DateTime::from_time(datetime!(1980-01-01 00:00:00 UTC)).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());
assert!(DateTime::from_time(datetime!(2107-12-31 23:59:59 UTC)).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());
assert!(DateTime::from_time(datetime!(2108-01-01 00:00:00 UTC)).is_err());
}
#[test]
@ -474,7 +430,7 @@ mod test {
#[cfg(feature = "time")]
assert_eq!(
format!("{}", dt.to_time().rfc3339()),
dt.to_time().unwrap().format(&Rfc3339).unwrap(),
"2018-11-17T10:38:30Z"
);
}
@ -491,10 +447,7 @@ mod test {
assert_eq!(dt.second(), 62);
#[cfg(feature = "time")]
assert_eq!(
format!("{}", dt.to_time().rfc3339()),
"2107-15-31T31:63:62Z"
);
assert!(dt.to_time().is_err());
let dt = DateTime::from_msdos(0x0000, 0x0000);
assert_eq!(dt.year(), 1980);
@ -505,10 +458,7 @@ mod test {
assert_eq!(dt.second(), 0);
#[cfg(feature = "time")]
assert_eq!(
format!("{}", dt.to_time().rfc3339()),
"1980-00-00T00:00:00Z"
);
assert!(dt.to_time().is_err());
}
#[cfg(feature = "time")]
@ -517,8 +467,8 @@ mod test {
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());
let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
assert!(DateTime::from_time(clock).is_ok());
}
}

View file

@ -22,6 +22,12 @@ use flate2::write::DeflateEncoder;
#[cfg(feature = "bzip2")]
use bzip2::write::BzEncoder;
#[cfg(feature = "time")]
use time::OffsetDateTime;
#[cfg(feature = "zstd")]
use zstd::stream::write::Encoder as ZstdEncoder;
enum GenericZipWriter<W: Write + io::Seek> {
Closed,
Storer(W),
@ -33,6 +39,8 @@ enum GenericZipWriter<W: Write + io::Seek> {
Deflater(DeflateEncoder<W>),
#[cfg(feature = "bzip2")]
Bzip2(BzEncoder<W>),
#[cfg(feature = "zstd")]
Zstd(ZstdEncoder<'static, W>),
}
/// ZIP archive generator
@ -113,7 +121,7 @@ impl FileOptions {
)))]
compression_method: CompressionMethod::Stored,
#[cfg(feature = "time")]
last_modified_time: DateTime::from_time(time::now()).unwrap_or_default(),
last_modified_time: DateTime::from_time(OffsetDateTime::now_utc()).unwrap_or_default(),
#[cfg(not(feature = "time"))]
last_modified_time: DateTime::default(),
permissions: None,
@ -125,6 +133,7 @@ impl FileOptions {
///
/// The default is `CompressionMethod::Deflated`. If the deflate compression feature is
/// disabled, `CompressionMethod::Stored` becomes the default.
#[must_use]
pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions {
self.compression_method = method;
self
@ -134,6 +143,7 @@ impl FileOptions {
///
/// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01
/// otherwise
#[must_use]
pub fn last_modified_time(mut self, mod_time: DateTime) -> FileOptions {
self.last_modified_time = mod_time;
self
@ -144,6 +154,7 @@ impl FileOptions {
/// 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
#[must_use]
pub fn unix_permissions(mut self, mode: u32) -> FileOptions {
self.permissions = Some(mode & 0o777);
self
@ -154,6 +165,7 @@ impl FileOptions {
/// If set to `false` and the file exceeds the limit, an I/O error is thrown. If set to `true`,
/// readers will require ZIP64 support and if the file does not exceed the limit, 20 B are
/// wasted. The default is `false`.
#[must_use]
pub fn large_file(mut self, large: bool) -> FileOptions {
self.large_file = large;
self
@ -234,7 +246,10 @@ impl<A: Read + Write + io::Seek> ZipWriter<A> {
let (archive_offset, directory_start, number_of_files) =
ZipArchive::get_directory_counts(&mut readwriter, &footer, cde_start_pos)?;
if let Err(_) = readwriter.seek(io::SeekFrom::Start(directory_start)) {
if readwriter
.seek(io::SeekFrom::Start(directory_start))
.is_err()
{
return Err(ZipError::InvalidArchive(
"Could not seek to start of central directory",
));
@ -304,7 +319,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
{
self.finish_file()?;
let raw_values = raw_values.unwrap_or_else(|| ZipRawValues {
let raw_values = raw_values.unwrap_or(ZipRawValues {
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
@ -546,7 +561,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
}
let file = self.files.last_mut().unwrap();
validate_extra_data(&file)?;
validate_extra_data(file)?;
if !self.writing_to_central_extra_field_only {
let writer = self.inner.get_plain();
@ -604,11 +619,11 @@ impl<W: Write + io::Seek> ZipWriter<W> {
where
S: Into<String>,
{
let options = FileOptions::default()
let mut options = FileOptions::default()
.last_modified_time(file.last_modified())
.compression_method(file.compression());
if let Some(perms) = file.unix_mode() {
options.unix_permissions(perms);
options = options.unix_permissions(perms);
}
let raw_values = ZipRawValues {
@ -805,6 +820,8 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
GenericZipWriter::Deflater(w) => w.finish()?,
#[cfg(feature = "bzip2")]
GenericZipWriter::Bzip2(w) => w.finish()?,
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(w) => w.finish()?,
GenericZipWriter::Closed => {
return Err(io::Error::new(
io::ErrorKind::BrokenPipe,
@ -836,6 +853,10 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
"AES compression is not supported for writing",
))
}
#[cfg(feature = "zstd")]
CompressionMethod::Zstd => {
GenericZipWriter::Zstd(ZstdEncoder::new(bare, 0).unwrap())
}
CompressionMethod::Unsupported(..) => {
return Err(ZipError::UnsupportedArchive("Unsupported compression"))
}
@ -856,15 +877,14 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
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),
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(ref mut w) => Some(w as &mut dyn Write),
GenericZipWriter::Closed => None,
}
}
fn is_closed(&self) -> bool {
match *self {
GenericZipWriter::Closed => true,
_ => false,
}
matches!(*self, GenericZipWriter::Closed)
}
fn get_plain(&mut self) -> &mut W {
@ -885,6 +905,8 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
GenericZipWriter::Deflater(..) => Some(CompressionMethod::Deflated),
#[cfg(feature = "bzip2")]
GenericZipWriter::Bzip2(..) => Some(CompressionMethod::Bzip2),
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(..) => Some(CompressionMethod::Zstd),
GenericZipWriter::Closed => None,
}
}
@ -938,7 +960,7 @@ fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipR
writer.write_all(file.file_name.as_bytes())?;
// zip64 extra field
if file.large_file {
write_local_zip64_extra_field(writer, &file)?;
write_local_zip64_extra_field(writer, file)?;
}
Ok(())
@ -1056,7 +1078,7 @@ fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> {
)));
}
while data.len() > 0 {
while !data.is_empty() {
let left = data.len();
if left < 4 {
return Err(ZipError::Io(io::Error::new(
@ -1236,7 +1258,7 @@ mod test {
};
writer.start_file("mimetype", options).unwrap();
writer
.write(b"application/vnd.oasis.opendocument.text")
.write_all(b"application/vnd.oasis.opendocument.text")
.unwrap();
let result = writer.finish().unwrap();

View file

@ -47,7 +47,7 @@ impl ZipCryptoKeys {
}
fn crc32(crc: Wrapping<u32>, input: u8) -> Wrapping<u32> {
return (crc >> 8) ^ Wrapping(CRCTABLE[((crc & Wrapping(0xff)).0 as u8 ^ input) as usize]);
(crc >> 8) ^ Wrapping(CRCTABLE[((crc & Wrapping(0xff)).0 as u8 ^ input) as usize])
}
}
@ -71,7 +71,7 @@ impl<R: std::io::Read> ZipCryptoReader<R> {
/// password byte sequence that is unrepresentable in UTF-8.
pub fn new(file: R, password: &[u8]) -> ZipCryptoReader<R> {
let mut result = ZipCryptoReader {
file: file,
file,
keys: ZipCryptoKeys::new(),
};
@ -129,11 +129,11 @@ pub struct ZipCryptoReaderValid<R> {
}
impl<R: std::io::Read> std::io::Read for ZipCryptoReaderValid<R> {
fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result<usize> {
fn read(&mut self, 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);
let result = self.reader.file.read(buf);
for byte in buf.iter_mut() {
*byte = self.reader.keys.decrypt_byte(*byte);
}

View file

@ -100,7 +100,7 @@ fn read_zip<R: Read + Seek>(zip_file: R) -> zip::result::ZipResult<zip::ZipArchi
"test_with_extra_data/🐢.txt",
ENTRY_NAME,
];
let expected_file_names = HashSet::from_iter(expected_file_names.iter().map(|&v| v));
let expected_file_names = HashSet::from_iter(expected_file_names.iter().copied());
let file_names = archive.file_names().collect::<HashSet<_>>();
assert_eq!(file_names, expected_file_names);
@ -134,17 +134,17 @@ fn check_zip_contents(zip_file: &mut Cursor<Vec<u8>>, name: &str) {
fn check_zip_file_contents<R: Read + Seek>(archive: &mut zip::ZipArchive<R>, name: &str) {
let file_contents: String = read_zip_file(archive, name).unwrap();
assert!(file_contents.as_bytes() == LOREM_IPSUM);
assert_eq!(file_contents.as_bytes(), LOREM_IPSUM);
}
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
const LOREM_IPSUM : &[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.
";
const EXTRA_DATA: &'static [u8] = b"Extra Data";
const EXTRA_DATA: &[u8] = b"Extra Data";
const ENTRY_NAME: &str = "test/lorem_ipsum.txt";

31
tests/issue_234.rs Normal file
View file

@ -0,0 +1,31 @@
use zip::result::ZipError;
const BUF: &[u8] = &[
0, 80, 75, 1, 2, 127, 120, 0, 3, 3, 75, 80, 232, 3, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 7, 0, 0, 0,
0, 65, 0, 1, 0, 0, 0, 4, 0, 0, 224, 255, 0, 255, 255, 255, 255, 255, 255, 20, 39, 221, 221,
221, 221, 221, 221, 205, 221, 221, 221, 42, 221, 221, 221, 221, 221, 221, 221, 221, 38, 34, 34,
219, 80, 75, 5, 6, 0, 0, 0, 0, 5, 96, 0, 1, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 234, 236, 124,
221, 221, 37, 221, 221, 221, 221, 221, 129, 4, 0, 0, 221, 221, 80, 75, 1, 2, 127, 120, 0, 4, 0,
0, 2, 127, 120, 0, 79, 75, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0,
234, 0, 0, 0, 3, 8, 4, 232, 3, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 3, 0,
221, 209, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 58, 58, 42, 75, 9, 2, 127,
120, 0, 99, 99, 99, 99, 99, 99, 94, 7, 0, 0, 0, 0, 0, 0, 213, 213, 213, 213, 213, 213, 213,
213, 213, 7, 0, 0, 211, 211, 211, 211, 124, 236, 99, 99, 99, 94, 7, 0, 0, 0, 0, 0, 0, 213, 213,
213, 213, 213, 213, 213, 213, 213, 7, 0, 0, 211, 211, 211, 211, 124, 236, 234, 0, 0, 0, 3, 8,
0, 0, 0, 12, 0, 0, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 0, 0, 58, 58, 58, 42, 175, 221, 253, 221,
221, 221, 221, 221, 80, 75, 9, 2, 127, 120, 0, 99, 99, 99, 99, 99, 99, 94, 7, 0, 0, 0, 0, 0, 0,
213, 213, 213, 213, 213, 213, 213, 213, 213, 7, 0, 0, 211, 211, 211, 211, 124, 236, 221, 221,
221, 221, 221, 80, 75, 9, 2, 127, 120, 0, 99, 99, 99, 99, 99, 99, 94, 7, 0, 0, 0, 0, 0, 0, 213,
213, 213, 213, 213, 213, 213, 213, 213, 7, 0, 0, 211, 211, 211, 211, 124, 236,
];
#[test]
fn invalid_header() {
let reader = std::io::Cursor::new(&BUF);
let archive = zip::ZipArchive::new(reader);
match archive {
Err(ZipError::InvalidArchive(_)) => {}
value => panic!("Unexpected value: {:?}", value),
}
}