Skip to main content

rust_pptx/
lib.rs

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}