From 065058d30d39b607e243760c70b11f1a5edff929 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 4 Aug 2023 21:50:42 +0900 Subject: [PATCH] feat: support deflate64 compression with deflate64 crate --- Cargo.toml | 1 + README.md | 2 ++ src/compression.rs | 13 ++++++++++ src/deflate64.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ src/read.rs | 14 +++++++++++ src/write.rs | 6 +++++ 7 files changed, 99 insertions(+) create mode 100644 src/deflate64.rs diff --git a/Cargo.toml b/Cargo.toml index 510df9ca..941e2651 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ pbkdf2 = {version = "0.11.0", optional = true } sha1 = {version = "0.10.1", optional = true } time = { version = "0.3.7", optional = true, default-features = false, features = ["std"] } zstd = { version = "0.11.2", optional = true } +deflate64 = { version = "0.1.3", optional = true } [target.'cfg(any(all(target_arch = "arm", target_pointer_width = "32"), target_arch = "mips", target_arch = "powerpc"))'.dependencies] crossbeam-utils = "0.8.8" diff --git a/README.md b/README.md index f06cdbb5..8ed79612 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Supported compression formats: * stored (i.e. none) * deflate +* deflate64 (decompression only) * bzip2 * zstd @@ -46,6 +47,7 @@ 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. +* `deflate64`: Enables the deflate64 compression algorithm. Decompression is only supported. * `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. diff --git a/src/compression.rs b/src/compression.rs index baec9399..5b75380e 100644 --- a/src/compression.rs +++ b/src/compression.rs @@ -22,6 +22,10 @@ pub enum CompressionMethod { feature = "deflate-zlib" ))] Deflated, + /// Compress the file using Deflate64. + /// Decoding deflate64 is supported but encoding deflate64 is not supported. + #[cfg(feature = "deflate64")] + Deflate64, /// Compress the file using BZIP2 #[cfg(feature = "bzip2")] Bzip2, @@ -60,6 +64,9 @@ impl CompressionMethod { feature = "deflate-zlib" )))] pub const DEFLATE: Self = CompressionMethod::Unsupported(8); + #[cfg(feature = "deflate64")] + pub const DEFLATE64: Self = CompressionMethod::Deflate64; + #[cfg(not(feature = "deflate64"))] pub const DEFLATE64: Self = CompressionMethod::Unsupported(9); pub const PKWARE_IMPLODE: Self = CompressionMethod::Unsupported(10); #[cfg(feature = "bzip2")] @@ -100,6 +107,8 @@ impl CompressionMethod { feature = "deflate-zlib" ))] 8 => CompressionMethod::Deflated, + #[cfg(feature = "deflate64")] + 9 => CompressionMethod::Deflate64, #[cfg(feature = "bzip2")] 12 => CompressionMethod::Bzip2, #[cfg(feature = "zstd")] @@ -126,6 +135,8 @@ impl CompressionMethod { feature = "deflate-zlib" ))] CompressionMethod::Deflated => 8, + #[cfg(feature = "deflate64")] + CompressionMethod::Deflate64 => 9, #[cfg(feature = "bzip2")] CompressionMethod::Bzip2 => 12, #[cfg(feature = "aes-crypto")] @@ -154,6 +165,8 @@ pub const SUPPORTED_COMPRESSION_METHODS: &[CompressionMethod] = &[ feature = "deflate-zlib" ))] CompressionMethod::Deflated, + #[cfg(feature = "deflate64")] + CompressionMethod::Deflate64, #[cfg(feature = "bzip2")] CompressionMethod::Bzip2, #[cfg(feature = "zstd")] diff --git a/src/deflate64.rs b/src/deflate64.rs new file mode 100644 index 00000000..5e972ffb --- /dev/null +++ b/src/deflate64.rs @@ -0,0 +1,61 @@ +// TODO: move this module to deflate64 crate + +use deflate64::InflaterManaged; +use std::io; +use std::io::{BufRead, BufReader, Read}; + +const IN_BUFFER_MAX_SIZE: usize = 8192; + +pub(crate) struct BufferedDeflate64Decoder { + inner: R, + inflator: Box, +} + +impl BufferedDeflate64Decoder> { + pub(crate) fn new(inner: R) -> Self { + Self { + inner: BufReader::new(inner), + inflator: Box::new(InflaterManaged::new()), + } + } +} + +impl BufferedDeflate64Decoder { + pub(crate) fn into_inner(self) -> R { + self.inner + } +} + +struct State { + inflater: InflaterManaged, + in_buffer_size: u16, + in_buffer: [u8; IN_BUFFER_MAX_SIZE], +} + +impl Read for BufferedDeflate64Decoder { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + loop { + let input = self.inner.fill_buf()?; + let eof = input.is_empty(); + + let result = self.inflator.inflate(input, buf); + + self.inner.consume(result.bytes_consumed); + + if result.data_error { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid deflate64", + )); + } + + if result.bytes_written == 0 && !eof && !self.inflator.finished() { + // if we haven't ready any data and we haven't hit EOF yet, + // ask again. We must not return 0 in such case + continue; + } + + Ok(result.bytes_written) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index e2228e5b..be5b2662 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,8 @@ mod aes_ctr; mod compression; mod cp437; mod crc32; +#[cfg(feature = "deflate64")] +mod deflate64; pub mod read; pub mod result; mod spec; diff --git a/src/read.rs b/src/read.rs index b702b4f2..121e0149 100644 --- a/src/read.rs +++ b/src/read.rs @@ -23,6 +23,9 @@ use std::sync::Arc; ))] use flate2::read::DeflateDecoder; +#[cfg(feature = "deflate64")] +use crate::deflate64::BufferedDeflate64Decoder; + #[cfg(feature = "bzip2")] use bzip2::read::BzDecoder; @@ -129,6 +132,8 @@ enum ZipFileReader<'a> { feature = "deflate-zlib" ))] Deflated(Crc32Reader>>), + #[cfg(feature = "deflate64")] + Deflate64(Crc32Reader>>>), #[cfg(feature = "bzip2")] Bzip2(Crc32Reader>>), #[cfg(feature = "zstd")] @@ -147,6 +152,8 @@ impl<'a> Read for ZipFileReader<'a> { feature = "deflate-zlib" ))] ZipFileReader::Deflated(r) => r.read(buf), + #[cfg(feature = "deflate64")] + ZipFileReader::Deflate64(r) => r.read(buf), #[cfg(feature = "bzip2")] ZipFileReader::Bzip2(r) => r.read(buf), #[cfg(feature = "zstd")] @@ -168,6 +175,8 @@ impl<'a> ZipFileReader<'a> { feature = "deflate-zlib" ))] ZipFileReader::Deflated(r) => r.into_inner().into_inner().into_inner(), + #[cfg(feature = "deflate64")] + ZipFileReader::Deflate64(r) => r.into_inner().into_inner().into_inner().into_inner(), #[cfg(feature = "bzip2")] ZipFileReader::Bzip2(r) => r.into_inner().into_inner().into_inner(), #[cfg(feature = "zstd")] @@ -277,6 +286,11 @@ fn make_reader( let deflate_reader = DeflateDecoder::new(reader); ZipFileReader::Deflated(Crc32Reader::new(deflate_reader, crc32, ae2_encrypted)) } + #[cfg(feature = "deflate64")] + CompressionMethod::Deflate64 => { + let deflate64_reader = BufferedDeflate64Decoder::new(reader); + ZipFileReader::Deflate64(Crc32Reader::new(deflate64_reader, crc32, ae2_encrypted)) + } #[cfg(feature = "bzip2")] CompressionMethod::Bzip2 => { let bzip2_reader = BzDecoder::new(reader); diff --git a/src/write.rs b/src/write.rs index 4cdc031b..b5f6a933 100644 --- a/src/write.rs +++ b/src/write.rs @@ -958,6 +958,12 @@ impl GenericZipWriter { ))? as u32, ), )), + #[cfg(feature = "deflate64")] + CompressionMethod::Deflate64 => { + return Err(ZipError::UnsupportedArchive( + "Compressing Deflate64 is not supported", + )); + } #[cfg(feature = "bzip2")] CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzEncoder::new( bare,