1use crate::core::{ErrorKind, XmlError, XmlResult};
8
9pub const DEFAULT_MAX_DOCUMENT_BYTES: usize = 10 * 1024 * 1024;
10pub const DEFAULT_MAX_TEXT_BYTES: usize = 1024 * 1024;
11pub const DEFAULT_MAX_DEPTH: usize = 128;
12pub const DEFAULT_MAX_NODES: usize = 100_000;
13pub const DEFAULT_MAX_QUERY_STEPS: usize = 100_000;
14pub const DEFAULT_MAX_TRANSFORM_EXPANSION: usize = 100_000;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct SecurityLimits {
19 max_document_bytes: usize,
20 max_text_bytes: usize,
21 max_depth: usize,
22 max_nodes: usize,
23 max_query_steps: usize,
24 max_transform_expansion: usize,
25}
26
27impl SecurityLimits {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn with_max_document_bytes(mut self, limit: usize) -> Self {
33 self.max_document_bytes = limit;
34 self
35 }
36
37 pub fn with_max_text_bytes(mut self, limit: usize) -> Self {
38 self.max_text_bytes = limit;
39 self
40 }
41
42 pub fn with_max_depth(mut self, limit: usize) -> Self {
43 self.max_depth = limit;
44 self
45 }
46
47 pub fn with_max_nodes(mut self, limit: usize) -> Self {
48 self.max_nodes = limit;
49 self
50 }
51
52 pub fn with_max_query_steps(mut self, limit: usize) -> Self {
53 self.max_query_steps = limit;
54 self
55 }
56
57 pub fn with_max_transform_expansion(mut self, limit: usize) -> Self {
58 self.max_transform_expansion = limit;
59 self
60 }
61
62 pub fn max_document_bytes(&self) -> usize {
63 self.max_document_bytes
64 }
65
66 pub fn max_text_bytes(&self) -> usize {
67 self.max_text_bytes
68 }
69
70 pub fn max_depth(&self) -> usize {
71 self.max_depth
72 }
73
74 pub fn max_nodes(&self) -> usize {
75 self.max_nodes
76 }
77
78 pub fn max_query_steps(&self) -> usize {
79 self.max_query_steps
80 }
81
82 pub fn max_transform_expansion(&self) -> usize {
83 self.max_transform_expansion
84 }
85
86 pub fn check_document_size(&self, bytes: usize) -> XmlResult<()> {
87 if bytes > self.max_document_bytes {
88 return Err(limit_error(format!(
89 "XML document exceeds maximum size of {} bytes",
90 self.max_document_bytes
91 )));
92 }
93 Ok(())
94 }
95
96 pub fn check_text_size(&self, bytes: usize) -> XmlResult<()> {
97 if bytes > self.max_text_bytes {
98 return Err(limit_error(format!(
99 "XML text exceeds maximum of {} bytes",
100 self.max_text_bytes
101 )));
102 }
103 Ok(())
104 }
105
106 pub fn check_depth(&self, depth: usize) -> XmlResult<()> {
107 if depth > self.max_depth {
108 return Err(limit_error(format!(
109 "XML depth exceeds maximum of {}",
110 self.max_depth
111 )));
112 }
113 Ok(())
114 }
115
116 pub fn check_nodes(&self, nodes: usize) -> XmlResult<()> {
117 if nodes > self.max_nodes {
118 return Err(limit_error(format!(
119 "XML node count exceeds maximum of {}",
120 self.max_nodes
121 )));
122 }
123 Ok(())
124 }
125
126 pub fn check_query_steps(&self, steps: usize) -> XmlResult<()> {
127 if steps > self.max_query_steps {
128 return Err(limit_error(format!(
129 "query step count exceeds maximum of {}",
130 self.max_query_steps
131 )));
132 }
133 Ok(())
134 }
135
136 pub fn check_transform_expansion(&self, expansions: usize) -> XmlResult<()> {
137 if expansions > self.max_transform_expansion {
138 return Err(limit_error(format!(
139 "transform expansion exceeds maximum of {}",
140 self.max_transform_expansion
141 )));
142 }
143 Ok(())
144 }
145}
146
147impl Default for SecurityLimits {
148 fn default() -> Self {
149 Self {
150 max_document_bytes: DEFAULT_MAX_DOCUMENT_BYTES,
151 max_text_bytes: DEFAULT_MAX_TEXT_BYTES,
152 max_depth: DEFAULT_MAX_DEPTH,
153 max_nodes: DEFAULT_MAX_NODES,
154 max_query_steps: DEFAULT_MAX_QUERY_STEPS,
155 max_transform_expansion: DEFAULT_MAX_TRANSFORM_EXPANSION,
156 }
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Default)]
162pub struct EntityPolicy {
163 allow_external_entities: bool,
164 allow_network: bool,
165 allow_filesystem: bool,
166}
167
168impl EntityPolicy {
169 pub fn secure() -> Self {
170 Self::default()
171 }
172
173 pub fn with_external_entities(mut self, allow: bool) -> Self {
174 self.allow_external_entities = allow;
175 self
176 }
177
178 pub fn with_network(mut self, allow: bool) -> Self {
179 self.allow_network = allow;
180 self
181 }
182
183 pub fn with_filesystem(mut self, allow: bool) -> Self {
184 self.allow_filesystem = allow;
185 self
186 }
187
188 pub fn external_entities_allowed(&self) -> bool {
189 self.allow_external_entities
190 }
191
192 pub fn network_allowed(&self) -> bool {
193 self.allow_network
194 }
195
196 pub fn filesystem_allowed(&self) -> bool {
197 self.allow_filesystem
198 }
199
200 pub fn reject_external_entity(&self, entity: &str) -> XmlResult<()> {
201 if self.allow_external_entities {
202 return Ok(());
203 }
204 Err(XmlError::new(
205 ErrorKind::Parse,
206 format!("entity reference `&{entity};` is disabled by default"),
207 ))
208 }
209
210 pub fn reject_doctype(&self) -> XmlResult<()> {
211 if self.allow_external_entities {
212 return Ok(());
213 }
214 Err(XmlError::new(
215 ErrorKind::Parse,
216 "DOCTYPE declarations are disabled by default",
217 ))
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Default)]
222pub struct ParserSecurityConfig {
223 limits: SecurityLimits,
224 entity_policy: EntityPolicy,
225}
226
227impl ParserSecurityConfig {
228 pub fn new() -> Self {
229 Self::default()
230 }
231
232 pub fn with_limits(mut self, limits: SecurityLimits) -> Self {
233 self.limits = limits;
234 self
235 }
236
237 pub fn with_entity_policy(mut self, policy: EntityPolicy) -> Self {
238 self.entity_policy = policy;
239 self
240 }
241
242 pub fn limits(&self) -> &SecurityLimits {
243 &self.limits
244 }
245
246 pub fn entity_policy(&self) -> &EntityPolicy {
247 &self.entity_policy
248 }
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Default)]
252pub struct QuerySecurityConfig {
253 limits: SecurityLimits,
254}
255
256impl QuerySecurityConfig {
257 pub fn new() -> Self {
258 Self::default()
259 }
260
261 pub fn with_limits(mut self, limits: SecurityLimits) -> Self {
262 self.limits = limits;
263 self
264 }
265
266 pub fn limits(&self) -> &SecurityLimits {
267 &self.limits
268 }
269
270 pub fn check_steps(&self, steps: usize) -> XmlResult<()> {
271 self.limits.check_query_steps(steps)
272 }
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Default)]
276pub struct TransformSecurityConfig {
277 limits: SecurityLimits,
278}
279
280impl TransformSecurityConfig {
281 pub fn new() -> Self {
282 Self::default()
283 }
284
285 pub fn with_limits(mut self, limits: SecurityLimits) -> Self {
286 self.limits = limits;
287 self
288 }
289
290 pub fn limits(&self) -> &SecurityLimits {
291 &self.limits
292 }
293
294 pub fn check_expansion(&self, expansions: usize) -> XmlResult<()> {
295 self.limits.check_transform_expansion(expansions)
296 }
297}
298
299fn limit_error(message: String) -> XmlError {
300 XmlError::new(ErrorKind::Parse, message)
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn security_defaults_are_safe() {
309 let parser = ParserSecurityConfig::default();
310
311 assert!(!parser.entity_policy().external_entities_allowed());
312 assert!(!parser.entity_policy().network_allowed());
313 assert!(!parser.entity_policy().filesystem_allowed());
314 assert_eq!(parser.limits().max_depth(), DEFAULT_MAX_DEPTH);
315 assert_eq!(
316 parser.limits().max_document_bytes(),
317 DEFAULT_MAX_DOCUMENT_BYTES
318 );
319 }
320
321 #[test]
322 fn security_limits_reject_depth_size_and_nodes() {
323 let limits = SecurityLimits::default()
324 .with_max_depth(1)
325 .with_max_document_bytes(4)
326 .with_max_text_bytes(2)
327 .with_max_nodes(1);
328
329 assert!(limits.check_depth(2).is_err());
330 assert!(limits.check_document_size(5).is_err());
331 assert!(limits.check_text_size(3).is_err());
332 assert!(limits.check_nodes(2).is_err());
333 }
334
335 #[test]
336 fn security_entity_policy_blocks_external_entities_by_default() {
337 let policy = EntityPolicy::default();
338
339 assert_eq!(
340 policy.reject_external_entity("xxe").unwrap_err().kind(),
341 &ErrorKind::Parse
342 );
343 assert_eq!(
344 policy.reject_doctype().unwrap_err().kind(),
345 &ErrorKind::Parse
346 );
347 }
348
349 #[test]
350 fn security_query_has_step_limit() {
351 let config = QuerySecurityConfig::default()
352 .with_limits(SecurityLimits::default().with_max_query_steps(1));
353
354 assert!(config.check_steps(2).is_err());
355 }
356
357 #[test]
358 fn security_transform_rejects_expansion_excess() {
359 let config = TransformSecurityConfig::default()
360 .with_limits(SecurityLimits::default().with_max_transform_expansion(1));
361
362 assert!(config.check_expansion(2).is_err());
363 }
364}