#[derive(Debug, Clone)]
pub struct ProcessingLimits {
pub max_file_bytes: u64,
pub max_stream_bytes: u64,
pub max_total_memory_bytes: u64,
pub max_object_depth: u32,
pub max_operator_count: u64,
pub max_image_pixels: u64,
pub max_xfa_nesting_depth: u32,
pub max_formcalc_depth: u32,
}
impl Default for ProcessingLimits {
fn default() -> Self {
Self {
max_file_bytes: 500 * 1024 * 1024, max_stream_bytes: 256 * 1024 * 1024, max_total_memory_bytes: 1024 * 1024 * 1024, max_object_depth: 100,
max_operator_count: 10_000_000,
max_image_pixels: 16384 * 16384, max_xfa_nesting_depth: 50,
max_formcalc_depth: 200,
}
}
}
impl ProcessingLimits {
pub fn new() -> Self {
Self::default()
}
pub fn wasm() -> Self {
Self {
max_file_bytes: 50 * 1024 * 1024, max_stream_bytes: 32 * 1024 * 1024, max_total_memory_bytes: 128 * 1024 * 1024, max_object_depth: 50,
max_operator_count: 1_000_000,
max_image_pixels: 8192 * 8192, max_xfa_nesting_depth: 30,
max_formcalc_depth: 100,
}
}
pub fn unlimited() -> Self {
Self {
max_file_bytes: u64::MAX,
max_stream_bytes: u64::MAX,
max_total_memory_bytes: u64::MAX,
max_object_depth: u32::MAX,
max_operator_count: u64::MAX,
max_image_pixels: u64::MAX,
max_xfa_nesting_depth: u32::MAX,
max_formcalc_depth: u32::MAX,
}
}
pub fn max_file_bytes(mut self, bytes: u64) -> Self {
self.max_file_bytes = bytes;
self
}
pub fn max_stream_bytes(mut self, bytes: u64) -> Self {
self.max_stream_bytes = bytes;
self
}
pub fn max_total_memory_bytes(mut self, bytes: u64) -> Self {
self.max_total_memory_bytes = bytes;
self
}
pub fn max_object_depth(mut self, depth: u32) -> Self {
self.max_object_depth = depth;
self
}
pub fn max_operator_count(mut self, count: u64) -> Self {
self.max_operator_count = count;
self
}
pub fn max_image_pixels(mut self, pixels: u64) -> Self {
self.max_image_pixels = pixels;
self
}
pub fn max_xfa_nesting_depth(mut self, depth: u32) -> Self {
self.max_xfa_nesting_depth = depth;
self
}
pub fn max_formcalc_depth(mut self, depth: u32) -> Self {
self.max_formcalc_depth = depth;
self
}
pub fn check_file_size(&self, bytes: u64) -> Result<(), LimitError> {
if bytes > self.max_file_bytes {
Err(LimitError::FileTooLarge {
actual_bytes: bytes,
limit_bytes: self.max_file_bytes,
})
} else {
Ok(())
}
}
pub fn check_stream_size(&self, bytes: u64) -> Result<(), LimitError> {
if bytes > self.max_stream_bytes {
Err(LimitError::StreamTooLarge {
actual_bytes: bytes,
limit_bytes: self.max_stream_bytes,
})
} else {
Ok(())
}
}
pub fn check_image_pixels(&self, width: u64, height: u64) -> Result<(), LimitError> {
let pixels = width.saturating_mul(height);
if pixels > self.max_image_pixels {
Err(LimitError::ImageTooLarge {
width,
height,
pixels,
limit_pixels: self.max_image_pixels,
})
} else {
Ok(())
}
}
pub fn check_object_depth(&self, depth: u32) -> Result<(), LimitError> {
if depth > self.max_object_depth {
Err(LimitError::ObjectDepthExceeded {
depth,
limit: self.max_object_depth,
})
} else {
Ok(())
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LimitError {
FileTooLarge {
actual_bytes: u64,
limit_bytes: u64,
},
StreamTooLarge {
actual_bytes: u64,
limit_bytes: u64,
},
ImageTooLarge {
width: u64,
height: u64,
pixels: u64,
limit_pixels: u64,
},
ObjectDepthExceeded {
depth: u32,
limit: u32,
},
TooManyOperators {
count: u64,
limit: u64,
},
XfaNestingTooDeep {
depth: u32,
limit: u32,
},
FormCalcRecursionTooDeep {
depth: u32,
limit: u32,
},
}
impl std::fmt::Display for LimitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FileTooLarge {
actual_bytes,
limit_bytes,
} => write!(
f,
"PDF file too large: {} MB (limit: {} MB)",
actual_bytes / 1024 / 1024,
limit_bytes / 1024 / 1024
),
Self::StreamTooLarge {
actual_bytes,
limit_bytes,
} => write!(
f,
"Decompressed stream too large: {} MB (limit: {} MB)",
actual_bytes / 1024 / 1024,
limit_bytes / 1024 / 1024
),
Self::ImageTooLarge {
width,
height,
pixels,
limit_pixels,
} => write!(
f,
"Image too large: {}×{} ({} MP, limit: {} MP)",
width,
height,
pixels / 1_000_000,
limit_pixels / 1_000_000
),
Self::ObjectDepthExceeded { depth, limit } => write!(
f,
"Object reference depth exceeded: {} (limit: {})",
depth, limit
),
Self::TooManyOperators { count, limit } => write!(
f,
"Content stream has too many operators: {} (limit: {})",
count, limit
),
Self::XfaNestingTooDeep { depth, limit } => write!(
f,
"XFA template nesting too deep: {} (limit: {})",
depth, limit
),
Self::FormCalcRecursionTooDeep { depth, limit } => write!(
f,
"FormCalc recursion too deep: {} (limit: {})",
depth, limit
),
}
}
}
impl std::error::Error for LimitError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_limits() {
let l = ProcessingLimits::default();
assert_eq!(l.max_file_bytes, 500 * 1024 * 1024);
assert_eq!(l.max_stream_bytes, 256 * 1024 * 1024);
assert_eq!(l.max_image_pixels, 16384 * 16384);
}
#[test]
fn test_wasm_limits_stricter_than_default() {
let wasm = ProcessingLimits::wasm();
let default = ProcessingLimits::default();
assert!(wasm.max_file_bytes < default.max_file_bytes);
assert!(wasm.max_stream_bytes < default.max_stream_bytes);
assert!(wasm.max_image_pixels < default.max_image_pixels);
}
#[test]
fn test_file_size_check() {
let l = ProcessingLimits::default();
assert!(l.check_file_size(100 * 1024 * 1024).is_ok()); assert!(l.check_file_size(500 * 1024 * 1024).is_ok()); assert!(l.check_file_size(501 * 1024 * 1024).is_err()); }
#[test]
fn test_image_pixel_check() {
let l = ProcessingLimits::default();
assert!(l.check_image_pixels(1920, 1080).is_ok());
assert!(l.check_image_pixels(16384, 16384).is_ok()); assert!(l.check_image_pixels(16385, 16384).is_err()); }
#[test]
fn test_stream_size_check() {
let l = ProcessingLimits::default();
assert!(l.check_stream_size(100 * 1024 * 1024).is_ok());
assert!(l.check_stream_size(256 * 1024 * 1024).is_ok()); assert!(l.check_stream_size(257 * 1024 * 1024).is_err()); }
#[test]
fn test_builder_pattern() {
let l = ProcessingLimits::default()
.max_file_bytes(10 * 1024 * 1024)
.max_stream_bytes(5 * 1024 * 1024);
assert_eq!(l.max_file_bytes, 10 * 1024 * 1024);
assert_eq!(l.max_stream_bytes, 5 * 1024 * 1024);
}
#[test]
fn test_limit_error_display() {
let err = LimitError::FileTooLarge {
actual_bytes: 600 * 1024 * 1024,
limit_bytes: 500 * 1024 * 1024,
};
let msg = err.to_string();
assert!(msg.contains("600 MB"));
assert!(msg.contains("500 MB"));
}
}