use base64; use line_wrap; use std::{borrow::Cow, io::Write}; use xml_rs::{ name::Name, namespace::Namespace, writer::{EmitterConfig, Error as XmlWriterError, EventWriter, XmlEvent}, }; use crate::{ error::{self, Error, ErrorKind, EventKind}, stream::{Writer, XmlWriteOptions}, Date, Integer, Uid, }; static XML_PROLOGUE: &str = r#" "#; #[derive(PartialEq)] enum Element { Dictionary, Array, } pub struct XmlWriter { xml_writer: EventWriter, stack: Vec, expecting_key: bool, written_prologue: bool, // Not very nice empty_namespace: Namespace, } impl XmlWriter { pub fn new(writer: W) -> XmlWriter { let opts = XmlWriteOptions::default(); XmlWriter::new_with_options(writer, &opts) } pub fn new_with_options(writer: W, opts: &XmlWriteOptions) -> XmlWriter { let config = EmitterConfig::new() .line_separator("\n") .indent_string(opts.indent_str.clone()) .perform_indent(true) .write_document_declaration(false) .normalize_empty_elements(true) .cdata_to_characters(true) .keep_element_names_stack(false) .autopad_comments(true) .pad_self_closing(false); XmlWriter { xml_writer: EventWriter::new_with_config(writer, config), stack: Vec::new(), expecting_key: false, written_prologue: false, empty_namespace: Namespace::empty(), } } fn write_element_and_value(&mut self, name: &str, value: &str) -> Result<(), Error> { self.start_element(name)?; self.write_value(value)?; self.end_element(name)?; Ok(()) } fn start_element(&mut self, name: &str) -> Result<(), Error> { self.xml_writer .write(XmlEvent::StartElement { name: Name::local(name), attributes: Cow::Borrowed(&[]), namespace: Cow::Borrowed(&self.empty_namespace), }) .map_err(from_xml_error)?; Ok(()) } fn end_element(&mut self, name: &str) -> Result<(), Error> { self.xml_writer .write(XmlEvent::EndElement { name: Some(Name::local(name)), }) .map_err(from_xml_error)?; Ok(()) } fn write_value(&mut self, value: &str) -> Result<(), Error> { self.xml_writer .write(XmlEvent::Characters(value)) .map_err(from_xml_error)?; Ok(()) } pub fn into_inner(self) -> W { self.xml_writer.into_inner() } fn write_event Result<(), Error>>( &mut self, f: F, ) -> Result<(), Error> { if !self.written_prologue { self.xml_writer .inner_mut() .write_all(XML_PROLOGUE.as_bytes()) .map_err(error::from_io_without_position)?; self.written_prologue = true; } f(self)?; // If there are no more open tags then write the element if self.stack.is_empty() { // We didn't tell the xml_writer about the tag so we'll skip telling it // about the tag as well. self.xml_writer .inner_mut() .write_all(b"\n") .map_err(error::from_io_without_position)?; self.xml_writer .inner_mut() .flush() .map_err(error::from_io_without_position)?; } Ok(()) } fn write_value_event Result<(), Error>>( &mut self, event_kind: EventKind, f: F, ) -> Result<(), Error> { self.write_event(|this| { if this.expecting_key { return Err(ErrorKind::UnexpectedEventType { expected: EventKind::DictionaryKeyOrEndCollection, found: event_kind, } .without_position()); } f(this)?; this.expecting_key = this.stack.last() == Some(&Element::Dictionary); Ok(()) }) } } impl Writer for XmlWriter { fn write_start_array(&mut self, _len: Option) -> Result<(), Error> { self.write_value_event(EventKind::StartArray, |this| { this.start_element("array")?; this.stack.push(Element::Array); Ok(()) }) } fn write_start_dictionary(&mut self, _len: Option) -> Result<(), Error> { self.write_value_event(EventKind::StartDictionary, |this| { this.start_element("dict")?; this.stack.push(Element::Dictionary); Ok(()) }) } fn write_end_collection(&mut self) -> Result<(), Error> { self.write_event(|this| { match (this.stack.pop(), this.expecting_key) { (Some(Element::Dictionary), true) => { this.end_element("dict")?; } (Some(Element::Array), _) => { this.end_element("array")?; } (Some(Element::Dictionary), false) | (None, _) => { return Err(ErrorKind::UnexpectedEventType { expected: EventKind::ValueOrStartCollection, found: EventKind::EndCollection, } .without_position()); } } this.expecting_key = this.stack.last() == Some(&Element::Dictionary); Ok(()) }) } fn write_boolean(&mut self, value: bool) -> Result<(), Error> { self.write_value_event(EventKind::Boolean, |this| { let value_str = if value { "true" } else { "false" }; this.start_element(value_str)?; this.end_element(value_str) }) } fn write_data(&mut self, value: &[u8]) -> Result<(), Error> { self.write_value_event(EventKind::Data, |this| { let base64_data = base64_encode_plist(&value, this.stack.len()); this.write_element_and_value("data", &base64_data) }) } fn write_date(&mut self, value: Date) -> Result<(), Error> { self.write_value_event(EventKind::Date, |this| { this.write_element_and_value("date", &value.to_rfc3339()) }) } fn write_integer(&mut self, value: Integer) -> Result<(), Error> { self.write_value_event(EventKind::Integer, |this| { this.write_element_and_value("integer", &value.to_string()) }) } fn write_real(&mut self, value: f64) -> Result<(), Error> { self.write_value_event(EventKind::Real, |this| { this.write_element_and_value("real", &value.to_string()) }) } fn write_string(&mut self, value: &str) -> Result<(), Error> { self.write_event(|this| { if this.expecting_key { this.write_element_and_value("key", &*value)?; this.expecting_key = false; } else { this.write_element_and_value("string", &*value)?; this.expecting_key = this.stack.last() == Some(&Element::Dictionary); } Ok(()) }) } fn write_uid(&mut self, _value: Uid) -> Result<(), Error> { Err(ErrorKind::UidNotSupportedInXmlPlist.without_position()) } } pub(crate) fn from_xml_error(err: XmlWriterError) -> Error { match err { XmlWriterError::Io(err) => ErrorKind::Io(err).without_position(), XmlWriterError::DocumentStartAlreadyEmitted | XmlWriterError::LastElementNameNotAvailable | XmlWriterError::EndElementNameIsNotEqualToLastStartElementName | XmlWriterError::EndElementNameIsNotSpecified => unreachable!(), } } fn base64_encode_plist(data: &[u8], indent: usize) -> String { // XML plist data elements are always formatted by apple tools as // // AAAA..AA (68 characters per line) // // Allocate space for base 64 string and line endings up front const LINE_LEN: usize = 68; let mut line_ending = Vec::with_capacity(1 + indent); line_ending.push(b'\n'); (0..indent).for_each(|_| line_ending.push(b'\t')); // Find the max length of `data` encoded as a base 64 string with padding let base64_max_string_len = data.len() * 4 / 3 + 4; // Find the max length of the formatted base 64 string as: max length of the base 64 string // + line endings and indents at the start of the string and after every line let base64_max_string_len_with_formatting = base64_max_string_len + (2 + base64_max_string_len / LINE_LEN) * line_ending.len(); let mut output = vec![0; base64_max_string_len_with_formatting]; // Start output with a line ending and indent output[..line_ending.len()].copy_from_slice(&line_ending); // Encode `data` as a base 64 string let base64_string_len = base64::encode_config_slice(data, base64::STANDARD, &mut output[line_ending.len()..]); // Line wrap the base 64 encoded string let line_wrap_len = line_wrap::line_wrap( &mut output[line_ending.len()..], base64_string_len, LINE_LEN, &line_wrap::SliceLineEnding::new(&line_ending), ); // Add the final line ending and indent output[line_ending.len() + base64_string_len + line_wrap_len..][..line_ending.len()] .copy_from_slice(&line_ending); // Ensure output is the correct length output.truncate(base64_string_len + line_wrap_len + 2 * line_ending.len()); String::from_utf8(output).expect("base 64 string must be valid utf8") } #[cfg(test)] mod tests { use std::io::Cursor; use super::*; use crate::stream::Event; #[test] fn streaming_parser() { let plist = &[ Event::StartDictionary(None), Event::String("Author".into()), Event::String("William Shakespeare".into()), Event::String("Lines".into()), Event::StartArray(None), Event::String("It is a tale told by an idiot,".into()), Event::String("Full of sound and fury, signifying nothing.".into()), Event::Data((0..128).collect::>().into()), Event::EndCollection, Event::String("Death".into()), Event::Integer(1564.into()), Event::String("Height".into()), Event::Real(1.60), Event::String("Data".into()), Event::Data(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0].into()), Event::String("Birthdate".into()), Event::Date(super::Date::from_rfc3339("1981-05-16T11:32:06Z").unwrap()), Event::String("Comment".into()), Event::String("2 < 3".into()), // make sure characters are escaped Event::String("BiggestNumber".into()), Event::Integer(18446744073709551615u64.into()), Event::String("SmallestNumber".into()), Event::Integer((-9223372036854775808i64).into()), Event::String("IsTrue".into()), Event::Boolean(true), Event::String("IsNotFalse".into()), Event::Boolean(false), Event::EndCollection, ]; let mut cursor = Cursor::new(Vec::new()); { let mut plist_w = XmlWriter::new(&mut cursor); for item in plist { plist_w.write(item).unwrap(); } } let comparison = " \tAuthor \tWilliam Shakespeare \tLines \t \t\tIt is a tale told by an idiot, \t\tFull of sound and fury, signifying nothing. \t\t \t\tAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEy \t\tMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2Rl \t\tZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8= \t\t \t \tDeath \t1564 \tHeight \t1.6 \tData \t \tAAAAvgAAAAMAAAAeAAAA \t \tBirthdate \t1981-05-16T11:32:06Z \tComment \t2 < 3 \tBiggestNumber \t18446744073709551615 \tSmallestNumber \t-9223372036854775808 \tIsTrue \t \tIsNotFalse \t "; let s = String::from_utf8(cursor.into_inner()).unwrap(); assert_eq!(s, comparison); } }