Skip to main content

plist/stream/
mod.rs

1//! An abstraction of a plist file as a stream of events. Used to support multiple encodings.
2
3mod binary_reader;
4pub use self::binary_reader::BinaryReader;
5
6mod binary_writer;
7pub use self::binary_writer::BinaryWriter;
8
9mod xml_reader;
10pub use self::xml_reader::XmlReader;
11
12mod xml_writer;
13pub use self::xml_writer::XmlWriter;
14#[cfg(feature = "serde")]
15pub(crate) use xml_writer::encode_data_base64 as xml_encode_data_base64;
16
17mod ascii_reader;
18pub use self::ascii_reader::AsciiReader;
19
20use std::{
21    borrow::Cow,
22    io::{self, BufReader, Read, Seek},
23    vec,
24};
25
26use crate::{
27    Date, Integer, Uid, Value, dictionary,
28    error::{Error, ErrorKind},
29};
30
31/// An encoding of a plist as a flat structure.
32///
33/// Output by the event readers.
34///
35/// Dictionary keys and values are represented as pairs of values e.g.:
36///
37/// ```ignore rust
38/// StartDictionary
39/// String("Height") // Key
40/// Real(181.2)      // Value
41/// String("Age")    // Key
42/// Integer(28)      // Value
43/// EndDictionary
44/// ```
45///
46/// ## Lifetimes
47///
48/// This type has a lifetime parameter; during serialization, data is borrowed
49/// from a [`Value`], and the lifetime of the event is the lifetime of the
50/// [`Value`] being serialized.
51///
52/// During deserialization, data is always copied anyway, and this lifetime
53/// is always `'static`.
54#[derive(Clone, Debug, PartialEq)]
55#[non_exhaustive]
56pub enum Event<'a> {
57    // While the length of an array or dict cannot be feasably greater than max(usize) this better
58    // conveys the concept of an effectively unbounded event stream.
59    StartArray(Option<u64>),
60    StartDictionary(Option<u64>),
61    EndCollection,
62
63    Boolean(bool),
64    Data(Cow<'a, [u8]>),
65    Date(Date),
66    Integer(Integer),
67    Real(f64),
68    String(Cow<'a, str>),
69    Uid(Uid),
70}
71
72/// An owned [`Event`].
73///
74/// During deserialization, events are always owned; this type alias helps
75/// keep that code a bit clearer.
76pub type OwnedEvent = Event<'static>;
77
78/// An `Event` stream returned by `Value::into_events`.
79pub struct Events<'a> {
80    stack: Vec<StackItem<'a>>,
81}
82
83enum StackItem<'a> {
84    Root(&'a Value),
85    Array(std::slice::Iter<'a, Value>),
86    Dict(dictionary::Iter<'a>),
87    DictValue(&'a Value),
88}
89
90/// Options for customizing serialization of XML plists.
91#[derive(Clone, Debug)]
92pub struct XmlWriteOptions {
93    root_element: bool,
94    indent_char: u8,
95    indent_count: usize,
96}
97
98impl XmlWriteOptions {
99    /// Specify the sequence of characters used for indentation.
100    ///
101    /// This may be either an `&'static str` or an owned `String`.
102    ///
103    /// The default is `\t`.
104    ///
105    /// Since replacing `xml-rs` with `quick-xml`, the indent string has to consist of a single
106    /// repeating ascii character. This is a backwards compatibility function, prefer using
107    /// [`XmlWriteOptions::indent`].
108    #[deprecated(since = "1.4.0", note = "please use `indent` instead")]
109    pub fn indent_string(self, indent_str: impl Into<Cow<'static, str>>) -> Self {
110        let indent_str = indent_str.into();
111        let indent_str = indent_str.as_ref();
112
113        if indent_str.is_empty() {
114            return self.indent(0, 0);
115        }
116
117        assert!(indent_str.is_ascii(), "indent str must be ascii");
118        let indent_str = indent_str.as_bytes();
119        assert!(
120            indent_str.iter().all(|chr| chr == &indent_str[0]),
121            "indent str must consist of a single repeating character"
122        );
123
124        self.indent(indent_str[0], indent_str.len())
125    }
126
127    /// Specifies the character and amount used for indentation.
128    ///
129    /// `indent_char` must be a valid UTF8 character.
130    ///
131    /// The default is indenting with a single tab.
132    pub fn indent(mut self, indent_char: u8, indent_count: usize) -> Self {
133        self.indent_char = indent_char;
134        self.indent_count = indent_count;
135        self
136    }
137
138    /// Selects whether to write the XML prologue, plist document type and root element.
139    ///
140    /// In other words the following:
141    /// ```xml
142    /// <?xml version="1.0" encoding="UTF-8"?>
143    /// <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
144    /// <plist version="1.0">
145    /// ...
146    /// </plist>
147    /// ```
148    ///
149    /// The default is `true`.
150    pub fn root_element(mut self, write_root: bool) -> Self {
151        self.root_element = write_root;
152        self
153    }
154}
155
156impl Default for XmlWriteOptions {
157    fn default() -> Self {
158        XmlWriteOptions {
159            indent_char: b'\t',
160            indent_count: 1,
161            root_element: true,
162        }
163    }
164}
165
166impl<'a> Events<'a> {
167    pub(crate) fn new(value: &'a Value) -> Events<'a> {
168        Events {
169            stack: vec![StackItem::Root(value)],
170        }
171    }
172}
173
174impl<'a> Iterator for Events<'a> {
175    type Item = Event<'a>;
176
177    fn next(&mut self) -> Option<Event<'a>> {
178        fn handle_value<'c, 'b: 'c>(
179            value: &'b Value,
180            stack: &'c mut Vec<StackItem<'b>>,
181        ) -> Event<'b> {
182            match value {
183                Value::Array(array) => {
184                    let len = array.len();
185                    let iter = array.iter();
186                    stack.push(StackItem::Array(iter));
187                    Event::StartArray(Some(len as u64))
188                }
189                Value::Dictionary(dict) => {
190                    let len = dict.len();
191                    let iter = dict.into_iter();
192                    stack.push(StackItem::Dict(iter));
193                    Event::StartDictionary(Some(len as u64))
194                }
195                Value::Boolean(value) => Event::Boolean(*value),
196                Value::Data(value) => Event::Data(Cow::Borrowed(value)),
197                Value::Date(value) => Event::Date(*value),
198                Value::Real(value) => Event::Real(*value),
199                Value::Integer(value) => Event::Integer(*value),
200                Value::String(value) => Event::String(Cow::Borrowed(value.as_str())),
201                Value::Uid(value) => Event::Uid(*value),
202            }
203        }
204
205        Some(match self.stack.pop()? {
206            StackItem::Root(value) | StackItem::DictValue(value) => {
207                handle_value(value, &mut self.stack)
208            }
209            StackItem::Array(mut array) => {
210                if let Some(value) = array.next() {
211                    // There might still be more items in the array so return it to the stack.
212                    self.stack.push(StackItem::Array(array));
213                    handle_value(value, &mut self.stack)
214                } else {
215                    Event::EndCollection
216                }
217            }
218            StackItem::Dict(mut dict) => {
219                if let Some((key, value)) = dict.next() {
220                    // There might still be more items in the dictionary so return it to the stack.
221                    self.stack.push(StackItem::Dict(dict));
222                    // The next event to be returned must be the dictionary value.
223                    self.stack.push(StackItem::DictValue(value));
224                    // Return the key event now.
225                    Event::String(Cow::Borrowed(key))
226                } else {
227                    Event::EndCollection
228                }
229            }
230        })
231    }
232}
233
234pub struct Reader<R: Read + Seek>(ReaderInner<R>);
235
236enum ReaderInner<R: Read + Seek> {
237    Uninitialized(Option<R>),
238    Binary(BinaryReader<R>),
239    Xml(XmlReader<BufReader<R>>),
240    Ascii(AsciiReader<BufReader<R>>),
241}
242
243impl<R: Read + Seek> Reader<R> {
244    pub fn new(reader: R) -> Reader<R> {
245        Reader(ReaderInner::Uninitialized(Some(reader)))
246    }
247
248    fn init(&mut self, mut reader: R) -> Result<Option<OwnedEvent>, Error> {
249        // Rewind reader back to the start.
250        if let Err(err) = reader.rewind().map_err(from_io_offset_0) {
251            self.0 = ReaderInner::Uninitialized(Some(reader));
252            return Err(err);
253        }
254
255        // A plist is binary if it starts with magic bytes.
256        match Reader::is_binary(&mut reader) {
257            Ok(true) => {
258                self.0 = ReaderInner::Binary(BinaryReader::new(reader));
259                return self.next().transpose();
260            }
261            Ok(false) => (),
262            Err(err) => {
263                self.0 = ReaderInner::Uninitialized(Some(reader));
264                return Err(err);
265            }
266        }
267
268        // If a plist is not binary, try to parse as XML.
269        // Use a `BufReader` for XML and ASCII plists as it is required by `quick-xml` and will
270        // definitely speed up ASCII parsing as well.
271        let mut xml_reader = XmlReader::new(BufReader::new(reader));
272        let mut reader = match xml_reader.next() {
273            res @ (Some(Ok(_)) | None) => {
274                self.0 = ReaderInner::Xml(xml_reader);
275                return res.transpose();
276            }
277            Some(Err(err)) if xml_reader.xml_doc_started() => {
278                self.0 = ReaderInner::Uninitialized(Some(xml_reader.into_inner().into_inner()));
279                return Err(err);
280            }
281            Some(Err(_)) => xml_reader.into_inner(),
282        };
283
284        // Rewind reader back to the start.
285        if let Err(err) = reader.rewind().map_err(from_io_offset_0) {
286            self.0 = ReaderInner::Uninitialized(Some(reader.into_inner()));
287            return Err(err);
288        }
289
290        // If no valid XML markup is found, try to parse as ASCII.
291        let mut ascii_reader = AsciiReader::new(reader);
292        match ascii_reader.next() {
293            res @ (Some(Ok(_)) | None) => {
294                self.0 = ReaderInner::Ascii(ascii_reader);
295                res.transpose()
296            }
297            Some(Err(err)) => {
298                self.0 = ReaderInner::Uninitialized(Some(ascii_reader.into_inner().into_inner()));
299                Err(err)
300            }
301        }
302    }
303
304    fn is_binary(reader: &mut R) -> Result<bool, Error> {
305        let mut magic = [0; 8];
306        reader.read_exact(&mut magic).map_err(from_io_offset_0)?;
307        reader.rewind().map_err(from_io_offset_0)?;
308
309        Ok(&magic == b"bplist00")
310    }
311}
312
313impl<R: Read + Seek> Iterator for Reader<R> {
314    type Item = Result<OwnedEvent, Error>;
315
316    fn next(&mut self) -> Option<Result<OwnedEvent, Error>> {
317        match self.0 {
318            ReaderInner::Xml(ref mut parser) => parser.next(),
319            ReaderInner::Binary(ref mut parser) => parser.next(),
320            ReaderInner::Ascii(ref mut parser) => parser.next(),
321            ReaderInner::Uninitialized(ref mut reader) => {
322                let reader = reader.take().unwrap();
323                self.init(reader).transpose()
324            }
325        }
326    }
327}
328
329fn from_io_offset_0(err: io::Error) -> Error {
330    ErrorKind::Io(err).with_byte_offset(0)
331}
332
333/// Supports writing event streams in different plist encodings.
334pub trait Writer: private::Sealed {
335    fn write(&mut self, event: Event) -> Result<(), Error> {
336        match event {
337            Event::StartArray(len) => self.write_start_array(len),
338            Event::StartDictionary(len) => self.write_start_dictionary(len),
339            Event::EndCollection => self.write_end_collection(),
340            Event::Boolean(value) => self.write_boolean(value),
341            Event::Data(value) => self.write_data(value),
342            Event::Date(value) => self.write_date(value),
343            Event::Integer(value) => self.write_integer(value),
344            Event::Real(value) => self.write_real(value),
345            Event::String(value) => self.write_string(value),
346            Event::Uid(value) => self.write_uid(value),
347        }
348    }
349
350    fn write_start_array(&mut self, len: Option<u64>) -> Result<(), Error>;
351    fn write_start_dictionary(&mut self, len: Option<u64>) -> Result<(), Error>;
352    fn write_end_collection(&mut self) -> Result<(), Error>;
353
354    fn write_boolean(&mut self, value: bool) -> Result<(), Error>;
355    fn write_data(&mut self, value: Cow<[u8]>) -> Result<(), Error>;
356    fn write_date(&mut self, value: Date) -> Result<(), Error>;
357    fn write_integer(&mut self, value: Integer) -> Result<(), Error>;
358    fn write_real(&mut self, value: f64) -> Result<(), Error>;
359    fn write_string(&mut self, value: Cow<str>) -> Result<(), Error>;
360    fn write_uid(&mut self, value: Uid) -> Result<(), Error>;
361}
362
363pub(crate) mod private {
364    use std::io::Write;
365
366    pub trait Sealed {}
367
368    impl<W: Write> Sealed for super::BinaryWriter<W> {}
369    impl<W: Write> Sealed for super::XmlWriter<W> {}
370}
371
372#[cfg(test)]
373mod tests {
374    use std::fs::File;
375
376    use super::{Event::*, *};
377
378    const ANIMALS_PLIST_EVENTS: &[Event] = &[
379        StartDictionary(None),
380        String(Cow::Borrowed("AnimalColors")),
381        StartDictionary(None),
382        String(Cow::Borrowed("lamb")), // key
383        String(Cow::Borrowed("black")),
384        String(Cow::Borrowed("pig")), // key
385        String(Cow::Borrowed("pink")),
386        String(Cow::Borrowed("worm")), // key
387        String(Cow::Borrowed("pink")),
388        EndCollection,
389        String(Cow::Borrowed("AnimalSmells")),
390        StartDictionary(None),
391        String(Cow::Borrowed("lamb")), // key
392        String(Cow::Borrowed("lambish")),
393        String(Cow::Borrowed("pig")), // key
394        String(Cow::Borrowed("piggish")),
395        String(Cow::Borrowed("worm")), // key
396        String(Cow::Borrowed("wormy")),
397        EndCollection,
398        String(Cow::Borrowed("AnimalSounds")),
399        StartDictionary(None),
400        String(Cow::Borrowed("Lisa")), // key
401        String(Cow::Borrowed("Why is the worm talking like a lamb?")),
402        String(Cow::Borrowed("lamb")), // key
403        String(Cow::Borrowed("baa")),
404        String(Cow::Borrowed("pig")), // key
405        String(Cow::Borrowed("oink")),
406        String(Cow::Borrowed("worm")), // key
407        String(Cow::Borrowed("baa")),
408        EndCollection,
409        EndCollection,
410    ];
411
412    #[test]
413    fn autodetect_binary() {
414        let reader = File::open("./tests/data/binary.plist").unwrap();
415        let mut streaming_parser = Reader::new(reader);
416        let events: Result<Vec<_>, _> = streaming_parser.by_ref().collect();
417
418        assert!(matches!(streaming_parser.0, ReaderInner::Binary(_)));
419        // The contents of this plist are tested for elsewhere.
420        assert!(events.is_ok());
421    }
422
423    #[test]
424    fn autodetect_xml() {
425        let reader = File::open("./tests/data/xml-animals.plist").unwrap();
426        let mut streaming_parser = Reader::new(reader);
427        let events: Result<Vec<_>, _> = streaming_parser.by_ref().collect();
428
429        assert!(matches!(streaming_parser.0, ReaderInner::Xml(_)));
430        assert_eq!(events.unwrap(), ANIMALS_PLIST_EVENTS);
431    }
432
433    #[test]
434    fn autodetect_ascii() {
435        let reader = File::open("./tests/data/ascii-animals.plist").unwrap();
436        let mut streaming_parser = Reader::new(reader);
437        let events: Result<Vec<_>, _> = streaming_parser.by_ref().collect();
438
439        assert!(matches!(streaming_parser.0, ReaderInner::Ascii(_)));
440        assert_eq!(events.unwrap(), ANIMALS_PLIST_EVENTS);
441    }
442}