1 use base64;
2 use line_wrap;
3 use std::{borrow::Cow, io::Write};
4 use xml_rs::{
5     name::Name,
6     namespace::Namespace,
7     writer::{EmitterConfig, Error as XmlWriterError, EventWriter, XmlEvent},
8 };
9 
10 use crate::{
11     error::{self, Error, ErrorKind, EventKind},
12     stream::{Writer, XmlWriteOptions},
13     Date, Integer, Uid,
14 };
15 
16 static XML_PROLOGUE: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
17 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
18 <plist version="1.0">
19 "#;
20 
21 #[derive(PartialEq)]
22 enum Element {
23     Dictionary,
24     Array,
25 }
26 
27 pub struct XmlWriter<W: Write> {
28     xml_writer: EventWriter<W>,
29     stack: Vec<Element>,
30     expecting_key: bool,
31     written_prologue: bool,
32     // Not very nice
33     empty_namespace: Namespace,
34 }
35 
36 impl<W: Write> XmlWriter<W> {
new(writer: W) -> XmlWriter<W>37     pub fn new(writer: W) -> XmlWriter<W> {
38         let opts = XmlWriteOptions::default();
39         XmlWriter::new_with_options(writer, &opts)
40     }
41 
new_with_options(writer: W, opts: &XmlWriteOptions) -> XmlWriter<W>42     pub fn new_with_options(writer: W, opts: &XmlWriteOptions) -> XmlWriter<W> {
43         let config = EmitterConfig::new()
44             .line_separator("\n")
45             .indent_string(opts.indent_str.clone())
46             .perform_indent(true)
47             .write_document_declaration(false)
48             .normalize_empty_elements(true)
49             .cdata_to_characters(true)
50             .keep_element_names_stack(false)
51             .autopad_comments(true)
52             .pad_self_closing(false);
53 
54         XmlWriter {
55             xml_writer: EventWriter::new_with_config(writer, config),
56             stack: Vec::new(),
57             expecting_key: false,
58             written_prologue: false,
59             empty_namespace: Namespace::empty(),
60         }
61     }
62 
write_element_and_value(&mut self, name: &str, value: &str) -> Result<(), Error>63     fn write_element_and_value(&mut self, name: &str, value: &str) -> Result<(), Error> {
64         self.start_element(name)?;
65         self.write_value(value)?;
66         self.end_element(name)?;
67         Ok(())
68     }
69 
start_element(&mut self, name: &str) -> Result<(), Error>70     fn start_element(&mut self, name: &str) -> Result<(), Error> {
71         self.xml_writer
72             .write(XmlEvent::StartElement {
73                 name: Name::local(name),
74                 attributes: Cow::Borrowed(&[]),
75                 namespace: Cow::Borrowed(&self.empty_namespace),
76             })
77             .map_err(from_xml_error)?;
78         Ok(())
79     }
80 
end_element(&mut self, name: &str) -> Result<(), Error>81     fn end_element(&mut self, name: &str) -> Result<(), Error> {
82         self.xml_writer
83             .write(XmlEvent::EndElement {
84                 name: Some(Name::local(name)),
85             })
86             .map_err(from_xml_error)?;
87         Ok(())
88     }
89 
write_value(&mut self, value: &str) -> Result<(), Error>90     fn write_value(&mut self, value: &str) -> Result<(), Error> {
91         self.xml_writer
92             .write(XmlEvent::Characters(value))
93             .map_err(from_xml_error)?;
94         Ok(())
95     }
96 
into_inner(self) -> W97     pub fn into_inner(self) -> W {
98         self.xml_writer.into_inner()
99     }
100 
write_event<F: FnOnce(&mut Self) -> Result<(), Error>>( &mut self, f: F, ) -> Result<(), Error>101     fn write_event<F: FnOnce(&mut Self) -> Result<(), Error>>(
102         &mut self,
103         f: F,
104     ) -> Result<(), Error> {
105         if !self.written_prologue {
106             self.xml_writer
107                 .inner_mut()
108                 .write_all(XML_PROLOGUE.as_bytes())
109                 .map_err(error::from_io_without_position)?;
110 
111             self.written_prologue = true;
112         }
113 
114         f(self)?;
115 
116         // If there are no more open tags then write the </plist> element
117         if self.stack.is_empty() {
118             // We didn't tell the xml_writer about the <plist> tag so we'll skip telling it
119             // about the </plist> tag as well.
120             self.xml_writer
121                 .inner_mut()
122                 .write_all(b"\n</plist>")
123                 .map_err(error::from_io_without_position)?;
124             self.xml_writer
125                 .inner_mut()
126                 .flush()
127                 .map_err(error::from_io_without_position)?;
128         }
129 
130         Ok(())
131     }
132 
write_value_event<F: FnOnce(&mut Self) -> Result<(), Error>>( &mut self, event_kind: EventKind, f: F, ) -> Result<(), Error>133     fn write_value_event<F: FnOnce(&mut Self) -> Result<(), Error>>(
134         &mut self,
135         event_kind: EventKind,
136         f: F,
137     ) -> Result<(), Error> {
138         self.write_event(|this| {
139             if this.expecting_key {
140                 return Err(ErrorKind::UnexpectedEventType {
141                     expected: EventKind::DictionaryKeyOrEndCollection,
142                     found: event_kind,
143                 }
144                 .without_position());
145             }
146             f(this)?;
147             this.expecting_key = this.stack.last() == Some(&Element::Dictionary);
148             Ok(())
149         })
150     }
151 }
152 
153 impl<W: Write> Writer for XmlWriter<W> {
write_start_array(&mut self, _len: Option<u64>) -> Result<(), Error>154     fn write_start_array(&mut self, _len: Option<u64>) -> Result<(), Error> {
155         self.write_value_event(EventKind::StartArray, |this| {
156             this.start_element("array")?;
157             this.stack.push(Element::Array);
158             Ok(())
159         })
160     }
161 
write_start_dictionary(&mut self, _len: Option<u64>) -> Result<(), Error>162     fn write_start_dictionary(&mut self, _len: Option<u64>) -> Result<(), Error> {
163         self.write_value_event(EventKind::StartDictionary, |this| {
164             this.start_element("dict")?;
165             this.stack.push(Element::Dictionary);
166             Ok(())
167         })
168     }
169 
write_end_collection(&mut self) -> Result<(), Error>170     fn write_end_collection(&mut self) -> Result<(), Error> {
171         self.write_event(|this| {
172             match (this.stack.pop(), this.expecting_key) {
173                 (Some(Element::Dictionary), true) => {
174                     this.end_element("dict")?;
175                 }
176                 (Some(Element::Array), _) => {
177                     this.end_element("array")?;
178                 }
179                 (Some(Element::Dictionary), false) | (None, _) => {
180                     return Err(ErrorKind::UnexpectedEventType {
181                         expected: EventKind::ValueOrStartCollection,
182                         found: EventKind::EndCollection,
183                     }
184                     .without_position());
185                 }
186             }
187             this.expecting_key = this.stack.last() == Some(&Element::Dictionary);
188             Ok(())
189         })
190     }
191 
write_boolean(&mut self, value: bool) -> Result<(), Error>192     fn write_boolean(&mut self, value: bool) -> Result<(), Error> {
193         self.write_value_event(EventKind::Boolean, |this| {
194             let value_str = if value { "true" } else { "false" };
195             this.start_element(value_str)?;
196             this.end_element(value_str)
197         })
198     }
199 
write_data(&mut self, value: &[u8]) -> Result<(), Error>200     fn write_data(&mut self, value: &[u8]) -> Result<(), Error> {
201         self.write_value_event(EventKind::Data, |this| {
202             let base64_data = base64_encode_plist(&value, this.stack.len());
203             this.write_element_and_value("data", &base64_data)
204         })
205     }
206 
write_date(&mut self, value: Date) -> Result<(), Error>207     fn write_date(&mut self, value: Date) -> Result<(), Error> {
208         self.write_value_event(EventKind::Date, |this| {
209             this.write_element_and_value("date", &value.to_rfc3339())
210         })
211     }
212 
write_integer(&mut self, value: Integer) -> Result<(), Error>213     fn write_integer(&mut self, value: Integer) -> Result<(), Error> {
214         self.write_value_event(EventKind::Integer, |this| {
215             this.write_element_and_value("integer", &value.to_string())
216         })
217     }
218 
write_real(&mut self, value: f64) -> Result<(), Error>219     fn write_real(&mut self, value: f64) -> Result<(), Error> {
220         self.write_value_event(EventKind::Real, |this| {
221             this.write_element_and_value("real", &value.to_string())
222         })
223     }
224 
write_string(&mut self, value: &str) -> Result<(), Error>225     fn write_string(&mut self, value: &str) -> Result<(), Error> {
226         self.write_event(|this| {
227             if this.expecting_key {
228                 this.write_element_and_value("key", &*value)?;
229                 this.expecting_key = false;
230             } else {
231                 this.write_element_and_value("string", &*value)?;
232                 this.expecting_key = this.stack.last() == Some(&Element::Dictionary);
233             }
234             Ok(())
235         })
236     }
237 
write_uid(&mut self, _value: Uid) -> Result<(), Error>238     fn write_uid(&mut self, _value: Uid) -> Result<(), Error> {
239         Err(ErrorKind::UidNotSupportedInXmlPlist.without_position())
240     }
241 }
242 
from_xml_error(err: XmlWriterError) -> Error243 pub(crate) fn from_xml_error(err: XmlWriterError) -> Error {
244     match err {
245         XmlWriterError::Io(err) => ErrorKind::Io(err).without_position(),
246         XmlWriterError::DocumentStartAlreadyEmitted
247         | XmlWriterError::LastElementNameNotAvailable
248         | XmlWriterError::EndElementNameIsNotEqualToLastStartElementName
249         | XmlWriterError::EndElementNameIsNotSpecified => unreachable!(),
250     }
251 }
252 
base64_encode_plist(data: &[u8], indent: usize) -> String253 fn base64_encode_plist(data: &[u8], indent: usize) -> String {
254     // XML plist data elements are always formatted by apple tools as
255     // <data>
256     // AAAA..AA (68 characters per line)
257     // </data>
258     // Allocate space for base 64 string and line endings up front
259     const LINE_LEN: usize = 68;
260     let mut line_ending = Vec::with_capacity(1 + indent);
261     line_ending.push(b'\n');
262     (0..indent).for_each(|_| line_ending.push(b'\t'));
263 
264     // Find the max length of `data` encoded as a base 64 string with padding
265     let base64_max_string_len = data.len() * 4 / 3 + 4;
266 
267     // Find the max length of the formatted base 64 string as: max length of the base 64 string
268     // + line endings and indents at the start of the string and after every line
269     let base64_max_string_len_with_formatting =
270         base64_max_string_len + (2 + base64_max_string_len / LINE_LEN) * line_ending.len();
271 
272     let mut output = vec![0; base64_max_string_len_with_formatting];
273 
274     // Start output with a line ending and indent
275     output[..line_ending.len()].copy_from_slice(&line_ending);
276 
277     // Encode `data` as a base 64 string
278     let base64_string_len =
279         base64::encode_config_slice(data, base64::STANDARD, &mut output[line_ending.len()..]);
280 
281     // Line wrap the base 64 encoded string
282     let line_wrap_len = line_wrap::line_wrap(
283         &mut output[line_ending.len()..],
284         base64_string_len,
285         LINE_LEN,
286         &line_wrap::SliceLineEnding::new(&line_ending),
287     );
288 
289     // Add the final line ending and indent
290     output[line_ending.len() + base64_string_len + line_wrap_len..][..line_ending.len()]
291         .copy_from_slice(&line_ending);
292 
293     // Ensure output is the correct length
294     output.truncate(base64_string_len + line_wrap_len + 2 * line_ending.len());
295     String::from_utf8(output).expect("base 64 string must be valid utf8")
296 }
297 
298 #[cfg(test)]
299 mod tests {
300     use std::io::Cursor;
301 
302     use super::*;
303     use crate::stream::Event;
304 
305     #[test]
streaming_parser()306     fn streaming_parser() {
307         let plist = &[
308             Event::StartDictionary(None),
309             Event::String("Author".into()),
310             Event::String("William Shakespeare".into()),
311             Event::String("Lines".into()),
312             Event::StartArray(None),
313             Event::String("It is a tale told by an idiot,".into()),
314             Event::String("Full of sound and fury, signifying nothing.".into()),
315             Event::Data((0..128).collect::<Vec<_>>().into()),
316             Event::EndCollection,
317             Event::String("Death".into()),
318             Event::Integer(1564.into()),
319             Event::String("Height".into()),
320             Event::Real(1.60),
321             Event::String("Data".into()),
322             Event::Data(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0].into()),
323             Event::String("Birthdate".into()),
324             Event::Date(super::Date::from_rfc3339("1981-05-16T11:32:06Z").unwrap()),
325             Event::String("Comment".into()),
326             Event::String("2 < 3".into()), // make sure characters are escaped
327             Event::String("BiggestNumber".into()),
328             Event::Integer(18446744073709551615u64.into()),
329             Event::String("SmallestNumber".into()),
330             Event::Integer((-9223372036854775808i64).into()),
331             Event::String("IsTrue".into()),
332             Event::Boolean(true),
333             Event::String("IsNotFalse".into()),
334             Event::Boolean(false),
335             Event::EndCollection,
336         ];
337 
338         let mut cursor = Cursor::new(Vec::new());
339 
340         {
341             let mut plist_w = XmlWriter::new(&mut cursor);
342 
343             for item in plist {
344                 plist_w.write(item).unwrap();
345             }
346         }
347 
348         let comparison = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
349 <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
350 <plist version=\"1.0\">
351 <dict>
352 \t<key>Author</key>
353 \t<string>William Shakespeare</string>
354 \t<key>Lines</key>
355 \t<array>
356 \t\t<string>It is a tale told by an idiot,</string>
357 \t\t<string>Full of sound and fury, signifying nothing.</string>
358 \t\t<data>
359 \t\tAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEy
360 \t\tMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2Rl
361 \t\tZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=
362 \t\t</data>
363 \t</array>
364 \t<key>Death</key>
365 \t<integer>1564</integer>
366 \t<key>Height</key>
367 \t<real>1.6</real>
368 \t<key>Data</key>
369 \t<data>
370 \tAAAAvgAAAAMAAAAeAAAA
371 \t</data>
372 \t<key>Birthdate</key>
373 \t<date>1981-05-16T11:32:06Z</date>
374 \t<key>Comment</key>
375 \t<string>2 &lt; 3</string>
376 \t<key>BiggestNumber</key>
377 \t<integer>18446744073709551615</integer>
378 \t<key>SmallestNumber</key>
379 \t<integer>-9223372036854775808</integer>
380 \t<key>IsTrue</key>
381 \t<true/>
382 \t<key>IsNotFalse</key>
383 \t<false/>
384 </dict>
385 </plist>";
386 
387         let s = String::from_utf8(cursor.into_inner()).unwrap();
388 
389         assert_eq!(s, comparison);
390     }
391 }
392