1use std::borrow::Cow;
8use std::str::from_utf8;
9
10use nom::{
11 branch::alt,
12 bytes::streaming::{tag, tag_no_case, take_while, take_while1},
13 character::streaming::char,
14 combinator::{map, map_res, opt, recognize, value},
15 multi::{many0, many1},
16 sequence::{delimited, pair, preceded, terminated, tuple},
17 IResult,
18};
19
20use crate::{
21 parser::{
22 core::*, rfc2087, rfc2971, rfc3501::body::*, rfc3501::body_structure::*, rfc4314, rfc4315,
23 rfc4551, rfc5161, rfc5256, rfc5464, rfc7162,
24 },
25 types::*,
26};
27
28use super::gmail;
29
30pub mod body;
31pub mod body_structure;
32
33fn is_tag_char(c: u8) -> bool {
34 c != b'+' && is_astring_char(c)
35}
36
37fn status_ok(i: &[u8]) -> IResult<&[u8], Status> {
38 map(tag_no_case("OK"), |_s| Status::Ok)(i)
39}
40fn status_no(i: &[u8]) -> IResult<&[u8], Status> {
41 map(tag_no_case("NO"), |_s| Status::No)(i)
42}
43fn status_bad(i: &[u8]) -> IResult<&[u8], Status> {
44 map(tag_no_case("BAD"), |_s| Status::Bad)(i)
45}
46fn status_preauth(i: &[u8]) -> IResult<&[u8], Status> {
47 map(tag_no_case("PREAUTH"), |_s| Status::PreAuth)(i)
48}
49fn status_bye(i: &[u8]) -> IResult<&[u8], Status> {
50 map(tag_no_case("BYE"), |_s| Status::Bye)(i)
51}
52
53fn status(i: &[u8]) -> IResult<&[u8], Status> {
54 alt((status_ok, status_no, status_bad, status_preauth, status_bye))(i)
55}
56
57pub(crate) fn mailbox(i: &[u8]) -> IResult<&[u8], &str> {
58 map(astring_utf8, |s| {
59 if s.eq_ignore_ascii_case("INBOX") {
60 "INBOX"
61 } else {
62 s
63 }
64 })(i)
65}
66
67fn flag_extension(i: &[u8]) -> IResult<&[u8], &str> {
68 map_res(
69 recognize(pair(tag(b"\\"), take_while(is_atom_char))),
70 from_utf8,
71 )(i)
72}
73
74pub(crate) fn flag(i: &[u8]) -> IResult<&[u8], &str> {
75 alt((
83 flag_extension,
84 map_res(take_while1(is_astring_char), from_utf8),
85 ))(i)
86}
87
88fn flag_list(i: &[u8]) -> IResult<&[u8], Vec<Cow<'_, str>>> {
89 parenthesized_list(map(flag_perm, Cow::Borrowed))(i)
102}
103
104fn flag_perm(i: &[u8]) -> IResult<&[u8], &str> {
105 alt((map_res(tag(b"\\*"), from_utf8), flag))(i)
106}
107
108fn resp_text_code_alert(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
109 map(tag_no_case(b"ALERT"), |_| ResponseCode::Alert)(i)
110}
111
112fn resp_text_code_badcharset(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
113 map(
114 preceded(
115 tag_no_case(b"BADCHARSET"),
116 opt(preceded(
117 tag(b" "),
118 parenthesized_nonempty_list(map(astring_utf8, Cow::Borrowed)),
119 )),
120 ),
121 ResponseCode::BadCharset,
122 )(i)
123}
124
125fn resp_text_code_capability(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
126 map(capability_data, ResponseCode::Capabilities)(i)
127}
128
129fn resp_text_code_parse(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
130 map(tag_no_case(b"PARSE"), |_| ResponseCode::Parse)(i)
131}
132
133fn resp_text_code_permanent_flags(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
134 map(
135 preceded(
136 tag_no_case(b"PERMANENTFLAGS "),
137 parenthesized_list(map(flag_perm, Cow::Borrowed)),
138 ),
139 ResponseCode::PermanentFlags,
140 )(i)
141}
142
143fn resp_text_code_read_only(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
144 map(tag_no_case(b"READ-ONLY"), |_| ResponseCode::ReadOnly)(i)
145}
146
147fn resp_text_code_read_write(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
148 map(tag_no_case(b"READ-WRITE"), |_| ResponseCode::ReadWrite)(i)
149}
150
151fn resp_text_code_try_create(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
152 map(tag_no_case(b"TRYCREATE"), |_| ResponseCode::TryCreate)(i)
153}
154
155fn resp_text_code_uid_validity(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
156 map(
157 preceded(tag_no_case(b"UIDVALIDITY "), number),
158 ResponseCode::UidValidity,
159 )(i)
160}
161
162fn resp_text_code_uid_next(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
163 map(
164 preceded(tag_no_case(b"UIDNEXT "), number),
165 ResponseCode::UidNext,
166 )(i)
167}
168
169fn resp_text_code_unseen(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
170 map(
171 preceded(tag_no_case(b"UNSEEN "), number),
172 ResponseCode::Unseen,
173 )(i)
174}
175
176fn resp_text_code(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
177 delimited(
180 tag(b"["),
181 alt((
182 resp_text_code_alert,
183 resp_text_code_badcharset,
184 resp_text_code_capability,
185 resp_text_code_parse,
186 resp_text_code_permanent_flags,
187 resp_text_code_uid_validity,
188 resp_text_code_uid_next,
189 resp_text_code_unseen,
190 resp_text_code_read_only,
191 resp_text_code_read_write,
192 resp_text_code_try_create,
193 rfc4551::resp_text_code_highest_mod_seq,
194 rfc4315::resp_text_code_append_uid,
195 rfc4315::resp_text_code_copy_uid,
196 rfc4315::resp_text_code_uid_not_sticky,
197 rfc5464::resp_text_code_metadata_long_entries,
198 rfc5464::resp_text_code_metadata_max_size,
199 rfc5464::resp_text_code_metadata_too_many,
200 rfc5464::resp_text_code_metadata_no_private,
201 )),
202 tag(b"]"),
203 )(i)
204}
205
206fn capability(i: &[u8]) -> IResult<&[u8], Capability<'_>> {
207 alt((
208 map(tag_no_case(b"IMAP4rev1"), |_| Capability::Imap4rev1),
209 map(
210 map(preceded(tag_no_case(b"AUTH="), atom), Cow::Borrowed),
211 Capability::Auth,
212 ),
213 map(map(atom, Cow::Borrowed), Capability::Atom),
214 ))(i)
215}
216
217fn ensure_capabilities_contains_imap4rev(
218 capabilities: Vec<Capability<'_>>,
219) -> Result<Vec<Capability<'_>>, ()> {
220 if capabilities.contains(&Capability::Imap4rev1) {
221 Ok(capabilities)
222 } else {
223 Err(())
224 }
225}
226
227fn capability_data(i: &[u8]) -> IResult<&[u8], Vec<Capability<'_>>> {
228 map_res(
229 preceded(
230 tag_no_case(b"CAPABILITY"),
231 many0(preceded(char(' '), capability)),
232 ),
233 ensure_capabilities_contains_imap4rev,
234 )(i)
235}
236
237fn mailbox_data_search(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
238 map(
239 terminated(
242 preceded(tag_no_case(b"SEARCH"), many0(preceded(tag(" "), number))),
243 opt(tag(" ")),
244 ),
245 MailboxDatum::Search,
246 )(i)
247}
248
249fn mailbox_data_flags(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
250 map(
251 preceded(tag_no_case("FLAGS "), flag_list),
252 MailboxDatum::Flags,
253 )(i)
254}
255
256fn mailbox_data_exists(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
257 map(
258 terminated(number, tag_no_case(" EXISTS")),
259 MailboxDatum::Exists,
260 )(i)
261}
262
263fn name_attribute(i: &[u8]) -> IResult<&[u8], NameAttribute<'_>> {
264 alt((
265 value(NameAttribute::NoInferiors, tag_no_case(b"\\Noinferiors")),
267 value(NameAttribute::NoSelect, tag_no_case(b"\\Noselect")),
268 value(NameAttribute::Marked, tag_no_case(b"\\Marked")),
269 value(NameAttribute::Unmarked, tag_no_case(b"\\Unmarked")),
270 value(NameAttribute::All, tag_no_case(b"\\All")),
272 value(NameAttribute::Archive, tag_no_case(b"\\Archive")),
273 value(NameAttribute::Drafts, tag_no_case(b"\\Drafts")),
274 value(NameAttribute::Flagged, tag_no_case(b"\\Flagged")),
275 value(NameAttribute::Junk, tag_no_case(b"\\Junk")),
276 value(NameAttribute::Sent, tag_no_case(b"\\Sent")),
277 value(NameAttribute::Trash, tag_no_case(b"\\Trash")),
278 map(
280 map_res(
281 recognize(pair(tag(b"\\"), take_while(is_atom_char))),
282 from_utf8,
283 ),
284 |s| NameAttribute::Extension(Cow::Borrowed(s)),
285 ),
286 ))(i)
287}
288
289#[allow(clippy::type_complexity)]
290fn mailbox_list(i: &[u8]) -> IResult<&[u8], (Vec<NameAttribute<'_>>, Option<&str>, &str)> {
291 map(
292 tuple((
293 parenthesized_list(name_attribute),
294 tag(b" "),
295 alt((map(quoted_utf8, Some), map(nil, |_| None))),
296 tag(b" "),
297 mailbox,
298 )),
299 |(name_attributes, _, delimiter, _, name)| (name_attributes, delimiter, name),
300 )(i)
301}
302
303fn mailbox_data_list(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
304 map(preceded(tag_no_case("LIST "), mailbox_list), |data| {
305 MailboxDatum::List {
306 name_attributes: data.0,
307 delimiter: data.1.map(Cow::Borrowed),
308 name: Cow::Borrowed(data.2),
309 }
310 })(i)
311}
312
313fn mailbox_data_lsub(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
314 map(preceded(tag_no_case("LSUB "), mailbox_list), |data| {
315 MailboxDatum::List {
316 name_attributes: data.0,
317 delimiter: data.1.map(Cow::Borrowed),
318 name: Cow::Borrowed(data.2),
319 }
320 })(i)
321}
322
323fn status_att(i: &[u8]) -> IResult<&[u8], StatusAttribute> {
326 alt((
327 rfc4551::status_att_val_highest_mod_seq,
328 map(
329 preceded(tag_no_case("MESSAGES "), number),
330 StatusAttribute::Messages,
331 ),
332 map(
333 preceded(tag_no_case("RECENT "), number),
334 StatusAttribute::Recent,
335 ),
336 map(
337 preceded(tag_no_case("UIDNEXT "), number),
338 StatusAttribute::UidNext,
339 ),
340 map(
341 preceded(tag_no_case("UIDVALIDITY "), number),
342 StatusAttribute::UidValidity,
343 ),
344 map(
345 preceded(tag_no_case("UNSEEN "), number),
346 StatusAttribute::Unseen,
347 ),
348 ))(i)
349}
350
351fn status_att_list(i: &[u8]) -> IResult<&[u8], Vec<StatusAttribute>> {
352 parenthesized_list(status_att)(i)
356}
357
358fn mailbox_data_status(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
359 map(
360 tuple((tag_no_case("STATUS "), mailbox, tag(" "), status_att_list)),
361 |(_, mailbox, _, status)| MailboxDatum::Status {
362 mailbox: Cow::Borrowed(mailbox),
363 status,
364 },
365 )(i)
366}
367
368fn mailbox_data_recent(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
369 map(
370 terminated(number, tag_no_case(" RECENT")),
371 MailboxDatum::Recent,
372 )(i)
373}
374
375fn mailbox_data(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
376 alt((
377 mailbox_data_flags,
378 mailbox_data_exists,
379 mailbox_data_list,
380 mailbox_data_lsub,
381 mailbox_data_status,
382 mailbox_data_recent,
383 mailbox_data_search,
384 gmail::mailbox_data_gmail_labels,
385 gmail::mailbox_data_gmail_msgid,
386 gmail::mailbox_data_gmail_thrid,
387 rfc5256::mailbox_data_sort,
388 ))(i)
389}
390
391fn address(i: &[u8]) -> IResult<&[u8], Address<'_>> {
394 paren_delimited(map(
395 tuple((
396 nstring,
397 tag(" "),
398 nstring,
399 tag(" "),
400 nstring,
401 tag(" "),
402 nstring,
403 )),
404 |(name, _, adl, _, mailbox, _, host)| Address {
405 name: name.map(Cow::Borrowed),
406 adl: adl.map(Cow::Borrowed),
407 mailbox: mailbox.map(Cow::Borrowed),
408 host: host.map(Cow::Borrowed),
409 },
410 ))(i)
411}
412
413fn opt_addresses(i: &[u8]) -> IResult<&[u8], Option<Vec<Address<'_>>>> {
414 alt((
415 map(nil, |_s| None),
416 map(
417 paren_delimited(many1(terminated(address, opt(char(' '))))),
418 Some,
419 ),
420 ))(i)
421}
422
423pub(crate) fn envelope(i: &[u8]) -> IResult<&[u8], Envelope<'_>> {
447 paren_delimited(map(
448 tuple((
449 nstring,
450 tag(" "),
451 nstring,
452 tag(" "),
453 opt_addresses,
454 tag(" "),
455 opt_addresses,
456 tag(" "),
457 opt_addresses,
458 tag(" "),
459 opt_addresses,
460 tag(" "),
461 opt_addresses,
462 tag(" "),
463 opt_addresses,
464 tag(" "),
465 nstring,
466 tag(" "),
467 nstring,
468 )),
469 |(
470 date,
471 _,
472 subject,
473 _,
474 from,
475 _,
476 sender,
477 _,
478 reply_to,
479 _,
480 to,
481 _,
482 cc,
483 _,
484 bcc,
485 _,
486 in_reply_to,
487 _,
488 message_id,
489 )| Envelope {
490 date: date.map(Cow::Borrowed),
491 subject: subject.map(Cow::Borrowed),
492 from,
493 sender,
494 reply_to,
495 to,
496 cc,
497 bcc,
498 in_reply_to: in_reply_to.map(Cow::Borrowed),
499 message_id: message_id.map(Cow::Borrowed),
500 },
501 ))(i)
502}
503
504fn msg_att_envelope(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
505 map(preceded(tag_no_case("ENVELOPE "), envelope), |envelope| {
506 AttributeValue::Envelope(Box::new(envelope))
507 })(i)
508}
509
510fn msg_att_internal_date(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
511 map(
512 preceded(tag_no_case("INTERNALDATE "), nstring_utf8),
513 |date| AttributeValue::InternalDate(Cow::Borrowed(date.unwrap())),
514 )(i)
515}
516
517fn msg_att_flags(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
518 map(
519 preceded(tag_no_case("FLAGS "), flag_list),
520 AttributeValue::Flags,
521 )(i)
522}
523
524fn msg_att_rfc822(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
525 map(preceded(tag_no_case("RFC822 "), nstring), |v| {
526 AttributeValue::Rfc822(v.map(Cow::Borrowed))
527 })(i)
528}
529
530fn msg_att_rfc822_header(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
531 map(
533 tuple((tag_no_case("RFC822.HEADER "), opt(tag(b" ")), nstring)),
534 |(_, _, raw)| AttributeValue::Rfc822Header(raw.map(Cow::Borrowed)),
535 )(i)
536}
537
538fn msg_att_rfc822_size(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
539 map(
540 preceded(tag_no_case("RFC822.SIZE "), number),
541 AttributeValue::Rfc822Size,
542 )(i)
543}
544
545fn msg_att_rfc822_text(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
546 map(preceded(tag_no_case("RFC822.TEXT "), nstring), |v| {
547 AttributeValue::Rfc822Text(v.map(Cow::Borrowed))
548 })(i)
549}
550
551fn msg_att_uid(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
552 map(preceded(tag_no_case("UID "), number), AttributeValue::Uid)(i)
553}
554
555fn msg_att(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
569 alt((
570 msg_att_body_section,
571 msg_att_body_structure,
572 msg_att_envelope,
573 msg_att_internal_date,
574 msg_att_flags,
575 rfc4551::msg_att_mod_seq,
576 msg_att_rfc822,
577 msg_att_rfc822_header,
578 msg_att_rfc822_size,
579 msg_att_rfc822_text,
580 msg_att_uid,
581 gmail::msg_att_gmail_labels,
582 gmail::msg_att_gmail_msgid,
583 gmail::msg_att_gmail_thrid,
584 ))(i)
585}
586
587fn msg_att_list(i: &[u8]) -> IResult<&[u8], Vec<AttributeValue<'_>>> {
588 parenthesized_nonempty_list(msg_att)(i)
589}
590
591fn message_data_fetch(i: &[u8]) -> IResult<&[u8], Response<'_>> {
593 map(
594 tuple((number, tag_no_case(" FETCH "), msg_att_list)),
595 |(num, _, attrs)| Response::Fetch(num, attrs),
596 )(i)
597}
598
599fn message_data_expunge(i: &[u8]) -> IResult<&[u8], u32> {
601 terminated(number, tag_no_case(" EXPUNGE"))(i)
602}
603
604fn imap_tag(i: &[u8]) -> IResult<&[u8], RequestId> {
606 map(map_res(take_while1(is_tag_char), from_utf8), |s| {
607 RequestId(s.to_string())
608 })(i)
609}
610
611fn resp_text(i: &[u8]) -> IResult<&[u8], (Option<ResponseCode<'_>>, Option<&str>)> {
616 map(tuple((opt(resp_text_code), text)), |(code, text)| {
617 let res = if text.is_empty() {
618 None
619 } else if code.is_some() {
620 Some(&text[1..])
621 } else {
622 Some(text)
623 };
624 (code, res)
625 })(i)
626}
627
628fn trailing_resp_text(i: &[u8]) -> IResult<&[u8], (Option<ResponseCode<'_>>, Option<&str>)> {
630 map(opt(tuple((tag(b" "), resp_text))), |resptext| {
631 resptext.map(|(_, tuple)| tuple).unwrap_or((None, None))
632 })(i)
633}
634
635pub(crate) fn continue_req(i: &[u8]) -> IResult<&[u8], Response<'_>> {
637 map(
640 tuple((tag("+"), opt(tag(" ")), resp_text, tag("\r\n"))),
641 |(_, _, text, _)| Response::Continue {
642 code: text.0,
643 information: text.1.map(Cow::Borrowed),
644 },
645 )(i)
646}
647
648pub(crate) fn response_tagged(i: &[u8]) -> IResult<&[u8], Response<'_>> {
653 map(
654 tuple((
655 imap_tag,
656 tag(b" "),
657 status,
658 trailing_resp_text,
659 tag(b"\r\n"),
660 )),
661 |(tag, _, status, text, _)| Response::Done {
662 tag,
663 status,
664 code: text.0,
665 information: text.1.map(Cow::Borrowed),
666 },
667 )(i)
668}
669
670fn resp_cond(i: &[u8]) -> IResult<&[u8], Response<'_>> {
678 map(tuple((status, trailing_resp_text)), |(status, text)| {
679 Response::Data {
680 status,
681 code: text.0,
682 information: text.1.map(Cow::Borrowed),
683 }
684 })(i)
685}
686
687pub(crate) fn response_data(i: &[u8]) -> IResult<&[u8], Response<'_>> {
690 delimited(
691 tag(b"* "),
692 alt((
693 resp_cond,
694 map(mailbox_data, Response::MailboxData),
695 map(message_data_expunge, Response::Expunge),
696 message_data_fetch,
697 map(capability_data, Response::Capabilities),
698 rfc5161::resp_enabled,
699 rfc5464::metadata_solicited,
700 rfc5464::metadata_unsolicited,
701 rfc7162::resp_vanished,
702 rfc2087::quota,
703 rfc2087::quota_root,
704 rfc2971::resp_id,
705 rfc4314::acl,
706 rfc4314::list_rights,
707 rfc4314::my_rights,
708 )),
709 preceded(
710 many0(tag(b" ")), tag(b"\r\n"),
712 ),
713 )(i)
714}
715
716#[cfg(test)]
717mod tests {
718 use crate::types::*;
719 use assert_matches::assert_matches;
720 use std::borrow::Cow;
721
722 #[test]
723 fn test_list() {
724 match super::mailbox(b"iNboX ") {
725 Ok((_, mb)) => {
726 assert_eq!(mb, "INBOX");
727 }
728 rsp => panic!("unexpected response {rsp:?}"),
729 }
730 }
731
732 #[test]
733 fn test_envelope() {
734 let env = br#"ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" "IMAP4rev1 WG mtg summary and minutes" (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) ((NIL NIL "imap" "cac.washington.edu")) ((NIL NIL "minutes" "CNRI.Reston.VA.US") ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL "<B27397-0100000@cac.washington.edu>") "#;
735 match super::msg_att_envelope(env) {
736 Ok((_, AttributeValue::Envelope(_))) => {}
737 rsp => panic!("unexpected response {rsp:?}"),
738 }
739 }
740
741 #[test]
742 fn test_opt_addresses() {
743 let addr = b"((NIL NIL \"minutes\" \"CNRI.Reston.VA.US\") (\"John Klensin\" NIL \"KLENSIN\" \"MIT.EDU\")) ";
744 match super::opt_addresses(addr) {
745 Ok((_, _addresses)) => {}
746 rsp => panic!("unexpected response {rsp:?}"),
747 }
748 }
749
750 #[test]
751 fn test_opt_addresses_no_space() {
752 let addr =
753 br#"((NIL NIL "test" "example@example.com")(NIL NIL "test" "example@example.com"))"#;
754 match super::opt_addresses(addr) {
755 Ok((_, _addresses)) => {}
756 rsp => panic!("unexpected response {rsp:?}"),
757 }
758 }
759
760 #[test]
761 fn test_addresses() {
762 match super::address(b"(\"John Klensin\" NIL \"KLENSIN\" \"MIT.EDU\") ") {
763 Ok((_, _address)) => {}
764 rsp => panic!("unexpected response {rsp:?}"),
765 }
766
767 match super::address(b"({12}\r\nJoh\xff Klensin NIL \"KLENSIN\" \"MIT.EDU\") ") {
769 Ok((_, _address)) => {}
770 rsp => panic!("unexpected response {rsp:?}"),
771 }
772 }
773
774 #[test]
775 fn test_capability_data() {
776 assert_matches!(
778 super::capability_data(b"CAPABILITY IMAP4rev1\r\n"),
779 Ok((_, capabilities)) => {
780 assert_eq!(capabilities, vec![Capability::Imap4rev1])
781 }
782 );
783
784 assert_matches!(
785 super::capability_data(b"CAPABILITY XPIG-LATIN IMAP4rev1 STARTTLS AUTH=GSSAPI\r\n"),
786 Ok((_, capabilities)) => {
787 assert_eq!(capabilities, vec![
788 Capability::Atom(Cow::Borrowed("XPIG-LATIN")),
789 Capability::Imap4rev1,
790 Capability::Atom(Cow::Borrowed("STARTTLS")),
791 Capability::Auth(Cow::Borrowed("GSSAPI")),
792 ])
793 }
794 );
795
796 assert_matches!(
797 super::capability_data(b"CAPABILITY IMAP4rev1 AUTH=GSSAPI AUTH=PLAIN\r\n"),
798 Ok((_, capabilities)) => {
799 assert_eq!(capabilities, vec![
800 Capability::Imap4rev1,
801 Capability::Auth(Cow::Borrowed("GSSAPI")),
802 Capability::Auth(Cow::Borrowed("PLAIN")),
803 ])
804 }
805 );
806
807 assert_matches!(
809 super::capability_data(b"CAPABILITY AUTH=GSSAPI AUTH=PLAIN\r\n"),
810 Err(_)
811 );
812 }
813
814 #[test]
815 fn test_surgemail_select_flags() {
816 assert_matches!(
818 super::flag_list(b"(\\Answered \\Flagged \\Deleted \\Draft \\Seen $Forwarded )"),
819 Ok(([], flags)) => {
820 assert_eq!(flags, vec![
821 "\\Answered",
822 "\\Flagged",
823 "\\Deleted",
824 "\\Draft",
825 "\\Seen",
826 "$Forwarded"
827 ])
828 }
829 );
830 }
831}