pub mod chunk;
pub mod deflate_scan;
pub mod parallel;
pub mod speculative;
#[inline]
pub fn functional_status(component: &str, check: &str, ok: bool, detail: &str) {
#[cfg(feature = "testmatrix")]
nornir_testmatrix::functional_status(component, check, ok, detail);
#[cfg(not(feature = "testmatrix"))]
{
let _ = (component, check, ok, detail);
}
}
pub use parallel::{
decompress_gz, decompress_gz_capped, decompress_gz_into, decompress_gz_stream, deflate_offset,
output_too_large, GzError, OUTPUT_TOO_LARGE,
};
pub const PARALLEL_THRESHOLD: usize = 4 * 1024 * 1024;
pub fn decompress_tar_gz_filter(data: &[u8], needle: &str) -> Result<Vec<(String, Vec<u8>)>, GzError> {
let decompressed = decompress_gz(data)?;
extract_tar_entries(&decompressed, needle, usize::MAX)
}
pub fn decompress_tar_gz_filter_capped(
data: &[u8],
needle: &str,
max_out: usize,
) -> Result<Vec<(String, Vec<u8>)>, GzError> {
let decompressed = decompress_gz_capped(data, max_out)?;
extract_tar_entries(&decompressed, needle, max_out)
}
fn extract_tar_entries(
tar_data: &[u8],
needle: &str,
max_entry: usize,
) -> Result<Vec<(String, Vec<u8>)>, GzError> {
use std::io::Read;
let mut archive = tar::Archive::new(tar_data);
let mut results = Vec::new();
let entries = archive.entries()
.map_err(|_| GzError("tar parse failed"))?;
for entry in entries {
let mut entry = entry.map_err(|_| GzError("tar entry read failed"))?;
let path = entry.path()
.map_err(|_| GzError("tar entry path failed"))?
.to_string_lossy()
.to_string();
if needle.is_empty() || path.contains(needle) {
let mut data = Vec::new();
let read = entry
.by_ref()
.take(max_entry as u64)
.read_to_end(&mut data)
.map_err(|_| GzError("tar entry data read failed"))?;
if read >= max_entry && max_entry != usize::MAX {
return Err(parallel::output_too_large());
}
results.push((path, data));
}
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_tar_gz(name: &str, body: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
{
let enc = flate2::write::GzEncoder::new(&mut out, flate2::Compression::fast());
let mut tar = tar::Builder::new(enc);
let mut h = tar::Header::new_gnu();
h.set_path(name).unwrap();
h.set_size(body.len() as u64);
h.set_mode(0o644);
h.set_cksum();
tar.append(&h, body).unwrap();
tar.finish().unwrap();
}
out
}
#[test]
fn tar_gz_filter_capped_small_roundtrips() {
let archive = make_tar_gz("package/package.json", br#"{"name":"x"}"#);
let entries =
decompress_tar_gz_filter_capped(&archive, "package.json", 1024 * 1024).unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0, "package/package.json");
assert_eq!(entries[0].1, br#"{"name":"x"}"#);
}
#[test]
fn tar_gz_filter_capped_outer_bomb_errors() {
let archive = make_tar_gz("package/big.bin", &vec![0u8; 256 * 1024]);
let err = decompress_tar_gz_filter_capped(&archive, "", 4096).unwrap_err();
assert_eq!(err.0, OUTPUT_TOO_LARGE);
}
#[test]
fn tar_gz_filter_capped_entry_cap_trips() {
let body = vec![0u8; 200 * 1024];
let archive = make_tar_gz("package/package.json", &body);
let err = decompress_tar_gz_filter_capped(&archive, "package.json", 64 * 1024)
.unwrap_err();
assert_eq!(err.0, OUTPUT_TOO_LARGE);
}
#[test]
fn legacy_uncapped_still_works() {
let archive = make_tar_gz("a/b.json", b"hello");
let entries = decompress_tar_gz_filter(&archive, "b.json").unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].1, b"hello");
}
}