#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic,
clippy::shadow_reuse,
clippy::shadow_unrelated
)]
use super::crc32::crc32;
use super::{
DecompressError, decompress_gzip, decompress_gzip_into, decompress_http_deflate, decompress_http_deflate_into,
decompress_raw_deflate, decompress_raw_deflate_into,
};
use alloc::vec::Vec;
fn long_text() -> Vec<u8> {
let unit = b"The quick brown fox jumps over the lazy dog. ";
let mut v = Vec::new();
while v.len() < 1800 {
v.extend_from_slice(unit);
}
v.truncate(1800);
v
}
const GZIP_EMPTY: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
const GZIP_A: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4b, 0x04, 0x00, 0x43, 0xbe, 0xb7, 0xe8, 0x01, 0x00,
0x00, 0x00,
];
const GZIP_HI: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcb, 0xc8, 0x04, 0x00, 0xac, 0x2a, 0x93, 0xd8, 0x02,
0x00, 0x00, 0x00,
];
const GZIP_HELLO_WORLD: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, 0x2f,
0xca, 0x49, 0x01, 0x00, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00,
];
const GZIP_AAAA: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4b, 0x4c, 0x84, 0x01, 0x00, 0xf0, 0xcd, 0x11, 0x4c,
0x0a, 0x00, 0x00, 0x00,
];
const GZIP_LONG: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x0b, 0xc9, 0x48, 0x55, 0x28, 0x2c, 0xcd, 0x4c, 0xce,
0x56, 0x48, 0x2a, 0xca, 0x2f, 0xcf, 0x53, 0x48, 0xcb, 0xaf, 0x50, 0xc8, 0x2a, 0xcd, 0x2d, 0x28, 0x56, 0xc8, 0x2f,
0x4b, 0x2d, 0x52, 0x28, 0x01, 0x4a, 0xe7, 0x24, 0x56, 0x55, 0x2a, 0xa4, 0xe4, 0xa7, 0xeb, 0x29, 0x84, 0x8c, 0x2a,
0x1e, 0x55, 0x3c, 0xaa, 0x78, 0x54, 0xf1, 0xa8, 0xe2, 0x51, 0xc5, 0xc3, 0x4b, 0x31, 0x00, 0xe6, 0xc3, 0x95, 0x64,
0x08, 0x07, 0x00, 0x00,
];
const GZIP_FNAME: &[u8] = &[
0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00,
0xcb, 0x4b, 0xcc, 0x4d, 0x05, 0x00, 0x06, 0x7e, 0x23, 0x5e, 0x04, 0x00, 0x00, 0x00,
];
const GZIP_FCOMMENT: &[u8] = &[
0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x4b, 0xce, 0x2d,
0x01, 0x00, 0xa4, 0x0c, 0xeb, 0x32, 0x03, 0x00, 0x00, 0x00,
];
const GZIP_FEXTRA: &[u8] = &[
0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x04, 0x00, 0x41, 0x50, 0x00, 0x00, 0x4b, 0xad, 0x00,
0x00, 0x13, 0x74, 0x8d, 0x07, 0x02, 0x00, 0x00, 0x00,
];
const GZIP_FHCRC: &[u8] = &[
0x1f, 0x8b, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x12, 0xab, 0xcb, 0x48, 0x06, 0x00, 0xb2, 0xc3, 0x46,
0x38, 0x02, 0x00, 0x00, 0x00,
];
const GZIP_ALLFLAGS: &[u8] = &[
0x1f, 0x8b, 0x08, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x04, 0x00, 0x58, 0x59, 0x00, 0x00, 0x6e, 0x00, 0x63,
0x00, 0x5d, 0x56, 0x4b, 0xcc, 0xc9, 0x01, 0x00, 0xdd, 0x71, 0x18, 0x3b, 0x03, 0x00, 0x00, 0x00,
];
const GZIP_FIXED: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x4b, 0xcb, 0xac, 0x48, 0x4d, 0x51, 0xc8, 0x28, 0x4d,
0x4b, 0xcb, 0x4d, 0xcc, 0x53, 0x28, 0x49, 0x2d, 0x2e, 0x51, 0x48, 0x49, 0x2c, 0x49, 0x54, 0x54, 0x04, 0x00, 0x63,
0x55, 0xe8, 0xd9, 0x19, 0x00, 0x00, 0x00,
];
const GZIP_DYNAMIC: &[u8] = &[
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4b, 0xa9, 0xcc, 0x4b, 0xcc, 0xcd, 0x4c, 0x56, 0xc8,
0x28, 0x4d, 0x4b, 0xcb, 0x4d, 0xcc, 0x53, 0x28, 0x48, 0xac, 0xcc, 0xc9, 0x4f, 0x4c, 0x51, 0xa8, 0xa8, 0xac, 0x02,
0x00, 0xbf, 0xfe, 0x0e, 0x9e, 0x1b, 0x00, 0x00, 0x00,
];
const ZLIB_HI: &[u8] = &[0x78, 0xda, 0xcb, 0xc8, 0x04, 0x00, 0x01, 0x3b, 0x00, 0xd2];
const ZLIB_HELLO: &[u8] = &[
0x78, 0xda, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00, 0x06, 0x2c, 0x02, 0x15,
];
const ZLIB_LONG: &[u8] = &[
0x78, 0xda, 0x0b, 0xc9, 0x48, 0x55, 0x28, 0x2c, 0xcd, 0x4c, 0xce, 0x56, 0x48, 0x2a, 0xca, 0x2f, 0xcf, 0x53, 0x48,
0xcb, 0xaf, 0x50, 0xc8, 0x2a, 0xcd, 0x2d, 0x28, 0x56, 0xc8, 0x2f, 0x4b, 0x2d, 0x52, 0x28, 0x01, 0x4a, 0xe7, 0x24,
0x56, 0x55, 0x2a, 0xa4, 0xe4, 0xa7, 0xeb, 0x29, 0x84, 0x8c, 0x2a, 0x1e, 0x55, 0x3c, 0xaa, 0x78, 0x54, 0xf1, 0xa8,
0xe2, 0x51, 0xc5, 0xc3, 0x4b, 0x31, 0x00, 0x88, 0x0a, 0x86, 0x37,
];
const RAW_HI: &[u8] = &[0xcb, 0xc8, 0x04, 0x00];
const RAW_HELLO_WORLD: &[u8] = &[
0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0x01, 0x00,
];
const RAW_FIXED: &[u8] = &[
0x4b, 0xcb, 0xac, 0x48, 0x4d, 0x51, 0x28, 0x4a, 0x2c, 0x07, 0x00,
];
const RAW_LONG: &[u8] = &[
0x0b, 0xc9, 0x48, 0x55, 0x28, 0x2c, 0xcd, 0x4c, 0xce, 0x56, 0x48, 0x2a, 0xca, 0x2f, 0xcf, 0x53, 0x48, 0xcb, 0xaf,
0x50, 0xc8, 0x2a, 0xcd, 0x2d, 0x28, 0x56, 0xc8, 0x2f, 0x4b, 0x2d, 0x52, 0x28, 0x01, 0x4a, 0xe7, 0x24, 0x56, 0x55,
0x2a, 0xa4, 0xe4, 0xa7, 0xeb, 0x29, 0x84, 0x8c, 0x2a, 0x1e, 0x55, 0x3c, 0xaa, 0x78, 0x54, 0xf1, 0xa8, 0xe2, 0x51,
0xc5, 0xc3, 0x4b, 0x31, 0x00,
];
const RAW_STORED: &[u8] = &[0x01, 0x04, 0x00, 0xfb, 0xff, 0x41, 0x42, 0x43, 0x44];
const RAW_MULTI_STORED: &[u8] = &[
0x00, 0x05, 0x00, 0xfa, 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x01, 0x05, 0x00, 0xfa, 0xff, 0x57, 0x6f, 0x72, 0x6c,
0x64,
];
#[test]
fn gzip_empty_and_tiny() {
assert_eq!(decompress_gzip(GZIP_EMPTY, 64).unwrap(), b"");
assert_eq!(decompress_gzip(GZIP_A, 64).unwrap(), b"a");
assert_eq!(decompress_gzip(GZIP_HI, 64).unwrap(), b"hi");
assert_eq!(decompress_gzip(GZIP_HELLO_WORLD, 64).unwrap(), b"hello world");
assert_eq!(decompress_gzip(GZIP_AAAA, 64).unwrap(), b"aaaaaaaaaa");
}
#[test]
fn decompress_into_reuses_output_capacity() {
let mut out = Vec::with_capacity(256);
decompress_gzip_into(GZIP_HELLO_WORLD, 64, &mut out).unwrap();
assert_eq!(out.as_slice(), b"hello world");
let cap = out.capacity();
assert!(cap >= 11);
decompress_gzip_into(GZIP_HI, 64, &mut out).unwrap();
assert_eq!(out.as_slice(), b"hi");
assert_eq!(out.capacity(), cap, "second inflate must keep pooled capacity");
decompress_raw_deflate_into(RAW_HI, 64, &mut out).unwrap();
assert_eq!(out.as_slice(), b"hi");
assert_eq!(out.capacity(), cap);
decompress_http_deflate_into(ZLIB_HI, 64, &mut out).unwrap();
assert_eq!(out.as_slice(), b"hi");
assert_eq!(out.capacity(), cap);
}
#[test]
fn gzip_optional_header_fields() {
assert_eq!(decompress_gzip(GZIP_FNAME, 64).unwrap(), b"name");
assert_eq!(decompress_gzip(GZIP_FCOMMENT, 64).unwrap(), b"cmt");
assert_eq!(decompress_gzip(GZIP_FEXTRA, 64).unwrap(), b"ex");
assert_eq!(decompress_gzip(GZIP_FHCRC, 64).unwrap(), b"hc");
assert_eq!(decompress_gzip(GZIP_ALLFLAGS, 64).unwrap(), b"all");
}
#[test]
fn gzip_fixed_and_dynamic_huffman() {
assert_eq!(decompress_gzip(GZIP_FIXED, 64).unwrap(), b"fixed huffman test data!!");
assert_eq!(
decompress_gzip(GZIP_DYNAMIC, 64).unwrap(),
b"dynamic huffman payload xyz"
);
}
#[test]
fn raw_stored_and_multi_block() {
assert_eq!(decompress_raw_deflate(RAW_STORED, 64).unwrap(), b"ABCD");
assert_eq!(decompress_raw_deflate(RAW_MULTI_STORED, 64).unwrap(), b"HelloWorld");
}
#[test]
fn raw_fixed_huffman() {
assert_eq!(decompress_raw_deflate(RAW_FIXED, 64).unwrap(), b"fixed raw");
}
#[test]
fn zlib_and_raw_deflate() {
assert_eq!(decompress_raw_deflate(RAW_HI, 64).unwrap(), b"hi");
assert_eq!(decompress_raw_deflate(RAW_HELLO_WORLD, 64).unwrap(), b"hello world");
assert_eq!(decompress_http_deflate(ZLIB_HI, 64).unwrap(), b"hi");
assert_eq!(decompress_http_deflate(ZLIB_HELLO, 64).unwrap(), b"hello");
assert_eq!(decompress_http_deflate(RAW_HI, 64).unwrap(), b"hi");
}
#[test]
fn long_corpus_gzip_zlib_raw() {
let expect = long_text();
assert_eq!(decompress_gzip(GZIP_LONG, 4096).unwrap(), expect);
assert_eq!(decompress_http_deflate(ZLIB_LONG, 4096).unwrap(), expect);
assert_eq!(decompress_raw_deflate(RAW_LONG, 4096).unwrap(), expect);
}
#[test]
fn invalid_gzip_magic_and_cm() {
let mut bad = GZIP_HI.to_vec();
if let Some(b) = bad.get_mut(0) {
*b = 0x00;
}
assert_eq!(decompress_gzip(&bad, 64), Err(DecompressError::InvalidInput));
let mut bad_cm = GZIP_HI.to_vec();
if let Some(b) = bad_cm.get_mut(2) {
*b = 0x00;
}
assert_eq!(decompress_gzip(&bad_cm, 64), Err(DecompressError::InvalidInput));
}
#[test]
fn invalid_reserved_flg() {
let mut bad = GZIP_HI.to_vec();
if let Some(b) = bad.get_mut(3) {
*b |= 0x20;
}
assert_eq!(decompress_gzip(&bad, 64), Err(DecompressError::InvalidInput));
}
#[test]
fn truncated_and_bad_crc_isize() {
assert_eq!(decompress_gzip(&GZIP_HI[..10], 64), Err(DecompressError::InvalidInput));
let mut bad_crc = GZIP_HI.to_vec();
let n = bad_crc.len();
if let Some(b) = bad_crc.get_mut(n.saturating_sub(8)) {
*b ^= 0xff;
}
assert_eq!(decompress_gzip(&bad_crc, 64), Err(DecompressError::InvalidInput));
let mut bad_isize = GZIP_HI.to_vec();
let n = bad_isize.len();
if let Some(b) = bad_isize.get_mut(n.saturating_sub(1)) {
*b ^= 0xff;
}
assert_eq!(decompress_gzip(&bad_isize, 64), Err(DecompressError::InvalidInput));
}
#[test]
fn distance_too_far() {
let bad: &[u8] = &[0x05, 0x02];
assert_eq!(decompress_raw_deflate(bad, 64), Err(DecompressError::InvalidInput));
}
#[test]
fn limit_exceeded() {
assert_eq!(decompress_gzip(GZIP_HI, 1), Err(DecompressError::LimitExceeded));
assert_eq!(
decompress_raw_deflate(RAW_STORED, 2),
Err(DecompressError::LimitExceeded)
);
}
#[test]
fn http_deflate_zlib_path_and_raw_fallback() {
assert_eq!(decompress_http_deflate(ZLIB_HI, 64).unwrap(), b"hi");
assert_eq!(decompress_http_deflate(RAW_HELLO_WORLD, 64).unwrap(), b"hello world");
}
#[test]
fn fixture_gzip_roundtrips() {
let cases: &[(&str, &[u8], &[u8])] = &[
(
"empty",
include_bytes!("fixtures/gzip_empty.bin"),
include_bytes!("fixtures/plain_empty.bin"),
),
(
"a",
include_bytes!("fixtures/gzip_a.bin"),
include_bytes!("fixtures/plain_a.bin"),
),
(
"hi",
include_bytes!("fixtures/gzip_hi.bin"),
include_bytes!("fixtures/plain_hi.bin"),
),
(
"hello",
include_bytes!("fixtures/gzip_hello.bin"),
include_bytes!("fixtures/plain_hello.bin"),
),
(
"zeros",
include_bytes!("fixtures/gzip_zeros.bin"),
include_bytes!("fixtures/plain_zeros.bin"),
),
(
"repeat",
include_bytes!("fixtures/gzip_repeat.bin"),
include_bytes!("fixtures/plain_repeat.bin"),
),
(
"corpus",
include_bytes!("fixtures/gzip_corpus.bin"),
include_bytes!("fixtures/plain_corpus.bin"),
),
(
"stored",
include_bytes!("fixtures/gzip_stored.bin"),
include_bytes!("fixtures/plain_stored.bin"),
),
(
"fname",
include_bytes!("fixtures/gzip_fname.bin"),
include_bytes!("fixtures/plain_fname.bin"),
),
(
"comment",
include_bytes!("fixtures/gzip_comment.bin"),
include_bytes!("fixtures/plain_comment.bin"),
),
(
"extra",
include_bytes!("fixtures/gzip_extra.bin"),
include_bytes!("fixtures/plain_extra.bin"),
),
(
"fhcrc",
include_bytes!("fixtures/gzip_fhcrc.bin"),
include_bytes!("fixtures/plain_fhcrc.bin"),
),
(
"all_opts",
include_bytes!("fixtures/gzip_all_opts.bin"),
include_bytes!("fixtures/plain_all_opts.bin"),
),
];
for (name, gz, plain) in cases {
let out = decompress_gzip(gz, plain.len().saturating_add(64)).unwrap_or_else(|e| {
panic!("gzip fixture {name} failed: {e:?}");
});
assert_eq!(out.as_slice(), *plain, "gzip fixture {name}");
}
}
#[test]
fn fixture_zlib_and_raw_corpus() {
let plain = include_bytes!("fixtures/plain_corpus2.bin");
assert_eq!(
decompress_http_deflate(include_bytes!("fixtures/zlib_corpus.bin"), 4096).unwrap(),
plain
);
assert_eq!(
decompress_raw_deflate(include_bytes!("fixtures/raw_corpus.bin"), 4096).unwrap(),
plain
);
assert_eq!(
decompress_http_deflate(include_bytes!("fixtures/zlib_hi.bin"), 64).unwrap(),
b"hi"
);
assert_eq!(
decompress_raw_deflate(include_bytes!("fixtures/raw_hi.bin"), 64).unwrap(),
b"hi"
);
}
#[test]
fn crc32_rfc1952_appendix_vectors() {
assert_eq!(crc32(b""), 0);
assert_eq!(crc32(b"123456789"), 0xcbf4_3926);
}
#[test]
fn zlib_rejects_fdict() {
let bad = [0x78u8, 0x3f, 0x00, 0x00, 0x00, 0x00];
assert_eq!(decompress_http_deflate(&bad, 64), Err(DecompressError::InvalidInput));
}
#[test]
fn gzip_rejects_truncated_optional_fields() {
let bad = [
0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, b'a', b'b',
];
assert_eq!(decompress_gzip(&bad, 64), Err(DecompressError::InvalidInput));
}
fn stored_block(
payload: &[u8],
final_block: bool,
) -> Vec<u8> {
assert!(u16::try_from(payload.len()).is_ok());
let mut out = Vec::with_capacity(5 + payload.len());
out.push(u8::from(final_block));
let len = u16::try_from(payload.len()).unwrap_or(0);
let nlen = !len;
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(&nlen.to_le_bytes());
out.extend_from_slice(payload);
out
}
#[test]
fn decompression_bomb_stored_block_hits_limit() {
let payload = alloc::vec![0u8; 4096];
let raw = stored_block(&payload, true);
assert_eq!(decompress_raw_deflate(&raw, 64), Err(DecompressError::LimitExceeded));
assert_eq!(decompress_raw_deflate(&raw, 4096).unwrap().len(), 4096);
}
#[test]
fn property_stored_blocks_roundtrip() {
use proptest::prelude::*;
use proptest::test_runner::Config;
let mut config = Config::with_cases(if cfg!(miri) {
8
} else {
256
});
config.failure_persistence = None;
proptest::proptest!(config, |(payload in prop::collection::vec(any::<u8>(), 0..200))| {
let raw = stored_block(&payload, true);
let out = decompress_raw_deflate(&raw, payload.len().saturating_add(1)).expect("inflate stored");
prop_assert_eq!(out.as_slice(), payload.as_slice());
});
}
#[test]
fn property_multi_stored_blocks_roundtrip() {
use proptest::prelude::*;
use proptest::test_runner::Config;
let mut config = Config::with_cases(if cfg!(miri) {
8
} else {
256
});
config.failure_persistence = None;
proptest::proptest!(config, |(a in prop::collection::vec(any::<u8>(), 0..80), b in prop::collection::vec(any::<u8>(), 0..80))| {
let mut raw = stored_block(&a, false);
raw.extend_from_slice(&stored_block(&b, true));
let mut expect = a;
expect.extend_from_slice(&b);
let out = decompress_raw_deflate(&raw, expect.len().saturating_add(1)).expect("inflate multi");
prop_assert_eq!(out, expect);
});
}