Skip to main content

rust_pptx/
lib.rs

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}