1use std::collections::BTreeMap;
2
3use quick_xml::Reader;
4use quick_xml::events::{BytesStart, Event};
5
6use crate::PptxError;
7
8#[derive(Clone, Debug)]
9pub struct ParseLimits {
10 pub max_xml_bytes: usize,
11 pub max_xml_events: usize,
12 pub max_xml_text_bytes: usize,
13 pub max_xml_depth: usize,
14 pub max_attributes_per_element: usize,
15 pub max_attribute_bytes: usize,
16 pub max_relationships: usize,
17 pub max_shapes: usize,
18 pub max_paragraphs: usize,
19 pub max_runs: usize,
20}
21
22impl Default for ParseLimits {
23 fn default() -> Self {
24 Self {
25 max_xml_bytes: 128 * 1024 * 1024,
26 max_xml_events: 4_000_000,
27 max_xml_text_bytes: 128 * 1024 * 1024,
28 max_xml_depth: 256,
29 max_attributes_per_element: 1_024,
30 max_attribute_bytes: 4 * 1024 * 1024,
31 max_relationships: 250_000,
32 max_shapes: 100_000,
33 max_paragraphs: 500_000,
34 max_runs: 2_000_000,
35 }
36 }
37}
38
39#[derive(Debug)]
40pub(crate) struct ParseBudget<'a> {
41 limits: &'a ParseLimits,
42 xml_bytes: usize,
43 xml_events: usize,
44 xml_text_bytes: usize,
45 relationships: usize,
46 shapes: usize,
47 paragraphs: usize,
48 runs: usize,
49}
50
51impl<'a> ParseBudget<'a> {
52 pub fn new(limits: &'a ParseLimits) -> Self {
53 Self {
54 limits,
55 xml_bytes: 0,
56 xml_events: 0,
57 xml_text_bytes: 0,
58 relationships: 0,
59 shapes: 0,
60 paragraphs: 0,
61 runs: 0,
62 }
63 }
64
65 pub fn xml_events_spent(&self) -> usize {
67 self.xml_events
68 }
69
70 pub fn charge_relationship(&mut self, part: &str) -> Result<(), PptxError> {
71 charge(
72 &mut self.relationships,
73 1,
74 self.limits.max_relationships,
75 "relationships",
76 part,
77 )
78 }
79
80 pub fn charge_shape(&mut self, part: &str) -> Result<(), PptxError> {
81 charge(&mut self.shapes, 1, self.limits.max_shapes, "shapes", part)
82 }
83
84 pub fn charge_paragraph(&mut self, part: &str) -> Result<(), PptxError> {
85 charge(
86 &mut self.paragraphs,
87 1,
88 self.limits.max_paragraphs,
89 "paragraphs",
90 part,
91 )
92 }
93
94 pub fn charge_run(&mut self, part: &str) -> Result<(), PptxError> {
95 charge(&mut self.runs, 1, self.limits.max_runs, "runs", part)
96 }
97}
98
99fn charge(
100 used: &mut usize,
101 amount: usize,
102 maximum: usize,
103 kind: &'static str,
104 part: &str,
105) -> Result<(), PptxError> {
106 *used = used
107 .checked_add(amount)
108 .ok_or_else(|| PptxError::ResourceLimit {
109 part: part.to_owned(),
110 kind,
111 })?;
112 if *used > maximum {
113 return Err(PptxError::ResourceLimit {
114 part: part.to_owned(),
115 kind,
116 });
117 }
118 Ok(())
119}
120
121#[derive(Clone, Debug, PartialEq, Eq)]
122pub(crate) struct XmlElement {
123 pub name: String,
124 pub attributes: BTreeMap<String, String>,
125 pub children: Vec<XmlNode>,
126}
127
128impl XmlElement {
129 pub fn local_name(&self) -> &str {
130 local_name(&self.name)
131 }
132
133 pub fn attribute(&self, name: &str) -> Option<&str> {
134 self.attributes.get(name).map(String::as_str)
135 }
136
137 pub fn attribute_local(&self, name: &str) -> Option<&str> {
138 self.attributes
139 .iter()
140 .find(|(key, _)| local_name(key) == name)
141 .map(|(_, value)| value.as_str())
142 }
143
144 pub fn child(&self, name: &str) -> Option<&XmlElement> {
145 self.child_elements()
146 .find(|child| child.local_name() == name)
147 }
148
149 pub fn children_named<'a>(
150 &'a self,
151 name: &'a str,
152 ) -> impl Iterator<Item = &'a XmlElement> + 'a {
153 self.child_elements()
154 .filter(move |child| child.local_name() == name)
155 }
156
157 pub fn child_elements(&self) -> impl Iterator<Item = &XmlElement> {
158 self.children.iter().filter_map(|child| match child {
159 XmlNode::Element(element) => Some(element),
160 XmlNode::Text(_) => None,
161 })
162 }
163
164 pub fn descendants_named<'a>(&'a self, name: &'a str) -> Vec<&'a XmlElement> {
165 let mut output = Vec::new();
166 collect_descendants(self, name, &mut output);
167 output
168 }
169
170 pub fn text_content(&self) -> String {
171 let mut output = String::new();
172 append_text(self, &mut output);
173 output
174 }
175
176 pub fn new(name: impl Into<String>) -> Self {
177 Self {
178 name: name.into(),
179 attributes: BTreeMap::new(),
180 children: Vec::new(),
181 }
182 }
183
184 pub fn with_attribute(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
185 self.attributes.insert(name.into(), value.into());
186 self
187 }
188
189 pub fn with_child(mut self, child: XmlElement) -> Self {
190 self.children.push(XmlNode::Element(child));
191 self
192 }
193
194 pub fn with_text(mut self, text: impl Into<String>) -> Self {
195 self.children.push(XmlNode::Text(text.into()));
196 self
197 }
198
199 pub fn set_attribute(&mut self, name: impl Into<String>, value: impl Into<String>) {
200 self.attributes.insert(name.into(), value.into());
201 }
202
203 pub fn child_mut(&mut self, name: &str) -> Option<&mut XmlElement> {
204 self.children.iter_mut().find_map(|child| match child {
205 XmlNode::Element(element) if element.local_name() == name => Some(element),
206 _ => None,
207 })
208 }
209}
210
211#[derive(Clone, Debug, PartialEq, Eq)]
212pub(crate) enum XmlNode {
213 Element(XmlElement),
214 Text(String),
215}
216
217fn collect_descendants<'a>(element: &'a XmlElement, name: &str, output: &mut Vec<&'a XmlElement>) {
218 for child in element.child_elements() {
219 if child.local_name() == name {
220 output.push(child);
221 }
222 collect_descendants(child, name, output);
223 }
224}
225
226fn append_text(element: &XmlElement, output: &mut String) {
227 for child in &element.children {
228 match child {
229 XmlNode::Element(element) => append_text(element, output),
230 XmlNode::Text(text) => output.push_str(text),
231 }
232 }
233}
234
235pub(crate) fn local_name(name: &str) -> &str {
236 name.rsplit_once(':').map_or(name, |(_, local)| local)
237}
238
239pub(crate) fn serialize_xml(root: &XmlElement) -> Vec<u8> {
240 let mut output =
241 String::from("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n");
242 write_element(root, &mut output);
243 output.into_bytes()
244}
245
246fn write_element(element: &XmlElement, output: &mut String) {
247 output.push('<');
248 output.push_str(&element.name);
249 for (key, value) in &element.attributes {
250 output.push(' ');
251 output.push_str(key);
252 output.push_str("=\"");
253 escape_into(value, true, output);
254 output.push('"');
255 }
256 if element.children.is_empty() {
257 output.push_str("/>");
258 return;
259 }
260 output.push('>');
261 for child in &element.children {
262 match child {
263 XmlNode::Element(child) => write_element(child, output),
264 XmlNode::Text(text) => escape_into(text, false, output),
265 }
266 }
267 output.push_str("</");
268 output.push_str(&element.name);
269 output.push('>');
270}
271
272fn escape_into(value: &str, attribute: bool, output: &mut String) {
273 for character in value.chars() {
274 match character {
275 '&' => output.push_str("&"),
276 '<' => output.push_str("<"),
277 '>' => output.push_str(">"),
278 '"' if attribute => output.push_str("""),
279 '\r' => output.push_str("
"),
281 '\n' | '\t' if attribute => {
282 output.push_str(&format!("&#x{:X};", character as u32));
283 }
284 _ => output.push(character),
285 }
286 }
287}
288
289pub(crate) fn parse_xml(
290 xml: &[u8],
291 part: &str,
292 budget: &mut ParseBudget<'_>,
293) -> Result<XmlElement, PptxError> {
294 charge(
295 &mut budget.xml_bytes,
296 xml.len(),
297 budget.limits.max_xml_bytes,
298 "xmlBytes",
299 part,
300 )?;
301 let mut reader = Reader::from_reader(xml);
302 reader.config_mut().trim_text(false);
303 reader.config_mut().check_end_names = true;
304 let mut roots = Vec::new();
305 let mut stack = Vec::new();
306
307 loop {
308 let event = reader
309 .read_event()
310 .map_err(|error| malformed(&reader, part, error.to_string()))?;
311 charge(
312 &mut budget.xml_events,
313 1,
314 budget.limits.max_xml_events,
315 "xmlEvents",
316 part,
317 )?;
318 match event {
319 Event::Start(start) => {
320 check_depth(stack.len() + 1, budget, part)?;
321 stack.push(decode_element(&reader, start, part, budget)?);
322 }
323 Event::Empty(start) => {
324 check_depth(stack.len() + 1, budget, part)?;
325 let element = decode_element(&reader, start, part, budget)?;
326 append_element(element, &mut stack, &mut roots, part)?;
327 }
328 Event::End(_) => {
329 let element = stack
330 .pop()
331 .ok_or_else(|| malformed(&reader, part, "unexpected closing element"))?;
332 append_element(element, &mut stack, &mut roots, part)?;
333 }
334 Event::Text(text) => {
335 let decoded = text
336 .decode()
337 .map_err(|error| malformed(&reader, part, error.to_string()))?;
338 let unescaped = quick_xml::escape::unescape(&decoded)
339 .map_err(|error| malformed(&reader, part, error.to_string()))?;
340 charge(
341 &mut budget.xml_text_bytes,
342 unescaped.len(),
343 budget.limits.max_xml_text_bytes,
344 "xmlTextBytes",
345 part,
346 )?;
347 append_text_node(unescaped.into_owned(), &mut stack, &reader, part)?;
348 }
349 Event::CData(text) => {
350 let decoded = text
351 .decode()
352 .map_err(|error| malformed(&reader, part, error.to_string()))?
353 .into_owned();
354 charge(
355 &mut budget.xml_text_bytes,
356 decoded.len(),
357 budget.limits.max_xml_text_bytes,
358 "xmlTextBytes",
359 part,
360 )?;
361 append_text_node(decoded, &mut stack, &reader, part)?;
362 }
363 Event::DocType(_) => {
364 return Err(PptxError::UnsafeXml {
365 part: part.to_owned(),
366 kind: "DTD/entity declarations are forbidden",
367 });
368 }
369 Event::GeneralRef(reference) => {
370 let decoded = reference
371 .decode()
372 .map_err(|error| malformed(&reader, part, error.to_string()))?;
373 let resolved = if reference.is_char_ref() {
374 reference
375 .resolve_char_ref()
376 .map_err(|error| malformed(&reader, part, error.to_string()))?
377 .filter(|character| is_legal_xml_character(*character))
378 .map(|character| character.to_string())
379 } else {
380 quick_xml::escape::resolve_predefined_entity(&decoded).map(str::to_owned)
381 };
382 let Some(resolved) = resolved else {
383 return Err(PptxError::UnsafeXml {
384 part: part.to_owned(),
385 kind: "non-predefined or illegal entity reference",
386 });
387 };
388 charge(
389 &mut budget.xml_text_bytes,
390 resolved.len(),
391 budget.limits.max_xml_text_bytes,
392 "xmlTextBytes",
393 part,
394 )?;
395 append_text_node(resolved, &mut stack, &reader, part)?;
396 }
397 Event::Decl(_) | Event::PI(_) | Event::Comment(_) => {}
398 Event::Eof => break,
399 }
400 }
401
402 if !stack.is_empty() {
403 return Err(malformed(&reader, part, "unclosed element"));
404 }
405 if roots.len() != 1 {
406 return Err(malformed(
407 &reader,
408 part,
409 "XML part must have exactly one root element",
410 ));
411 }
412 Ok(roots.pop().expect("root count checked"))
413}
414
415fn check_depth(depth: usize, budget: &ParseBudget<'_>, part: &str) -> Result<(), PptxError> {
416 if depth > budget.limits.max_xml_depth {
417 return Err(PptxError::ResourceLimit {
418 part: part.to_owned(),
419 kind: "xmlDepth",
420 });
421 }
422 Ok(())
423}
424
425fn decode_element(
426 reader: &Reader<&[u8]>,
427 start: BytesStart<'_>,
428 part: &str,
429 budget: &mut ParseBudget<'_>,
430) -> Result<XmlElement, PptxError> {
431 let name = reader
432 .decoder()
433 .decode(start.name().as_ref())
434 .map_err(|error| malformed(reader, part, error.to_string()))?
435 .into_owned();
436 let mut attributes = BTreeMap::new();
437 let mut attribute_bytes = 0usize;
438 for (index, attribute) in start.attributes().enumerate() {
439 if index >= budget.limits.max_attributes_per_element {
440 return Err(PptxError::ResourceLimit {
441 part: part.to_owned(),
442 kind: "attributesPerElement",
443 });
444 }
445 let attribute = attribute.map_err(|error| malformed(reader, part, error.to_string()))?;
446 let key = reader
447 .decoder()
448 .decode(attribute.key.as_ref())
449 .map_err(|error| malformed(reader, part, error.to_string()))?
450 .into_owned();
451 #[allow(deprecated)]
452 let value = attribute
453 .decode_and_unescape_value(reader.decoder())
454 .map_err(|error| malformed(reader, part, error.to_string()))?
455 .into_owned();
456 attribute_bytes = attribute_bytes
457 .checked_add(key.len() + value.len())
458 .ok_or_else(|| PptxError::ResourceLimit {
459 part: part.to_owned(),
460 kind: "attributeBytes",
461 })?;
462 if attribute_bytes > budget.limits.max_attribute_bytes {
463 return Err(PptxError::ResourceLimit {
464 part: part.to_owned(),
465 kind: "attributeBytes",
466 });
467 }
468 charge(
469 &mut budget.xml_text_bytes,
470 key.len() + value.len(),
471 budget.limits.max_xml_text_bytes,
472 "xmlTextBytes",
473 part,
474 )?;
475 if attributes.insert(key.clone(), value).is_some() {
476 return Err(malformed(
477 reader,
478 part,
479 format!("duplicate attribute {key}"),
480 ));
481 }
482 }
483 Ok(XmlElement {
484 name,
485 attributes,
486 children: Vec::new(),
487 })
488}
489
490fn append_element(
491 element: XmlElement,
492 stack: &mut [XmlElement],
493 roots: &mut Vec<XmlElement>,
494 part: &str,
495) -> Result<(), PptxError> {
496 if let Some(parent) = stack.last_mut() {
497 parent.children.push(XmlNode::Element(element));
498 } else if roots.is_empty() {
499 roots.push(element);
500 } else {
501 return Err(PptxError::MalformedXml {
502 part: part.to_owned(),
503 offset: 0,
504 message: "multiple root elements".to_owned(),
505 });
506 }
507 Ok(())
508}
509
510fn append_text_node(
511 text: String,
512 stack: &mut [XmlElement],
513 reader: &Reader<&[u8]>,
514 part: &str,
515) -> Result<(), PptxError> {
516 if let Some(parent) = stack.last_mut() {
517 parent.children.push(XmlNode::Text(text));
518 } else if !text.trim().is_empty() {
519 return Err(malformed(reader, part, "text outside root element"));
520 }
521 Ok(())
522}
523
524fn malformed(reader: &Reader<&[u8]>, part: &str, message: impl Into<String>) -> PptxError {
525 PptxError::MalformedXml {
526 part: part.to_owned(),
527 offset: reader.buffer_position(),
528 message: message.into(),
529 }
530}
531
532fn is_legal_xml_character(character: char) -> bool {
533 matches!(character, '\u{9}' | '\u{a}' | '\u{d}')
534 || ('\u{20}'..='\u{d7ff}').contains(&character)
535 || ('\u{e000}'..='\u{fffd}').contains(&character)
536 || ('\u{10000}'..='\u{10ffff}').contains(&character)
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn rejects_dtd_unknown_entities_and_depth_overflow() {
545 let limits = ParseLimits::default();
546 let mut budget = ParseBudget::new(&limits);
547 assert!(matches!(
548 parse_xml(b"<!DOCTYPE x><x/>", "x.xml", &mut budget),
549 Err(PptxError::UnsafeXml { .. })
550 ));
551
552 let mut budget = ParseBudget::new(&limits);
553 assert!(matches!(
554 parse_xml(b"<x>&file;</x>", "x.xml", &mut budget),
555 Err(PptxError::UnsafeXml { .. })
556 ));
557
558 let shallow = ParseLimits {
559 max_xml_depth: 1,
560 ..ParseLimits::default()
561 };
562 let mut budget = ParseBudget::new(&shallow);
563 assert!(matches!(
564 parse_xml(b"<x><y/></x>", "x.xml", &mut budget),
565 Err(PptxError::ResourceLimit {
566 kind: "xmlDepth",
567 ..
568 })
569 ));
570 }
571}