imessage_database/util/
typedstream.rs

1/*!
2 Helpers for working with `Property` types in the Crabstep deserializer.
3*/
4
5use crabstep::{OutputData, PropertyIterator, deserializer::iter::Property};
6
7/// Represents a range pair that contains a type index and a length.
8#[derive(Debug)]
9pub struct TypeLengthPair {
10    /// The type index of the property
11    pub type_index: i64,
12    /// The length of the text affected by the referenced property
13    pub length: u64,
14}
15
16/// Converts a `Property` to a range pair used to denote a type index and a length
17#[inline(always)]
18pub fn as_type_length_pair<'a>(property: &'a mut Property<'a, 'a>) -> Option<TypeLengthPair> {
19    if let Property::Group(group) = property {
20        let mut iter = group.iter();
21        if let Some(Property::Primitive(OutputData::SignedInteger(type_index))) = iter.next() {
22            if let Some(Property::Primitive(OutputData::UnsignedInteger(length))) = iter.next() {
23                return Some(TypeLengthPair {
24                    type_index: *type_index,
25                    length: *length,
26                });
27            }
28        }
29    }
30
31    None
32}
33
34/// Converts a `Property` to an `Option<i64>` if it is a signed integer or similar structure.
35#[must_use]
36#[inline(always)]
37pub fn as_signed_integer(property: &Property<'_, '_>) -> Option<i64> {
38    if let Property::Group(group) = property {
39        let mut iter = group.iter();
40        let val = iter.next()?;
41        if let Property::Primitive(OutputData::SignedInteger(value)) = val {
42            return Some(*value);
43        } else if let Property::Object { name, data, .. } = val {
44            if *name == "NSNumber" {
45                // Clone the iterator to be able to call next() on it
46                let mut data_iter = data.clone();
47                return as_signed_integer(&data_iter.next()?);
48            }
49        }
50    }
51    None
52}
53
54/// Converts a `Property` to an `Option<u64>` if it is an unsigned integer or similar structure.
55#[must_use]
56#[inline(always)]
57pub fn as_unsigned_integer<'a>(property: &'a Property<'a, 'a>) -> Option<u64> {
58    if let Property::Group(group) = property {
59        let mut iter = group.iter();
60        let val = iter.next()?;
61        if let Property::Primitive(OutputData::UnsignedInteger(value)) = val {
62            return Some(*value);
63        } else if let Property::Object { name, data, .. } = val {
64            if *name == "NSNumber" {
65                // Clone the iterator to be able to call next() on it
66                let mut data_iter = data.clone();
67                return as_unsigned_integer(&data_iter.next()?);
68            }
69        }
70    }
71    None
72}
73
74/// Converts a `Property` to an `Option<f64>` if it is an unsigned integer or similar structure.
75#[must_use]
76#[inline(always)]
77pub fn as_float<'a>(property: &'a Property<'a, 'a>) -> Option<f64> {
78    if let Property::Group(group) = property {
79        let mut iter = group.iter();
80        let val = iter.next()?;
81        if let Property::Primitive(OutputData::Double(value)) = val {
82            return Some(*value);
83        } else if let Property::Object { name, data, .. } = val {
84            if *name == "NSNumber" {
85                // Clone the iterator to be able to call next() on it
86                let mut data_iter = data.clone();
87                return as_float(&data_iter.next()?);
88            }
89        }
90    }
91    None
92}
93
94/// Converts a `Property` to an `Option<&str>` if it is a `NSString` or similar structure.
95#[inline(always)]
96pub fn as_nsstring<'a>(property: &'a mut Property<'a, 'a>) -> Option<&'a str> {
97    if let Property::Group(group) = property {
98        let mut iter = group.iter_mut();
99        if let Some(Property::Object { name, data, .. }) = iter.next() {
100            if *name == "NSString" || *name == "NSAttributedString" || *name == "NSMutableString" {
101                if let Some(Property::Group(prim)) = data.next() {
102                    if let Some(Property::Primitive(OutputData::String(s))) = prim.first() {
103                        return Some(s);
104                    }
105                }
106            }
107        }
108    }
109    None
110}
111
112/// Converts a `Property` to Vec<Property> if it is a `NSDictionary`
113#[inline(always)]
114pub fn as_ns_dictionary<'a>(
115    property: &'a mut Property<'a, 'a>,
116) -> Option<&'a mut PropertyIterator<'a, 'a>> {
117    if let Property::Group(group) = property {
118        let mut iter = group.iter_mut();
119        if let Some(Property::Object {
120            class: _,
121            name,
122            data,
123        }) = iter.next()
124        {
125            if *name == "NSDictionary" {
126                return Some(data);
127            }
128        }
129    }
130
131    None
132}
133
134/// Given a mutable reference to a resolved `Property`,\
135/// walks 2 levels of nested groups under an NSURL→NSString and returns the inner &str.
136#[inline(always)]
137pub fn as_nsurl<'a>(property: &'a mut Property<'a, 'a>) -> Option<&'a str> {
138    // only care about top‐level Group
139    if let Property::Group(groups) = property {
140        for level1 in groups.iter_mut() {
141            // look for Object(name="NSURL", data=...)
142            if let Property::Object {
143                name,
144                data: url_data,
145                ..
146            } = level1
147            {
148                if *name != "NSURL" {
149                    continue;
150                }
151                // first level under NSURL
152                for level2 in url_data {
153                    if let Property::Group(mut inner) = level2 {
154                        for level3 in &mut inner {
155                            // look for Object(name="NSString", data=...)
156                            if let Property::Object {
157                                name,
158                                data: str_data,
159                                ..
160                            } = level3
161                            {
162                                if *name != "NSString" {
163                                    continue;
164                                }
165                                // second level under NSString
166                                for level4 in str_data {
167                                    if let Property::Group(bottom) = level4 {
168                                        for p in bottom {
169                                            if let Property::Primitive(OutputData::String(s)) = p {
170                                                return Some(s);
171                                            }
172                                        }
173                                    }
174                                }
175                            }
176                        }
177                    }
178                }
179            }
180        }
181    }
182    None
183}