use std::ffi::c_void;
use core_foundation::array::{CFArray, CFArrayGetValueAtIndex};
use core_foundation::base::{CFType, TCFType};
use core_foundation::boolean::CFBoolean;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use super::proxy_dict::{self, DictValue, ProxyDict};
pub(super) fn cf_string_to_string(value: &CFString) -> Option<String> {
std::panic::catch_unwind(|| value.to_string()).ok()
}
pub(super) fn to_proxy_dict(dictionary: &CFDictionary) -> ProxyDict {
let mut dict = ProxyDict::new();
let (keys, values) = dictionary.get_keys_and_values();
for (key, value) in keys.into_iter().zip(values) {
if key.is_null() || value.is_null() {
continue;
}
let (key, value) = unsafe {
(
CFType::wrap_under_get_rule(key),
CFType::wrap_under_get_rule(value),
)
};
let Some(key) = key.downcast::<CFString>() else {
continue;
};
let Some(key) = cf_string_to_string(&key) else {
crate::trace::warning!(
"a proxies dictionary key holds content that could not be reencoded as \
UTF-8; skipping the entry, which no key this crate reads can reach"
);
continue;
};
match to_dict_value(&value) {
Some(value) => dict.insert(key, value),
None => {
if proxy_dict::is_known_key(&key) {
crate::trace::warning!(
key = key.as_str(),
"a proxies key this crate reads holds a Core Foundation value this \
crate could not convert to its schema (not a string, number, \
boolean or array — or a string whose content could not be \
reencoded as UTF-8); recording it as set but unreadable"
);
dict.insert(key, DictValue::Unreadable);
}
}
}
}
dict
}
fn to_dict_value(value: &CFType) -> Option<DictValue> {
if let Some(text) = value.downcast::<CFString>() {
return cf_string_to_string(&text).map(DictValue::Text);
}
if let Some(number) = value.downcast::<CFNumber>() {
return number.to_i64().map(DictValue::Number);
}
if let Some(flag) = value.downcast::<CFBoolean>() {
return Some(DictValue::Number(i64::from(bool::from(flag))));
}
if value.instance_of::<CFArray>() {
let (items, unreadable) = strings_in(value);
return Some(DictValue::List { items, unreadable });
}
None
}
fn strings_in(value: &CFType) -> (Vec<String>, usize) {
let array =
unsafe { CFArray::<*const c_void>::wrap_under_get_rule(value.as_CFTypeRef().cast()) };
let mut out = Vec::new();
let mut unreadable = 0;
for index in 0..array.len() {
let item = unsafe { CFArrayGetValueAtIndex(array.as_concrete_TypeRef(), index) };
if item.is_null() {
unreadable += 1;
continue;
}
let item = unsafe { CFType::wrap_under_get_rule(item) };
match item
.downcast::<CFString>()
.and_then(|text| cf_string_to_string(&text))
{
Some(text) => out.push(text),
None => unreadable += 1,
}
}
(out, unreadable)
}