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