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)
97}
98
99fn flag_perm(i: &[u8]) -> IResult<&[u8], &str> {
100 alt((map_res(tag(b"\\*"), from_utf8), flag))(i)
101}
102
103fn resp_text_code_alert(i: &[u8]) -> IResult<&[u8], ResponseCode> {
104 map(tag_no_case(b"ALERT"), |_| ResponseCode::Alert)(i)
105}
106
107fn resp_text_code_badcharset(i: &[u8]) -> IResult<&[u8], ResponseCode> {
108 map(
109 preceded(
110 tag_no_case(b"BADCHARSET"),
111 opt(preceded(
112 tag(b" "),
113 parenthesized_nonempty_list(map(astring_utf8, Cow::Borrowed)),
114 )),
115 ),
116 ResponseCode::BadCharset,
117 )(i)
118}
119
120fn resp_text_code_capability(i: &[u8]) -> IResult<&[u8], ResponseCode> {
121 map(capability_data, ResponseCode::Capabilities)(i)
122}
123
124fn resp_text_code_parse(i: &[u8]) -> IResult<&[u8], ResponseCode> {
125 map(tag_no_case(b"PARSE"), |_| ResponseCode::Parse)(i)
126}
127
128fn resp_text_code_permanent_flags(i: &[u8]) -> IResult<&[u8], ResponseCode> {
129 map(
130 preceded(
131 tag_no_case(b"PERMANENTFLAGS "),
132 parenthesized_list(map(flag_perm, Cow::Borrowed)),
133 ),
134 ResponseCode::PermanentFlags,
135 )(i)
136}
137
138fn resp_text_code_read_only(i: &[u8]) -> IResult<&[u8], ResponseCode> {
139 map(tag_no_case(b"READ-ONLY"), |_| ResponseCode::ReadOnly)(i)
140}
141
142fn resp_text_code_read_write(i: &[u8]) -> IResult<&[u8], ResponseCode> {
143 map(tag_no_case(b"READ-WRITE"), |_| ResponseCode::ReadWrite)(i)
144}
145
146fn resp_text_code_try_create(i: &[u8]) -> IResult<&[u8], ResponseCode> {
147 map(tag_no_case(b"TRYCREATE"), |_| ResponseCode::TryCreate)(i)
148}
149
150fn resp_text_code_uid_validity(i: &[u8]) -> IResult<&[u8], ResponseCode> {
151 map(
152 preceded(tag_no_case(b"UIDVALIDITY "), number),
153 ResponseCode::UidValidity,
154 )(i)
155}
156
157fn resp_text_code_uid_next(i: &[u8]) -> IResult<&[u8], ResponseCode> {
158 map(
159 preceded(tag_no_case(b"UIDNEXT "), number),
160 ResponseCode::UidNext,
161 )(i)
162}
163
164fn resp_text_code_unseen(i: &[u8]) -> IResult<&[u8], ResponseCode> {
165 map(
166 preceded(tag_no_case(b"UNSEEN "), number),
167 ResponseCode::Unseen,
168 )(i)
169}
170
171fn resp_text_code(i: &[u8]) -> IResult<&[u8], ResponseCode> {
172 delimited(
175 tag(b"["),
176 alt((
177 resp_text_code_alert,
178 resp_text_code_badcharset,
179 resp_text_code_capability,
180 resp_text_code_parse,
181 resp_text_code_permanent_flags,
182 resp_text_code_uid_validity,
183 resp_text_code_uid_next,
184 resp_text_code_unseen,
185 resp_text_code_read_only,
186 resp_text_code_read_write,
187 resp_text_code_try_create,
188 rfc4551::resp_text_code_highest_mod_seq,
189 rfc4315::resp_text_code_append_uid,
190 rfc4315::resp_text_code_copy_uid,
191 rfc4315::resp_text_code_uid_not_sticky,
192 rfc5464::resp_text_code_metadata_long_entries,
193 rfc5464::resp_text_code_metadata_max_size,
194 rfc5464::resp_text_code_metadata_too_many,
195 rfc5464::resp_text_code_metadata_no_private,
196 )),
197 tag(b"]"),
198 )(i)
199}
200
201fn capability(i: &[u8]) -> IResult<&[u8], Capability> {
202 alt((
203 map(tag_no_case(b"IMAP4rev1"), |_| Capability::Imap4rev1),
204 map(
205 map(preceded(tag_no_case(b"AUTH="), atom), Cow::Borrowed),
206 Capability::Auth,
207 ),
208 map(map(atom, Cow::Borrowed), Capability::Atom),
209 ))(i)
210}
211
212fn ensure_capabilities_contains_imap4rev(
213 capabilities: Vec<Capability<'_>>,
214) -> Result<Vec<Capability<'_>>, ()> {
215 if capabilities.contains(&Capability::Imap4rev1) {
216 Ok(capabilities)
217 } else {
218 Err(())
219 }
220}
221
222fn capability_data(i: &[u8]) -> IResult<&[u8], Vec<Capability>> {
223 map_res(
224 preceded(
225 tag_no_case(b"CAPABILITY"),
226 many0(preceded(char(' '), capability)),
227 ),
228 ensure_capabilities_contains_imap4rev,
229 )(i)
230}
231
232fn mailbox_data_search(i: &[u8]) -> IResult<&[u8], MailboxDatum> {
233 map(
234 terminated(
237 preceded(tag_no_case(b"SEARCH"), many0(preceded(tag(" "), number))),
238 opt(tag(" ")),
239 ),
240 MailboxDatum::Search,
241 )(i)
242}
243
244fn mailbox_data_flags(i: &[u8]) -> IResult<&[u8], MailboxDatum> {
245 map(
246 preceded(tag_no_case("FLAGS "), flag_list),
247 MailboxDatum::Flags,
248 )(i)
249}
250
251fn mailbox_data_exists(i: &[u8]) -> IResult<&[u8], MailboxDatum> {
252 map(
253 terminated(number, tag_no_case(" EXISTS")),
254 MailboxDatum::Exists,
255 )(i)
256}
257
258fn name_attribute(i: &[u8]) -> IResult<&[u8], NameAttribute> {
259 alt((
260 value(NameAttribute::NoInferiors, tag_no_case(b"\\Noinferiors")),
262 value(NameAttribute::NoSelect, tag_no_case(b"\\Noselect")),
263 value(NameAttribute::Marked, tag_no_case(b"\\Marked")),
264 value(NameAttribute::Unmarked, tag_no_case(b"\\Unmarked")),
265 value(NameAttribute::All, tag_no_case(b"\\All")),
267 value(NameAttribute::Archive, tag_no_case(b"\\Archive")),
268 value(NameAttribute::Drafts, tag_no_case(b"\\Drafts")),
269 value(NameAttribute::Flagged, tag_no_case(b"\\Flagged")),
270 value(NameAttribute::Junk, tag_no_case(b"\\Junk")),
271 value(NameAttribute::Sent, tag_no_case(b"\\Sent")),
272 value(NameAttribute::Trash, tag_no_case(b"\\Trash")),
273 map(
275 map_res(
276 recognize(pair(tag(b"\\"), take_while(is_atom_char))),
277 from_utf8,
278 ),
279 |s| NameAttribute::Extension(Cow::Borrowed(s)),
280 ),
281 ))(i)
282}
283
284#[allow(clippy::type_complexity)]
285fn mailbox_list(i: &[u8]) -> IResult<&[u8], (Vec<NameAttribute>, Option<&str>, &str)> {
286 map(
287 tuple((
288 parenthesized_list(name_attribute),
289 tag(b" "),
290 alt((map(quoted_utf8, Some), map(nil, |_| None))),
291 tag(b" "),
292 mailbox,
293 )),
294 |(name_attributes, _, delimiter, _, name)| (name_attributes, delimiter, name),
295 )(i)
296}
297
298fn mailbox_data_list(i: &[u8]) -> IResult<&[u8], MailboxDatum> {
299 map(preceded(tag_no_case("LIST "), mailbox_list), |data| {
300 MailboxDatum::List {
301 name_attributes: data.0,
302 delimiter: data.1.map(Cow::Borrowed),
303 name: Cow::Borrowed(data.2),
304 }
305 })(i)
306}
307
308fn mailbox_data_lsub(i: &[u8]) -> IResult<&[u8], MailboxDatum> {
309 map(preceded(tag_no_case("LSUB "), mailbox_list), |data| {
310 MailboxDatum::List {
311 name_attributes: data.0,
312 delimiter: data.1.map(Cow::Borrowed),
313 name: Cow::Borrowed(data.2),
314 }
315 })(i)
316}
317
318fn status_att(i: &[u8]) -> IResult<&[u8], StatusAttribute> {
321 alt((
322 rfc4551::status_att_val_highest_mod_seq,
323 map(
324 preceded(tag_no_case("MESSAGES "), number),
325 StatusAttribute::Messages,
326 ),
327 map(
328 preceded(tag_no_case("RECENT "), number),
329 StatusAttribute::Recent,
330 ),
331 map(
332 preceded(tag_no_case("UIDNEXT "), number),
333 StatusAttribute::UidNext,
334 ),
335 map(
336 preceded(tag_no_case("UIDVALIDITY "), number),
337 StatusAttribute::UidValidity,
338 ),
339 map(
340 preceded(tag_no_case("UNSEEN "), number),
341 StatusAttribute::Unseen,
342 ),
343 ))(i)
344}
345
346fn status_att_list(i: &[u8]) -> IResult<&[u8], Vec<StatusAttribute>> {
347 parenthesized_list(status_att)(i)
351}
352
353fn mailbox_data_status(i: &[u8]) -> IResult<&[u8], MailboxDatum> {
354 map(
355 tuple((tag_no_case("STATUS "), mailbox, tag(" "), status_att_list)),
356 |(_, mailbox, _, status)| MailboxDatum::Status {
357 mailbox: Cow::Borrowed(mailbox),
358 status,
359 },
360 )(i)
361}
362
363fn mailbox_data_recent(i: &[u8]) -> IResult<&[u8], MailboxDatum> {
364 map(
365 terminated(number, tag_no_case(" RECENT")),
366 MailboxDatum::Recent,
367 )(i)
368}
369
370fn mailbox_data(i: &[u8]) -> IResult<&[u8], MailboxDatum> {
371 alt((
372 mailbox_data_flags,
373 mailbox_data_exists,
374 mailbox_data_list,
375 mailbox_data_lsub,
376 mailbox_data_status,
377 mailbox_data_recent,
378 mailbox_data_search,
379 gmail::mailbox_data_gmail_labels,
380 gmail::mailbox_data_gmail_msgid,
381 rfc5256::mailbox_data_sort,
382 ))(i)
383}
384
385fn address(i: &[u8]) -> IResult<&[u8], Address> {
388 paren_delimited(map(
389 tuple((
390 nstring,
391 tag(" "),
392 nstring,
393 tag(" "),
394 nstring,
395 tag(" "),
396 nstring,
397 )),
398 |(name, _, adl, _, mailbox, _, host)| Address {
399 name: name.map(Cow::Borrowed),
400 adl: adl.map(Cow::Borrowed),
401 mailbox: mailbox.map(Cow::Borrowed),
402 host: host.map(Cow::Borrowed),
403 },
404 ))(i)
405}
406
407fn opt_addresses(i: &[u8]) -> IResult<&[u8], Option<Vec<Address>>> {
408 alt((
409 map(nil, |_s| None),
410 map(
411 paren_delimited(many1(terminated(address, opt(char(' '))))),
412 Some,
413 ),
414 ))(i)
415}
416
417pub(crate) fn envelope(i: &[u8]) -> IResult<&[u8], Envelope> {
441 paren_delimited(map(
442 tuple((
443 nstring,
444 tag(" "),
445 nstring,
446 tag(" "),
447 opt_addresses,
448 tag(" "),
449 opt_addresses,
450 tag(" "),
451 opt_addresses,
452 tag(" "),
453 opt_addresses,
454 tag(" "),
455 opt_addresses,
456 tag(" "),
457 opt_addresses,
458 tag(" "),
459 nstring,
460 tag(" "),
461 nstring,
462 )),
463 |(
464 date,
465 _,
466 subject,
467 _,
468 from,
469 _,
470 sender,
471 _,
472 reply_to,
473 _,
474 to,
475 _,
476 cc,
477 _,
478 bcc,
479 _,
480 in_reply_to,
481 _,
482 message_id,
483 )| Envelope {
484 date: date.map(Cow::Borrowed),
485 subject: subject.map(Cow::Borrowed),
486 from,
487 sender,
488 reply_to,
489 to,
490 cc,
491 bcc,
492 in_reply_to: in_reply_to.map(Cow::Borrowed),
493 message_id: message_id.map(Cow::Borrowed),
494 },
495 ))(i)
496}
497
498fn msg_att_envelope(i: &[u8]) -> IResult<&[u8], AttributeValue> {
499 map(preceded(tag_no_case("ENVELOPE "), envelope), |envelope| {
500 AttributeValue::Envelope(Box::new(envelope))
501 })(i)
502}
503
504fn msg_att_internal_date(i: &[u8]) -> IResult<&[u8], AttributeValue> {
505 map(
506 preceded(tag_no_case("INTERNALDATE "), nstring_utf8),
507 |date| AttributeValue::InternalDate(Cow::Borrowed(date.unwrap())),
508 )(i)
509}
510
511fn msg_att_flags(i: &[u8]) -> IResult<&[u8], AttributeValue> {
512 map(
513 preceded(tag_no_case("FLAGS "), flag_list),
514 AttributeValue::Flags,
515 )(i)
516}
517
518fn msg_att_rfc822(i: &[u8]) -> IResult<&[u8], AttributeValue> {
519 map(preceded(tag_no_case("RFC822 "), nstring), |v| {
520 AttributeValue::Rfc822(v.map(Cow::Borrowed))
521 })(i)
522}
523
524fn msg_att_rfc822_header(i: &[u8]) -> IResult<&[u8], AttributeValue> {
525 map(
527 tuple((tag_no_case("RFC822.HEADER "), opt(tag(b" ")), nstring)),
528 |(_, _, raw)| AttributeValue::Rfc822Header(raw.map(Cow::Borrowed)),
529 )(i)
530}
531
532fn msg_att_rfc822_size(i: &[u8]) -> IResult<&[u8], AttributeValue> {
533 map(
534 preceded(tag_no_case("RFC822.SIZE "), number),
535 AttributeValue::Rfc822Size,
536 )(i)
537}
538
539fn msg_att_rfc822_text(i: &[u8]) -> IResult<&[u8], AttributeValue> {
540 map(preceded(tag_no_case("RFC822.TEXT "), nstring), |v| {
541 AttributeValue::Rfc822Text(v.map(Cow::Borrowed))
542 })(i)
543}
544
545fn msg_att_uid(i: &[u8]) -> IResult<&[u8], AttributeValue> {
546 map(preceded(tag_no_case("UID "), number), AttributeValue::Uid)(i)
547}
548
549fn msg_att(i: &[u8]) -> IResult<&[u8], AttributeValue> {
563 alt((
564 msg_att_body_section,
565 msg_att_body_structure,
566 msg_att_envelope,
567 msg_att_internal_date,
568 msg_att_flags,
569 rfc4551::msg_att_mod_seq,
570 msg_att_rfc822,
571 msg_att_rfc822_header,
572 msg_att_rfc822_size,
573 msg_att_rfc822_text,
574 msg_att_uid,
575 gmail::msg_att_gmail_labels,
576 gmail::msg_att_gmail_msgid,
577 ))(i)
578}
579
580fn msg_att_list(i: &[u8]) -> IResult<&[u8], Vec<AttributeValue>> {
581 parenthesized_nonempty_list(msg_att)(i)
582}
583
584fn message_data_fetch(i: &[u8]) -> IResult<&[u8], Response> {
586 map(
587 tuple((number, tag_no_case(" FETCH "), msg_att_list)),
588 |(num, _, attrs)| Response::Fetch(num, attrs),
589 )(i)
590}
591
592fn message_data_expunge(i: &[u8]) -> IResult<&[u8], u32> {
594 terminated(number, tag_no_case(" EXPUNGE"))(i)
595}
596
597fn imap_tag(i: &[u8]) -> IResult<&[u8], RequestId> {
599 map(map_res(take_while1(is_tag_char), from_utf8), |s| {
600 RequestId(s.to_string())
601 })(i)
602}
603
604fn resp_text(i: &[u8]) -> IResult<&[u8], (Option<ResponseCode>, Option<&str>)> {
609 map(tuple((opt(resp_text_code), text)), |(code, text)| {
610 let res = if text.is_empty() {
611 None
612 } else if code.is_some() {
613 Some(&text[1..])
614 } else {
615 Some(text)
616 };
617 (code, res)
618 })(i)
619}
620
621fn trailing_resp_text(i: &[u8]) -> IResult<&[u8], (Option<ResponseCode>, Option<&str>)> {
623 map(opt(tuple((tag(b" "), resp_text))), |resptext| {
624 resptext.map(|(_, tuple)| tuple).unwrap_or((None, None))
625 })(i)
626}
627
628pub(crate) fn continue_req(i: &[u8]) -> IResult<&[u8], Response> {
630 map(
633 tuple((tag("+"), opt(tag(" ")), resp_text, tag("\r\n"))),
634 |(_, _, text, _)| Response::Continue {
635 code: text.0,
636 information: text.1.map(Cow::Borrowed),
637 },
638 )(i)
639}
640
641pub(crate) fn response_tagged(i: &[u8]) -> IResult<&[u8], Response> {
646 map(
647 tuple((
648 imap_tag,
649 tag(b" "),
650 status,
651 trailing_resp_text,
652 tag(b"\r\n"),
653 )),
654 |(tag, _, status, text, _)| Response::Done {
655 tag,
656 status,
657 code: text.0,
658 information: text.1.map(Cow::Borrowed),
659 },
660 )(i)
661}
662
663fn resp_cond(i: &[u8]) -> IResult<&[u8], Response> {
671 map(tuple((status, trailing_resp_text)), |(status, text)| {
672 Response::Data {
673 status,
674 code: text.0,
675 information: text.1.map(Cow::Borrowed),
676 }
677 })(i)
678}
679
680pub(crate) fn response_data(i: &[u8]) -> IResult<&[u8], Response> {
683 delimited(
684 tag(b"* "),
685 alt((
686 resp_cond,
687 map(mailbox_data, Response::MailboxData),
688 map(message_data_expunge, Response::Expunge),
689 message_data_fetch,
690 map(capability_data, Response::Capabilities),
691 rfc5161::resp_enabled,
692 rfc5464::metadata_solicited,
693 rfc5464::metadata_unsolicited,
694 rfc7162::resp_vanished,
695 rfc2087::quota,
696 rfc2087::quota_root,
697 rfc2971::resp_id,
698 rfc4314::acl,
699 rfc4314::list_rights,
700 rfc4314::my_rights,
701 )),
702 preceded(
703 many0(tag(b" ")), tag(b"\r\n"),
705 ),
706 )(i)
707}
708
709#[cfg(test)]
710mod tests {
711 use crate::types::*;
712 use assert_matches::assert_matches;
713 use std::borrow::Cow;
714
715 #[test]
716 fn test_list() {
717 match super::mailbox(b"iNboX ") {
718 Ok((_, mb)) => {
719 assert_eq!(mb, "INBOX");
720 }
721 rsp => panic!("unexpected response {rsp:?}"),
722 }
723 }
724
725 #[test]
726 fn test_envelope() {
727 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>") "#;
728 match super::msg_att_envelope(env) {
729 Ok((_, AttributeValue::Envelope(_))) => {}
730 rsp => panic!("unexpected response {rsp:?}"),
731 }
732 }
733
734 #[test]
735 fn test_opt_addresses() {
736 let addr = b"((NIL NIL \"minutes\" \"CNRI.Reston.VA.US\") (\"John Klensin\" NIL \"KLENSIN\" \"MIT.EDU\")) ";
737 match super::opt_addresses(addr) {
738 Ok((_, _addresses)) => {}
739 rsp => panic!("unexpected response {rsp:?}"),
740 }
741 }
742
743 #[test]
744 fn test_opt_addresses_no_space() {
745 let addr =
746 br#"((NIL NIL "test" "example@example.com")(NIL NIL "test" "example@example.com"))"#;
747 match super::opt_addresses(addr) {
748 Ok((_, _addresses)) => {}
749 rsp => panic!("unexpected response {rsp:?}"),
750 }
751 }
752
753 #[test]
754 fn test_addresses() {
755 match super::address(b"(\"John Klensin\" NIL \"KLENSIN\" \"MIT.EDU\") ") {
756 Ok((_, _address)) => {}
757 rsp => panic!("unexpected response {rsp:?}"),
758 }
759
760 match super::address(b"({12}\r\nJoh\xff Klensin NIL \"KLENSIN\" \"MIT.EDU\") ") {
762 Ok((_, _address)) => {}
763 rsp => panic!("unexpected response {rsp:?}"),
764 }
765 }
766
767 #[test]
768 fn test_capability_data() {
769 assert_matches!(
771 super::capability_data(b"CAPABILITY IMAP4rev1\r\n"),
772 Ok((_, capabilities)) => {
773 assert_eq!(capabilities, vec![Capability::Imap4rev1])
774 }
775 );
776
777 assert_matches!(
778 super::capability_data(b"CAPABILITY XPIG-LATIN IMAP4rev1 STARTTLS AUTH=GSSAPI\r\n"),
779 Ok((_, capabilities)) => {
780 assert_eq!(capabilities, vec![
781 Capability::Atom(Cow::Borrowed("XPIG-LATIN")),
782 Capability::Imap4rev1,
783 Capability::Atom(Cow::Borrowed("STARTTLS")),
784 Capability::Auth(Cow::Borrowed("GSSAPI")),
785 ])
786 }
787 );
788
789 assert_matches!(
790 super::capability_data(b"CAPABILITY IMAP4rev1 AUTH=GSSAPI AUTH=PLAIN\r\n"),
791 Ok((_, capabilities)) => {
792 assert_eq!(capabilities, vec![
793 Capability::Imap4rev1,
794 Capability::Auth(Cow::Borrowed("GSSAPI")),
795 Capability::Auth(Cow::Borrowed("PLAIN")),
796 ])
797 }
798 );
799
800 assert_matches!(
802 super::capability_data(b"CAPABILITY AUTH=GSSAPI AUTH=PLAIN\r\n"),
803 Err(_)
804 );
805 }
806}