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