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