aboutsummaryrefslogtreecommitdiffstats
path: root/src/events
diff options
context:
space:
mode:
Diffstat (limited to 'src/events')
-rw-r--r--src/events/binary_reader.rs385
-rw-r--r--src/events/mod.rs95
-rw-r--r--src/events/xml_reader.rs199
-rw-r--r--src/events/xml_writer.rs259
4 files changed, 938 insertions, 0 deletions
diff --git a/src/events/binary_reader.rs b/src/events/binary_reader.rs
new file mode 100644
index 0000000..e077904
--- /dev/null
+++ b/src/events/binary_reader.rs
@@ -0,0 +1,385 @@
+use byteorder::{BigEndian, ReadBytesExt};
+use std::io::{Read, Seek, SeekFrom};
+use std::mem::size_of;
+use std::string::{FromUtf16Error, FromUtf8Error};
+
+use events::Event;
+use {u64_to_usize, Date, Error, Result};
+
+impl From<FromUtf8Error> for Error {
+ fn from(_: FromUtf8Error) -> Error {
+ Error::InvalidData
+ }
+}
+
+impl From<FromUtf16Error> for Error {
+ fn from(_: FromUtf16Error) -> Error {
+ Error::InvalidData
+ }
+}
+
+struct StackItem {
+ object_refs: Vec<u64>,
+ ty: StackType,
+}
+
+enum StackType {
+ Array,
+ Dict,
+ Root,
+}
+
+/// https://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
+/// https://hg.python.org/cpython/file/3.4/Lib/plistlib.py
+pub struct BinaryReader<R> {
+ stack: Vec<StackItem>,
+ object_offsets: Vec<u64>,
+ reader: R,
+ ref_size: u8,
+ finished: bool,
+ // The largest single allocation allowed for this Plist.
+ // Equal to the number of bytes in the Plist minus the magic and trailer.
+ max_allocation_bytes: usize,
+ // The maximum number of nested arrays and dicts allowed in the plist.
+ max_stack_depth: usize,
+ // The maximum number of objects that can be created. Default 10 * object_offsets.len().
+ // Binary plists can contain circular references.
+ max_objects: usize,
+ // The number of objects created so far.
+ current_objects: usize,
+}
+
+impl<R: Read + Seek> BinaryReader<R> {
+ pub fn new(reader: R) -> BinaryReader<R> {
+ BinaryReader {
+ stack: Vec::new(),
+ object_offsets: Vec::new(),
+ reader: reader,
+ ref_size: 0,
+ finished: false,
+ max_allocation_bytes: 0,
+ max_stack_depth: 200,
+ max_objects: 0,
+ current_objects: 0,
+ }
+ }
+
+ fn can_allocate(&self, len: u64, size: usize) -> bool {
+ let byte_len = len.saturating_mul(size as u64);
+ byte_len <= self.max_allocation_bytes as u64
+ }
+
+ fn allocate_vec<T>(&self, len: u64, size: usize) -> Result<Vec<T>> {
+ if self.can_allocate(len, size) {
+ Ok(Vec::with_capacity(len as usize))
+ } else {
+ Err(Error::InvalidData)
+ }
+ }
+
+ fn read_trailer(&mut self) -> Result<()> {
+ self.reader.seek(SeekFrom::Start(0))?;
+ let mut magic = [0; 8];
+ self.reader.read_exact(&mut magic)?;
+ if &magic != b"bplist00" {
+ return Err(Error::InvalidData);
+ }
+
+ // Trailer starts with 6 bytes of padding
+ let trailer_start = self.reader.seek(SeekFrom::End(-32 + 6))?;
+
+ let offset_size = self.reader.read_u8()?;
+ match offset_size {
+ 1 | 2 | 4 | 8 => (),
+ _ => return Err(Error::InvalidData),
+ }
+
+ self.ref_size = self.reader.read_u8()?;
+ match self.ref_size {
+ 1 | 2 | 4 | 8 => (),
+ _ => return Err(Error::InvalidData),
+ }
+
+ let num_objects = self.reader.read_u64::<BigEndian>()?;
+ let top_object = self.reader.read_u64::<BigEndian>()?;
+ let offset_table_offset = self.reader.read_u64::<BigEndian>()?;
+
+ // File size minus trailer and header
+ // Truncated to max(usize)
+ self.max_allocation_bytes = trailer_start.saturating_sub(8) as usize;
+
+ // Read offset table
+ self.reader.seek(SeekFrom::Start(offset_table_offset))?;
+ self.object_offsets = self.read_ints(num_objects, offset_size)?;
+
+ self.max_objects = self.object_offsets.len() * 10;
+
+ // Seek to top object
+ self.stack.push(StackItem {
+ object_refs: vec![top_object],
+ ty: StackType::Root,
+ });
+
+ Ok(())
+ }
+
+ fn read_ints(&mut self, len: u64, size: u8) -> Result<Vec<u64>> {
+ let mut ints = self.allocate_vec(len, size as usize)?;
+ for _ in 0..len {
+ match size {
+ 1 => ints.push(self.reader.read_u8()? as u64),
+ 2 => ints.push(self.reader.read_u16::<BigEndian>()? as u64),
+ 4 => ints.push(self.reader.read_u32::<BigEndian>()? as u64),
+ 8 => ints.push(self.reader.read_u64::<BigEndian>()? as u64),
+ _ => return Err(Error::InvalidData),
+ }
+ }
+ Ok(ints)
+ }
+
+ fn read_refs(&mut self, len: u64) -> Result<Vec<u64>> {
+ let ref_size = self.ref_size;
+ self.read_ints(len, ref_size)
+ }
+
+ fn read_object_len(&mut self, len: u8) -> Result<u64> {
+ if (len & 0x0f) == 0x0f {
+ let len_power_of_two = self.reader.read_u8()? & 0x03;
+ Ok(match len_power_of_two {
+ 0 => self.reader.read_u8()? as u64,
+ 1 => self.reader.read_u16::<BigEndian>()? as u64,
+ 2 => self.reader.read_u32::<BigEndian>()? as u64,
+ 3 => self.reader.read_u64::<BigEndian>()?,
+ _ => return Err(Error::InvalidData),
+ })
+ } else {
+ Ok(len as u64)
+ }
+ }
+
+ fn read_data(&mut self, len: u64) -> Result<Vec<u8>> {
+ let mut data = self.allocate_vec(len, size_of::<u8>())?;
+ data.resize(len as usize, 0);
+ self.reader.read_exact(&mut data)?;
+ Ok(data)
+ }
+
+ fn seek_to_object(&mut self, object_ref: u64) -> Result<u64> {
+ let object_ref = u64_to_usize(object_ref)?;
+ let offset = *self
+ .object_offsets
+ .get(object_ref)
+ .ok_or(Error::InvalidData)?;
+ Ok(self.reader.seek(SeekFrom::Start(offset))?)
+ }
+
+ fn read_next(&mut self) -> Result<Option<Event>> {
+ if self.ref_size == 0 {
+ // Initialise here rather than in new
+ self.read_trailer()?;
+ }
+
+ let object_ref = match self.stack.last_mut() {
+ Some(stack_item) => stack_item.object_refs.pop(),
+ // Reached the end of the plist
+ None => return Ok(None),
+ };
+
+ match object_ref {
+ Some(object_ref) => {
+ if self.current_objects > self.max_objects {
+ return Err(Error::InvalidData);
+ }
+ self.current_objects += 1;
+ self.seek_to_object(object_ref)?;
+ }
+ None => {
+ // We're at the end of an array or dict. Pop the top stack item and return
+ let item = self.stack.pop().unwrap();
+ match item.ty {
+ StackType::Array => return Ok(Some(Event::EndArray)),
+ StackType::Dict => return Ok(Some(Event::EndDictionary)),
+ // We're at the end of the plist
+ StackType::Root => return Ok(None),
+ }
+ }
+ }
+
+ let token = self.reader.read_u8()?;
+ let ty = (token & 0xf0) >> 4;
+ let size = token & 0x0f;
+
+ let result = match (ty, size) {
+ (0x0, 0x00) => return Err(Error::InvalidData), // null
+ (0x0, 0x08) => Some(Event::BooleanValue(false)),
+ (0x0, 0x09) => Some(Event::BooleanValue(true)),
+ (0x0, 0x0f) => return Err(Error::InvalidData), // fill
+ (0x1, 0) => Some(Event::IntegerValue(self.reader.read_u8()? as i64)),
+ (0x1, 1) => Some(Event::IntegerValue(
+ self.reader.read_u16::<BigEndian>()? as i64
+ )),
+ (0x1, 2) => Some(Event::IntegerValue(
+ self.reader.read_u32::<BigEndian>()? as i64
+ )),
+ (0x1, 3) => Some(Event::IntegerValue(self.reader.read_i64::<BigEndian>()?)),
+ (0x1, 4) => return Err(Error::InvalidData), // 128 bit int
+ (0x1, _) => return Err(Error::InvalidData), // variable length int
+ (0x2, 2) => Some(Event::RealValue(self.reader.read_f32::<BigEndian>()? as f64)),
+ (0x2, 3) => Some(Event::RealValue(self.reader.read_f64::<BigEndian>()?)),
+ (0x2, _) => return Err(Error::InvalidData), // odd length float
+ (0x3, 3) => {
+ // Date. Seconds since 1/1/2001 00:00:00.
+ let secs = self.reader.read_f64::<BigEndian>()?;
+ Some(Event::DateValue(Date::from_seconds_since_plist_epoch(
+ secs,
+ )?))
+ }
+ (0x4, n) => {
+ // Data
+ let len = self.read_object_len(n)?;
+ Some(Event::DataValue(self.read_data(len)?))
+ }
+ (0x5, n) => {
+ // ASCII string
+ let len = self.read_object_len(n)?;
+ let raw = self.read_data(len)?;
+ let string = String::from_utf8(raw)?;
+ Some(Event::StringValue(string))
+ }
+ (0x6, n) => {
+ // UTF-16 string
+ let len_utf16_codepoints = self.read_object_len(n)?;
+ let mut raw_utf16 = self.allocate_vec(len_utf16_codepoints, size_of::<u16>())?;
+
+ for _ in 0..len_utf16_codepoints {
+ raw_utf16.push(self.reader.read_u16::<BigEndian>()?);
+ }
+
+ let string = String::from_utf16(&raw_utf16)?;
+ Some(Event::StringValue(string))
+ }
+ (0xa, n) => {
+ // Array
+ let len = self.read_object_len(n)?;
+ let mut object_refs = self.read_refs(len)?;
+ // Reverse so we can pop off the end of the stack in order
+ object_refs.reverse();
+
+ self.stack.push(StackItem {
+ ty: StackType::Array,
+ object_refs: object_refs,
+ });
+
+ Some(Event::StartArray(Some(len)))
+ }
+ (0xd, n) => {
+ // Dict
+ let len = self.read_object_len(n)?;
+ let key_refs = self.read_refs(len)?;
+ let value_refs = self.read_refs(len)?;
+
+ let mut object_refs = self.allocate_vec(len * 2, self.ref_size as usize)?;
+ let len = key_refs.len();
+ for i in 1..len + 1 {
+ // Reverse so we can pop off the end of the stack in order
+ object_refs.push(value_refs[len - i]);
+ object_refs.push(key_refs[len - i]);
+ }
+
+ self.stack.push(StackItem {
+ ty: StackType::Dict,
+ object_refs: object_refs,
+ });
+
+ Some(Event::StartDictionary(Some(len as u64)))
+ }
+ (_, _) => return Err(Error::InvalidData),
+ };
+
+ // Prevent stack overflows when recursively parsing plist.
+ if self.stack.len() > self.max_stack_depth {
+ return Err(Error::InvalidData);
+ }
+
+ Ok(result)
+ }
+}
+
+impl<R: Read + Seek> Iterator for BinaryReader<R> {
+ type Item = Result<Event>;
+
+ fn next(&mut self) -> Option<Result<Event>> {
+ if self.finished {
+ None
+ } else {
+ match self.read_next() {
+ Ok(Some(event)) => Some(Ok(event)),
+ Err(err) => {
+ self.finished = true;
+ Some(Err(err))
+ }
+ Ok(None) => {
+ self.finished = true;
+ None
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use humantime::parse_rfc3339_weak;
+ use std::fs::File;
+ use std::path::Path;
+
+ use super::*;
+ use events::Event;
+ use events::Event::*;
+
+ #[test]
+ fn streaming_parser() {
+ let reader = File::open(&Path::new("./tests/data/binary.plist")).unwrap();
+ let streaming_parser = BinaryReader::new(reader);
+ let events: Vec<Event> = streaming_parser.map(|e| e.unwrap()).collect();
+
+ let comparison = &[
+ StartDictionary(Some(6)),
+ StringValue("Lines".to_owned()),
+ StartArray(Some(2)),
+ StringValue("It is a tale told by an idiot,".to_owned()),
+ StringValue("Full of sound and fury, signifying nothing.".to_owned()),
+ EndArray,
+ StringValue("Death".to_owned()),
+ IntegerValue(1564),
+ StringValue("Height".to_owned()),
+ RealValue(1.60),
+ StringValue("Birthdate".to_owned()),
+ DateValue(parse_rfc3339_weak("1981-05-16 11:32:06").unwrap().into()),
+ StringValue("Author".to_owned()),
+ StringValue("William Shakespeare".to_owned()),
+ StringValue("Data".to_owned()),
+ DataValue(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0]),
+ EndDictionary,
+ ];
+
+ assert_eq!(events, comparison);
+ }
+
+ #[test]
+ fn utf16_plist() {
+ let reader = File::open(&Path::new("./tests/data/utf16_bplist.plist")).unwrap();
+ let streaming_parser = BinaryReader::new(reader);
+ let mut events: Vec<Event> = streaming_parser.map(|e| e.unwrap()).collect();
+
+ assert_eq!(events[2], StringValue("\u{2605} or better".to_owned()));
+
+ let poem = if let StringValue(ref mut poem) = events[4] {
+ poem
+ } else {
+ panic!("not a string")
+ };
+ assert_eq!(poem.len(), 643);
+ assert_eq!(poem.pop().unwrap(), '\u{2605}');
+ }
+}
diff --git a/src/events/mod.rs b/src/events/mod.rs
new file mode 100644
index 0000000..b933ceb
--- /dev/null
+++ b/src/events/mod.rs
@@ -0,0 +1,95 @@
+mod binary_reader;
+pub use self::binary_reader::BinaryReader;
+
+mod xml_reader;
+pub use self::xml_reader::XmlReader;
+
+mod xml_writer;
+pub use self::xml_writer::XmlWriter;
+
+use std::io::{Read, Seek, SeekFrom};
+use {Date, Result};
+
+/// An encoding of a plist as a flat structure.
+///
+/// Output by the event readers.
+///
+/// Dictionary keys and values are represented as pairs of values e.g.:
+///
+/// ```ignore rust
+/// StartDictionary
+/// StringValue("Height") // Key
+/// RealValue(181.2) // Value
+/// StringValue("Age") // Key
+/// IntegerValue(28) // Value
+/// EndDictionary
+/// ```
+#[derive(Clone, Debug, PartialEq)]
+pub enum Event {
+ // While the length of an array or dict cannot be feasably greater than max(usize) this better
+ // conveys the concept of an effectively unbounded event stream.
+ StartArray(Option<u64>),
+ EndArray,
+
+ StartDictionary(Option<u64>),
+ EndDictionary,
+
+ BooleanValue(bool),
+ DataValue(Vec<u8>),
+ DateValue(Date),
+ IntegerValue(i64),
+ RealValue(f64),
+ StringValue(String),
+}
+
+pub struct Reader<R: Read + Seek>(ReaderInner<R>);
+
+enum ReaderInner<R: Read + Seek> {
+ Uninitialized(Option<R>),
+ Xml(XmlReader<R>),
+ Binary(BinaryReader<R>),
+}
+
+impl<R: Read + Seek> Reader<R> {
+ pub fn new(reader: R) -> Reader<R> {
+ Reader(ReaderInner::Uninitialized(Some(reader)))
+ }
+
+ fn is_binary(reader: &mut R) -> Result<bool> {
+ reader.seek(SeekFrom::Start(0))?;
+ let mut magic = [0; 8];
+ reader.read_exact(&mut magic)?;
+ reader.seek(SeekFrom::Start(0))?;
+
+ Ok(&magic == b"bplist00")
+ }
+}
+
+impl<R: Read + Seek> Iterator for Reader<R> {
+ type Item = Result<Event>;
+
+ fn next(&mut self) -> Option<Result<Event>> {
+ let mut reader = match self.0 {
+ ReaderInner::Xml(ref mut parser) => return parser.next(),
+ ReaderInner::Binary(ref mut parser) => return parser.next(),
+ ReaderInner::Uninitialized(ref mut reader) => reader.take().unwrap(),
+ };
+
+ let event_reader = match Reader::is_binary(&mut reader) {
+ Ok(true) => ReaderInner::Binary(BinaryReader::new(reader)),
+ Ok(false) => ReaderInner::Xml(XmlReader::new(reader)),
+ Err(err) => {
+ ::std::mem::replace(&mut self.0, ReaderInner::Uninitialized(Some(reader)));
+ return Some(Err(err));
+ }
+ };
+
+ ::std::mem::replace(&mut self.0, event_reader);
+
+ self.next()
+ }
+}
+
+pub trait Writer {
+ fn write(&mut self, event: &Event) -> Result<()>;
+}
diff --git a/src/events/xml_reader.rs b/src/events/xml_reader.rs
new file mode 100644
index 0000000..46e0062
--- /dev/null
+++ b/src/events/xml_reader.rs
@@ -0,0 +1,199 @@
+use base64;
+use std::io::Read;
+use std::str::FromStr;
+use xml_rs::reader::{EventReader, ParserConfig, XmlEvent};
+
+use events::Event;
+use {Date, Error, Result};
+
+pub struct XmlReader<R: Read> {
+ xml_reader: EventReader<R>,
+ queued_event: Option<XmlEvent>,
+ element_stack: Vec<String>,
+ finished: bool,
+}
+
+impl<R: Read> XmlReader<R> {
+ pub fn new(reader: R) -> XmlReader<R> {
+ let config = ParserConfig::new()
+ .trim_whitespace(false)
+ .whitespace_to_characters(true)
+ .cdata_to_characters(true)
+ .ignore_comments(true)
+ .coalesce_characters(true);
+
+ XmlReader {
+ xml_reader: EventReader::new_with_config(reader, config),
+ queued_event: None,
+ element_stack: Vec::new(),
+ finished: false,
+ }
+ }
+
+ fn read_content<F>(&mut self, f: F) -> Result<Event>
+ where
+ F: FnOnce(String) -> Result<Event>,
+ {
+ match self.xml_reader.next() {
+ Ok(XmlEvent::Characters(s)) => f(s),
+ Ok(event @ XmlEvent::EndElement { .. }) => {
+ self.queued_event = Some(event);
+ f("".to_owned())
+ }
+ _ => Err(Error::InvalidData),
+ }
+ }
+
+ fn next_event(&mut self) -> ::std::result::Result<XmlEvent, ()> {
+ if let Some(event) = self.queued_event.take() {
+ Ok(event)
+ } else {
+ self.xml_reader.next().map_err(|_| ())
+ }
+ }
+
+ fn read_next(&mut self) -> Option<Result<Event>> {
+ loop {
+ match self.next_event() {
+ Ok(XmlEvent::StartElement { name, .. }) => {
+ // Add the current element to the element stack
+ self.element_stack.push(name.local_name.clone());
+
+ match &name.local_name[..] {
+ "plist" => (),
+ "array" => return Some(Ok(Event::StartArray(None))),
+ "dict" => return Some(Ok(Event::StartDictionary(None))),
+ "key" => return Some(self.read_content(|s| Ok(Event::StringValue(s)))),
+ "true" => return Some(Ok(Event::BooleanValue(true))),
+ "false" => return Some(Ok(Event::BooleanValue(false))),
+ "data" => {
+ return Some(self.read_content(|s| {
+ let data = base64::decode_config(&s, base64::MIME)
+ .map_err(|_| Error::InvalidData)?;
+ Ok(Event::DataValue(data))
+ }))
+ }
+ "date" => {
+ return Some(
+ self.read_content(|s| {
+ Ok(Event::DateValue(Date::from_rfc3339(&s)?))
+ }),
+ )
+ }
+ "integer" => {
+ return Some(self.read_content(|s| match FromStr::from_str(&s) {
+ Ok(i) => Ok(Event::IntegerValue(i)),
+ Err(_) => Err(Error::InvalidData),
+ }))
+ }
+ "real" => {
+ return Some(self.read_content(|s| match FromStr::from_str(&s) {
+ Ok(f) => Ok(Event::RealValue(f)),
+ Err(_) => Err(Error::InvalidData),
+ }))
+ }
+ "string" => return Some(self.read_content(|s| Ok(Event::StringValue(s)))),
+ _ => return Some(Err(Error::InvalidData)),
+ }
+ }
+ Ok(XmlEvent::EndElement { name, .. }) => {
+ // Check the corrent element is being closed
+ match self.element_stack.pop() {
+ Some(ref open_name) if &name.local_name == open_name => (),
+ Some(ref _open_name) => return Some(Err(Error::InvalidData)),
+ None => return Some(Err(Error::InvalidData)),
+ }
+
+ match &name.local_name[..] {
+ "array" => return Some(Ok(Event::EndArray)),
+ "dict" => return Some(Ok(Event::EndDictionary)),
+ "plist" => (),
+ _ => (),
+ }
+ }
+ Ok(XmlEvent::EndDocument) => {
+ if self.element_stack.is_empty() {
+ return None;
+ } else {
+ return Some(Err(Error::UnexpectedEof));
+ }
+ }
+ Err(_) => return Some(Err(Error::InvalidData)),
+ _ => (),
+ }
+ }
+ }
+}
+
+impl<R: Read> Iterator for XmlReader<R> {
+ type Item = Result<Event>;
+
+ fn next(&mut self) -> Option<Result<Event>> {
+ if self.finished {
+ None
+ } else {
+ match self.read_next() {
+ Some(Ok(event)) => Some(Ok(event)),
+ Some(Err(err)) => {
+ self.finished = true;
+ Some(Err(err))
+ }
+ None => {
+ self.finished = true;
+ None
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use humantime::parse_rfc3339_weak;
+ use std::fs::File;
+ use std::path::Path;
+
+ use super::*;
+ use events::Event;
+ use events::Event::*;
+
+ #[test]
+ fn streaming_parser() {
+ let reader = File::open(&Path::new("./tests/data/xml.plist")).unwrap();
+ let streaming_parser = XmlReader::new(reader);
+ let events: Vec<Event> = streaming_parser.map(|e| e.unwrap()).collect();
+
+ let comparison = &[
+ StartDictionary(None),
+ StringValue("Author".to_owned()),
+ StringValue("William Shakespeare".to_owned()),
+ StringValue("Lines".to_owned()),
+ StartArray(None),
+ StringValue("It is a tale told by an idiot,".to_owned()),
+ StringValue("Full of sound and fury, signifying nothing.".to_owned()),
+ EndArray,
+ StringValue("Death".to_owned()),
+ IntegerValue(1564),
+ StringValue("Height".to_owned()),
+ RealValue(1.60),
+ StringValue("Data".to_owned()),
+ DataValue(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0]),
+ StringValue("Birthdate".to_owned()),
+ DateValue(parse_rfc3339_weak("1981-05-16 11:32:06").unwrap().into()),
+ StringValue("Blank".to_owned()),
+ StringValue("".to_owned()),
+ EndDictionary,
+ ];
+
+ assert_eq!(events, comparison);
+ }
+
+ #[test]
+ fn bad_data() {
+ let reader = File::open(&Path::new("./tests/data/xml_error.plist")).unwrap();
+ let streaming_parser = XmlReader::new(reader);
+ let events: Vec<_> = streaming_parser.collect();
+
+ assert!(events.last().unwrap().is_err());
+ }
+}
diff --git a/src/events/xml_writer.rs b/src/events/xml_writer.rs
new file mode 100644
index 0000000..f89ea81
--- /dev/null
+++ b/src/events/xml_writer.rs
@@ -0,0 +1,259 @@
+use base64;
+use std::borrow::Cow;
+use std::io::Write;
+use xml_rs::name::Name;
+use xml_rs::namespace::Namespace;
+use xml_rs::writer::{EmitterConfig, Error as XmlWriterError, EventWriter, XmlEvent};
+
+use events::{Event, Writer};
+use {Error, Result};
+
+impl From<XmlWriterError> for Error {
+ fn from(err: XmlWriterError) -> Error {
+ match err {
+ XmlWriterError::Io(err) => Error::Io(err),
+ _ => Error::InvalidData,
+ }
+ }
+}
+
+enum Element {
+ Dictionary(DictionaryState),
+ Array,
+ Root,
+}
+
+enum DictionaryState {
+ ExpectKey,
+ ExpectValue,
+}
+
+pub struct XmlWriter<W: Write> {
+ xml_writer: EventWriter<W>,
+ stack: Vec<Element>,
+ // Not very nice
+ empty_namespace: Namespace,
+}
+
+impl<W: Write> XmlWriter<W> {
+ pub fn new(writer: W) -> XmlWriter<W> {
+ let config = EmitterConfig::new()
+ .line_separator("\n")
+ .indent_string("\t")
+ .perform_indent(true)
+ .write_document_declaration(false)
+ .normalize_empty_elements(true)
+ .cdata_to_characters(true)
+ .keep_element_names_stack(false)
+ .autopad_comments(true);
+
+ XmlWriter {
+ xml_writer: EventWriter::new_with_config(writer, config),
+ stack: Vec::new(),
+ empty_namespace: Namespace::empty(),
+ }
+ }
+
+ fn write_element_and_value(&mut self, name: &str, value: &str) -> Result<()> {
+ self.start_element(name)?;
+ self.write_value(value)?;
+ self.end_element(name)?;
+ Ok(())
+ }
+
+ fn start_element(&mut self, name: &str) -> Result<()> {
+ self.xml_writer.write(XmlEvent::StartElement {
+ name: Name::local(name),
+ attributes: Cow::Borrowed(&[]),
+ namespace: Cow::Borrowed(&self.empty_namespace),
+ })?;
+ Ok(())
+ }
+
+ fn end_element(&mut self, name: &str) -> Result<()> {
+ self.xml_writer.write(XmlEvent::EndElement {
+ name: Some(Name::local(name)),
+ })?;
+ Ok(())
+ }
+
+ fn write_value(&mut self, value: &str) -> Result<()> {
+ self.xml_writer.write(XmlEvent::Characters(value))?;
+ Ok(())
+ }
+
+ fn maybe_end_plist(&mut self) -> Result<()> {
+ // If there are no more open tags then write the </plist> element
+ if self.stack.len() == 1 {
+ // We didn't tell the xml_writer about the <plist> tag so we'll skip telling it
+ // about the </plist> tag as well.
+ self.xml_writer.inner_mut().write(b"\n</plist>")?;
+ if let Some(Element::Root) = self.stack.pop() {
+ } else {
+ return Err(Error::InvalidData);
+ }
+ }
+ Ok(())
+ }
+
+ pub fn write(&mut self, event: &Event) -> Result<()> {
+ <Self as Writer>::write(self, event)
+ }
+}
+
+impl<W: Write> Writer for XmlWriter<W> {
+ fn write(&mut self, event: &Event) -> Result<()> {
+ match self.stack.pop() {
+ Some(Element::Dictionary(DictionaryState::ExpectKey)) => {
+ match *event {
+ Event::StringValue(ref value) => {
+ self.write_element_and_value("key", &*value)?;
+ self.stack
+ .push(Element::Dictionary(DictionaryState::ExpectValue));
+ }
+ Event::EndDictionary => {
+ self.end_element("dict")?;
+ // We might be closing the last tag here as well
+ self.maybe_end_plist()?;
+ }
+ _ => return Err(Error::InvalidData),
+ };
+ return Ok(());
+ }
+ Some(Element::Dictionary(DictionaryState::ExpectValue)) => self
+ .stack
+ .push(Element::Dictionary(DictionaryState::ExpectKey)),
+ Some(other) => self.stack.push(other),
+ None => {
+ // Write prologue
+ let prologue = r#"<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+"#;
+ self.xml_writer.inner_mut().write(prologue.as_bytes())?;
+
+ self.stack.push(Element::Root);
+ }
+ }
+
+ match *event {
+ Event::StartArray(_) => {
+ self.start_element("array")?;
+ self.stack.push(Element::Array);
+ }
+ Event::EndArray => {
+ self.end_element("array")?;
+ if let Some(Element::Array) = self.stack.pop() {
+ } else {
+ return Err(Error::InvalidData);
+ }
+ }
+
+ Event::StartDictionary(_) => {
+ self.start_element("dict")?;
+ self.stack
+ .push(Element::Dictionary(DictionaryState::ExpectKey));
+ }
+ Event::EndDictionary => return Err(Error::InvalidData),
+
+ Event::BooleanValue(true) => {
+ self.start_element("true")?;
+ self.end_element("true")?;
+ }
+ Event::BooleanValue(false) => {
+ self.start_element("false")?;
+ self.end_element("false")?;
+ }
+ Event::DataValue(ref value) => {
+ let base64_data = base64::encode_config(&value, base64::MIME);
+ self.write_element_and_value("data", &base64_data)?;
+ }
+ Event::DateValue(ref value) => {
+ self.write_element_and_value("date", &value.to_rfc3339())?
+ }
+ Event::IntegerValue(ref value) => {
+ self.write_element_and_value("integer", &value.to_string())?
+ }
+ Event::RealValue(ref value) => {
+ self.write_element_and_value("real", &value.to_string())?
+ }
+ Event::StringValue(ref value) => self.write_element_and_value("string", &*value)?,
+ };
+
+ self.maybe_end_plist()?;
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use humantime::parse_rfc3339_weak;
+ use std::io::Cursor;
+
+ use super::*;
+ use events::Event::*;
+
+ #[test]
+ fn streaming_parser() {
+ let plist = &[
+ StartDictionary(None),
+ StringValue("Author".to_owned()),
+ StringValue("William Shakespeare".to_owned()),
+ StringValue("Lines".to_owned()),
+ StartArray(None),
+ StringValue("It is a tale told by an idiot,".to_owned()),
+ StringValue("Full of sound and fury, signifying nothing.".to_owned()),
+ EndArray,
+ StringValue("Death".to_owned()),
+ IntegerValue(1564),
+ StringValue("Height".to_owned()),
+ RealValue(1.60),
+ StringValue("Data".to_owned()),
+ DataValue(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0]),
+ StringValue("Birthdate".to_owned()),
+ DateValue(parse_rfc3339_weak("1981-05-16 11:32:06").unwrap().into()),
+ StringValue("Comment".to_owned()),
+ StringValue("2 < 3".to_owned()), // make sure characters are escaped
+ EndDictionary,
+ ];
+
+ 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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
+<plist version=\"1.0\">
+<dict>
+\t<key>Author</key>
+\t<string>William Shakespeare</string>
+\t<key>Lines</key>
+\t<array>
+\t\t<string>It is a tale told by an idiot,</string>
+\t\t<string>Full of sound and fury, signifying nothing.</string>
+\t</array>
+\t<key>Death</key>
+\t<integer>1564</integer>
+\t<key>Height</key>
+\t<real>1.6</real>
+\t<key>Data</key>
+\t<data>AAAAvgAAAAMAAAAeAAAA</data>
+\t<key>Birthdate</key>
+\t<date>1981-05-16T11:32:06Z</date>
+\t<key>Comment</key>
+\t<string>2 &lt; 3</string>
+</dict>
+</plist>";
+
+ let s = String::from_utf8(cursor.into_inner()).unwrap();
+
+ assert_eq!(s, comparison);
+ }
+}