plist_plus/
lib.rs

1#![allow(clippy::not_unsafe_ptr_arg_deref)]
2use error::PlistError;
3#[doc = include_str!("../README.md")]
4use log::{trace, warn};
5use rand::Rng;
6use std::{ffi::CString, fmt::Formatter, os::raw::c_char};
7
8pub mod error;
9mod iterator;
10mod types;
11mod unsafe_bindings;
12
13/// The main struct for the plist library
14/// This struct contains a pointer to the C compatible structure
15pub struct Plist {
16    pub(crate) plist_t: unsafe_bindings::plist_t,
17    pub plist_type: PlistType,
18    pub(crate) id: u32,
19    pub(crate) false_drop: bool,
20}
21
22unsafe impl Send for Plist {}
23unsafe impl Sync for Plist {}
24
25/// The type of a given plist
26#[derive(PartialEq, Eq, Debug, Clone)]
27pub enum PlistType {
28    Boolean,
29    Integer,
30    Real,
31    Date,
32    Data,
33    String,
34    Array,
35    Dictionary,
36    Unknown,
37    Key,
38    Uid,
39    None,
40}
41
42impl Plist {
43    /// Returns a pointer to the underlying C compatible structure
44    /// This is compatible with libraries such as libimobiledevice
45    pub fn get_pointer(&self) -> *mut std::ffi::c_void {
46        self.plist_t
47    }
48    /// This takes a string in the form of XML and returns a Plist struct
49    pub fn from_xml(xml: String) -> Result<Plist, PlistError> {
50        let xml = match CString::new(xml) {
51            Ok(s) => s,
52            Err(_) => {
53                warn!("Could not convert string to CString");
54                return Err(PlistError::InvalidArg);
55            }
56        };
57        let xml_len = std::convert::TryInto::try_into(xml.as_bytes().len()).unwrap();
58        let mut plist_t = unsafe { std::mem::zeroed() };
59        trace!("Parsing xml");
60        unsafe {
61            unsafe_bindings::plist_from_xml(xml.as_ptr() as *const c_char, xml_len, &mut plist_t)
62        };
63        Ok(plist_t.into())
64    }
65    /// This takes a string in the form of binary and returns a Plist struct
66    pub fn from_bin(bin: Vec<u8>) -> Result<Plist, PlistError> {
67        let mut plist_t = unsafe { std::mem::zeroed() };
68        let result = unsafe {
69            unsafe_bindings::plist_from_bin(
70                bin.as_ptr() as *const c_char,
71                bin.len() as u32,
72                &mut plist_t,
73            )
74        };
75        if result != 0 {
76            return Err(result.into());
77        }
78        Ok(plist_t.into())
79    }
80    pub fn from_memory(bin: Vec<u8>) -> Result<Plist, PlistError> {
81        let mut plist_t = unsafe { std::mem::zeroed() };
82        let result = unsafe {
83            unsafe_bindings::plist_from_memory(
84                bin.as_ptr() as *const c_char,
85                bin.len() as u32,
86                &mut plist_t,
87                std::ptr::null_mut(),
88            )
89        };
90        if result != 0 {
91            return Err(result.into());
92        }
93        Ok(plist_t.into())
94    }
95    /// This will back the plist to the plist it came from
96    /// This is unsafe due to how the underlying C library works
97    /// It will return a second copy of the plist, and should be false dropped if used
98    /// # Safety
99    /// Don't be stupid
100    pub unsafe fn get_parent(self) -> Plist {
101        trace!("Getting parent");
102        unsafe_bindings::plist_get_parent(self.plist_t).into()
103    }
104    /// Gets the type of the plist from the C library
105    pub fn get_node_type(&self) -> PlistType {
106        trace!("Getting node type");
107        unsafe { unsafe_bindings::plist_get_node_type(self.plist_t) }.into() // puts on sunglasses
108    }
109    /// Queries if the plist has a binary structure
110    pub fn is_binary(&self) -> bool {
111        let plist_data = unsafe { std::mem::zeroed() };
112        let plist_len = unsafe { std::mem::zeroed() };
113        trace!("Getting plist data");
114        unsafe {
115            unsafe_bindings::plist_get_data_val(self.plist_t, plist_data, plist_len);
116        }
117        trace!("Checking if plist is binary");
118        !matches!(
119            unsafe {
120                unsafe_bindings::plist_is_binary(*plist_data, (*plist_len).try_into().unwrap())
121            },
122            0
123        )
124    }
125    /// Traverses a list of plists
126    /// Reimplimented from the C function because function overloading is evil
127    pub fn access_path(self, plists: Vec<String>) -> Result<Plist, PlistError> {
128        let mut current = self;
129        let mut i = 0;
130        while i < plists.len() {
131            match current.plist_type {
132                PlistType::Array => {
133                    current = match current.array_get_item(i as u32) {
134                        Ok(item) => item,
135                        Err(_) => return Err(PlistError::InvalidArg),
136                    };
137                }
138                PlistType::Dictionary => {
139                    current = match current.dict_get_item(&plists[i]) {
140                        Ok(item) => item,
141                        Err(_) => return Err(PlistError::InvalidArg),
142                    };
143                }
144                _ => {
145                    return Err(PlistError::InvalidArg);
146                }
147            }
148            i += 1;
149        }
150        Ok(current.plist_t.into())
151    }
152
153    /// Disposes of the Rust structure without calling the destructor of the C structure
154    /// This is necessary when a function absorbs another plist.
155    /// That way, the rest of the plist struct is dropped, but the data at the pointer is not.
156    /// This prevents many segfaults, but may cause unknown memory leaks.
157    /// Needs more research...
158    pub fn false_drop(mut self) {
159        self.false_drop = true;
160        trace!("False dropping {}", self.id);
161    }
162
163    /// Compares two structs and determines if they are equal
164    pub fn compare_node_values(node_l: Plist, node_r: Plist) -> bool {
165        trace!("Comparing node values");
166        matches!(
167            unsafe { unsafe_bindings::plist_compare_node_value(node_l.plist_t, node_r.plist_t) }
168                .to_string()
169                .as_str(),
170            "TRUE"
171        )
172    }
173
174    pub fn get_display_value(&self) -> Result<String, PlistError> {
175        let mut to_return;
176        match self.plist_type {
177            PlistType::Boolean => {
178                to_return = self.get_bool_val()?.to_string();
179            }
180            PlistType::Integer => {
181                to_return = self.get_uint_val()?.to_string();
182            }
183            PlistType::Real => {
184                to_return = self.get_real_val()?.to_string();
185            }
186            PlistType::Data => {
187                to_return = format!("{:?}", self.get_data_val()?);
188            }
189            PlistType::Date => {
190                todo!();
191            }
192            PlistType::String => {
193                to_return = format!("\"{}\"", self.get_string_val()?);
194            }
195            PlistType::Array => {
196                to_return = "[".to_string();
197                for item in self.clone() {
198                    println!("Item go!");
199                    to_return = format!("{}{}", to_return, item.plist.get_display_value()?);
200                }
201                to_return = format!("{}]", to_return);
202            }
203            PlistType::Dictionary => {
204                to_return = "{ ".to_string();
205                for line in self.clone() {
206                    to_return = format!(
207                        "{}{}: {}, ",
208                        to_return,
209                        line.key.unwrap(),
210                        line.plist.get_display_value()?
211                    );
212                }
213                // Chop off the last comma and space
214                to_return = format!(
215                    "{} }}",
216                    to_return
217                        .chars()
218                        .take(to_return.len() - 2)
219                        .collect::<String>()
220                );
221            }
222            PlistType::Uid => {
223                todo!();
224            }
225            PlistType::Key => {
226                todo!();
227            }
228            PlistType::Unknown => {
229                to_return = "Unknown".to_string();
230            }
231            PlistType::None => {
232                to_return = "None".to_string();
233            }
234        }
235
236        Ok(to_return)
237    }
238}
239
240impl From<unsafe_bindings::plist_t> for Plist {
241    fn from(plist_t: unsafe_bindings::plist_t) -> Self {
242        let mut rng = rand::thread_rng();
243        let id = rng.gen::<u32>();
244        trace!("Creating plist from plist_t with id {}", id);
245        Plist {
246            plist_t,
247            plist_type: unsafe { unsafe_bindings::plist_get_node_type(plist_t) }.into(),
248            id,
249            false_drop: false,
250        }
251    }
252}
253
254impl From<Plist> for String {
255    fn from(plist: Plist) -> Self {
256        let plist_t = plist.plist_t;
257        let mut plist_data = std::ptr::null_mut();
258        let mut plist_size = 0;
259        trace!("Converting plist to XML data");
260        unsafe {
261            unsafe_bindings::plist_to_xml(plist_t, &mut plist_data, &mut plist_size);
262        }
263        trace!("Assembling XML data");
264        let plist_data = unsafe {
265            std::slice::from_raw_parts(plist_data as *const u8, plist_size.try_into().unwrap())
266        };
267        let plist_data = std::str::from_utf8(plist_data).unwrap();
268
269        String::from(plist_data)
270    }
271}
272
273impl ToString for Plist {
274    fn to_string(&self) -> String {
275        let mut plist_data = std::ptr::null_mut();
276        let mut plist_size = 0;
277        trace!("Converting plist to XML data");
278        unsafe {
279            unsafe_bindings::plist_to_xml(self.plist_t, &mut plist_data, &mut plist_size);
280        }
281        trace!("Assembling XML data");
282
283        let plist_data =
284            unsafe { std::slice::from_raw_parts(plist_data as *mut u8, plist_size as usize) };
285        let plist_data = std::str::from_utf8(plist_data).unwrap();
286
287        plist_data.to_string()
288    }
289}
290
291impl From<Plist> for Vec<u8> {
292    fn from(plist: Plist) -> Self {
293        let plist_t = plist.plist_t;
294        let mut plist_data = std::ptr::null_mut();
295        let mut plist_size = 0;
296        trace!("Converting plist to binary data");
297        unsafe {
298            unsafe_bindings::plist_to_bin(plist_t, &mut plist_data, &mut plist_size);
299        }
300        trace!("Assembling binary data");
301        let plist_data = unsafe {
302            std::slice::from_raw_parts(plist_data as *const u8, plist_size.try_into().unwrap())
303        };
304
305        plist_data.to_vec()
306    }
307}
308
309impl Clone for Plist {
310    fn clone(&self) -> Self {
311        trace!("Cloning plist");
312        let plist_t = unsafe { unsafe_bindings::plist_copy(self.plist_t) };
313        trace!("Getting type of cloned plist");
314        plist_t.into()
315    }
316}
317
318impl std::fmt::Debug for Plist {
319    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
320        let plist_data = self.to_string();
321        write!(f, "{:?}: {}", self.plist_type, plist_data)
322    }
323}
324
325impl Drop for Plist {
326    fn drop(&mut self) {
327        if !self.false_drop {
328            trace!("Dropping plist {}", self.id);
329            unsafe { unsafe_bindings::plist_free(self.plist_t) }
330            trace!("Plist dropped");
331        }
332    }
333}
334
335impl From<i32> for PlistType {
336    fn from(i: i32) -> Self {
337        match i {
338            0 => PlistType::Boolean,
339            1 => PlistType::Integer,
340            2 => PlistType::Real,
341            3 => PlistType::String,
342            4 => PlistType::Array,
343            5 => PlistType::Dictionary,
344            6 => PlistType::Date,
345            7 => PlistType::Data,
346            8 => PlistType::Key,
347            9 => PlistType::Uid,
348            10 => PlistType::None,
349            _ => PlistType::Unknown,
350        }
351    }
352}
353
354impl From<u32> for PlistType {
355    fn from(i: u32) -> Self {
356        match i {
357            0 => PlistType::Boolean,
358            1 => PlistType::Integer,
359            2 => PlistType::Real,
360            3 => PlistType::String,
361            4 => PlistType::Array,
362            5 => PlistType::Dictionary,
363            6 => PlistType::Date,
364            7 => PlistType::Data,
365            8 => PlistType::Key,
366            9 => PlistType::Uid,
367            10 => PlistType::None,
368            _ => PlistType::Unknown,
369        }
370    }
371}
372
373impl From<PlistType> for String {
374    fn from(plist_type: PlistType) -> String {
375        match plist_type {
376            PlistType::Boolean => "Boolean".to_string(),
377            PlistType::Integer => "Integer".to_string(),
378            PlistType::Real => "Real".to_string(),
379            PlistType::Date => "Date".to_string(),
380            PlistType::Data => "Data".to_string(),
381            PlistType::String => "String".to_string(),
382            PlistType::Array => "Array".to_string(),
383            PlistType::Dictionary => "Dictionary".to_string(),
384            PlistType::Unknown => "Unknown".to_string(),
385            PlistType::Key => "Key".to_string(),
386            PlistType::Uid => "Uid".to_string(),
387            PlistType::None => "None".to_string(),
388        }
389    }
390}