use flate2::read::ZlibDecoder;
use std::collections::BTreeMap;
use std::io::Read;
use super::object::PdfObject;
pub fn decompress_stream(
data: &[u8],
dict: &BTreeMap<String, PdfObject>,
) -> Result<Vec<u8>, String> {
let filter = dict.get("Filter");
match filter {
Some(PdfObject::Name(name)) if name == "FlateDecode" => {
let decompressed = decompress_zlib(data)?;
if let Some(decode_parms) = dict.get("DecodeParms").and_then(|p| p.as_dict()) {
apply_predictor(&decompressed, decode_parms)
} else {
Ok(decompressed)
}
}
Some(PdfObject::Array(filters)) => {
let mut current_data = data.to_vec();
for f in filters {
if let Some(name) = f.as_name() {
if name == "FlateDecode" {
current_data = decompress_zlib(¤t_data)?;
}
}
}
Ok(current_data)
}
None => Ok(data.to_vec()), Some(_other) => {
Ok(data.to_vec())
}
}
}
pub const MAX_DECOMPRESSED_BYTES: u64 = 128 * 1024 * 1024;
pub fn decompress_zlib(data: &[u8]) -> Result<Vec<u8>, String> {
decompress_zlib_bounded(data, MAX_DECOMPRESSED_BYTES)
}
pub fn decompress_zlib_bounded(data: &[u8], max_bytes: u64) -> Result<Vec<u8>, String> {
let decoder = ZlibDecoder::new(data);
let mut decompressed = Vec::new();
decoder
.take(max_bytes)
.read_to_end(&mut decompressed)
.map_err(|e| format!("Zlib decompression error: {}", e))?;
Ok(decompressed)
}
fn apply_predictor(
data: &[u8],
parms: &BTreeMap<String, PdfObject>,
) -> Result<Vec<u8>, String> {
let predictor = parms
.get("Predictor")
.and_then(|v| v.as_i64())
.unwrap_or(1);
if predictor <= 1 {
return Ok(data.to_vec()); }
let columns = parms
.get("Columns")
.and_then(|v| v.as_i64())
.unwrap_or(1) as usize;
let colors = parms
.get("Colors")
.and_then(|v| v.as_i64())
.unwrap_or(1) as usize;
let bpc = parms
.get("BitsPerComponent")
.and_then(|v| v.as_i64())
.unwrap_or(8) as usize;
let bytes_per_pixel = ((colors * bpc) + 7) / 8;
let bytes_per_pixel = bytes_per_pixel.max(1);
let row_len = ((columns * colors * bpc) + 7) / 8;
if (10..=15).contains(&predictor) {
let stride = row_len + 1;
let num_rows = data.len() / stride;
let mut out = Vec::with_capacity(num_rows * row_len);
let mut prev_row = vec![0u8; row_len];
for row_idx in 0..num_rows {
let row_start = row_idx * stride;
let filter_type = data[row_start];
let raw_row = &data[row_start + 1..row_start + stride];
let mut current_row = vec![0u8; row_len];
match filter_type {
0 => {
current_row.copy_from_slice(raw_row);
}
1 => {
for i in 0..row_len {
let left = if i >= bytes_per_pixel { current_row[i - bytes_per_pixel] } else { 0 };
current_row[i] = raw_row[i].wrapping_add(left);
}
}
2 => {
for i in 0..row_len {
let up = prev_row[i];
current_row[i] = raw_row[i].wrapping_add(up);
}
}
3 => {
for i in 0..row_len {
let left = if i >= bytes_per_pixel { current_row[i - bytes_per_pixel] as u16 } else { 0 };
let up = prev_row[i] as u16;
let avg = ((left + up) / 2) as u8;
current_row[i] = raw_row[i].wrapping_add(avg);
}
}
4 => {
for i in 0..row_len {
let a = if i >= bytes_per_pixel { current_row[i - bytes_per_pixel] } else { 0 };
let b = prev_row[i];
let c = if i >= bytes_per_pixel { prev_row[i - bytes_per_pixel] } else { 0 };
let paeth = paeth_predictor(a, b, c);
current_row[i] = raw_row[i].wrapping_add(paeth);
}
}
_ => {
current_row.copy_from_slice(raw_row);
}
}
out.extend_from_slice(¤t_row);
prev_row = current_row;
}
Ok(out)
} else {
Ok(data.to_vec())
}
}
fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {
let p = (a as i32) + (b as i32) - (c as i32);
let pa = (p - a as i32).abs();
let pb = (p - b as i32).abs();
let pc = (p - c as i32).abs();
if pa <= pb && pa <= pc {
a
} else if pb <= pc {
b
} else {
c
}
}
#[cfg(test)]
mod tests {
use super::*;
use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::io::Write;
#[test]
fn test_zlib_roundtrip() {
let original = b"Hello Pure Rust PDF Parser! Spatial RAG without MuPDF!";
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(original).unwrap();
let compressed = encoder.finish().unwrap();
let mut dict = BTreeMap::new();
dict.insert("Filter".into(), PdfObject::Name("FlateDecode".into()));
let decompressed = decompress_stream(&compressed, &dict).unwrap();
assert_eq!(decompressed, original);
}
#[test]
fn test_zlib_decompression_bomb_capped() {
let uncompressed_size = 2 * 1024 * 1024;
let original = vec![0u8; uncompressed_size];
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&original).unwrap();
let compressed = encoder.finish().unwrap();
let cap_64kb = 64 * 1024;
let decompressed = decompress_zlib_bounded(&compressed, cap_64kb as u64).unwrap();
assert_eq!(decompressed.len(), cap_64kb);
let decompressed_full = decompress_zlib(&compressed).unwrap();
assert_eq!(decompressed_full.len(), uncompressed_size);
assert!((decompressed_full.len() as u64) <= MAX_DECOMPRESSED_BYTES);
}
}