imessage_database/util/
streamtyped.rs1use crate::error::streamtyped::StreamTypedError;
9
10const START_PATTERN: [u8; 2] = [0x0001, 0x002b];
14
15const END_PATTERN: [u8; 2] = [0x0086, 0x0084];
19
20pub fn parse(mut stream: Vec<u8>) -> Result<String, StreamTypedError> {
58 for idx in 0..stream.len() {
60 if idx + 2 > stream.len() {
61 return Err(StreamTypedError::NoStartPattern);
62 }
63 let part = &stream[idx..idx + 2];
64
65 if part == START_PATTERN {
66 stream.drain(..idx + 2);
68 break;
69 }
70 }
71
72 for idx in 1..stream.len() {
74 if idx >= stream.len() - 2 {
75 return Err(StreamTypedError::NoEndPattern);
76 }
77 let part = &stream[idx..idx + 2];
78
79 if part == END_PATTERN {
80 stream.truncate(idx);
82 break;
83 }
84 }
85
86 match String::from_utf8(stream)
89 .map_err(|non_utf8| String::from_utf8_lossy(non_utf8.as_bytes()).into_owned())
90 {
91 Ok(string) => drop_chars(1, string),
94 Err(string) => drop_chars(3, string),
97 }
98}
99
100fn drop_chars(offset: usize, mut string: String) -> Result<String, StreamTypedError> {
102 let (position, _) = string
104 .char_indices()
105 .nth(offset)
106 .ok_or(StreamTypedError::InvalidPrefix)?;
107
108 string.drain(..position);
110 Ok(string)
111}
112
113#[cfg(test)]
114mod tests {
115 use std::env::current_dir;
116 use std::fs::File;
117 use std::io::Read;
118 use std::vec;
119
120 use crate::util::streamtyped::{drop_chars, parse};
121
122 #[test]
123 fn test_parse_text_clean() {
124 let plist_path = current_dir()
125 .unwrap()
126 .as_path()
127 .join("test_data/typedstream/AttributedBodyTextOnly");
128 let mut file = File::open(plist_path).unwrap();
129 let mut bytes = vec![];
130 file.read_to_end(&mut bytes).unwrap();
131 let parsed = parse(bytes).unwrap();
132
133 let expected = "Noter test".to_string();
134
135 assert_eq!(parsed, expected);
136 }
137
138 #[test]
139 fn test_parse_text_space() {
140 let plist_path = current_dir()
141 .unwrap()
142 .as_path()
143 .join("test_data/typedstream/AttributedBodyTextOnly2");
144 let mut file = File::open(plist_path).unwrap();
145 let mut bytes = vec![];
146 file.read_to_end(&mut bytes).unwrap();
147 let parsed = parse(bytes).unwrap();
148
149 let expected = "Test 3".to_string();
150
151 assert_eq!(parsed, expected);
152 }
153
154 #[test]
155 fn test_parse_text_weird_font() {
156 let plist_path = current_dir()
157 .unwrap()
158 .as_path()
159 .join("test_data/typedstream/WeirdText");
160 let mut file = File::open(plist_path).unwrap();
161 let mut bytes = vec![];
162 file.read_to_end(&mut bytes).unwrap();
163 let parsed = parse(bytes).unwrap();
164
165 let expected = "𝖍𝖊𝖑𝖑𝖔 𝖜𝖔𝖗𝖑𝖉".to_string();
166
167 assert_eq!(parsed, expected);
168 }
169
170 #[test]
171 fn test_parse_text_url() {
172 let plist_path = current_dir()
173 .unwrap()
174 .as_path()
175 .join("test_data/typedstream/URL");
176 let mut file = File::open(plist_path).unwrap();
177 let mut bytes = vec![];
178 file.read_to_end(&mut bytes).unwrap();
179 let parsed = parse(bytes).unwrap();
180
181 let expected = "https://github.com/ReagentX/Logria".to_string();
182
183 assert_eq!(parsed, expected);
184 }
185
186 #[test]
187 fn test_parse_text_multi_part() {
188 let plist_path = current_dir()
189 .unwrap()
190 .as_path()
191 .join("test_data/typedstream/MultiPart");
192 let mut file = File::open(plist_path).unwrap();
193 let mut bytes = vec![];
194 file.read_to_end(&mut bytes).unwrap();
195 let parsed = parse(bytes).unwrap();
196
197 let expected = "\u{FFFC}test 1\u{FFFC}test 2 \u{FFFC}test 3".to_string();
198
199 assert_eq!(parsed, expected);
200 }
201
202 #[test]
203 fn test_parse_text_app() {
204 let plist_path = current_dir()
206 .unwrap()
207 .as_path()
208 .join("test_data/typedstream/ExtraData");
209 let mut file = File::open(plist_path).unwrap();
210 let mut bytes = vec![];
211 file.read_to_end(&mut bytes).unwrap();
212 let parsed = parse(bytes).unwrap();
213
214 let expected = "This is parsing";
215
216 assert_eq!(&parsed[..expected.len()], expected);
217 }
218
219 #[test]
220 fn test_parse_text_long() {
221 let plist_path = current_dir()
222 .unwrap()
223 .as_path()
224 .join("test_data/typedstream/LongMessage");
225 let mut file = File::open(plist_path).unwrap();
226 let mut bytes = vec![];
227 file.read_to_end(&mut bytes).unwrap();
228 let parsed = parse(bytes).unwrap();
229
230 let expected = "Sed nibh velit,";
231
232 assert_eq!(&parsed[..expected.len()], expected);
233 assert_eq!(parsed.len(), 2359);
234 }
235
236 #[test]
237 fn test_parse_text_blank() {
238 let plist_path = current_dir()
239 .unwrap()
240 .as_path()
241 .join("test_data/typedstream/Blank");
242 let mut file = File::open(plist_path).unwrap();
243 let mut bytes = vec![];
244 file.read_to_end(&mut bytes).unwrap();
245 let parsed = parse(bytes);
246
247 assert!(&parsed.is_err());
248 }
249
250 #[test]
251 fn test_parse_text_multi_part_deleted() {
252 let plist_path = current_dir()
253 .unwrap()
254 .as_path()
255 .join("test_data/typedstream/MultiPartWithDeleted");
256 let mut file = File::open(plist_path).unwrap();
257 let mut bytes = vec![];
258 file.read_to_end(&mut bytes).unwrap();
259 let parsed = parse(bytes).unwrap();
260 println!("{parsed:?}");
261
262 let expected = "From arbitrary byte stream:\r\u{FFFC}To native Rust data structures:\r";
263
264 assert_eq!(parsed, expected);
265 }
266
267 #[test]
268 fn test_parse_text_attachment() {
269 let plist_path = current_dir()
270 .unwrap()
271 .as_path()
272 .join("test_data/typedstream/Attachment");
273 let mut file = File::open(plist_path).unwrap();
274 let mut bytes = vec![];
275 file.read_to_end(&mut bytes).unwrap();
276 let parsed = parse(bytes).unwrap();
277 println!("{parsed:?}");
278
279 let expected =
280 "\u{FFFC}This is how the notes look to me fyi, in case it helps make sense of anything";
281
282 assert_eq!(parsed, expected);
283 }
284
285 #[test]
286 fn test_parse_text_array() {
287 let plist_path = current_dir()
288 .unwrap()
289 .as_path()
290 .join("test_data/typedstream/Array");
291 let mut file = File::open(plist_path).unwrap();
292 let mut bytes = vec![];
293 file.read_to_end(&mut bytes).unwrap();
294 let parsed = parse(bytes).unwrap();
295 println!("{parsed:?}");
296
297 let expected = "A single ChatGPT instance takes 5MW of power to run";
298
299 assert_eq!(parsed, expected);
300 }
301
302 #[test]
303 fn test_can_drop_chars() {
304 assert_eq!(
305 drop_chars(1, String::from("Hello world")).unwrap(),
306 String::from("ello world")
307 );
308 }
309
310 #[test]
311 fn test_can_drop_chars_none() {
312 assert_eq!(
313 drop_chars(0, String::from("Hello world")).unwrap(),
314 String::from("Hello world")
315 );
316 }
317
318 #[test]
319 fn test_cant_drop_all() {
320 assert!(drop_chars(1000, String::from("Hello world")).is_err());
321 }
322}