use plist::{Dictionary, Value};
use crate::error::plist::PlistParseError;
pub fn parse_ns_keyed_archiver(plist: &Value) -> Result<Value, PlistParseError> {
let body = plist_as_dictionary(plist)?;
let objects = extract_array_key(body, "$objects")?;
let root = extract_uid_key(extract_dictionary(body, "$top")?, "root")?;
follow_uid(objects, root, None, None)
}
fn follow_uid<'a>(
objects: &'a [Value],
root: usize,
parent: Option<&'a Value>,
item: Option<&'a Value>,
) -> Result<Value, PlistParseError> {
let item = match item {
Some(item) => item,
None => objects
.get(root)
.ok_or(PlistParseError::NoValueAtIndex(root))?,
};
match item {
Value::Array(arr) => {
let mut array = vec![];
for item in arr {
if let Some(idx) = item.as_uid() {
array.push(follow_uid(objects, idx.get() as usize, parent, None)?);
}
}
Ok(plist::Value::Array(array))
}
Value::Dictionary(dict) => {
let mut dictionary = Dictionary::new();
if let Some(relative) = dict.get("NS.relative") {
if let Some(idx) = relative.as_uid()
&& let Some(p) = &parent
{
dictionary.insert(
value_to_key_string(p),
follow_uid(objects, idx.get() as usize, Some(p), None)?,
);
}
}
else if dict.contains_key("NS.keys") && dict.contains_key("NS.objects") {
let keys = extract_array_key(dict, "NS.keys")?;
let values = extract_array_key(dict, "NS.objects")?;
if keys.len() != values.len() {
return Err(PlistParseError::InvalidDictionarySize(
keys.len(),
values.len(),
));
}
for idx in 0..keys.len() {
let key_index = extract_uid_idx(keys, idx)?;
let value_index = extract_uid_idx(values, idx)?;
let key = follow_uid(objects, key_index, None, None)?;
let value = follow_uid(objects, value_index, Some(&key), None)?;
dictionary.insert(value_to_key_string(&key), value);
}
}
else {
for (key, val) in dict {
if key == "$class" {
continue;
}
if let Some(idx) = val.as_uid() {
let key_value = Value::String(key.clone());
dictionary.insert(
key.clone(),
follow_uid(objects, idx.get() as usize, Some(&key_value), None)?,
);
}
else if let Some(p) = parent {
dictionary.insert(
value_to_key_string(p),
follow_uid(objects, root, Some(p), Some(val))?,
);
}
}
}
Ok(plist::Value::Dictionary(dictionary))
}
Value::Uid(uid) => follow_uid(objects, uid.get() as usize, None, None),
_ => Ok(item.to_owned()),
}
}
fn value_to_key_string(v: &Value) -> String {
match v {
Value::String(s) => s.clone(),
Value::Integer(i) => i.to_string(),
Value::Real(f) => f.to_string(),
Value::Boolean(b) => b.to_string(),
Value::Date(d) => format!("{d:?}"),
Value::Data(_) => "data".to_string(),
Value::Array(_) => "array".to_string(),
Value::Dictionary(_) => "dict".to_string(),
Value::Uid(u) => u.get().to_string(),
_ => "unknown".to_string(),
}
}
pub fn plist_as_dictionary(plist: &Value) -> Result<&Dictionary, PlistParseError> {
plist
.as_dictionary()
.ok_or_else(|| PlistParseError::InvalidType("body".to_string(), "dictionary".to_string()))
}
pub fn extract_dictionary<'a>(
body: &'a Dictionary,
key: &str,
) -> Result<&'a Dictionary, PlistParseError> {
body.get(key)
.ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
.as_dictionary()
.ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "dictionary".to_string()))
}
pub fn extract_array_key<'a>(
body: &'a Dictionary,
key: &str,
) -> Result<&'a Vec<Value>, PlistParseError> {
body.get(key)
.ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
.as_array()
.ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "array".to_string()))
}
fn extract_uid_key(body: &Dictionary, key: &str) -> Result<usize, PlistParseError> {
Ok(body
.get(key)
.ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
.as_uid()
.ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "uid".to_string()))?
.get() as usize)
}
pub fn extract_bytes_key<'a>(body: &'a Dictionary, key: &str) -> Result<&'a [u8], PlistParseError> {
body.get(key)
.ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
.as_data()
.ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "data".to_string()))
}
pub fn extract_int_key(body: &Dictionary, key: &str) -> Result<i64, PlistParseError> {
Ok(body
.get(key)
.ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
.as_real()
.ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "real".to_string()))?
as i64)
}
pub fn extract_string_key<'a>(body: &'a Dictionary, key: &str) -> Result<&'a str, PlistParseError> {
body.get(key)
.ok_or_else(|| PlistParseError::MissingKey(key.to_string()))?
.as_string()
.ok_or_else(|| PlistParseError::InvalidType(key.to_string(), "string".to_string()))
}
fn extract_uid_idx(body: &[Value], idx: usize) -> Result<usize, PlistParseError> {
Ok(body
.get(idx)
.ok_or(PlistParseError::NoValueAtIndex(idx))?
.as_uid()
.ok_or_else(|| PlistParseError::InvalidTypeIndex(idx, "uid".to_string()))?
.get() as usize)
}
pub fn extract_dict_idx(body: &[Value], idx: usize) -> Result<&Dictionary, PlistParseError> {
body.get(idx)
.ok_or(PlistParseError::NoValueAtIndex(idx))?
.as_dictionary()
.ok_or_else(|| PlistParseError::InvalidTypeIndex(idx, "dictionary".to_string()))
}
#[must_use]
pub fn get_string_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a str> {
payload
.as_dictionary()?
.get(key)?
.as_string()
.filter(|s| !s.is_empty())
}
#[must_use]
pub fn get_owned_string_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<String> {
get_string_from_dict(payload, key).map(String::from)
}
#[must_use]
pub fn get_value_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a Value> {
payload.as_dictionary()?.get(key)
}
#[must_use]
pub fn get_bool_from_dict<'a>(payload: &'a Value, key: &'a str) -> Option<bool> {
payload.as_dictionary()?.get(key)?.as_boolean()
}
#[must_use]
pub fn get_string_from_nested_dict<'a>(payload: &'a Value, key: &'a str) -> Option<&'a str> {
payload
.as_dictionary()?
.get(key)?
.as_dictionary()?
.get(key)?
.as_string()
.filter(|s| !s.is_empty())
}
#[must_use]
pub fn get_float_from_nested_dict<'a>(payload: &'a Value, key: &'a str) -> Option<f64> {
payload
.as_dictionary()?
.get(key)?
.as_dictionary()?
.get(key)?
.as_real()
}