From 2e9408ae4cad10a00c604a008bd4f8c0704f0ac7 Mon Sep 17 00:00:00 2001 From: oyvindln Date: Mon, 1 Nov 2021 18:56:41 +0100 Subject: [PATCH] feat(inflate): add option to ignore and not compute zlib checksum when decompressing Do this in a mostly backwards-compatible way, so it may be a bit clunky. Closes #102 --- miniz_oxide/src/inflate/core.rs | 32 +++++++++++++++++++++++++++++-- miniz_oxide/src/inflate/stream.rs | 25 ++++++++++++++++++++++-- miniz_oxide/src/lib.rs | 6 +++++- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/miniz_oxide/src/inflate/core.rs b/miniz_oxide/src/inflate/core.rs index b18dbddd..1e9a046a 100644 --- a/miniz_oxide/src/inflate/core.rs +++ b/miniz_oxide/src/inflate/core.rs @@ -109,8 +109,20 @@ pub mod inflate_flags { pub const TINFL_FLAG_HAS_MORE_INPUT: u32 = 2; /// The output buffer should not wrap around. pub const TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: u32 = 4; - /// Should we calculate the adler32 checksum of the output data? + /// Should we calculate the adler32 checksum of the output data even if we're not inflating a + /// zlib stream? + /// + /// NOTE: Enable/disabling this between calls to decompress will result in an incorect checksum. pub const TINFL_FLAG_COMPUTE_ADLER32: u32 = 8; + /// Should we ignore adler32 checksum even if we are inflating a zlib stream. + /// Overrides TINFL_FLAG_COMPUTE_ADLER32 if both are enabled. + /// + /// NOTE: This flag does not exist in miniz as it does not support this and is a + /// custom addition for miniz_oxide. + /// NOTE: Should not be changed from enabled to disabled after decompression has started, + /// this will result in checksum failure (outside the unlikely event where the checksum happens + /// to match anyway). + pub const TINFL_FLAG_IGNORE_ADLER32: u32 = 64; } use self::inflate_flags::*; @@ -174,6 +186,7 @@ impl DecompressorOxide { } /// Returns the adler32 checksum of the currently decompressed data. + /// Note: Will return Some(1) if decompressing zlib but ignoring adler32. #[inline] pub fn adler32(&self) -> Option { if self.state != State::Start && !self.state.is_failure() && self.z_header0 != 0 { @@ -182,6 +195,16 @@ impl DecompressorOxide { None } } + + /// Returns the adler32 that was read from the zlib header if it exists. + #[inline] + pub fn adler32_header(&self) -> Option { + if self.state != State::Start && self.state != State::BadZlibHeader && self.z_header0 != 0 { + Some(self.z_adler32) + } else { + None + } + } } impl Default for DecompressorOxide { @@ -1616,7 +1639,12 @@ pub fn decompress( // If this is a zlib stream, and update the adler32 checksum with the decompressed bytes if // requested. - let need_adler = flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32) != 0; + let need_adler = if (flags & TINFL_FLAG_IGNORE_ADLER32) == 0 { + flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32) != 0 + } else { + // If TINFL_FLAG_IGNORE_ADLER32 is enabled, ignore the checksum. + false + }; if need_adler && status as i32 >= 0 { let out_buf_pos = out_buf.position(); r.check_adler32 = update_adler32(r.check_adler32, &out_buf.get_ref()[out_pos..out_buf_pos]); diff --git a/miniz_oxide/src/inflate/stream.rs b/miniz_oxide/src/inflate/stream.rs index 4e0c2aca..9fad6561 100644 --- a/miniz_oxide/src/inflate/stream.rs +++ b/miniz_oxide/src/inflate/stream.rs @@ -179,8 +179,15 @@ pub fn inflate( return StreamResult::error(MZError::Stream); } - let mut decomp_flags = inflate_flags::TINFL_FLAG_COMPUTE_ADLER32; - if state.data_format == DataFormat::Zlib { + let mut decomp_flags = if state.data_format == DataFormat::Zlib { + inflate_flags::TINFL_FLAG_COMPUTE_ADLER32 + } else { + inflate_flags::TINFL_FLAG_IGNORE_ADLER32 + }; + + if (state.data_format == DataFormat::Zlib) + | (state.data_format == DataFormat::ZLibIgnoreChecksum) + { decomp_flags |= inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER; } @@ -375,5 +382,19 @@ mod test { assert_eq!(status, MZStatus::StreamEnd); assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]); assert_eq!(res.bytes_consumed, encoded.len()); + assert_eq!(state.decompressor().adler32(), Some(459605011)); + + // Test state when not computing adler. + state = InflateState::new_boxed(DataFormat::ZLibIgnoreChecksum); + out.iter_mut().map(|x| *x = 0).count(); + let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish); + let status = res.status.expect("Failed to decompress!"); + assert_eq!(status, MZStatus::StreamEnd); + assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]); + assert_eq!(res.bytes_consumed, encoded.len()); + // Not computed, so should be Some(1) + assert_eq!(state.decompressor().adler32(), Some(1)); + // Should still have the checksum read from the header file. + assert_eq!(state.decompressor().adler32_header(), Some(459605011)) } } diff --git a/miniz_oxide/src/lib.rs b/miniz_oxide/src/lib.rs index d459fe6a..1c329a47 100644 --- a/miniz_oxide/src/lib.rs +++ b/miniz_oxide/src/lib.rs @@ -105,9 +105,13 @@ pub enum MZError { /// How compressed data is wrapped. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] pub enum DataFormat { /// Wrapped using the [zlib](http://www.zlib.org/rfc-zlib.html) format. Zlib, + /// Zlib wrapped but ignore and don't compute the adler32 checksum. + /// Currently only used for inflate, behaves the same as Zlib for compression. + ZLibIgnoreChecksum, /// Raw DEFLATE. Raw, } @@ -123,7 +127,7 @@ impl DataFormat { pub(crate) fn to_window_bits(self) -> i32 { match self { - DataFormat::Zlib => shared::MZ_DEFAULT_WINDOW_BITS, + DataFormat::Zlib | DataFormat::ZLibIgnoreChecksum => shared::MZ_DEFAULT_WINDOW_BITS, DataFormat::Raw => -shared::MZ_DEFAULT_WINDOW_BITS, } }