Skip to main content

deser_plist/
format.rs

1/// The encodings of property lists.
2///
3/// Deserialization detects the format automatically, the serializer
4/// writes the format of its [`SerializerConfig`](crate::SerializerConfig).
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
6pub enum Format {
7    /// The XML format (`<plist version="1.0">`).
8    ///
9    /// This is the default when serializing.
10    #[default]
11    Xml,
12    /// The binary format (`bplist00`).
13    Binary,
14    /// The OpenStep format, also known as ASCII or old-style property lists
15    /// (`{ key = value; }`).
16    ///
17    /// Xcode project files (`.pbxproj`) and `.strings` files use it.  The
18    /// format has no types besides strings, dictionaries, arrays and data.
19    Ascii,
20}
21
22impl Format {
23    /// Detects the format of a property list.
24    ///
25    /// Binary property lists start with `bplist`, XML documents with a
26    /// markup declaration or an element.  Everything else is treated as
27    /// OpenStep.  Byte order marks are skipped.
28    ///
29    /// ```
30    /// use deser_plist::Format;
31    ///
32    /// assert_eq!(Format::detect(b"bplist00..."), Format::Binary);
33    /// assert_eq!(Format::detect(b"<?xml version=\"1.0\"?>"), Format::Xml);
34    /// assert_eq!(Format::detect(b"<plist><true/></plist>"), Format::Xml);
35    /// assert_eq!(Format::detect(b"{ a = b; }"), Format::Ascii);
36    /// assert_eq!(Format::detect(b"<0fbd7a>"), Format::Ascii);
37    /// ```
38    pub fn detect(input: &[u8]) -> Format {
39        if input.starts_with(b"bplist") {
40            return Format::Binary;
41        }
42        // for UTF-16 only ASCII characters matter, every other byte is
43        // skipped.
44        let (input, step) = if input.starts_with(b"\xfe\xff") {
45            (&input[3.min(input.len())..], 2)
46        } else if input.starts_with(b"\xff\xfe") {
47            (&input[2..], 2)
48        } else if let Some(rest) = input.strip_prefix(b"\xef\xbb\xbf") {
49            (rest, 1)
50        } else {
51            (input, 1)
52        };
53        let mut chars = input.iter().step_by(step).copied();
54        let Some(first) = chars.by_ref().find(|c| !c.is_ascii_whitespace()) else {
55            return Format::Ascii;
56        };
57        if first != b'<' {
58            return Format::Ascii;
59        }
60        // `<` starts data in OpenStep, which only holds hex digits and
61        // whitespace.  All tags of XML plists have other letters.
62        for c in chars {
63            match c {
64                b'>' => return Format::Ascii,
65                c if c.is_ascii_hexdigit() || c.is_ascii_whitespace() => {}
66                _ => return Format::Xml,
67            }
68        }
69        Format::Xml
70    }
71
72    /// Returns `true` for the text formats (XML and OpenStep).
73    pub fn is_text(self) -> bool {
74        !matches!(self, Format::Binary)
75    }
76}
77
78#[test]
79fn test_detect() {
80    assert_eq!(Format::detect(b""), Format::Ascii);
81    assert_eq!(Format::detect(b"  \n"), Format::Ascii);
82    assert_eq!(Format::detect(b"\xef\xbb\xbf<plist/>"), Format::Xml);
83    assert_eq!(Format::detect(b"\xff\xfe<\0?\0"), Format::Xml);
84    assert_eq!(Format::detect(b"\xfe\xff\0<\0?"), Format::Xml);
85    assert_eq!(Format::detect(b"\xff\xfe{\0}\0"), Format::Ascii);
86    assert_eq!(Format::detect(b"<dict/>"), Format::Xml);
87    assert_eq!(Format::detect(b"<data>"), Format::Xml);
88    assert_eq!(Format::detect(b"< 0f bd >"), Format::Ascii);
89    assert_eq!(Format::detect(b"// !$*UTF8*$!\n{}"), Format::Ascii);
90    assert_eq!(Format::detect(b"\"a\" = \"b\";"), Format::Ascii);
91}