Skip to main content

easypdf_core/io/
resource_limits.rs

1//! PDF 操作的资源限制。
2
3/// PDF 读取与转换过程的资源上限。
4///
5/// # Examples
6///
7/// ```
8/// use easypdf_core::ResourceLimits;
9///
10/// let limits = ResourceLimits::new()
11///     .with_max_input_bytes(100 * 1024 * 1024)
12///     .with_max_decompressed_size(512 * 1024 * 1024);
13/// assert_eq!(limits.max_input_bytes(), 100 * 1024 * 1024);
14/// ```
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16#[allow(clippy::struct_field_names)]
17pub struct ResourceLimits {
18    max_input_bytes: u64,
19    max_pages: usize,
20    max_extracted_text_bytes: usize,
21    max_decompressed_size: u64,
22    max_compression_ratio: u32,
23    max_element_count: usize,
24}
25
26impl ResourceLimits {
27    /// 创建默认资源限制。
28    ///
29    /// 默认值适用于一般用途的 PDF 处理。对于不可信输入,
30    /// 考虑使用 [`Self::strict`];对于受信任的内部文档,
31    /// 考虑使用 [`Self::permissive`]。
32    #[must_use]
33    pub const fn new() -> Self {
34        Self {
35            max_input_bytes: 256 * 1024 * 1024,
36            max_pages: 10_000,
37            max_extracted_text_bytes: 128 * 1024 * 1024,
38            max_decompressed_size: 2 * 1024 * 1024 * 1024,
39            max_compression_ratio: 100,
40            max_element_count: 5_000_000,
41        }
42    }
43
44    /// 创建严格资源限制,适用于处理不可信输入。
45    ///
46    /// 所有限制为默认值的约 1/4,能更早拒绝恶意文件。
47    #[must_use]
48    pub const fn strict() -> Self {
49        Self {
50            max_input_bytes: 64 * 1024 * 1024,
51            max_pages: 2_500,
52            max_extracted_text_bytes: 32 * 1024 * 1024,
53            max_decompressed_size: 512 * 1024 * 1024,
54            max_compression_ratio: 50,
55            max_element_count: 1_000_000,
56        }
57    }
58
59    /// 创建宽松资源限制,适用于受信任的大型文档。
60    ///
61    /// 所有限制为默认值的约 4 倍。
62    #[must_use]
63    pub const fn permissive() -> Self {
64        Self {
65            max_input_bytes: 1024 * 1024 * 1024,
66            max_pages: 100_000,
67            max_extracted_text_bytes: 512 * 1024 * 1024,
68            max_decompressed_size: 8 * 1024 * 1024 * 1024,
69            max_compression_ratio: 400,
70            max_element_count: 20_000_000,
71        }
72    }
73
74    // --- Builder methods ---
75
76    /// 设置最大输入字节数。
77    #[must_use]
78    pub const fn with_max_input_bytes(mut self, value: u64) -> Self {
79        self.max_input_bytes = value;
80        self
81    }
82
83    /// 设置最大页数。
84    #[must_use]
85    pub const fn with_max_pages(mut self, value: usize) -> Self {
86        self.max_pages = value;
87        self
88    }
89
90    /// 设置最大提取文本字节数。
91    #[must_use]
92    pub const fn with_max_extracted_text_bytes(mut self, value: usize) -> Self {
93        self.max_extracted_text_bytes = value;
94        self
95    }
96
97    /// 设置解压后最大字节数(防解压炸弹)。
98    #[must_use]
99    pub const fn with_max_decompressed_size(mut self, value: u64) -> Self {
100        self.max_decompressed_size = value;
101        self
102    }
103
104    /// 设置最大压缩比(解压后/压缩前)。
105    #[must_use]
106    pub const fn with_max_compression_ratio(mut self, value: u32) -> Self {
107        self.max_compression_ratio = value;
108        self
109    }
110
111    /// 设置最大 PDF 对象/元素数量。
112    #[must_use]
113    pub const fn with_max_element_count(mut self, value: usize) -> Self {
114        self.max_element_count = value;
115        self
116    }
117
118    // --- Getters ---
119
120    /// 返回最大输入字节数。
121    #[must_use]
122    pub const fn max_input_bytes(self) -> u64 {
123        self.max_input_bytes
124    }
125
126    /// 返回最大页数。
127    #[must_use]
128    pub const fn max_pages(self) -> usize {
129        self.max_pages
130    }
131
132    /// 返回最大提取文本字节数。
133    #[must_use]
134    pub const fn max_extracted_text_bytes(self) -> usize {
135        self.max_extracted_text_bytes
136    }
137
138    /// 返回解压后最大字节数。
139    #[must_use]
140    pub const fn max_decompressed_size(self) -> u64 {
141        self.max_decompressed_size
142    }
143
144    /// 返回最大压缩比。
145    #[must_use]
146    pub const fn max_compression_ratio(self) -> u32 {
147        self.max_compression_ratio
148    }
149
150    /// 返回最大 PDF 对象/元素数量。
151    #[must_use]
152    pub const fn max_element_count(self) -> usize {
153        self.max_element_count
154    }
155}
156
157impl Default for ResourceLimits {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn default_limits_are_sensible() {
169        let limits = ResourceLimits::default();
170        assert_eq!(limits.max_input_bytes(), 256 * 1024 * 1024);
171        assert_eq!(limits.max_pages(), 10_000);
172        assert_eq!(limits.max_decompressed_size(), 2 * 1024 * 1024 * 1024);
173        assert_eq!(limits.max_compression_ratio(), 100);
174        assert_eq!(limits.max_element_count(), 5_000_000);
175    }
176
177    #[test]
178    fn strict_is_more_restrictive() {
179        let default = ResourceLimits::default();
180        let strict = ResourceLimits::strict();
181        assert!(strict.max_input_bytes() < default.max_input_bytes());
182        assert!(strict.max_pages() < default.max_pages());
183        assert!(strict.max_decompressed_size() < default.max_decompressed_size());
184        assert!(strict.max_compression_ratio() < default.max_compression_ratio());
185        assert!(strict.max_element_count() < default.max_element_count());
186    }
187
188    #[test]
189    fn permissive_is_more_relaxed() {
190        let default = ResourceLimits::default();
191        let permissive = ResourceLimits::permissive();
192        assert!(permissive.max_input_bytes() > default.max_input_bytes());
193        assert!(permissive.max_pages() > default.max_pages());
194        assert!(permissive.max_decompressed_size() > default.max_decompressed_size());
195        assert!(permissive.max_compression_ratio() > default.max_compression_ratio());
196        assert!(permissive.max_element_count() > default.max_element_count());
197    }
198
199    #[test]
200    fn builder_overrides_all_fields() {
201        let limits = ResourceLimits::new()
202            .with_max_input_bytes(1024)
203            .with_max_pages(5)
204            .with_max_extracted_text_bytes(2048)
205            .with_max_decompressed_size(4096)
206            .with_max_compression_ratio(10)
207            .with_max_element_count(100);
208        assert_eq!(limits.max_input_bytes(), 1024);
209        assert_eq!(limits.max_pages(), 5);
210        assert_eq!(limits.max_extracted_text_bytes(), 2048);
211        assert_eq!(limits.max_decompressed_size(), 4096);
212        assert_eq!(limits.max_compression_ratio(), 10);
213        assert_eq!(limits.max_element_count(), 100);
214    }
215}