1mod legacy;
2mod model;
3mod pptx;
4
5pub use model::*;
6
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10pub enum ParseError {
11 #[error("input is empty")]
12 Empty,
13 #[error("input exceeds the configured {0} byte limit")]
14 InputLimit(usize),
15 #[error("unsupported presentation format")]
16 UnsupportedFormat,
17 #[error("encrypted presentations are not supported")]
18 Encrypted,
19 #[error("corrupt presentation: {0}")]
20 Corrupt(String),
21 #[error("resource limit exceeded: {0}")]
22 ResourceLimit(String),
23}
24
25#[derive(Debug, Clone)]
26pub struct ParseLimits {
27 pub max_input_bytes: usize,
28 pub max_entries: usize,
29 pub max_entry_bytes: usize,
30 pub max_total_uncompressed_bytes: usize,
31 pub max_xml_depth: usize,
32 pub max_xml_nodes: usize,
33 pub max_xml_attributes: usize,
34 pub max_xml_text_bytes: usize,
35 pub max_xml_attribute_bytes: usize,
36}
37
38impl Default for ParseLimits {
39 fn default() -> Self {
40 Self {
41 max_input_bytes: usize::MAX,
42 max_entries: 20_000,
43 max_entry_bytes: usize::MAX,
44 max_total_uncompressed_bytes: usize::MAX,
45 max_xml_depth: 256,
46 max_xml_nodes: 250_000,
47 max_xml_attributes: 1_000_000,
48 max_xml_text_bytes: 64 * 1024 * 1024,
49 max_xml_attribute_bytes: 32 * 1024 * 1024,
50 }
51 }
52}
53
54pub fn detect_format(bytes: &[u8]) -> Result<PresentationFormat, ParseError> {
55 if bytes.is_empty() {
56 return Err(ParseError::Empty);
57 }
58 if bytes.starts_with(b"PK\x03\x04")
59 || bytes.starts_with(b"PK\x05\x06")
60 || bytes.starts_with(b"PK\x07\x08")
61 {
62 return Ok(PresentationFormat::Pptx);
63 }
64 const OLE_MAGIC: &[u8; 8] = b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1";
65 if bytes.starts_with(OLE_MAGIC) {
66 return Ok(PresentationFormat::Ppt);
67 }
68 Err(ParseError::UnsupportedFormat)
69}
70
71pub fn parse_presentation(
72 bytes: &[u8],
73 limits: &ParseLimits,
74) -> Result<PresentationDocument, ParseError> {
75 if bytes.len() > limits.max_input_bytes {
76 return Err(ParseError::InputLimit(limits.max_input_bytes));
77 }
78 match detect_format(bytes)? {
79 PresentationFormat::Pptx => pptx::parse(bytes, limits),
80 PresentationFormat::Ppt => legacy::parse(bytes, limits),
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn detects_ooxml_and_ole_magic() {
90 assert_eq!(
91 detect_format(b"PK\x03\x04rest").unwrap(),
92 PresentationFormat::Pptx
93 );
94 assert_eq!(
95 detect_format(b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1rest").unwrap(),
96 PresentationFormat::Ppt
97 );
98 }
99
100 #[test]
101 fn rejects_unknown_input() {
102 assert!(matches!(
103 detect_format(b"not powerpoint"),
104 Err(ParseError::UnsupportedFormat)
105 ));
106 }
107}