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