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