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::CFError),
97}
98
99impl fmt::Display for CFPropertyListError {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        match self {
102            Self::CoreFoundation(error) => fmt::Display::fmt(error, f),
103            Self::Null(error) => fmt::Display::fmt(error, f),
104        }
105    }
106}
107
108impl std::error::Error for CFPropertyListError {}
109
110fn property_list_error(
111    operation: &'static str,
112    error_ptr: *mut std::ffi::c_void,
113) -> CFPropertyListError {
114    CoreFoundationError::from_raw(error_ptr).map_or_else(
115        || CFPropertyListError::Null(crate::CFError::new(operation)),
116        CFPropertyListError::CoreFoundation,
117    )
118}
119
120fn property_list_format(raw: isize) -> CFPropertyListFormat {
121    CFPropertyListFormat::try_from(raw)
122        .expect("Core Foundation returned an unknown CFPropertyListFormat")
123}
124
125/// Namespace for property-list parse / serialize helpers.
126pub struct CFPropertyList;
127
128impl CFPropertyList {
129    /// Deep-copy a property list, optionally changing its mutability semantics.
130    pub fn create_deep_copy(
131        property_list: &dyn AsCFType,
132        options: CFPropertyListMutabilityOptions,
133    ) -> Result<CFType, crate::CFError> {
134        let ptr = unsafe {
135            ffi::cf_property_list_create_deep_copy(property_list.as_ptr(), options.as_u64())
136        };
137        CFType::from_raw(ptr).ok_or(crate::CFError::new("CFPropertyListCreateDeepCopy"))
138    }
139
140    /// Decode a property list from an in-memory data blob.
141    pub fn create_with_data(
142        data: &CFData,
143        options: CFPropertyListMutabilityOptions,
144    ) -> Result<(CFType, CFPropertyListFormat), CFPropertyListError> {
145        let mut format = 0_isize;
146        let mut error = std::ptr::null_mut();
147        let ptr = unsafe {
148            ffi::cf_property_list_create_with_data(
149                data.as_ptr(),
150                options.as_u64(),
151                &mut format,
152                &mut error,
153            )
154        };
155        CFType::from_raw(ptr)
156            .map(|value| (value, property_list_format(format)))
157            .ok_or_else(|| property_list_error("CFPropertyListCreateWithData", error))
158    }
159
160    /// Decode a property list from an already-open Core Foundation read stream.
161    pub fn create_with_stream(
162        stream: &CFReadStream,
163        stream_length: usize,
164        options: CFPropertyListMutabilityOptions,
165    ) -> Result<(CFType, CFPropertyListFormat), CFPropertyListError> {
166        let mut format = 0_isize;
167        let mut error = std::ptr::null_mut();
168        let stream_length = isize::try_from(stream_length).unwrap_or(isize::MAX);
169        let ptr = unsafe {
170            ffi::cf_property_list_create_with_stream(
171                stream.as_ptr(),
172                stream_length,
173                options.as_u64(),
174                &mut format,
175                &mut error,
176            )
177        };
178        CFType::from_raw(ptr)
179            .map(|value| (value, property_list_format(format)))
180            .ok_or_else(|| property_list_error("CFPropertyListCreateWithStream", error))
181    }
182
183    /// Serialize a property list into a `CFData` blob.
184    pub fn create_data(
185        property_list: &dyn AsCFType,
186        format: CFPropertyListFormat,
187        options: u64,
188    ) -> Result<CFData, CFPropertyListError> {
189        let mut error = std::ptr::null_mut();
190        let ptr = unsafe {
191            ffi::cf_property_list_create_data(
192                property_list.as_ptr(),
193                format as isize,
194                options,
195                &mut error,
196            )
197        };
198        CFData::from_raw(ptr).ok_or_else(|| property_list_error("CFPropertyListCreateData", error))
199    }
200
201    /// Write a serialized property list to an already-open Core Foundation write stream.
202    pub fn write(
203        property_list: &dyn AsCFType,
204        stream: &CFWriteStream,
205        format: CFPropertyListFormat,
206        options: u64,
207    ) -> Result<usize, CFPropertyListError> {
208        let mut error = std::ptr::null_mut();
209        let written = unsafe {
210            ffi::cf_property_list_write(
211                property_list.as_ptr(),
212                stream.as_ptr(),
213                format as isize,
214                options,
215                &mut error,
216            )
217        };
218        if written > 0 {
219            usize::try_from(written).map_err(|_| {
220                CFPropertyListError::Null(crate::CFError::new("CFPropertyListWrite overflow"))
221            })
222        } else {
223            Err(property_list_error("CFPropertyListWrite", error))
224        }
225    }
226
227    /// Validate whether a Core Foundation object can be serialized as a property list.
228    #[must_use]
229    pub fn is_valid(property_list: &dyn AsCFType, format: CFPropertyListFormat) -> bool {
230        unsafe { ffi::cf_property_list_is_valid(property_list.as_ptr(), format as isize) }
231    }
232}