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