diff options
| author | Edward Barnard | 2018-05-17 20:30:41 +0100 |
|---|---|---|
| committer | Edward Barnard | 2018-05-17 20:50:21 +0100 |
| commit | 2af279c344a6a917ce4102fec82e6cba8cf8b37c (patch) | |
| tree | a2d3746d4981e8e22e30b91fd3b4d1821444777a /src/events | |
| parent | d8a4f4ef2a0e80f1dc6237678410807cce83cce0 (diff) | |
| download | rust-plist-2af279c344a6a917ce4102fec82e6cba8cf8b37c.tar.bz2 | |
Reorganise crate.
Diffstat (limited to 'src/events')
| -rw-r--r-- | src/events/binary_reader.rs | 385 | ||||
| -rw-r--r-- | src/events/mod.rs | 95 | ||||
| -rw-r--r-- | src/events/xml_reader.rs | 199 | ||||
| -rw-r--r-- | src/events/xml_writer.rs | 259 |
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 < 3</string> +</dict> +</plist>"; + + let s = String::from_utf8(cursor.into_inner()).unwrap(); + + assert_eq!(s, comparison); + } +} |
