Skip to main content

apple_cf/cf/
property_list.rs

1//! Core Foundation property-list helpers.
2//!
3#![allow(clippy::missing_errors_doc)]
4//!
5//! ```rust
6//! use apple_cf::cf::{
7//!     CFDictionary, CFPropertyList, CFPropertyListFormat, CFPropertyListMutabilityOptions,
8//!     CFString,
9//! };
10//!
11//! let key = CFString::new("name");
12//! let value = CFString::new("doom-fish");
13//! let plist = CFDictionary::from_pairs(&[(&key, &value)]);
14//!
15//! let data = CFPropertyList::create_data(&plist, CFPropertyListFormat::BinaryV1_0, 0)
16//!     .expect("serialize property list");
17//! let (decoded, detected_format) =
18//!     CFPropertyList::create_with_data(&data, CFPropertyListMutabilityOptions::IMMUTABLE)
19//!         .expect("decode property list");
20//!
21//! assert_eq!(detected_format, CFPropertyListFormat::BinaryV1_0);
22//! assert_eq!(decoded.type_id(), CFDictionary::type_id());
23//! ```
24
25use super::{AsCFType, CFData, CFError as CoreFoundationError, CFReadStream, CFType, CFWriteStream};
26use crate::ffi;
27use std::fmt;
28
29/// `CFPropertyListFormat` values mirrored from Core Foundation.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31#[repr(isize)]
32pub enum CFPropertyListFormat {
33    OpenStep = 1,
34    XmlV1_0 = 100,
35    BinaryV1_0 = 200,
36}
37
38impl TryFrom<isize> for CFPropertyListFormat {
39    type Error = isize;
40
41    fn try_from(value: isize) -> Result<Self, Self::Error> {
42        match value {
43            1 => Ok(Self::OpenStep),
44            100 => Ok(Self::XmlV1_0),
45            200 => Ok(Self::BinaryV1_0),
46            other => Err(other),
47        }
48    }
49}
50
51/// Mutability options for property-list decoding and deep copies.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
53pub struct CFPropertyListMutabilityOptions(u64);
54
55impl CFPropertyListMutabilityOptions {
56    /// Decode immutable containers and leaves.
57    pub const IMMUTABLE: Self = Self(0);
58    /// Decode mutable arrays and dictionaries, but immutable leaves.
59    pub const MUTABLE_CONTAINERS: Self = Self(1 << 0);
60    /// Decode mutable containers and mutable leaf strings/data values.
61    pub const MUTABLE_CONTAINERS_AND_LEAVES: Self = Self(1 << 1);
62
63    /// Create options from raw Core Foundation bits.
64    #[must_use]
65    pub const fn from_bits(bits: u64) -> Self {
66        Self(bits)
67    }
68
69    /// Raw Core Foundation bitmask.
70    #[must_use]
71    pub const fn as_u64(self) -> u64 {
72        self.0
73    }
74
75    /// Whether `other` is contained in this bitmask.
76    #[must_use]
77    pub const fn contains(self, other: Self) -> bool {
78        (self.0 & other.0) == other.0
79    }
80}
81
82impl From<CFPropertyListMutabilityOptions> for u64 {
83    fn from(options: CFPropertyListMutabilityOptions) -> Self {
84        options.0
85    }
86}
87
88/// Errors returned by property-list decode / serialize helpers.
89#[derive(Debug)]
90pub enum CFPropertyListError {
91    /// Core Foundation produced a `CFErrorRef` describing the failure.
92    CoreFoundation(CoreFoundationError),
93    /// The API returned `NULL` without populating a Core Foundation error.
94    Null(crate::CFError),
95}
96
97impl fmt::Display for CFPropertyListError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            Self::CoreFoundation(error) => fmt::Display::fmt(error, f),
101            Self::Null(error) => fmt::Display::fmt(error, f),
102        }
103    }
104}
105
106impl std::error::Error for CFPropertyListError {}
107
108fn property_list_error(
109    operation: &'static str,
110    error_ptr: *mut std::ffi::c_void,
111) -> CFPropertyListError {
112    CoreFoundationError::from_raw(error_ptr)
113        .map_or_else(|| CFPropertyListError::Null(crate::CFError::new(operation)), CFPropertyListError::CoreFoundation)
114}
115
116fn property_list_format(raw: isize) -> CFPropertyListFormat {
117    CFPropertyListFormat::try_from(raw)
118        .expect("Core Foundation returned an unknown CFPropertyListFormat")
119}
120
121/// Namespace for property-list parse / serialize helpers.
122pub struct CFPropertyList;
123
124impl CFPropertyList {
125    /// Deep-copy a property list, optionally changing its mutability semantics.
126    pub fn create_deep_copy(
127        property_list: &dyn AsCFType,
128        options: CFPropertyListMutabilityOptions,
129    ) -> Result<CFType, crate::CFError> {
130        let ptr = unsafe {
131            ffi::cf_property_list_create_deep_copy(property_list.as_ptr(), options.as_u64())
132        };
133        CFType::from_raw(ptr).ok_or(crate::CFError::new("CFPropertyListCreateDeepCopy"))
134    }
135
136    /// Decode a property list from an in-memory data blob.
137    pub fn create_with_data(
138        data: &CFData,
139        options: CFPropertyListMutabilityOptions,
140    ) -> Result<(CFType, CFPropertyListFormat), CFPropertyListError> {
141        let mut format = 0_isize;
142        let mut error = std::ptr::null_mut();
143        let ptr = unsafe {
144            ffi::cf_property_list_create_with_data(
145                data.as_ptr(),
146                options.as_u64(),
147                &mut format,
148                &mut error,
149            )
150        };
151        CFType::from_raw(ptr)
152            .map(|value| (value, property_list_format(format)))
153            .ok_or_else(|| property_list_error("CFPropertyListCreateWithData", error))
154    }
155
156    /// Decode a property list from an already-open Core Foundation read stream.
157    pub fn create_with_stream(
158        stream: &CFReadStream,
159        stream_length: usize,
160        options: CFPropertyListMutabilityOptions,
161    ) -> Result<(CFType, CFPropertyListFormat), CFPropertyListError> {
162        let mut format = 0_isize;
163        let mut error = std::ptr::null_mut();
164        let stream_length = isize::try_from(stream_length).unwrap_or(isize::MAX);
165        let ptr = unsafe {
166            ffi::cf_property_list_create_with_stream(
167                stream.as_ptr(),
168                stream_length,
169                options.as_u64(),
170                &mut format,
171                &mut error,
172            )
173        };
174        CFType::from_raw(ptr)
175            .map(|value| (value, property_list_format(format)))
176            .ok_or_else(|| property_list_error("CFPropertyListCreateWithStream", error))
177    }
178
179    /// Serialize a property list into a `CFData` blob.
180    pub fn create_data(
181        property_list: &dyn AsCFType,
182        format: CFPropertyListFormat,
183        options: u64,
184    ) -> Result<CFData, CFPropertyListError> {
185        let mut error = std::ptr::null_mut();
186        let ptr = unsafe {
187            ffi::cf_property_list_create_data(
188                property_list.as_ptr(),
189                format as isize,
190                options,
191                &mut error,
192            )
193        };
194        CFData::from_raw(ptr).ok_or_else(|| property_list_error("CFPropertyListCreateData", error))
195    }
196
197    /// Write a serialized property list to an already-open Core Foundation write stream.
198    pub fn write(
199        property_list: &dyn AsCFType,
200        stream: &CFWriteStream,
201        format: CFPropertyListFormat,
202        options: u64,
203    ) -> Result<usize, CFPropertyListError> {
204        let mut error = std::ptr::null_mut();
205        let written = unsafe {
206            ffi::cf_property_list_write(
207                property_list.as_ptr(),
208                stream.as_ptr(),
209                format as isize,
210                options,
211                &mut error,
212            )
213        };
214        if written > 0 {
215            usize::try_from(written).map_err(|_| {
216                CFPropertyListError::Null(crate::CFError::new("CFPropertyListWrite overflow"))
217            })
218        } else {
219            Err(property_list_error("CFPropertyListWrite", error))
220        }
221    }
222
223    /// Validate whether a Core Foundation object can be serialized as a property list.
224    #[must_use]
225    pub fn is_valid(property_list: &dyn AsCFType, format: CFPropertyListFormat) -> bool {
226        unsafe { ffi::cf_property_list_is_valid(property_list.as_ptr(), format as isize) }
227    }
228}