plist/stream/
xml_writer.rs

1use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
2use quick_xml::{
3    events::{BytesEnd, BytesStart, BytesText, Event as XmlEvent},
4    Error as XmlWriterError, Writer as EventWriter,
5};
6use std::{
7    borrow::Cow,
8    io::{self, Write},
9};
10
11use crate::{
12    error::{self, from_io_without_position, Error, ErrorKind, EventKind},
13    stream::{Writer, XmlWriteOptions},
14    Date, Integer, Uid,
15};
16
17const DATA_MAX_LINE_CHARS: usize = 68;
18const DATA_MAX_LINE_BYTES: usize = 51;
19
20static XML_PROLOGUE: &[u8] = br#"<?xml version="1.0" encoding="UTF-8"?>
21<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
22<plist version="1.0">
23"#;
24
25#[derive(PartialEq)]
26enum Element {
27    Dictionary,
28    Array,
29}
30
31pub struct XmlWriter<W: Write> {
32    xml_writer: EventWriter<W>,
33    write_root_element: bool,
34    indent_char: u8,
35    indent_count: usize,
36    started_plist: bool,
37    stack: Vec<Element>,
38    expecting_key: bool,
39    pending_collection: Option<PendingCollection>,
40}
41
42enum PendingCollection {
43    Array,
44    Dictionary,
45}
46
47impl<W: Write> XmlWriter<W> {
48    #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")]
49    pub fn new(writer: W) -> XmlWriter<W> {
50        let opts = XmlWriteOptions::default();
51        XmlWriter::new_with_options(writer, &opts)
52    }
53
54    pub fn new_with_options(writer: W, opts: &XmlWriteOptions) -> XmlWriter<W> {
55        let xml_writer = if opts.indent_count == 0 {
56            EventWriter::new(writer)
57        } else {
58            EventWriter::new_with_indent(writer, opts.indent_char, opts.indent_count)
59        };
60
61        XmlWriter {
62            xml_writer,
63            write_root_element: opts.root_element,
64            indent_char: opts.indent_char,
65            indent_count: opts.indent_count,
66            started_plist: false,
67            stack: Vec::new(),
68            expecting_key: false,
69            pending_collection: None,
70        }
71    }
72
73    #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")]
74    pub fn into_inner(self) -> W {
75        self.xml_writer.into_inner()
76    }
77
78    fn write_element_and_value(&mut self, name: &str, value: &str) -> Result<(), Error> {
79        self.xml_writer
80            .create_element(name)
81            .write_text_content(BytesText::new(value))
82            .map_err(from_io_without_position)?;
83        Ok(())
84    }
85
86    fn start_element(&mut self, name: &str) -> Result<(), Error> {
87        self.xml_writer
88            .write_event(XmlEvent::Start(BytesStart::new(name)))
89            .map_err(from_io_without_position)?;
90        Ok(())
91    }
92
93    fn end_element(&mut self, name: &str) -> Result<(), Error> {
94        self.xml_writer
95            .write_event(XmlEvent::End(BytesEnd::new(name)))
96            .map_err(from_io_without_position)?;
97        Ok(())
98    }
99
100    fn write_event<F: FnOnce(&mut Self) -> Result<(), Error>>(
101        &mut self,
102        f: F,
103    ) -> Result<(), Error> {
104        if !self.started_plist {
105            if self.write_root_element {
106                self.xml_writer
107                    .get_mut()
108                    .write_all(XML_PROLOGUE)
109                    .map_err(error::from_io_without_position)?;
110            }
111
112            self.started_plist = true;
113        }
114
115        f(self)?;
116
117        // If there are no more open tags then write the </plist> element
118        if self.stack.is_empty() {
119            if self.write_root_element {
120                // We didn't tell the xml_writer about the <plist> tag so we'll skip telling it
121                // about the </plist> tag as well.
122                self.xml_writer
123                    .get_mut()
124                    .write_all(b"\n</plist>")
125                    .map_err(error::from_io_without_position)?;
126            }
127
128            self.xml_writer
129                .get_mut()
130                .flush()
131                .map_err(error::from_io_without_position)?;
132        }
133
134        Ok(())
135    }
136
137    fn write_value_event<F: FnOnce(&mut Self) -> Result<(), Error>>(
138        &mut self,
139        event_kind: EventKind,
140        f: F,
141    ) -> Result<(), Error> {
142        self.handle_pending_collection()?;
143        self.write_event(|this| {
144            if this.expecting_key {
145                return Err(ErrorKind::UnexpectedEventType {
146                    expected: EventKind::DictionaryKeyOrEndCollection,
147                    found: event_kind,
148                }
149                .without_position());
150            }
151            f(this)?;
152            this.expecting_key = this.stack.last() == Some(&Element::Dictionary);
153            Ok(())
154        })
155    }
156
157    fn handle_pending_collection(&mut self) -> Result<(), Error> {
158        if let Some(PendingCollection::Array) = self.pending_collection {
159            self.pending_collection = None;
160
161            self.write_value_event(EventKind::StartArray, |this| {
162                this.start_element("array")?;
163                this.stack.push(Element::Array);
164                Ok(())
165            })
166        } else if let Some(PendingCollection::Dictionary) = self.pending_collection {
167            self.pending_collection = None;
168
169            self.write_value_event(EventKind::StartDictionary, |this| {
170                this.start_element("dict")?;
171                this.stack.push(Element::Dictionary);
172                this.expecting_key = true;
173                Ok(())
174            })
175        } else {
176            Ok(())
177        }
178    }
179}
180
181impl<W: Write> Writer for XmlWriter<W> {
182    fn write_start_array(&mut self, _len: Option<u64>) -> Result<(), Error> {
183        self.handle_pending_collection()?;
184        self.pending_collection = Some(PendingCollection::Array);
185        Ok(())
186    }
187
188    fn write_start_dictionary(&mut self, _len: Option<u64>) -> Result<(), Error> {
189        self.handle_pending_collection()?;
190        self.pending_collection = Some(PendingCollection::Dictionary);
191        Ok(())
192    }
193
194    fn write_end_collection(&mut self) -> Result<(), Error> {
195        self.write_event(|this| {
196            match this.pending_collection.take() {
197                Some(PendingCollection::Array) => {
198                    this.xml_writer
199                        .write_event(XmlEvent::Empty(BytesStart::new("array")))
200                        .map_err(from_io_without_position)?;
201                    this.expecting_key = this.stack.last() == Some(&Element::Dictionary);
202                    return Ok(());
203                }
204                Some(PendingCollection::Dictionary) => {
205                    this.xml_writer
206                        .write_event(XmlEvent::Empty(BytesStart::new("dict")))
207                        .map_err(from_io_without_position)?;
208                    this.expecting_key = this.stack.last() == Some(&Element::Dictionary);
209                    return Ok(());
210                }
211                _ => {}
212            }
213            match (this.stack.pop(), this.expecting_key) {
214                (Some(Element::Dictionary), true) => {
215                    this.end_element("dict")?;
216                }
217                (Some(Element::Array), _) => {
218                    this.end_element("array")?;
219                }
220                (Some(Element::Dictionary), false) | (None, _) => {
221                    return Err(ErrorKind::UnexpectedEventType {
222                        expected: EventKind::ValueOrStartCollection,
223                        found: EventKind::EndCollection,
224                    }
225                    .without_position());
226                }
227            }
228            this.expecting_key = this.stack.last() == Some(&Element::Dictionary);
229            Ok(())
230        })
231    }
232
233    fn write_boolean(&mut self, value: bool) -> Result<(), Error> {
234        self.write_value_event(EventKind::Boolean, |this| {
235            let value = if value { "true" } else { "false" };
236            this.xml_writer
237                .write_event(XmlEvent::Empty(BytesStart::new(value)))
238                .map_err(from_io_without_position)?;
239            Ok(())
240        })
241    }
242
243    fn write_data(&mut self, value: Cow<[u8]>) -> Result<(), Error> {
244        self.write_value_event(EventKind::Data, |this| {
245            this.xml_writer
246                .create_element("data")
247                .write_inner_content(|xml_writer| {
248                    write_data_base64(
249                        &value,
250                        true,
251                        this.indent_char,
252                        this.stack.len() * this.indent_count,
253                        xml_writer.get_mut(),
254                    )
255                })
256                .map_err(from_io_without_position)
257                .map(|_| ())
258        })
259    }
260
261    fn write_date(&mut self, value: Date) -> Result<(), Error> {
262        self.write_value_event(EventKind::Date, |this| {
263            this.write_element_and_value("date", &value.to_xml_format())
264        })
265    }
266
267    fn write_integer(&mut self, value: Integer) -> Result<(), Error> {
268        self.write_value_event(EventKind::Integer, |this| {
269            this.write_element_and_value("integer", &value.to_string())
270        })
271    }
272
273    fn write_real(&mut self, value: f64) -> Result<(), Error> {
274        self.write_value_event(EventKind::Real, |this| {
275            this.write_element_and_value("real", &value.to_string())
276        })
277    }
278
279    fn write_string(&mut self, value: Cow<str>) -> Result<(), Error> {
280        self.handle_pending_collection()?;
281        self.write_event(|this| {
282            if this.expecting_key {
283                this.write_element_and_value("key", &value)?;
284                this.expecting_key = false;
285            } else {
286                this.write_element_and_value("string", &value)?;
287                this.expecting_key = this.stack.last() == Some(&Element::Dictionary);
288            }
289            Ok(())
290        })
291    }
292
293    fn write_uid(&mut self, _value: Uid) -> Result<(), Error> {
294        Err(ErrorKind::UidNotSupportedInXmlPlist.without_position())
295    }
296}
297
298impl From<XmlWriterError> for Error {
299    fn from(err: XmlWriterError) -> Self {
300        match err {
301            XmlWriterError::Io(err) => match std::sync::Arc::try_unwrap(err) {
302                Ok(err) => ErrorKind::Io(err),
303                Err(err) => ErrorKind::Io(std::io::Error::from(err.kind())),
304            }
305            .without_position(),
306            _ => unreachable!(),
307        }
308    }
309}
310
311#[cfg(feature = "serde")]
312pub(crate) fn encode_data_base64(data: &[u8]) -> String {
313    // Pre-allocate space for the base64 encoded data.
314    let num_lines = (data.len() + DATA_MAX_LINE_BYTES - 1) / DATA_MAX_LINE_BYTES;
315    let max_len = num_lines * (DATA_MAX_LINE_CHARS + 1);
316
317    let mut base64 = Vec::with_capacity(max_len);
318    write_data_base64(data, false, b'\t', 0, &mut base64).expect("writing to a vec cannot fail");
319    String::from_utf8(base64).expect("encoded base64 is ascii")
320}
321
322fn write_data_base64(
323    data: &[u8],
324    write_initial_newline: bool,
325    indent_char: u8,
326    indent_repeat: usize,
327    mut writer: impl Write,
328) -> io::Result<()> {
329    // XML plist data elements are always formatted by apple tools as
330    // <data>
331    // AAAA..AA (68 characters per line)
332    // </data>
333    let mut encoded = [0; DATA_MAX_LINE_CHARS];
334    for (i, line) in data.chunks(DATA_MAX_LINE_BYTES).enumerate() {
335        // Write newline
336        if write_initial_newline || i > 0 {
337            writer.write_all(b"\n")?;
338        }
339
340        // Write indent
341        for _ in 0..indent_repeat {
342            writer.write_all(&[indent_char])?;
343        }
344
345        // Write bytes
346        let encoded_len = BASE64_STANDARD
347            .encode_slice(line, &mut encoded)
348            .expect("encoded base64 max line length is known");
349        writer.write_all(&encoded[..encoded_len])?;
350    }
351    Ok(())
352}
353
354#[cfg(test)]
355mod tests {
356    use std::io::Cursor;
357
358    use super::*;
359    use crate::stream::Event;
360
361    #[test]
362    fn streaming_parser() {
363        let plist = [
364            Event::StartDictionary(None),
365            Event::String("Author".into()),
366            Event::String("William Shakespeare".into()),
367            Event::String("Lines".into()),
368            Event::StartArray(None),
369            Event::String("It is a tale told by an idiot,".into()),
370            Event::String("Full of sound and fury, signifying nothing.".into()),
371            Event::Data((0..128).collect::<Vec<_>>().into()),
372            Event::EndCollection,
373            Event::String("Death".into()),
374            Event::Integer(1564.into()),
375            Event::String("Height".into()),
376            Event::Real(1.60),
377            Event::String("Data".into()),
378            Event::Data(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0].into()),
379            Event::String("Birthdate".into()),
380            Event::Date(super::Date::from_xml_format("1981-05-16T11:32:06Z").unwrap()),
381            Event::String("Comment".into()),
382            Event::String("2 < 3".into()), // make sure characters are escaped
383            Event::String("BiggestNumber".into()),
384            Event::Integer(18446744073709551615u64.into()),
385            Event::String("SmallestNumber".into()),
386            Event::Integer((-9223372036854775808i64).into()),
387            Event::String("IsTrue".into()),
388            Event::Boolean(true),
389            Event::String("IsNotFalse".into()),
390            Event::Boolean(false),
391            Event::EndCollection,
392        ];
393
394        let expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
395<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
396<plist version=\"1.0\">
397<dict>
398\t<key>Author</key>
399\t<string>William Shakespeare</string>
400\t<key>Lines</key>
401\t<array>
402\t\t<string>It is a tale told by an idiot,</string>
403\t\t<string>Full of sound and fury, signifying nothing.</string>
404\t\t<data>
405\t\tAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEy
406\t\tMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2Rl
407\t\tZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=
408\t\t</data>
409\t</array>
410\t<key>Death</key>
411\t<integer>1564</integer>
412\t<key>Height</key>
413\t<real>1.6</real>
414\t<key>Data</key>
415\t<data>
416\tAAAAvgAAAAMAAAAeAAAA
417\t</data>
418\t<key>Birthdate</key>
419\t<date>1981-05-16T11:32:06Z</date>
420\t<key>Comment</key>
421\t<string>2 &lt; 3</string>
422\t<key>BiggestNumber</key>
423\t<integer>18446744073709551615</integer>
424\t<key>SmallestNumber</key>
425\t<integer>-9223372036854775808</integer>
426\t<key>IsTrue</key>
427\t<true/>
428\t<key>IsNotFalse</key>
429\t<false/>
430</dict>
431</plist>";
432
433        let actual = events_to_xml(plist, XmlWriteOptions::default());
434
435        assert_eq!(actual, expected);
436    }
437
438    #[test]
439    fn custom_indent_string() {
440        let plist = [
441            Event::StartArray(None),
442            Event::String("It is a tale told by an idiot,".into()),
443            Event::String("Full of sound and fury, signifying nothing.".into()),
444            Event::EndCollection,
445        ];
446
447        let expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
448<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
449<plist version=\"1.0\">
450<array>
451...<string>It is a tale told by an idiot,</string>
452...<string>Full of sound and fury, signifying nothing.</string>
453</array>
454</plist>";
455
456        let actual = events_to_xml(plist, XmlWriteOptions::default().indent(b'.', 3));
457
458        assert_eq!(actual, expected);
459    }
460
461    #[test]
462    fn no_root() {
463        let plist = [
464            Event::StartArray(None),
465            Event::String("It is a tale told by an idiot,".into()),
466            Event::String("Full of sound and fury, signifying nothing.".into()),
467            Event::EndCollection,
468        ];
469
470        let expected = "<array>
471\t<string>It is a tale told by an idiot,</string>
472\t<string>Full of sound and fury, signifying nothing.</string>
473</array>";
474
475        let actual = events_to_xml(plist, XmlWriteOptions::default().root_element(false));
476
477        assert_eq!(actual, expected);
478    }
479
480    fn events_to_xml<'event>(
481        events: impl IntoIterator<Item = Event<'event>>,
482        options: XmlWriteOptions,
483    ) -> String {
484        let mut cursor = Cursor::new(Vec::new());
485        let mut writer = XmlWriter::new_with_options(&mut cursor, &options);
486        for event in events {
487            writer.write(event).unwrap();
488        }
489        String::from_utf8(cursor.into_inner()).unwrap()
490    }
491}