1use plist::{Dictionary, Value};
26
27use crate::error::plist::PlistParseError;
28
29const MAX_UID_DEPTH: usize = 256;
31
32pub fn parse_ns_keyed_archiver(plist: &Value) -> Result<Value, PlistParseError> {
71 let body = plist_as_dictionary(plist)?;
72 let objects = extract_array_key(body, "$objects")?;
73
74 let root = extract_uid_key(extract_dictionary(body, "$top")?, "root")?;
76
77 follow_uid(objects, root, None, None, 0)
78}
79
80fn follow_uid<'a>(
82 objects: &'a [Value],
83 root: usize,
84 parent: Option<&'a Value>,
85 item: Option<&'a Value>,
86 depth: usize,
87) -> Result<Value, PlistParseError> {
88 if depth >= MAX_UID_DEPTH {
89 return Err(PlistParseError::RecursionLimit);
90 }
91 let item = match item {
92 Some(item) => item,
93 None => objects
94 .get(root)
95 .ok_or(PlistParseError::NoValueAtIndex(root))?,
96 };
97
98 match item {
99 Value::Array(arr) => {
100 let mut array = vec![];
101 for item in arr {
102 if let Some(idx) = item.as_uid() {
103 array.push(follow_uid(
104 objects,
105 uid_to_index(idx)?,
106 parent,
107 None,
108 depth + 1,
109 )?);
110 }
111 }
112 Ok(plist::Value::Array(array))
113 }
114 Value::Dictionary(dict) => {
115 let mut dictionary = Dictionary::new();
116 if let Some(relative) = dict.get("NS.relative") {
118 if let Some(idx) = relative.as_uid()
119 && let Some(p) = &parent
120 {
121 dictionary.insert(
122 value_to_key_string(p),
123 follow_uid(objects, uid_to_index(idx)?, Some(p), None, depth + 1)?,
124 );
125 }
126 }
127 else if dict.contains_key("NS.keys") && dict.contains_key("NS.objects") {
129 let keys = extract_array_key(dict, "NS.keys")?;
130 let values = extract_array_key(dict, "NS.objects")?;
132 if keys.len() != values.len() {
134 return Err(PlistParseError::InvalidDictionarySize(
135 keys.len(),
136 values.len(),
137 ));
138 }
139
140 for idx in 0..keys.len() {
141 let key_index = extract_uid_idx(keys, idx)?;
142 let value_index = extract_uid_idx(values, idx)?;
143 let key = follow_uid(objects, key_index, None, None, depth + 1)?;
144 let value = follow_uid(objects, value_index, Some(&key), None, depth + 1)?;
145
146 dictionary.insert(value_to_key_string(&key), value);
147 }
148 }
149 else {
151 for (key, val) in dict {
152 if key == "$class" {
154 continue;
155 }
156 if let Some(idx) = val.as_uid() {
158 let key_value = Value::String(key.clone());
159 dictionary.insert(
160 key.clone(),
161 follow_uid(
162 objects,
163 uid_to_index(idx)?,
164 Some(&key_value),
165 None,
166 depth + 1,
167 )?,
168 );
169 }
170 else if let Some(p) = parent {
172 dictionary.insert(
173 value_to_key_string(p),
174 follow_uid(objects, root, Some(p), Some(val), depth + 1)?,
175 );
176 }
177 }
178 }
179 Ok(plist::Value::Dictionary(dictionary))
180 }
181 Value::Uid(uid) => follow_uid(objects, uid_to_index(uid)?, None, None, depth + 1),
182 _ => Ok(item.to_owned()),
183 }
184}
185
186fn value_to_key_string(v: &Value) -> String {
188 match v {
189 Value::String(s) => s.clone(),
190 Value::Integer(i) => i.to_string(),
191 Value::Real(f) => f.to_string(),
192 Value::Boolean(b) => b.to_string(),
193 Value::Date(d) => format!("{d:?}"),
194 Value::Data(_) => "data".to_string(),
195 Value::Array(_) => "array".to_string(),
196 Value::Dictionary(_) => "dict".to_string(),
197 Value::Uid(u) => u.get().to_string(),
198 _ => "unknown".to_string(),
199 }
200}
201
202pub fn plist_as_dictionary(plist: &Value) -> Result<&Dictionary, PlistParseError> {
204 plist
205 .as_dictionary()
206 .ok_or_else(|| PlistParseError::InvalidType("body".to_string(), "dictionary".to_string()))
207}
208
209pub fn rich_link_metadata_and_nested<'a>(
213 payload: &'a Value,
214 nested_key: &str,
215) -> Result<(&'a Value, &'a Value), PlistParseError> {
216 let base = payload
217 .as_dictionary()
218 .ok_or_else(|| PlistParseError::InvalidType("root".to_string(), "dictionary".to_string()))?
219 .get("richLinkMetadata")
220 .ok_or_else(|| PlistParseError::MissingKey("richLinkMetadata".to_string()))?;
221
222 let nested = base
223 .as_dictionary()
224 .ok_or_else(|| {
225 PlistParseError::InvalidType("richLinkMetadata".to_string(), "dictionary".to_string())
226 })?
227 .get(nested_key)
228 .ok_or_else(|| PlistParseError::MissingKey(nested_key.to_string()))?;
229
230 Ok((base, nested))
231}
232
233pub fn extract_dictionary<'a>(
235 body: &'a Dictionary,
236 key: &str,
237) -> Result<&'a Dictionary, PlistParseError> {
238 body.get(key)
239 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
240 .as_dictionary()
241 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "dictionary".to_string()))
242}
243
244pub fn extract_array_key<'a>(
246 body: &'a Dictionary,
247 key: &str,
248) -> Result<&'a Vec<Value>, PlistParseError> {
249 body.get(key)
250 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
251 .as_array()
252 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "array".to_string()))
253}
254
255fn extract_uid_key(body: &Dictionary, key: &str) -> Result<usize, PlistParseError> {
257 let uid = body
258 .get(key)
259 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
260 .as_uid()
261 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "uid".to_string()))?;
262 uid_to_index(uid)
263}
264
265pub fn extract_bytes_key<'a>(body: &'a Dictionary, key: &str) -> Result<&'a [u8], PlistParseError> {
267 body.get(key)
268 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
269 .as_data()
270 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "data".to_string()))
271}
272
273pub fn extract_int_key(body: &Dictionary, key: &str) -> Result<i64, PlistParseError> {
275 Ok(body
276 .get(key)
277 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
278 .as_real()
279 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "real".to_string()))?
280 as i64)
281}
282
283pub fn extract_string_key<'a>(body: &'a Dictionary, key: &str) -> Result<&'a str, PlistParseError> {
285 body.get(key)
286 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
287 .as_string()
288 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "string".to_string()))
289}
290
291fn extract_uid_idx(body: &[Value], idx: usize) -> Result<usize, PlistParseError> {
293 let uid = body
294 .get(idx)
295 .ok_or(PlistParseError::NoValueAtIndex(idx))?
296 .as_uid()
297 .ok_or_else(|| PlistParseError::InvalidTypeIndex(idx, "uid".to_string()))?;
298 uid_to_index(uid)
299}
300
301fn uid_to_index(uid: &plist::Uid) -> Result<usize, PlistParseError> {
303 usize::try_from(uid.get()).map_err(|_| PlistParseError::UidOutOfRange(uid.get()))
304}
305
306pub fn extract_dict_idx(body: &[Value], idx: usize) -> Result<&Dictionary, PlistParseError> {
308 body.get(idx)
309 .ok_or(PlistParseError::NoValueAtIndex(idx))?
310 .as_dictionary()
311 .ok_or_else(|| PlistParseError::InvalidTypeIndex(idx, "dictionary".to_string()))
312}
313
314#[must_use]
316pub fn get_string_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a str> {
317 payload
318 .as_dictionary()?
319 .get(key)?
320 .as_string()
321 .filter(|s| !s.is_empty())
322}
323
324#[must_use]
326pub fn get_owned_string_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<String> {
327 get_string_from_dict(payload, key).map(String::from)
328}
329
330#[must_use]
332pub fn get_value_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a Value> {
333 payload.as_dictionary()?.get(key)
334}
335
336#[must_use]
338pub fn get_bool_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<bool> {
339 payload.as_dictionary()?.get(key)?.as_boolean()
340}
341
342#[must_use]
344pub fn get_data_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a [u8]> {
345 payload.as_dictionary()?.get(key)?.as_data()
346}
347
348#[must_use]
350pub fn get_string_from_nested_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a str> {
351 payload
352 .as_dictionary()?
353 .get(key)?
354 .as_dictionary()?
355 .get(key)?
356 .as_string()
357 .filter(|s| !s.is_empty())
358}
359
360#[must_use]
362pub fn get_float_from_nested_dict<'a>(payload: &'a Value, key: &'a str) -> Option<f64> {
363 payload
364 .as_dictionary()?
365 .get(key)?
366 .as_dictionary()?
367 .get(key)?
368 .as_real()
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use plist::Uid;
375
376 fn dict(pairs: Vec<(&str, Value)>) -> Value {
378 let mut dictionary = Dictionary::new();
379 for (key, value) in pairs {
380 dictionary.insert(key.to_string(), value);
381 }
382 Value::Dictionary(dictionary)
383 }
384
385 #[test]
386 fn resolves_simple_archive() {
387 let archive = dict(vec![
389 (
390 "$objects",
391 Value::Array(vec![
392 Value::String("$null".to_string()),
393 Value::String("hello".to_string()),
394 ]),
395 ),
396 ("$top", dict(vec![("root", Value::Uid(Uid::new(1)))])),
397 ]);
398
399 assert_eq!(
400 parse_ns_keyed_archiver(&archive).unwrap(),
401 Value::String("hello".to_string())
402 );
403 }
404
405 #[test]
406 fn cyclic_uid_reference_is_rejected_not_overflowed() {
407 let archive = dict(vec![
411 (
412 "$objects",
413 Value::Array(vec![
414 Value::String("$null".to_string()),
415 Value::Uid(Uid::new(1)),
416 ]),
417 ),
418 ("$top", dict(vec![("root", Value::Uid(Uid::new(1)))])),
419 ]);
420
421 assert!(matches!(
422 parse_ns_keyed_archiver(&archive),
423 Err(PlistParseError::RecursionLimit)
424 ));
425 }
426}