Skip to main content

rust_pptx/
lib.rs

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