imap_patch_for_async_imap_lite/
parse.rs

1#![allow(missing_docs)]
2/// PATCH_FOR_ASYNC_IMAP_LITE [add]
3use imap_proto::{self, MailboxDatum, Response};
4use lazy_static::lazy_static;
5use regex::Regex;
6use std::collections::HashSet;
7use std::sync::mpsc;
8
9use super::error::{Error, ParseError, Result};
10use super::types::*;
11
12lazy_static! {
13    static ref AUTH_RESP_REGEX: Regex = Regex::new("^\\+ (.*)\r\n").unwrap();
14}
15
16pub fn parse_authenticate_response(line: &str) -> Result<&str> {
17    if let Some(cap) = AUTH_RESP_REGEX.captures_iter(line).next() {
18        let data = cap.get(1).map(|x| x.as_str()).unwrap_or("");
19        return Ok(data);
20    }
21    Err(Error::Parse(ParseError::Authentication(
22        line.to_string(),
23        None,
24    )))
25}
26
27enum MapOrNot<T> {
28    Map(T),
29    Not(Response<'static>),
30    #[allow(dead_code)]
31    Ignore,
32}
33
34unsafe fn parse_many<T, F>(
35    lines: Vec<u8>,
36    mut map: F,
37    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
38) -> ZeroCopyResult<Vec<T>>
39where
40    F: FnMut(Response<'static>) -> Result<MapOrNot<T>>,
41{
42    let f = |mut lines: &'static [u8]| {
43        let mut things = Vec::new();
44        loop {
45            if lines.is_empty() {
46                break Ok(things);
47            }
48
49            match imap_proto::parse_response(lines) {
50                Ok((rest, resp)) => {
51                    lines = rest;
52
53                    match map(resp)? {
54                        MapOrNot::Map(t) => things.push(t),
55                        MapOrNot::Not(resp) => match handle_unilateral(resp, unsolicited) {
56                            Some(Response::Fetch(..)) => continue,
57                            Some(resp) => break Err(resp.into()),
58                            None => {}
59                        },
60                        MapOrNot::Ignore => continue,
61                    }
62                }
63                _ => {
64                    break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
65                }
66            }
67        }
68    };
69
70    ZeroCopy::make(lines, f)
71}
72
73pub fn parse_names(
74    lines: Vec<u8>,
75    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
76) -> ZeroCopyResult<Vec<Name>> {
77    let f = |resp| match resp {
78        // https://github.com/djc/imap-proto/issues/4
79        Response::MailboxData(MailboxDatum::List {
80            flags,
81            delimiter,
82            name,
83        }) => Ok(MapOrNot::Map(Name {
84            attributes: flags.into_iter().map(NameAttribute::from).collect(),
85            delimiter,
86            name,
87        })),
88        resp => Ok(MapOrNot::Not(resp)),
89    };
90
91    unsafe { parse_many(lines, f, unsolicited) }
92}
93
94pub fn parse_fetches(
95    lines: Vec<u8>,
96    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
97) -> ZeroCopyResult<Vec<Fetch>> {
98    let f = |resp| match resp {
99        Response::Fetch(num, attrs) => {
100            let mut fetch = Fetch {
101                message: num,
102                flags: vec![],
103                uid: None,
104                size: None,
105                fetch: attrs,
106            };
107
108            // set some common fields eaglery
109            for attr in &fetch.fetch {
110                use imap_proto::AttributeValue;
111                match attr {
112                    AttributeValue::Flags(flags) => {
113                        fetch.flags.extend(flags.iter().cloned().map(Flag::from));
114                    }
115                    AttributeValue::Uid(uid) => fetch.uid = Some(*uid),
116                    AttributeValue::Rfc822Size(sz) => fetch.size = Some(*sz),
117                    _ => {}
118                }
119            }
120
121            Ok(MapOrNot::Map(fetch))
122        }
123        resp => Ok(MapOrNot::Not(resp)),
124    };
125
126    unsafe { parse_many(lines, f, unsolicited) }
127}
128
129pub fn parse_expunge(
130    lines: Vec<u8>,
131    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
132) -> Result<Vec<u32>> {
133    let f = |resp| match resp {
134        Response::Expunge(id) => Ok(MapOrNot::Map(id)),
135        resp => Ok(MapOrNot::Not(resp)),
136    };
137
138    unsafe { parse_many(lines, f, unsolicited).map(|ids| ids.take()) }
139}
140
141pub fn parse_capabilities(
142    lines: Vec<u8>,
143    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
144) -> ZeroCopyResult<Capabilities> {
145    let f = |mut lines| {
146        let mut caps = HashSet::new();
147        loop {
148            match imap_proto::parse_response(lines) {
149                Ok((rest, Response::Capabilities(c))) => {
150                    lines = rest;
151                    caps.extend(c);
152                }
153                Ok((rest, data)) => {
154                    lines = rest;
155                    if let Some(resp) = handle_unilateral(data, unsolicited) {
156                        break Err(resp.into());
157                    }
158                }
159                _ => {
160                    break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
161                }
162            }
163
164            if lines.is_empty() {
165                break Ok(Capabilities(caps));
166            }
167        }
168    };
169
170    unsafe { ZeroCopy::make(lines, f) }
171}
172
173pub fn parse_noop(
174    lines: Vec<u8>,
175    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
176) -> Result<()> {
177    let mut lines: &[u8] = &lines;
178
179    loop {
180        if lines.is_empty() {
181            break Ok(());
182        }
183
184        match imap_proto::parse_response(lines) {
185            Ok((rest, data)) => {
186                lines = rest;
187                if let Some(resp) = handle_unilateral(data, unsolicited) {
188                    break Err(resp.into());
189                }
190            }
191            _ => {
192                break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
193            }
194        }
195    }
196}
197
198pub fn parse_mailbox(
199    mut lines: &[u8],
200    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
201) -> Result<Mailbox> {
202    let mut mailbox = Mailbox::default();
203
204    loop {
205        match imap_proto::parse_response(lines) {
206            Ok((rest, Response::Data { status, code, .. })) => {
207                lines = rest;
208
209                if let imap_proto::Status::Ok = status {
210                } else {
211                    // how can this happen for a Response::Data?
212                    unreachable!();
213                }
214
215                use imap_proto::ResponseCode;
216                match code {
217                    Some(ResponseCode::UidValidity(uid)) => {
218                        mailbox.uid_validity = Some(uid);
219                    }
220                    Some(ResponseCode::UidNext(unext)) => {
221                        mailbox.uid_next = Some(unext);
222                    }
223                    Some(ResponseCode::Unseen(n)) => {
224                        mailbox.unseen = Some(n);
225                    }
226                    Some(ResponseCode::PermanentFlags(flags)) => {
227                        mailbox
228                            .permanent_flags
229                            .extend(flags.into_iter().map(String::from).map(Flag::from));
230                    }
231                    _ => {}
232                }
233            }
234            Ok((rest, Response::MailboxData(m))) => {
235                lines = rest;
236
237                match m {
238                    MailboxDatum::Status { mailbox, status } => {
239                        unsolicited
240                            .send(UnsolicitedResponse::Status {
241                                mailbox: mailbox.into(),
242                                attributes: status,
243                            })
244                            .unwrap();
245                    }
246                    MailboxDatum::Exists(e) => {
247                        mailbox.exists = e;
248                    }
249                    MailboxDatum::Recent(r) => {
250                        mailbox.recent = r;
251                    }
252                    MailboxDatum::Flags(flags) => {
253                        mailbox
254                            .flags
255                            .extend(flags.into_iter().map(String::from).map(Flag::from));
256                    }
257                    MailboxDatum::List { .. }
258                    | MailboxDatum::MetadataSolicited { .. }
259                    | MailboxDatum::MetadataUnsolicited { .. } => {}
260                }
261            }
262            Ok((rest, Response::Expunge(n))) => {
263                lines = rest;
264                unsolicited.send(UnsolicitedResponse::Expunge(n)).unwrap();
265            }
266            Ok((_, resp)) => {
267                break Err(resp.into());
268            }
269            _ => {
270                break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
271            }
272        }
273
274        if lines.is_empty() {
275            break Ok(mailbox);
276        }
277    }
278}
279
280pub fn parse_ids(
281    lines: &[u8],
282    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
283) -> Result<HashSet<u32>> {
284    let mut lines = &lines[..];
285    let mut ids = HashSet::new();
286    loop {
287        if lines.is_empty() {
288            break Ok(ids);
289        }
290
291        match imap_proto::parse_response(lines) {
292            Ok((rest, Response::IDs(c))) => {
293                lines = rest;
294                ids.extend(c);
295            }
296            Ok((rest, data)) => {
297                lines = rest;
298                if let Some(resp) = handle_unilateral(data, unsolicited) {
299                    break Err(resp.into());
300                }
301            }
302            _ => {
303                break Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
304            }
305        }
306    }
307}
308
309// check if this is simply a unilateral server response
310// (see Section 7 of RFC 3501):
311fn handle_unilateral<'a>(
312    res: Response<'a>,
313    unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
314) -> Option<Response<'a>> {
315    match res {
316        Response::MailboxData(MailboxDatum::Status { mailbox, status }) => {
317            unsolicited
318                .send(UnsolicitedResponse::Status {
319                    mailbox: mailbox.into(),
320                    attributes: status,
321                })
322                .unwrap();
323        }
324        Response::MailboxData(MailboxDatum::Recent(n)) => {
325            unsolicited.send(UnsolicitedResponse::Recent(n)).unwrap();
326        }
327        Response::MailboxData(MailboxDatum::Flags(_)) => {
328            // TODO: next breaking change:
329        }
330        Response::MailboxData(MailboxDatum::Exists(n)) => {
331            unsolicited.send(UnsolicitedResponse::Exists(n)).unwrap();
332        }
333        Response::Expunge(n) => {
334            unsolicited.send(UnsolicitedResponse::Expunge(n)).unwrap();
335        }
336        res => {
337            return Some(res);
338        }
339    }
340    None
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn parse_capability_test() {
349        let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"];
350        let lines = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
351        let (mut send, recv) = mpsc::channel();
352        let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
353        // shouldn't be any unexpected responses parsed
354        assert!(recv.try_recv().is_err());
355        assert_eq!(capabilities.len(), 4);
356        for e in expected_capabilities {
357            assert!(capabilities.has_str(e));
358        }
359    }
360
361    #[test]
362    fn parse_capability_case_insensitive_test() {
363        // Test that "IMAP4REV1" (instead of "IMAP4rev1") is accepted
364        let expected_capabilities = vec!["IMAP4rev1", "STARTTLS"];
365        let lines = b"* CAPABILITY IMAP4REV1 STARTTLS\r\n";
366        let (mut send, recv) = mpsc::channel();
367        let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
368        // shouldn't be any unexpected responses parsed
369        assert!(recv.try_recv().is_err());
370        assert_eq!(capabilities.len(), 2);
371        for e in expected_capabilities {
372            assert!(capabilities.has_str(e));
373        }
374    }
375
376    #[test]
377    #[should_panic]
378    fn parse_capability_invalid_test() {
379        let (mut send, recv) = mpsc::channel();
380        let lines = b"* JUNK IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n";
381        parse_capabilities(lines.to_vec(), &mut send).unwrap();
382        assert!(recv.try_recv().is_err());
383    }
384
385    #[test]
386    fn parse_names_test() {
387        let lines = b"* LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n";
388        let (mut send, recv) = mpsc::channel();
389        let names = parse_names(lines.to_vec(), &mut send).unwrap();
390        assert!(recv.try_recv().is_err());
391        assert_eq!(names.len(), 1);
392        assert_eq!(
393            names[0].attributes(),
394            &[NameAttribute::from("\\HasNoChildren")]
395        );
396        assert_eq!(names[0].delimiter(), Some("."));
397        assert_eq!(names[0].name(), "INBOX");
398    }
399
400    #[test]
401    fn parse_fetches_empty() {
402        let lines = b"";
403        let (mut send, recv) = mpsc::channel();
404        let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap();
405        assert!(recv.try_recv().is_err());
406        assert!(fetches.is_empty());
407    }
408
409    #[test]
410    fn parse_fetches_test() {
411        let lines = b"\
412                    * 24 FETCH (FLAGS (\\Seen) UID 4827943)\r\n\
413                    * 25 FETCH (FLAGS (\\Seen))\r\n";
414        let (mut send, recv) = mpsc::channel();
415        let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap();
416        assert!(recv.try_recv().is_err());
417        assert_eq!(fetches.len(), 2);
418        assert_eq!(fetches[0].message, 24);
419        assert_eq!(fetches[0].flags(), &[Flag::Seen]);
420        assert_eq!(fetches[0].uid, Some(4827943));
421        assert_eq!(fetches[0].body(), None);
422        assert_eq!(fetches[0].header(), None);
423        assert_eq!(fetches[1].message, 25);
424        assert_eq!(fetches[1].flags(), &[Flag::Seen]);
425        assert_eq!(fetches[1].uid, None);
426        assert_eq!(fetches[1].body(), None);
427        assert_eq!(fetches[1].header(), None);
428    }
429
430    #[test]
431    fn parse_fetches_w_unilateral() {
432        // https://github.com/mattnenterprise/rust-imap/issues/81
433        let lines = b"\
434            * 37 FETCH (UID 74)\r\n\
435            * 1 RECENT\r\n";
436        let (mut send, recv) = mpsc::channel();
437        let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap();
438        assert_eq!(recv.try_recv(), Ok(UnsolicitedResponse::Recent(1)));
439        assert_eq!(fetches.len(), 1);
440        assert_eq!(fetches[0].message, 37);
441        assert_eq!(fetches[0].uid, Some(74));
442    }
443
444    #[test]
445    fn parse_names_w_unilateral() {
446        let lines = b"\
447                    * LIST (\\HasNoChildren) \".\" \"INBOX\"\r\n\
448                    * 4 EXPUNGE\r\n";
449        let (mut send, recv) = mpsc::channel();
450        let names = parse_names(lines.to_vec(), &mut send).unwrap();
451
452        assert_eq!(recv.try_recv().unwrap(), UnsolicitedResponse::Expunge(4));
453
454        assert_eq!(names.len(), 1);
455        assert_eq!(
456            names[0].attributes(),
457            &[NameAttribute::from("\\HasNoChildren")]
458        );
459        assert_eq!(names[0].delimiter(), Some("."));
460        assert_eq!(names[0].name(), "INBOX");
461    }
462
463    #[test]
464    fn parse_capabilities_w_unilateral() {
465        let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"];
466        let lines = b"\
467                    * CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n\
468                    * STATUS dev.github (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n\
469                    * 4 EXISTS\r\n";
470        let (mut send, recv) = mpsc::channel();
471        let capabilities = parse_capabilities(lines.to_vec(), &mut send).unwrap();
472
473        assert_eq!(capabilities.len(), 4);
474        for e in expected_capabilities {
475            assert!(capabilities.has_str(e));
476        }
477
478        assert_eq!(
479            recv.try_recv().unwrap(),
480            UnsolicitedResponse::Status {
481                mailbox: "dev.github".to_string(),
482                attributes: vec![
483                    StatusAttribute::Messages(10),
484                    StatusAttribute::UidNext(11),
485                    StatusAttribute::UidValidity(1408806928),
486                    StatusAttribute::Unseen(0)
487                ]
488            }
489        );
490        assert_eq!(recv.try_recv().unwrap(), UnsolicitedResponse::Exists(4));
491    }
492
493    #[test]
494    fn parse_ids_w_unilateral() {
495        let lines = b"\
496            * SEARCH 23 42 4711\r\n\
497            * 1 RECENT\r\n\
498            * STATUS INBOX (MESSAGES 10 UIDNEXT 11 UIDVALIDITY 1408806928 UNSEEN 0)\r\n";
499        let (mut send, recv) = mpsc::channel();
500        let ids = parse_ids(lines, &mut send).unwrap();
501
502        assert_eq!(ids, [23, 42, 4711].iter().cloned().collect());
503
504        assert_eq!(recv.try_recv().unwrap(), UnsolicitedResponse::Recent(1));
505        assert_eq!(
506            recv.try_recv().unwrap(),
507            UnsolicitedResponse::Status {
508                mailbox: "INBOX".to_string(),
509                attributes: vec![
510                    StatusAttribute::Messages(10),
511                    StatusAttribute::UidNext(11),
512                    StatusAttribute::UidValidity(1408806928),
513                    StatusAttribute::Unseen(0)
514                ]
515            }
516        );
517    }
518
519    #[test]
520    fn parse_ids_test() {
521        let lines = b"* SEARCH 1600 1698 1739 1781 1795 1885 1891 1892 1893 1898 1899 1901 1911 1926 1932 1933 1993 1994 2007 2032 2033 2041 2053 2062 2063 2065 2066 2072 2078 2079 2082 2084 2095 2100 2101 2102 2103 2104 2107 2116 2120 2135 2138 2154 2163 2168 2172 2189 2193 2198 2199 2205 2212 2213 2221 2227 2267 2275 2276 2295 2300 2328 2330 2332 2333 2334\r\n\
522            * SEARCH 2335 2336 2337 2338 2339 2341 2342 2347 2349 2350 2358 2359 2362 2369 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2390 2392 2397 2400 2401 2403 2405 2409 2411 2414 2417 2419 2420 2424 2426 2428 2439 2454 2456 2467 2468 2469 2490 2515 2519 2520 2521\r\n";
523        let (mut send, recv) = mpsc::channel();
524        let ids = parse_ids(lines, &mut send).unwrap();
525        assert!(recv.try_recv().is_err());
526        let ids: HashSet<u32> = ids.iter().cloned().collect();
527        assert_eq!(
528            ids,
529            [
530                1600, 1698, 1739, 1781, 1795, 1885, 1891, 1892, 1893, 1898, 1899, 1901, 1911, 1926,
531                1932, 1933, 1993, 1994, 2007, 2032, 2033, 2041, 2053, 2062, 2063, 2065, 2066, 2072,
532                2078, 2079, 2082, 2084, 2095, 2100, 2101, 2102, 2103, 2104, 2107, 2116, 2120, 2135,
533                2138, 2154, 2163, 2168, 2172, 2189, 2193, 2198, 2199, 2205, 2212, 2213, 2221, 2227,
534                2267, 2275, 2276, 2295, 2300, 2328, 2330, 2332, 2333, 2334, 2335, 2336, 2337, 2338,
535                2339, 2341, 2342, 2347, 2349, 2350, 2358, 2359, 2362, 2369, 2371, 2372, 2373, 2374,
536                2375, 2376, 2377, 2378, 2379, 2380, 2381, 2382, 2383, 2384, 2385, 2386, 2390, 2392,
537                2397, 2400, 2401, 2403, 2405, 2409, 2411, 2414, 2417, 2419, 2420, 2424, 2426, 2428,
538                2439, 2454, 2456, 2467, 2468, 2469, 2490, 2515, 2519, 2520, 2521
539            ]
540            .iter()
541            .cloned()
542            .collect()
543        );
544
545        let lines = b"* SEARCH\r\n";
546        let (mut send, recv) = mpsc::channel();
547        let ids = parse_ids(lines, &mut send).unwrap();
548        assert!(recv.try_recv().is_err());
549        let ids: HashSet<u32> = ids.iter().cloned().collect();
550        assert_eq!(ids, HashSet::<u32>::new());
551    }
552}