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