Skip to content

Commit

Permalink
Add support for decompressing LZMA
Browse files Browse the repository at this point in the history
  • Loading branch information
Pr0methean committed Apr 11, 2024
1 parent 4a5d7d4 commit 7f8311e
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 2 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ time = { version = "0.3.34", optional = true, default-features = false, features
zstd = { version = "0.13.1", optional = true, default-features = false }
zopfli = { version = "0.8.0", optional = true }
deflate64 = { version = "0.1.8", optional = true }
lzma-rs = { version = "0.3.0", optional = true }

[target.'cfg(any(all(target_arch = "arm", target_pointer_width = "32"), target_arch = "mips", target_arch = "powerpc"))'.dependencies]
crossbeam-utils = "0.8.19"
Expand All @@ -49,8 +50,9 @@ deflate-miniz = ["flate2/default"]
deflate-zlib = ["flate2/zlib"]
deflate-zlib-ng = ["flate2/zlib-ng"]
deflate-zopfli = ["zopfli"]
lzma = ["lzma-rs/stream"]
unreserved = []
default = ["aes-crypto", "bzip2", "deflate", "deflate64", "deflate-zlib-ng", "deflate-zopfli", "time", "zstd"]
default = ["aes-crypto", "bzip2", "deflate", "deflate64", "deflate-zlib-ng", "deflate-zopfli", "lzma", "time", "zstd"]

[[bench]]
name = "read_entry"
Expand Down
10 changes: 10 additions & 0 deletions src/compression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ pub enum CompressionMethod {
/// Compress the file using ZStandard
#[cfg(feature = "zstd")]
Zstd,
/// Compress the file using LZMA
#[cfg(feature = "lzma")]
Lzma,
/// Unsupported compression method
#[cfg_attr(
not(fuzzing),
Expand Down Expand Up @@ -83,7 +86,10 @@ impl CompressionMethod {
pub const BZIP2: Self = CompressionMethod::Bzip2;
#[cfg(not(feature = "bzip2"))]
pub const BZIP2: Self = CompressionMethod::Unsupported(12);
#[cfg(not(feature = "lzma"))]
pub const LZMA: Self = CompressionMethod::Unsupported(14);
#[cfg(feature = "lzma")]
pub const LZMA: Self = CompressionMethod::Lzma;
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);
Expand Down Expand Up @@ -123,6 +129,8 @@ impl CompressionMethod {
9 => CompressionMethod::Deflate64,
#[cfg(feature = "bzip2")]
12 => CompressionMethod::Bzip2,
#[cfg(feature = "lzma")]
14 => CompressionMethod::Lzma,
#[cfg(feature = "zstd")]
93 => CompressionMethod::Zstd,
#[cfg(feature = "aes-crypto")]
Expand Down Expand Up @@ -157,6 +165,8 @@ impl CompressionMethod {
CompressionMethod::Aes => 99,
#[cfg(feature = "zstd")]
CompressionMethod::Zstd => 93,
#[cfg(feature = "lzma")]
CompressionMethod::Lzma => 14,

CompressionMethod::Unsupported(v) => v,
}
Expand Down
19 changes: 19 additions & 0 deletions src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ use zstd::stream::read::Decoder as ZstdDecoder;
/// Provides high level API for reading from a stream.
pub(crate) mod stream;

#[cfg(feature = "lzma")]
pub(crate) mod lzma;

// Put the struct declaration in a private module to convince rustdoc to display ZipArchive nicely
pub(crate) mod zip_archive {
use std::sync::Arc;
Expand Down Expand Up @@ -81,6 +84,8 @@ pub(crate) mod zip_archive {

use crate::result::ZipError::InvalidPassword;
pub use zip_archive::ZipArchive;
#[cfg(feature = "lzma")]
use crate::read::lzma::LzmaReader;

#[allow(clippy::large_enum_variant)]
pub(crate) enum CryptoReader<'a> {
Expand Down Expand Up @@ -147,6 +152,8 @@ pub(crate) enum ZipFileReader<'a> {
Bzip2(Crc32Reader<BzDecoder<CryptoReader<'a>>>),
#[cfg(feature = "zstd")]
Zstd(Crc32Reader<ZstdDecoder<'a, io::BufReader<CryptoReader<'a>>>>),
#[cfg(feature = "lzma")]
Lzma(Crc32Reader<LzmaReader<CryptoReader<'a>>>),
}

impl<'a> Read for ZipFileReader<'a> {
Expand All @@ -168,6 +175,8 @@ impl<'a> Read for ZipFileReader<'a> {
ZipFileReader::Bzip2(r) => r.read(buf),
#[cfg(feature = "zstd")]
ZipFileReader::Zstd(r) => r.read(buf),
#[cfg(feature = "lzma")]
ZipFileReader::Lzma(r) => r.read(buf)
}
}
}
Expand All @@ -192,6 +201,11 @@ impl<'a> ZipFileReader<'a> {
ZipFileReader::Bzip2(r) => r.into_inner().into_inner().into_inner(),
#[cfg(feature = "zstd")]
ZipFileReader::Zstd(r) => r.into_inner().finish().into_inner().into_inner(),
#[cfg(feature = "lzma")]
ZipFileReader::Lzma(r) => {
let inner: Box<_> = r.into_inner().finish().unwrap().into();
Read::take(Box::leak(inner), u64::MAX)
}
}
}
}
Expand Down Expand Up @@ -313,6 +327,11 @@ pub(crate) fn make_reader(
let zstd_reader = ZstdDecoder::new(reader).unwrap();
ZipFileReader::Zstd(Crc32Reader::new(zstd_reader, crc32, ae2_encrypted))
}
#[cfg(feature = "lzma")]
CompressionMethod::Lzma => {
let reader = LzmaReader::new(reader);
ZipFileReader::Lzma(Crc32Reader::new(reader, crc32, ae2_encrypted))
}
_ => panic!("Compression method not supported"),
}
}
Expand Down
47 changes: 47 additions & 0 deletions src/read/lzma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::collections::VecDeque;
use std::io::{copy, Error, Read, Result, Write};
use lzma_rs::decompress::{Options, Stream, UnpackedSize};

const COMPRESSED_BYTES_TO_BUFFER: usize = 4096;

const OPTIONS: Options = Options {
unpacked_size: UnpackedSize::ReadFromHeader,
memlimit: None,
allow_incomplete: true,
};

#[derive(Debug)]
pub struct LzmaReader<R> {
compressed_reader: R,
stream: Stream<VecDeque<u8>>
}

impl <R: Read> LzmaReader<R> {
pub fn new(inner: R) -> Self {
LzmaReader {
compressed_reader: inner,
stream: Stream::new_with_options(&OPTIONS, VecDeque::new())
}
}

pub fn finish(mut self) -> Result<VecDeque<u8>> {
copy(&mut self.compressed_reader, &mut self.stream)?;
self.stream.finish().map_err(Error::from)
}
}

impl <R: Read> Read for LzmaReader<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let mut bytes_read = self.stream.get_output_mut().unwrap().read(buf)?;
while bytes_read < buf.len() {
let mut next_compressed = [0u8; COMPRESSED_BYTES_TO_BUFFER];
let compressed_bytes_read = self.compressed_reader.read(&mut next_compressed)?;
if compressed_bytes_read == 0 {
break;
}
self.stream.write_all(&next_compressed[..compressed_bytes_read])?;
bytes_read += self.stream.get_output_mut().unwrap().read(&mut buf[bytes_read..])?;
}
Ok(bytes_read)
}
}
4 changes: 3 additions & 1 deletion src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ pub(crate) mod zip_writer {
pub(super) flush_on_finish_file: bool,
}
}
use crate::result::ZipError::InvalidArchive;
use crate::result::ZipError::{InvalidArchive, UnsupportedArchive};

Check failure on line 132 in src/write.rs

View workflow job for this annotation

GitHub Actions / Build and test (ubuntu-latest, stable)

unused import: `UnsupportedArchive`

Check failure on line 132 in src/write.rs

View workflow job for this annotation

GitHub Actions / Build and test (ubuntu-latest, nightly)

unused import: `UnsupportedArchive`

Check failure on line 132 in src/write.rs

View workflow job for this annotation

GitHub Actions / fuzz_write_with_no_features

unused import: `UnsupportedArchive`

Check failure on line 132 in src/write.rs

View workflow job for this annotation

GitHub Actions / fuzz_read_with_no_features

unused import: `UnsupportedArchive`
use crate::write::GenericZipWriter::{Closed, Storer};
use crate::zipcrypto::ZipCryptoKeys;
use crate::CompressionMethod::Stored;
Expand Down Expand Up @@ -1269,6 +1269,8 @@ impl<W: Write + Seek> GenericZipWriter<W> {
GenericZipWriter::Zstd(ZstdEncoder::new(bare, level as i32).unwrap())
}))
}
#[cfg(feature = "lzma")]
CompressionMethod::Lzma => Err(UnsupportedArchive("LZMA isn't supported for compression")),
CompressionMethod::Unsupported(..) => {
Err(ZipError::UnsupportedArchive("Unsupported compression"))
}
Expand Down

0 comments on commit 7f8311e

Please sign in to comment.