agent_first_data/document/format/
plist.rs1use std::collections::BTreeMap;
23
24use crate::document::{DocumentError, DocumentResult, Value};
25
26const BINARY_MAGIC: &[u8] = b"bplist";
27
28pub(crate) const WRONG_FORMAT_DETAIL: &str =
33 "the input is a binary property list; read it with --input-format plist";
34
35pub(crate) const WRONG_FORMAT_XML_DETAIL: &str =
37 "the input is an XML property list; read it with --input-format plist";
38
39pub(crate) fn looks_like_xml(bytes: &[u8]) -> bool {
44 let bytes = bytes.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(bytes);
45 let start = bytes
46 .iter()
47 .position(|byte| !byte.is_ascii_whitespace())
48 .unwrap_or(bytes.len());
49 let head = &bytes[start..];
50 head.starts_with(b"<?xml")
51 || head.starts_with(b"<plist")
52 || head.starts_with(b"<!DOCTYPE plist")
53}
54
55pub(crate) fn is_binary(bytes: &[u8]) -> bool {
57 bytes.starts_with(BINARY_MAGIC)
58}
59
60pub(crate) fn wrong_format(format: &str) -> DocumentError {
62 DocumentError::ParseError {
63 format: format.to_string(),
64 detail: WRONG_FORMAT_DETAIL.to_string(),
65 }
66}
67
68pub(crate) fn wrong_format_xml(format: &str) -> DocumentError {
70 DocumentError::ParseError {
71 format: format.to_string(),
72 detail: WRONG_FORMAT_XML_DETAIL.to_string(),
73 }
74}
75
76pub(crate) fn load_binary(bytes: &[u8]) -> DocumentResult<Value> {
78 let parsed = ::plist::Value::from_reader(std::io::Cursor::new(bytes)).map_err(|error| {
79 DocumentError::ParseError {
80 format: "plist".to_string(),
81 detail: error.to_string(),
82 }
83 })?;
84 convert(parsed)
85}
86
87pub fn load(content: &str) -> DocumentResult<Value> {
89 let parsed = ::plist::Value::from_reader_xml(content.as_bytes()).map_err(|error| {
90 DocumentError::ParseError {
91 format: "plist".to_string(),
92 detail: error.to_string(),
93 }
94 })?;
95 convert(parsed)
96}
97
98fn convert(value: ::plist::Value) -> DocumentResult<Value> {
99 Ok(match value {
100 ::plist::Value::Array(items) => Value::Array(
101 items
102 .into_iter()
103 .map(convert)
104 .collect::<DocumentResult<_>>()?,
105 ),
106 ::plist::Value::Dictionary(map) => Value::Object(
107 map.into_iter()
108 .map(|(key, value)| Ok((key, convert(value)?)))
109 .collect::<DocumentResult<BTreeMap<_, _>>>()?,
110 ),
111 ::plist::Value::Boolean(value) => Value::Bool(value),
112 ::plist::Value::Data(bytes) => Value::String(base64(&bytes)),
113 ::plist::Value::Date(date) => Value::String(date.to_xml_format()),
114 ::plist::Value::Real(value) => Value::Float(value),
115 ::plist::Value::Integer(value) => match (value.as_signed(), value.as_unsigned()) {
116 (Some(signed), _) => Value::Integer(signed),
117 (None, Some(unsigned)) => Value::Unsigned(unsigned),
118 (None, None) => return Err(unmapped("integer")),
119 },
120 ::plist::Value::String(value) => Value::String(value),
121 ::plist::Value::Uid(uid) => Value::Unsigned(uid.get()),
122 _ => return Err(unmapped("value")),
125 })
126}
127
128fn unmapped(kind: &str) -> DocumentError {
129 DocumentError::ParseError {
130 format: "plist".to_string(),
131 detail: format!("plist {kind} has no value mapping"),
132 }
133}
134
135fn base64(bytes: &[u8]) -> String {
136 const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
137 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
138 for chunk in bytes.chunks(3) {
139 let b0 = u32::from(chunk[0]);
140 let b1 = u32::from(chunk.get(1).copied().unwrap_or(0));
141 let b2 = u32::from(chunk.get(2).copied().unwrap_or(0));
142 let n = (b0 << 16) | (b1 << 8) | b2;
143 let sextet = |shift: u32| char::from(ALPHABET[((n >> shift) & 63) as usize]);
144 out.push(sextet(18));
145 out.push(sextet(12));
146 out.push(if chunk.len() > 1 { sextet(6) } else { '=' });
147 out.push(if chunk.len() > 2 { sextet(0) } else { '=' });
148 }
149 out
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn base64_pads_like_the_standard_alphabet() {
158 assert_eq!(base64(b""), "");
159 assert_eq!(base64(b"f"), "Zg==");
160 assert_eq!(base64(b"fo"), "Zm8=");
161 assert_eq!(base64(b"foo"), "Zm9v");
162 assert_eq!(base64(b"foob"), "Zm9vYg==");
163 }
164}