1use plist::{Dictionary, Value};
26
27use crate::error::plist::PlistParseError;
28
29const MAX_UID_DEPTH: usize = 256;
31const NULL_PLACEHOLDER: &str = "$null";
33
34pub fn parse_ns_keyed_archiver(plist: &Value) -> Result<Value, PlistParseError> {
73 let body = plist_as_dictionary(plist)?;
74 let objects = extract_array_key(body, "$objects")?;
75
76 let root = extract_uid_key(extract_dictionary(body, "$top")?, "root")?;
78
79 follow_uid(objects, root, None, None, 0)?.ok_or(PlistParseError::NoPayload)
82}
83
84fn follow_uid<'a>(
90 objects: &'a [Value],
91 root: usize,
92 parent: Option<&'a Value>,
93 item: Option<&'a Value>,
94 depth: usize,
95) -> Result<Option<Value>, PlistParseError> {
96 if depth >= MAX_UID_DEPTH {
97 return Err(PlistParseError::RecursionLimit);
98 }
99 if item.is_none() && is_null_placeholder(objects, root) {
100 return Ok(None);
101 }
102 let item = match item {
103 Some(item) => item,
104 None => objects
105 .get(root)
106 .ok_or(PlistParseError::NoValueAtIndex(root))?,
107 };
108
109 match item {
110 Value::Array(arr) => {
111 let mut array = vec![];
112 for item in arr {
113 if let Some(idx) = item.as_uid()
114 && let Some(value) =
115 follow_uid(objects, uid_to_index(idx)?, parent, None, depth + 1)?
116 {
117 array.push(value);
118 }
119 }
120 Ok(Some(plist::Value::Array(array)))
121 }
122 Value::Dictionary(dict) => {
123 let mut dictionary = Dictionary::new();
124 if let Some(relative) = dict.get("NS.relative") {
126 if let Some(idx) = relative.as_uid()
127 && let Some(p) = parent
128 && let Some(value) =
129 follow_uid(objects, uid_to_index(idx)?, Some(p), None, depth + 1)?
130 {
131 dictionary.insert(value_to_key_string(p), value);
132 }
133 }
134 else if dict.contains_key("NS.keys") && dict.contains_key("NS.objects") {
136 let keys = extract_array_key(dict, "NS.keys")?;
137 let values = extract_array_key(dict, "NS.objects")?;
139 if keys.len() != values.len() {
141 return Err(PlistParseError::InvalidDictionarySize(
142 keys.len(),
143 values.len(),
144 ));
145 }
146
147 for idx in 0..keys.len() {
148 let key_index = extract_uid_idx(keys, idx)?;
149 let value_index = extract_uid_idx(values, idx)?;
150 let Some(key) = follow_uid(objects, key_index, None, None, depth + 1)? else {
151 continue;
152 };
153 let Some(value) =
154 follow_uid(objects, value_index, Some(&key), None, depth + 1)?
155 else {
156 continue;
157 };
158
159 dictionary.insert(value_to_key_string(&key), value);
160 }
161 }
162 else {
164 for (key, val) in dict {
165 if key == "$class" {
167 continue;
168 }
169 if let Some(idx) = val.as_uid() {
171 let key_value = Value::String(key.clone());
172 if let Some(value) = follow_uid(
173 objects,
174 uid_to_index(idx)?,
175 Some(&key_value),
176 None,
177 depth + 1,
178 )? {
179 dictionary.insert(key.clone(), value);
180 }
181 }
182 else if let Some(p) = parent
184 && let Some(value) =
185 follow_uid(objects, root, Some(p), Some(val), depth + 1)?
186 {
187 dictionary.insert(value_to_key_string(p), value);
188 }
189 }
190 }
191 Ok(Some(plist::Value::Dictionary(dictionary)))
192 }
193 Value::Uid(uid) => follow_uid(objects, uid_to_index(uid)?, None, None, depth + 1),
194 _ => Ok(Some(item.to_owned())),
195 }
196}
197
198fn is_null_placeholder(objects: &[Value], index: usize) -> bool {
201 index == 0
202 && objects
203 .first()
204 .and_then(Value::as_string)
205 .is_some_and(|value| value == NULL_PLACEHOLDER)
206}
207
208fn value_to_key_string(v: &Value) -> String {
210 match v {
211 Value::String(s) => s.clone(),
212 Value::Integer(i) => i.to_string(),
213 Value::Real(f) => f.to_string(),
214 Value::Boolean(b) => b.to_string(),
215 Value::Date(d) => format!("{d:?}"),
216 Value::Data(_) => "data".to_string(),
217 Value::Array(_) => "array".to_string(),
218 Value::Dictionary(_) => "dict".to_string(),
219 Value::Uid(u) => u.get().to_string(),
220 _ => "unknown".to_string(),
221 }
222}
223
224pub fn plist_as_dictionary(plist: &Value) -> Result<&Dictionary, PlistParseError> {
226 plist
227 .as_dictionary()
228 .ok_or_else(|| PlistParseError::InvalidType("body".to_string(), "dictionary".to_string()))
229}
230
231pub fn rich_link_metadata_and_nested<'a>(
235 payload: &'a Value,
236 nested_key: &str,
237) -> Result<(&'a Value, &'a Value), PlistParseError> {
238 let base = payload
239 .as_dictionary()
240 .ok_or_else(|| PlistParseError::InvalidType("root".to_string(), "dictionary".to_string()))?
241 .get("richLinkMetadata")
242 .ok_or_else(|| PlistParseError::MissingKey("richLinkMetadata".to_string()))?;
243
244 let nested = base
245 .as_dictionary()
246 .ok_or_else(|| {
247 PlistParseError::InvalidType("richLinkMetadata".to_string(), "dictionary".to_string())
248 })?
249 .get(nested_key)
250 .ok_or_else(|| PlistParseError::MissingKey(nested_key.to_string()))?;
251
252 Ok((base, nested))
253}
254
255pub fn extract_dictionary<'a>(
257 body: &'a Dictionary,
258 key: &str,
259) -> Result<&'a Dictionary, PlistParseError> {
260 body.get(key)
261 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
262 .as_dictionary()
263 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "dictionary".to_string()))
264}
265
266pub fn extract_array_key<'a>(
268 body: &'a Dictionary,
269 key: &str,
270) -> Result<&'a Vec<Value>, PlistParseError> {
271 body.get(key)
272 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
273 .as_array()
274 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "array".to_string()))
275}
276
277fn extract_uid_key(body: &Dictionary, key: &str) -> Result<usize, PlistParseError> {
279 let uid = body
280 .get(key)
281 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
282 .as_uid()
283 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "uid".to_string()))?;
284 uid_to_index(uid)
285}
286
287pub fn extract_bytes_key<'a>(body: &'a Dictionary, key: &str) -> Result<&'a [u8], PlistParseError> {
289 body.get(key)
290 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
291 .as_data()
292 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "data".to_string()))
293}
294
295pub fn extract_int_key(body: &Dictionary, key: &str) -> Result<i64, PlistParseError> {
297 Ok(body
298 .get(key)
299 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
300 .as_real()
301 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "real".to_string()))?
302 as i64)
303}
304
305pub fn extract_string_key<'a>(body: &'a Dictionary, key: &str) -> Result<&'a str, PlistParseError> {
307 body.get(key)
308 .ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
309 .as_string()
310 .ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "string".to_string()))
311}
312
313fn extract_uid_idx(body: &[Value], idx: usize) -> Result<usize, PlistParseError> {
315 let uid = body
316 .get(idx)
317 .ok_or(PlistParseError::NoValueAtIndex(idx))?
318 .as_uid()
319 .ok_or_else(|| PlistParseError::InvalidTypeIndex(idx, "uid".to_string()))?;
320 uid_to_index(uid)
321}
322
323fn uid_to_index(uid: &plist::Uid) -> Result<usize, PlistParseError> {
325 usize::try_from(uid.get()).map_err(|_| PlistParseError::UidOutOfRange(uid.get()))
326}
327
328pub fn extract_dict_idx(body: &[Value], idx: usize) -> Result<&Dictionary, PlistParseError> {
330 body.get(idx)
331 .ok_or(PlistParseError::NoValueAtIndex(idx))?
332 .as_dictionary()
333 .ok_or_else(|| PlistParseError::InvalidTypeIndex(idx, "dictionary".to_string()))
334}
335
336#[must_use]
338pub fn get_string_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a str> {
339 payload
340 .as_dictionary()?
341 .get(key)?
342 .as_string()
343 .filter(|s| !s.is_empty())
344}
345
346#[must_use]
348pub fn get_owned_string_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<String> {
349 get_string_from_dict(payload, key).map(String::from)
350}
351
352#[must_use]
354pub fn get_value_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a Value> {
355 payload.as_dictionary()?.get(key)
356}
357
358#[must_use]
360pub fn get_bool_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<bool> {
361 payload.as_dictionary()?.get(key)?.as_boolean()
362}
363
364#[must_use]
366pub fn get_data_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a [u8]> {
367 payload.as_dictionary()?.get(key)?.as_data()
368}
369
370#[must_use]
372pub fn get_string_from_nested_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a str> {
373 payload
374 .as_dictionary()?
375 .get(key)?
376 .as_dictionary()?
377 .get(key)?
378 .as_string()
379 .filter(|s| !s.is_empty())
380}
381
382#[must_use]
384pub fn get_float_from_nested_dict<'a>(payload: &'a Value, key: &'a str) -> Option<f64> {
385 payload
386 .as_dictionary()?
387 .get(key)?
388 .as_dictionary()?
389 .get(key)?
390 .as_real()
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use plist::Uid;
397
398 fn dict(pairs: Vec<(&str, Value)>) -> Value {
400 let mut dictionary = Dictionary::new();
401 for (key, value) in pairs {
402 dictionary.insert(key.to_string(), value);
403 }
404 Value::Dictionary(dictionary)
405 }
406
407 #[test]
408 fn resolves_simple_archive() {
409 let archive = dict(vec![
411 (
412 "$objects",
413 Value::Array(vec![
414 Value::String("$null".to_string()),
415 Value::String("hello".to_string()),
416 ]),
417 ),
418 ("$top", dict(vec![("root", Value::Uid(Uid::new(1)))])),
419 ]);
420
421 assert_eq!(
422 parse_ns_keyed_archiver(&archive).unwrap(),
423 Value::String("hello".to_string())
424 );
425 }
426
427 #[test]
428 fn null_root_is_no_payload() {
429 let archive = dict(vec![
432 (
433 "$objects",
434 Value::Array(vec![Value::String(NULL_PLACEHOLDER.to_string())]),
435 ),
436 ("$top", dict(vec![("root", Value::Uid(Uid::new(0)))])),
437 ]);
438
439 assert!(matches!(
440 parse_ns_keyed_archiver(&archive),
441 Err(PlistParseError::NoPayload)
442 ));
443 }
444
445 #[test]
446 fn omits_null_values_from_normal_dictionaries() {
447 let archive = dict(vec![
452 (
453 "$objects",
454 Value::Array(vec![
455 Value::String(NULL_PLACEHOLDER.to_string()),
456 dict(vec![
457 ("missing", Value::Uid(Uid::new(0))),
458 ("present", Value::Uid(Uid::new(2))),
459 ("literal", Value::Uid(Uid::new(3))),
460 ]),
461 Value::String("kept".to_string()),
462 Value::String(NULL_PLACEHOLDER.to_string()),
463 ]),
464 ),
465 ("$top", dict(vec![("root", Value::Uid(Uid::new(1)))])),
466 ]);
467
468 assert_eq!(
469 parse_ns_keyed_archiver(&archive).unwrap(),
470 dict(vec![
471 ("present", Value::String("kept".to_string())),
472 ("literal", Value::String(NULL_PLACEHOLDER.to_string())),
473 ])
474 );
475 }
476
477 #[test]
478 fn omits_null_values_from_ns_dictionary_pairs() {
479 let archive = dict(vec![
480 (
481 "$objects",
482 Value::Array(vec![
483 Value::String(NULL_PLACEHOLDER.to_string()),
484 dict(vec![
485 (
486 "NS.keys",
487 Value::Array(vec![Value::Uid(Uid::new(2)), Value::Uid(Uid::new(3))]),
488 ),
489 (
490 "NS.objects",
491 Value::Array(vec![Value::Uid(Uid::new(4)), Value::Uid(Uid::new(0))]),
492 ),
493 ]),
494 Value::String("present".to_string()),
495 Value::String("missing".to_string()),
496 Value::String("kept".to_string()),
497 ]),
498 ),
499 ("$top", dict(vec![("root", Value::Uid(Uid::new(1)))])),
500 ]);
501
502 assert_eq!(
503 parse_ns_keyed_archiver(&archive).unwrap(),
504 dict(vec![("present", Value::String("kept".to_string()))])
505 );
506 }
507
508 #[test]
509 fn cyclic_uid_reference_is_rejected_not_overflowed() {
510 let archive = dict(vec![
514 (
515 "$objects",
516 Value::Array(vec![
517 Value::String("$null".to_string()),
518 Value::Uid(Uid::new(1)),
519 ]),
520 ),
521 ("$top", dict(vec![("root", Value::Uid(Uid::new(1)))])),
522 ]);
523
524 assert!(matches!(
525 parse_ns_keyed_archiver(&archive),
526 Err(PlistParseError::RecursionLimit)
527 ));
528 }
529}