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