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},
17 IResult, Parser,
18};
19
20use crate::{
21 core::*, rfc2087, rfc2971, rfc3501::body::*, rfc3501::body_structure::*, rfc4314, rfc4315,
22 rfc4315::UidSetMember, rfc4551, rfc5161, rfc5256, rfc5464, rfc5464::Metadata, rfc7162,
23 Response,
24};
25
26use super::gmail;
27
28pub mod body;
29pub mod body_structure;
30
31fn is_tag_char(c: u8) -> bool {
32 c != b'+' && is_astring_char(c)
33}
34
35fn status_ok(i: &[u8]) -> IResult<&[u8], Status> {
36 map(tag_no_case("OK"), |_s| Status::Ok).parse(i)
37}
38fn status_no(i: &[u8]) -> IResult<&[u8], Status> {
39 map(tag_no_case("NO"), |_s| Status::No).parse(i)
40}
41fn status_bad(i: &[u8]) -> IResult<&[u8], Status> {
42 map(tag_no_case("BAD"), |_s| Status::Bad).parse(i)
43}
44fn status_preauth(i: &[u8]) -> IResult<&[u8], Status> {
45 map(tag_no_case("PREAUTH"), |_s| Status::PreAuth).parse(i)
46}
47fn status_bye(i: &[u8]) -> IResult<&[u8], Status> {
48 map(tag_no_case("BYE"), |_s| Status::Bye).parse(i)
49}
50
51#[derive(Debug, Eq, PartialEq)]
52pub enum Status {
53 Ok,
54 No,
55 Bad,
56 PreAuth,
57 Bye,
58}
59
60impl Status {
61 fn parse(i: &[u8]) -> IResult<&[u8], Self> {
62 alt((status_ok, status_no, status_bad, status_preauth, status_bye)).parse(i)
63 }
64}
65
66pub(crate) fn mailbox(i: &[u8]) -> IResult<&[u8], Cow<'_, str>> {
67 map(astring_utf8, |s| {
68 if s.eq_ignore_ascii_case("INBOX") {
69 Cow::Borrowed("INBOX")
70 } else {
71 s
72 }
73 })
74 .parse(i)
75}
76
77fn flag_extension(i: &[u8]) -> IResult<&[u8], &str> {
78 map_res(
79 recognize(pair(tag("\\"), take_while(is_atom_char))),
80 from_utf8,
81 )
82 .parse(i)
83}
84
85pub(crate) fn flag(i: &[u8]) -> IResult<&[u8], &str> {
86 alt((
94 flag_extension,
95 map_res(take_while1(is_astring_char), from_utf8),
96 ))
97 .parse(i)
98}
99
100fn flag_list(i: &[u8]) -> IResult<&[u8], Vec<Cow<'_, str>>> {
101 parenthesized_list(map(flag_perm, Cow::Borrowed)).parse(i)
114}
115
116fn flag_perm(i: &[u8]) -> IResult<&[u8], &str> {
117 alt((map_res(tag("\\*"), from_utf8), flag)).parse(i)
118}
119
120fn resp_text_code_alert(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
121 map(tag_no_case("ALERT"), |_| ResponseCode::Alert).parse(i)
122}
123
124fn resp_text_code_badcharset(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
125 map(
126 preceded(
127 tag_no_case("BADCHARSET"),
128 opt(preceded(
129 tag(" "),
130 parenthesized_nonempty_list(astring_utf8),
131 )),
132 ),
133 ResponseCode::BadCharset,
134 )
135 .parse(i)
136}
137
138fn resp_text_code_capability(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
139 map(capability_data, ResponseCode::Capabilities).parse(i)
140}
141
142fn resp_text_code_parse(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
143 map(tag_no_case("PARSE"), |_| ResponseCode::Parse).parse(i)
144}
145
146fn resp_text_code_permanent_flags(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
147 map(
148 preceded(
149 tag_no_case("PERMANENTFLAGS "),
150 parenthesized_list(map(flag_perm, Cow::Borrowed)),
151 ),
152 ResponseCode::PermanentFlags,
153 )
154 .parse(i)
155}
156
157fn resp_text_code_read_only(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
158 map(tag_no_case("READ-ONLY"), |_| ResponseCode::ReadOnly).parse(i)
159}
160
161fn resp_text_code_read_write(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
162 map(tag_no_case("READ-WRITE"), |_| ResponseCode::ReadWrite).parse(i)
163}
164
165fn resp_text_code_try_create(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
166 map(tag_no_case("TRYCREATE"), |_| ResponseCode::TryCreate).parse(i)
167}
168
169fn resp_text_code_uid_validity(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
170 map(
171 preceded(tag_no_case("UIDVALIDITY "), number),
172 ResponseCode::UidValidity,
173 )
174 .parse(i)
175}
176
177fn resp_text_code_uid_next(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
178 map(
179 preceded(tag_no_case("UIDNEXT "), number),
180 ResponseCode::UidNext,
181 )
182 .parse(i)
183}
184
185fn resp_text_code_unseen(i: &[u8]) -> IResult<&[u8], ResponseCode<'_>> {
186 map(
187 preceded(tag_no_case("UNSEEN "), number),
188 ResponseCode::Unseen,
189 )
190 .parse(i)
191}
192
193#[derive(Debug, Eq, PartialEq)]
194#[non_exhaustive]
195pub enum ResponseCode<'a> {
196 Alert,
197 BadCharset(Option<Vec<Cow<'a, str>>>),
198 Capabilities(Vec<Capability<'a>>),
199 HighestModSeq(u64), Parse,
201 PermanentFlags(Vec<Cow<'a, str>>),
202 ReadOnly,
203 ReadWrite,
204 TryCreate,
205 UidNext(u32),
206 UidValidity(u32),
207 Unseen(u32),
208 AppendUid(u32, Vec<UidSetMember>),
209 CopyUid(u32, Vec<UidSetMember>, Vec<UidSetMember>),
210 UidNotSticky,
211 MetadataLongEntries(u64), MetadataMaxSize(u64), MetadataTooMany, MetadataNoPrivate, }
216
217impl<'a> ResponseCode<'a> {
218 fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
219 delimited(
222 tag("["),
223 alt((
224 resp_text_code_alert,
225 resp_text_code_badcharset,
226 resp_text_code_capability,
227 resp_text_code_parse,
228 resp_text_code_permanent_flags,
229 resp_text_code_uid_validity,
230 resp_text_code_uid_next,
231 resp_text_code_unseen,
232 resp_text_code_read_only,
233 resp_text_code_read_write,
234 resp_text_code_try_create,
235 rfc4551::resp_text_code_highest_mod_seq,
236 rfc4315::resp_text_code_append_uid,
237 rfc4315::resp_text_code_copy_uid,
238 rfc4315::resp_text_code_uid_not_sticky,
239 rfc5464::resp_text_code_metadata_long_entries,
240 rfc5464::resp_text_code_metadata_max_size,
241 rfc5464::resp_text_code_metadata_too_many,
242 rfc5464::resp_text_code_metadata_no_private,
243 )),
244 tag("]"),
245 )
246 .parse(i)
247 }
248
249 pub fn into_owned(self) -> ResponseCode<'static> {
250 match self {
251 ResponseCode::Alert => ResponseCode::Alert,
252 ResponseCode::BadCharset(v) => {
253 ResponseCode::BadCharset(v.map(|vs| vs.into_iter().map(to_owned_cow).collect()))
254 }
255 ResponseCode::Capabilities(v) => {
256 ResponseCode::Capabilities(v.into_iter().map(Capability::into_owned).collect())
257 }
258 ResponseCode::HighestModSeq(v) => ResponseCode::HighestModSeq(v),
259 ResponseCode::Parse => ResponseCode::Parse,
260 ResponseCode::PermanentFlags(v) => {
261 ResponseCode::PermanentFlags(v.into_iter().map(to_owned_cow).collect())
262 }
263 ResponseCode::ReadOnly => ResponseCode::ReadOnly,
264 ResponseCode::ReadWrite => ResponseCode::ReadWrite,
265 ResponseCode::TryCreate => ResponseCode::TryCreate,
266 ResponseCode::UidNext(v) => ResponseCode::UidNext(v),
267 ResponseCode::UidValidity(v) => ResponseCode::UidValidity(v),
268 ResponseCode::Unseen(v) => ResponseCode::Unseen(v),
269 ResponseCode::AppendUid(a, b) => ResponseCode::AppendUid(a, b),
270 ResponseCode::CopyUid(a, b, c) => ResponseCode::CopyUid(a, b, c),
271 ResponseCode::UidNotSticky => ResponseCode::UidNotSticky,
272 ResponseCode::MetadataLongEntries(v) => ResponseCode::MetadataLongEntries(v),
273 ResponseCode::MetadataMaxSize(v) => ResponseCode::MetadataMaxSize(v),
274 ResponseCode::MetadataTooMany => ResponseCode::MetadataTooMany,
275 ResponseCode::MetadataNoPrivate => ResponseCode::MetadataNoPrivate,
276 }
277 }
278}
279
280#[derive(Debug, Eq, PartialEq, Hash)]
281pub enum Capability<'a> {
282 Imap4rev1,
283 Auth(Cow<'a, str>),
284 Atom(Cow<'a, str>),
285}
286
287impl<'a> Capability<'a> {
288 fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
289 alt((
290 map(tag_no_case("IMAP4rev1"), |_| Capability::Imap4rev1),
291 map(
292 map(preceded(tag_no_case("AUTH="), atom), Cow::Borrowed),
293 Capability::Auth,
294 ),
295 map(map(atom, Cow::Borrowed), Capability::Atom),
296 ))
297 .parse(i)
298 }
299
300 pub fn into_owned(self) -> Capability<'static> {
301 match self {
302 Capability::Imap4rev1 => Capability::Imap4rev1,
303 Capability::Auth(v) => Capability::Auth(to_owned_cow(v)),
304 Capability::Atom(v) => Capability::Atom(to_owned_cow(v)),
305 }
306 }
307}
308
309fn ensure_capabilities_contains_imap4rev(
310 capabilities: Vec<Capability<'_>>,
311) -> Result<Vec<Capability<'_>>, ()> {
312 if capabilities.contains(&Capability::Imap4rev1) {
313 Ok(capabilities)
314 } else {
315 Err(())
316 }
317}
318
319fn capability_data(i: &[u8]) -> IResult<&[u8], Vec<Capability<'_>>> {
320 map_res(
321 preceded(
322 tag_no_case("CAPABILITY"),
323 many0(preceded(char(' '), Capability::parse)),
324 ),
325 ensure_capabilities_contains_imap4rev,
326 )
327 .parse(i)
328}
329
330fn mailbox_data_search(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
331 map(
332 terminated(
335 preceded(tag_no_case("SEARCH"), many0(preceded(tag(" "), number))),
336 opt(tag(" ")),
337 ),
338 MailboxDatum::Search,
339 )
340 .parse(i)
341}
342
343fn mailbox_data_flags(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
344 map(
345 preceded(tag_no_case("FLAGS "), flag_list),
346 MailboxDatum::Flags,
347 )
348 .parse(i)
349}
350
351fn mailbox_data_exists(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
352 map(
353 terminated(number, tag_no_case(" EXISTS")),
354 MailboxDatum::Exists,
355 )
356 .parse(i)
357}
358
359#[derive(Debug, Eq, PartialEq, Clone)]
365#[non_exhaustive]
366pub enum NameAttribute<'a> {
367 NoInferiors,
373 NoSelect,
377 Marked,
383 Unmarked,
388 All,
395 Archive,
402 Drafts,
411 Flagged,
418 Junk,
425 Sent,
432 Trash,
444 Extension(Cow<'a, str>),
447}
448
449impl<'a> NameAttribute<'a> {
450 fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
451 alt((
452 value(NameAttribute::NoInferiors, tag_no_case("\\Noinferiors")),
454 value(NameAttribute::NoSelect, tag_no_case("\\Noselect")),
455 value(NameAttribute::Marked, tag_no_case("\\Marked")),
456 value(NameAttribute::Unmarked, tag_no_case("\\Unmarked")),
457 value(NameAttribute::All, tag_no_case("\\All")),
459 value(NameAttribute::Archive, tag_no_case("\\Archive")),
460 value(NameAttribute::Drafts, tag_no_case("\\Drafts")),
461 value(NameAttribute::Flagged, tag_no_case("\\Flagged")),
462 value(NameAttribute::Junk, tag_no_case("\\Junk")),
463 value(NameAttribute::Sent, tag_no_case("\\Sent")),
464 value(NameAttribute::Trash, tag_no_case("\\Trash")),
465 map(
467 map_res(
468 recognize(pair(tag("\\"), take_while(is_atom_char))),
469 from_utf8,
470 ),
471 |s| NameAttribute::Extension(Cow::Borrowed(s)),
472 ),
473 ))
474 .parse(i)
475 }
476
477 pub fn into_owned(self) -> NameAttribute<'static> {
478 match self {
479 NameAttribute::NoInferiors => NameAttribute::NoInferiors,
481 NameAttribute::NoSelect => NameAttribute::NoSelect,
482 NameAttribute::Marked => NameAttribute::Marked,
483 NameAttribute::Unmarked => NameAttribute::Unmarked,
484 NameAttribute::All => NameAttribute::All,
486 NameAttribute::Archive => NameAttribute::Archive,
487 NameAttribute::Drafts => NameAttribute::Drafts,
488 NameAttribute::Flagged => NameAttribute::Flagged,
489 NameAttribute::Junk => NameAttribute::Junk,
490 NameAttribute::Sent => NameAttribute::Sent,
491 NameAttribute::Trash => NameAttribute::Trash,
492 NameAttribute::Extension(s) => NameAttribute::Extension(to_owned_cow(s)),
494 }
495 }
496}
497
498#[derive(Clone, Debug, Eq, PartialEq)]
499pub struct MailboxListData<'a> {
500 pub name_attributes: Vec<NameAttribute<'a>>,
501 pub delimiter: Option<Cow<'a, str>>,
502 pub name: Cow<'a, str>,
503}
504
505impl<'a> MailboxListData<'a> {
506 fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
507 map(
508 (
509 parenthesized_list(NameAttribute::parse),
510 tag(" "),
511 alt((map(quoted_utf8, Some), map(nil, |_| None))),
512 tag(" "),
513 mailbox,
514 ),
515 |(name_attributes, _, delimiter, _, name)| MailboxListData {
516 name_attributes,
517 delimiter,
518 name,
519 },
520 )
521 .parse(i)
522 }
523
524 pub fn into_owned(self) -> MailboxListData<'static> {
525 MailboxListData {
526 name_attributes: self
527 .name_attributes
528 .into_iter()
529 .map(|named_attribute| named_attribute.into_owned())
530 .collect(),
531 delimiter: self.delimiter.map(to_owned_cow),
532 name: to_owned_cow(self.name),
533 }
534 }
535}
536
537fn mailbox_data_list(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
538 map(
539 preceded(tag_no_case("LIST "), MailboxListData::parse),
540 MailboxDatum::List,
541 )
542 .parse(i)
543}
544
545fn mailbox_data_lsub(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
546 map(
547 preceded(tag_no_case("LSUB "), MailboxListData::parse),
548 MailboxDatum::List,
549 )
550 .parse(i)
551}
552
553#[derive(Debug, Eq, PartialEq, Clone)]
554#[non_exhaustive]
555pub enum StatusAttribute {
556 HighestModSeq(u64), Messages(u32),
558 Recent(u32),
559 UidNext(u32),
560 UidValidity(u32),
561 Unseen(u32),
562}
563
564impl StatusAttribute {
565 fn parse(i: &[u8]) -> IResult<&[u8], Self> {
568 alt((
569 rfc4551::status_att_val_highest_mod_seq,
570 map(
571 preceded(tag_no_case("MESSAGES "), number),
572 StatusAttribute::Messages,
573 ),
574 map(
575 preceded(tag_no_case("RECENT "), number),
576 StatusAttribute::Recent,
577 ),
578 map(
579 preceded(tag_no_case("UIDNEXT "), number),
580 StatusAttribute::UidNext,
581 ),
582 map(
583 preceded(tag_no_case("UIDVALIDITY "), number),
584 StatusAttribute::UidValidity,
585 ),
586 map(
587 preceded(tag_no_case("UNSEEN "), number),
588 StatusAttribute::Unseen,
589 ),
590 ))
591 .parse(i)
592 }
593}
594
595fn status_att_list(i: &[u8]) -> IResult<&[u8], Vec<StatusAttribute>> {
596 parenthesized_list(StatusAttribute::parse).parse(i)
600}
601
602fn mailbox_data_status(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
603 map(
604 (tag_no_case("STATUS "), mailbox, tag(" "), status_att_list),
605 |(_, mailbox, _, status)| MailboxDatum::Status { mailbox, status },
606 )
607 .parse(i)
608}
609
610fn mailbox_data_recent(i: &[u8]) -> IResult<&[u8], MailboxDatum<'_>> {
611 map(
612 terminated(number, tag_no_case(" RECENT")),
613 MailboxDatum::Recent,
614 )
615 .parse(i)
616}
617
618#[derive(Debug, Eq, PartialEq, Clone)]
619#[non_exhaustive]
620pub enum MailboxDatum<'a> {
621 Exists(u32),
622 Flags(Vec<Cow<'a, str>>),
623 List(MailboxListData<'a>),
624 Search(Vec<u32>),
625 Sort(Vec<u32>),
626 Status {
627 mailbox: Cow<'a, str>,
628 status: Vec<StatusAttribute>,
629 },
630 Recent(u32),
631 MetadataSolicited {
632 mailbox: Cow<'a, str>,
633 values: Vec<Metadata>,
634 },
635 MetadataUnsolicited {
636 mailbox: Cow<'a, str>,
637 values: Vec<Cow<'a, str>>,
638 },
639 GmailLabels(Vec<Cow<'a, str>>),
640 GmailMsgId(u64),
641 GmailThrId(u64),
642}
643
644impl<'a> MailboxDatum<'a> {
645 fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
646 alt((
647 mailbox_data_flags,
648 mailbox_data_exists,
649 mailbox_data_list,
650 mailbox_data_lsub,
651 mailbox_data_status,
652 mailbox_data_recent,
653 mailbox_data_search,
654 gmail::mailbox_data_gmail_labels,
655 gmail::mailbox_data_gmail_msgid,
656 gmail::mailbox_data_gmail_thrid,
657 rfc5256::mailbox_data_sort,
658 ))
659 .parse(i)
660 }
661
662 pub fn into_owned(self) -> MailboxDatum<'static> {
663 match self {
664 MailboxDatum::Exists(seq) => MailboxDatum::Exists(seq),
665 MailboxDatum::Flags(flags) => {
666 MailboxDatum::Flags(flags.into_iter().map(to_owned_cow).collect())
667 }
668 MailboxDatum::List(data) => MailboxDatum::List(data.into_owned()),
669 MailboxDatum::Search(seqs) => MailboxDatum::Search(seqs),
670 MailboxDatum::Sort(seqs) => MailboxDatum::Sort(seqs),
671 MailboxDatum::Status { mailbox, status } => MailboxDatum::Status {
672 mailbox: to_owned_cow(mailbox),
673 status,
674 },
675 MailboxDatum::Recent(seq) => MailboxDatum::Recent(seq),
676 MailboxDatum::MetadataSolicited { mailbox, values } => {
677 MailboxDatum::MetadataSolicited {
678 mailbox: to_owned_cow(mailbox),
679 values,
680 }
681 }
682 MailboxDatum::MetadataUnsolicited { mailbox, values } => {
683 MailboxDatum::MetadataUnsolicited {
684 mailbox: to_owned_cow(mailbox),
685 values: values.into_iter().map(to_owned_cow).collect(),
686 }
687 }
688 MailboxDatum::GmailLabels(labels) => {
689 MailboxDatum::GmailLabels(labels.into_iter().map(to_owned_cow).collect())
690 }
691 MailboxDatum::GmailMsgId(msgid) => MailboxDatum::GmailMsgId(msgid),
692 MailboxDatum::GmailThrId(thrid) => MailboxDatum::GmailThrId(thrid),
693 }
694 }
695}
696
697#[derive(Debug, Eq, PartialEq)]
698pub struct Address<'a> {
699 pub name: Option<Cow<'a, [u8]>>,
700 pub adl: Option<Cow<'a, [u8]>>,
701 pub mailbox: Option<Cow<'a, [u8]>>,
702 pub host: Option<Cow<'a, [u8]>>,
703}
704
705impl<'a> Address<'a> {
706 fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
709 paren_delimited(map(
710 (
711 nstring,
712 tag(" "),
713 nstring,
714 tag(" "),
715 nstring,
716 tag(" "),
717 nstring,
718 ),
719 |(name, _, adl, _, mailbox, _, host)| Address {
720 name: name.map(Cow::Borrowed),
721 adl: adl.map(Cow::Borrowed),
722 mailbox: mailbox.map(Cow::Borrowed),
723 host: host.map(Cow::Borrowed),
724 },
725 ))
726 .parse(i)
727 }
728
729 pub fn into_owned(self) -> Address<'static> {
730 Address {
731 name: self.name.map(to_owned_cow),
732 adl: self.adl.map(to_owned_cow),
733 mailbox: self.mailbox.map(to_owned_cow),
734 host: self.host.map(to_owned_cow),
735 }
736 }
737}
738
739fn opt_addresses(i: &[u8]) -> IResult<&[u8], Option<Vec<Address<'_>>>> {
740 alt((
741 map(nil, |_s| None),
742 map(
743 paren_delimited(many1(terminated(Address::parse, opt(char(' '))))),
744 Some,
745 ),
746 ))
747 .parse(i)
748}
749
750#[derive(Debug, Eq, PartialEq)]
754pub struct Envelope<'a> {
755 pub date: Option<Cow<'a, [u8]>>,
756 pub subject: Option<Cow<'a, [u8]>>,
757 pub from: Option<Vec<Address<'a>>>,
759 pub sender: Option<Vec<Address<'a>>>,
761 pub reply_to: Option<Vec<Address<'a>>>,
763 pub to: Option<Vec<Address<'a>>>,
764 pub cc: Option<Vec<Address<'a>>>,
765 pub bcc: Option<Vec<Address<'a>>>,
766 pub in_reply_to: Option<Cow<'a, [u8]>>,
767 pub message_id: Option<Cow<'a, [u8]>>,
768}
769
770impl<'a> Envelope<'a> {
771 pub(crate) fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
795 paren_delimited(map(
796 (
797 nstring,
798 tag(" "),
799 nstring,
800 tag(" "),
801 opt_addresses,
802 tag(" "),
803 opt_addresses,
804 tag(" "),
805 opt_addresses,
806 tag(" "),
807 opt_addresses,
808 tag(" "),
809 opt_addresses,
810 tag(" "),
811 opt_addresses,
812 tag(" "),
813 nstring,
814 tag(" "),
815 nstring,
816 ),
817 |(
818 date,
819 _,
820 subject,
821 _,
822 from,
823 _,
824 sender,
825 _,
826 reply_to,
827 _,
828 to,
829 _,
830 cc,
831 _,
832 bcc,
833 _,
834 in_reply_to,
835 _,
836 message_id,
837 )| Envelope {
838 date: date.map(Cow::Borrowed),
839 subject: subject.map(Cow::Borrowed),
840 from,
841 sender,
842 reply_to,
843 to,
844 cc,
845 bcc,
846 in_reply_to: in_reply_to.map(Cow::Borrowed),
847 message_id: message_id.map(Cow::Borrowed),
848 },
849 ))
850 .parse(i)
851 }
852
853 pub fn into_owned(self) -> Envelope<'static> {
854 Envelope {
855 date: self.date.map(to_owned_cow),
856 subject: self.subject.map(to_owned_cow),
857 from: self
858 .from
859 .map(|v| v.into_iter().map(|v| v.into_owned()).collect()),
860 sender: self
861 .sender
862 .map(|v| v.into_iter().map(|v| v.into_owned()).collect()),
863 reply_to: self
864 .reply_to
865 .map(|v| v.into_iter().map(|v| v.into_owned()).collect()),
866 to: self
867 .to
868 .map(|v| v.into_iter().map(|v| v.into_owned()).collect()),
869 cc: self
870 .cc
871 .map(|v| v.into_iter().map(|v| v.into_owned()).collect()),
872 bcc: self
873 .bcc
874 .map(|v| v.into_iter().map(|v| v.into_owned()).collect()),
875 in_reply_to: self.in_reply_to.map(to_owned_cow),
876 message_id: self.message_id.map(to_owned_cow),
877 }
878 }
879}
880
881fn msg_att_envelope(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
882 map(
883 preceded(tag_no_case("ENVELOPE "), Envelope::parse),
884 |envelope| AttributeValue::Envelope(Box::new(envelope)),
885 )
886 .parse(i)
887}
888
889fn msg_att_internal_date(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
890 map(
891 preceded(tag_no_case("INTERNALDATE "), nstring_utf8),
892 |date| AttributeValue::InternalDate(date.unwrap()),
893 )
894 .parse(i)
895}
896
897fn msg_att_flags(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
898 map(
899 preceded(tag_no_case("FLAGS "), flag_list),
900 AttributeValue::Flags,
901 )
902 .parse(i)
903}
904
905fn msg_att_rfc822(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
906 map(preceded(tag_no_case("RFC822 "), nstring), |v| {
907 AttributeValue::Rfc822(v.map(Cow::Borrowed))
908 })
909 .parse(i)
910}
911
912fn msg_att_rfc822_header(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
913 map(
915 (tag_no_case("RFC822.HEADER "), opt(tag(" ")), nstring),
916 |(_, _, raw)| AttributeValue::Rfc822Header(raw.map(Cow::Borrowed)),
917 )
918 .parse(i)
919}
920
921fn msg_att_rfc822_size(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
922 map(
923 preceded(tag_no_case("RFC822.SIZE "), number),
924 AttributeValue::Rfc822Size,
925 )
926 .parse(i)
927}
928
929fn msg_att_rfc822_text(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
930 map(preceded(tag_no_case("RFC822.TEXT "), nstring), |v| {
931 AttributeValue::Rfc822Text(v.map(Cow::Borrowed))
932 })
933 .parse(i)
934}
935
936fn msg_att_uid(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
937 map(preceded(tag_no_case("UID "), number), AttributeValue::Uid).parse(i)
938}
939
940fn msg_att_emailid(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
962 map(
963 preceded(
964 tag_no_case("EMAILID "),
965 paren_delimited(map_res(take_while1(is_astring_char), from_utf8)),
966 ),
967 |id| AttributeValue::EmailId(Cow::Borrowed(id)),
968 )
969 .parse(i)
970}
971
972fn msg_att_threadid(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
977 map(
978 preceded(
979 tag_no_case("THREADID "),
980 alt((
981 map(nil, |_| None),
982 map(
983 paren_delimited(map_res(take_while1(is_astring_char), from_utf8)),
984 |id| Some(Cow::Borrowed(id)),
985 ),
986 )),
987 ),
988 AttributeValue::ThreadId,
989 )
990 .parse(i)
991}
992
993fn msg_att_unknown(i: &[u8]) -> IResult<&[u8], AttributeValue<'_>> {
1011 map(
1012 pair(
1013 map_res(take_while1(is_atom_char), from_utf8),
1014 preceded(
1015 tag(" "),
1016 alt((
1017 value((), paren_delimited(take_while(|c: u8| c != b')'))),
1018 value((), nstring),
1019 value((), take_while1(|c: u8| c != b' ' && c != b')')),
1020 )),
1021 ),
1022 ),
1023 |_| AttributeValue::Unknown,
1024 )
1025 .parse(i)
1026}
1027
1028#[allow(clippy::large_enum_variant)]
1029#[derive(Debug, Eq, PartialEq)]
1030#[non_exhaustive]
1031pub enum AttributeValue<'a> {
1032 BodySection {
1033 section: Option<SectionPath>,
1034 index: Option<u32>,
1035 data: Option<Cow<'a, [u8]>>,
1036 },
1037 BodyStructure(BodyStructure<'a>),
1038 Envelope(Box<Envelope<'a>>),
1039 Flags(Vec<Cow<'a, str>>),
1040 InternalDate(Cow<'a, str>),
1041 ModSeq(u64), Rfc822(Option<Cow<'a, [u8]>>),
1043 Rfc822Header(Option<Cow<'a, [u8]>>),
1044 Rfc822Size(u32),
1045 Rfc822Text(Option<Cow<'a, [u8]>>),
1046 Uid(u32),
1047 GmailLabels(Vec<Cow<'a, str>>),
1049 GmailMsgId(u64),
1050 GmailThrId(u64),
1051 EmailId(Cow<'a, str>),
1055 ThreadId(Option<Cow<'a, str>>),
1060 Unknown,
1069}
1070
1071impl<'a> AttributeValue<'a> {
1072 fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
1073 alt((
1074 msg_att_body_section,
1075 msg_att_body_structure,
1076 msg_att_envelope,
1077 msg_att_internal_date,
1078 msg_att_flags,
1079 rfc4551::msg_att_mod_seq,
1080 msg_att_rfc822,
1081 msg_att_rfc822_header,
1082 msg_att_rfc822_size,
1083 msg_att_rfc822_text,
1084 msg_att_uid,
1085 gmail::msg_att_gmail_labels,
1086 gmail::msg_att_gmail_msgid,
1087 gmail::msg_att_gmail_thrid,
1088 msg_att_emailid,
1089 msg_att_threadid,
1090 msg_att_unknown,
1091 ))
1092 .parse(i)
1093 }
1094
1095 pub fn into_owned(self) -> AttributeValue<'static> {
1096 match self {
1097 AttributeValue::BodySection {
1098 section,
1099 index,
1100 data,
1101 } => AttributeValue::BodySection {
1102 section,
1103 index,
1104 data: data.map(to_owned_cow),
1105 },
1106 AttributeValue::BodyStructure(body) => AttributeValue::BodyStructure(body.into_owned()),
1107 AttributeValue::Envelope(e) => AttributeValue::Envelope(Box::new(e.into_owned())),
1108 AttributeValue::Flags(v) => {
1109 AttributeValue::Flags(v.into_iter().map(to_owned_cow).collect())
1110 }
1111 AttributeValue::InternalDate(v) => AttributeValue::InternalDate(to_owned_cow(v)),
1112 AttributeValue::ModSeq(v) => AttributeValue::ModSeq(v),
1113 AttributeValue::Rfc822(v) => AttributeValue::Rfc822(v.map(to_owned_cow)),
1114 AttributeValue::Rfc822Header(v) => AttributeValue::Rfc822Header(v.map(to_owned_cow)),
1115 AttributeValue::Rfc822Size(v) => AttributeValue::Rfc822Size(v),
1116 AttributeValue::Rfc822Text(v) => AttributeValue::Rfc822Text(v.map(to_owned_cow)),
1117 AttributeValue::Uid(v) => AttributeValue::Uid(v),
1118 AttributeValue::GmailLabels(v) => {
1119 AttributeValue::GmailLabels(v.into_iter().map(to_owned_cow).collect())
1120 }
1121 AttributeValue::GmailMsgId(v) => AttributeValue::GmailMsgId(v),
1122 AttributeValue::GmailThrId(v) => AttributeValue::GmailThrId(v),
1123 AttributeValue::EmailId(v) => AttributeValue::EmailId(to_owned_cow(v)),
1124 AttributeValue::ThreadId(v) => AttributeValue::ThreadId(v.map(to_owned_cow)),
1125 AttributeValue::Unknown => AttributeValue::Unknown,
1126 }
1127 }
1128}
1129
1130fn msg_att_list(i: &[u8]) -> IResult<&[u8], Vec<AttributeValue<'_>>> {
1131 parenthesized_nonempty_list(AttributeValue::parse).parse(i)
1132}
1133
1134fn message_data_fetch(i: &[u8]) -> IResult<&[u8], Response<'_>> {
1136 map(
1137 (number, tag_no_case(" FETCH "), msg_att_list),
1138 |(num, _, attrs)| Response::Fetch(num, attrs),
1139 )
1140 .parse(i)
1141}
1142
1143fn message_data_expunge(i: &[u8]) -> IResult<&[u8], u32> {
1145 terminated(number, tag_no_case(" EXPUNGE")).parse(i)
1146}
1147
1148#[derive(Clone, Debug, Eq, PartialEq)]
1149pub struct RequestId(pub String);
1150
1151impl RequestId {
1152 fn parse(i: &[u8]) -> IResult<&[u8], Self> {
1154 map(map_res(take_while1(is_tag_char), from_utf8), |s| {
1155 RequestId(s.to_string())
1156 })
1157 .parse(i)
1158 }
1159
1160 pub fn as_bytes(&self) -> &[u8] {
1161 self.0.as_bytes()
1162 }
1163}
1164
1165#[derive(Debug, Default, Eq, PartialEq)]
1166pub struct Outcome<'a> {
1167 pub code: Option<ResponseCode<'a>>,
1168 pub information: Option<Cow<'a, str>>,
1169}
1170
1171impl<'a> Outcome<'a> {
1172 fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self> {
1177 map((opt(ResponseCode::parse), text), |(code, text)| {
1178 let information = if text.is_empty() {
1179 None
1180 } else if code.is_some() {
1181 Some(match text {
1182 Cow::Borrowed(s) => Cow::Borrowed(&s[1..]),
1183 Cow::Owned(s) => Cow::Owned(s[1..].to_string()),
1184 })
1185 } else {
1186 Some(text)
1187 };
1188
1189 Outcome { code, information }
1190 })
1191 .parse(i)
1192 }
1193
1194 pub fn into_owned(self) -> Outcome<'static> {
1195 Outcome {
1196 code: self.code.map(ResponseCode::into_owned),
1197 information: self.information.map(to_owned_cow),
1198 }
1199 }
1200}
1201
1202fn trailing_resp_text(i: &[u8]) -> IResult<&[u8], Outcome<'_>> {
1204 map(opt((tag(" "), Outcome::parse)), |resptext| {
1205 resptext.map(|(_, tuple)| tuple).unwrap_or_default()
1206 })
1207 .parse(i)
1208}
1209
1210pub(crate) fn continue_req(i: &[u8]) -> IResult<&[u8], Response<'_>> {
1212 map(
1215 (tag("+"), opt(tag(" ")), Outcome::parse, tag("\r\n")),
1216 |(_, _, outcome, _)| Response::Continue(outcome),
1217 )
1218 .parse(i)
1219}
1220
1221pub(crate) fn response_tagged(i: &[u8]) -> IResult<&[u8], Response<'_>> {
1226 map(
1227 (
1228 RequestId::parse,
1229 tag(" "),
1230 Status::parse,
1231 trailing_resp_text,
1232 tag("\r\n"),
1233 ),
1234 |(tag, _, status, outcome, _)| Response::Done {
1235 tag,
1236 status,
1237 outcome,
1238 },
1239 )
1240 .parse(i)
1241}
1242
1243fn resp_cond(i: &[u8]) -> IResult<&[u8], Response<'_>> {
1251 map((Status::parse, trailing_resp_text), |(status, outcome)| {
1252 Response::Data { status, outcome }
1253 })
1254 .parse(i)
1255}
1256
1257pub(crate) fn response_data(i: &[u8]) -> IResult<&[u8], Response<'_>> {
1260 delimited(
1261 tag("* "),
1262 alt((
1263 resp_cond,
1264 map(MailboxDatum::parse, Response::MailboxData),
1265 map(message_data_expunge, Response::Expunge),
1266 message_data_fetch,
1267 map(capability_data, Response::Capabilities),
1268 rfc5161::resp_enabled,
1269 rfc5464::metadata_solicited,
1270 rfc5464::metadata_unsolicited,
1271 rfc7162::resp_vanished,
1272 rfc2087::quota,
1273 rfc2087::quota_root,
1274 rfc2971::resp_id,
1275 rfc4314::acl,
1276 rfc4314::list_rights,
1277 rfc4314::my_rights,
1278 )),
1279 preceded(
1280 many0(tag(" ")), tag("\r\n"),
1282 ),
1283 )
1284 .parse(i)
1285}
1286
1287#[cfg(test)]
1288mod tests {
1289 use super::{AttributeValue, Capability, NameAttribute};
1290 use assert_matches::assert_matches;
1291 use std::borrow::Cow;
1292
1293 #[test]
1294 fn test_list() {
1295 match super::mailbox(b"iNboX ") {
1296 Ok((_, mb)) => {
1297 assert_eq!(mb, "INBOX");
1298 }
1299 rsp => panic!("unexpected response {rsp:?}"),
1300 }
1301 }
1302
1303 #[test]
1304 fn test_envelope() {
1305 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>") "#;
1306 match super::msg_att_envelope(env) {
1307 Ok((_, AttributeValue::Envelope(_))) => {}
1308 rsp => panic!("unexpected response {rsp:?}"),
1309 }
1310 }
1311
1312 #[test]
1313 fn test_opt_addresses() {
1314 let addr = b"((NIL NIL \"minutes\" \"CNRI.Reston.VA.US\") (\"John Klensin\" NIL \"KLENSIN\" \"MIT.EDU\")) ";
1315 match super::opt_addresses(addr) {
1316 Ok((_, _addresses)) => {}
1317 rsp => panic!("unexpected response {rsp:?}"),
1318 }
1319 }
1320
1321 #[test]
1322 fn test_opt_addresses_no_space() {
1323 let addr =
1324 br#"((NIL NIL "test" "example@example.com")(NIL NIL "test" "example@example.com"))"#;
1325 match super::opt_addresses(addr) {
1326 Ok((_, _addresses)) => {}
1327 rsp => panic!("unexpected response {rsp:?}"),
1328 }
1329 }
1330
1331 #[test]
1332 fn test_addresses() {
1333 match super::Address::parse(b"(\"John Klensin\" NIL \"KLENSIN\" \"MIT.EDU\") ") {
1334 Ok((_, _address)) => {}
1335 rsp => panic!("unexpected response {rsp:?}"),
1336 }
1337
1338 match super::Address::parse(b"({12}\r\nJoh\xff Klensin NIL \"KLENSIN\" \"MIT.EDU\") ") {
1340 Ok((_, _address)) => {}
1341 rsp => panic!("unexpected response {rsp:?}"),
1342 }
1343 }
1344
1345 #[test]
1346 fn test_capability_data() {
1347 assert_matches!(
1349 super::capability_data(b"CAPABILITY IMAP4rev1\r\n"),
1350 Ok((_, capabilities)) => {
1351 assert_eq!(capabilities, vec![Capability::Imap4rev1])
1352 }
1353 );
1354
1355 assert_matches!(
1356 super::capability_data(b"CAPABILITY XPIG-LATIN IMAP4rev1 STARTTLS AUTH=GSSAPI\r\n"),
1357 Ok((_, capabilities)) => {
1358 assert_eq!(capabilities, vec![
1359 Capability::Atom(Cow::Borrowed("XPIG-LATIN")),
1360 Capability::Imap4rev1,
1361 Capability::Atom(Cow::Borrowed("STARTTLS")),
1362 Capability::Auth(Cow::Borrowed("GSSAPI")),
1363 ])
1364 }
1365 );
1366
1367 assert_matches!(
1368 super::capability_data(b"CAPABILITY IMAP4rev1 AUTH=GSSAPI AUTH=PLAIN\r\n"),
1369 Ok((_, capabilities)) => {
1370 assert_eq!(capabilities, vec![
1371 Capability::Imap4rev1,
1372 Capability::Auth(Cow::Borrowed("GSSAPI")),
1373 Capability::Auth(Cow::Borrowed("PLAIN")),
1374 ])
1375 }
1376 );
1377
1378 assert_matches!(
1380 super::capability_data(b"CAPABILITY AUTH=GSSAPI AUTH=PLAIN\r\n"),
1381 Err(_)
1382 );
1383 }
1384
1385 #[test]
1386 fn test_surgemail_select_flags() {
1387 assert_matches!(
1389 super::flag_list(b"(\\Answered \\Flagged \\Deleted \\Draft \\Seen $Forwarded )"),
1390 Ok(([], flags)) => {
1391 assert_eq!(flags, vec![
1392 "\\Answered",
1393 "\\Flagged",
1394 "\\Deleted",
1395 "\\Draft",
1396 "\\Seen",
1397 "$Forwarded"
1398 ])
1399 }
1400 );
1401 }
1402
1403 #[test]
1406 fn test_name_attribute_into_owned() {
1407 let name_attributes = [
1408 NameAttribute::NoInferiors,
1410 NameAttribute::NoSelect,
1411 NameAttribute::Marked,
1412 NameAttribute::Unmarked,
1413 NameAttribute::All,
1415 NameAttribute::Archive,
1416 NameAttribute::Drafts,
1417 NameAttribute::Flagged,
1418 NameAttribute::Junk,
1419 NameAttribute::Sent,
1420 NameAttribute::Trash,
1421 NameAttribute::Extension(Cow::Borrowed("Foobar")),
1423 ];
1424
1425 for name_attribute in name_attributes {
1426 let owned_name_attribute = name_attribute.clone().into_owned();
1427 assert_eq!(name_attribute, owned_name_attribute);
1428 }
1429 }
1430
1431 #[test]
1432 fn test_attribute_value_unknown_into_owned() {
1433 assert_eq!(
1434 AttributeValue::Unknown.into_owned(),
1435 AttributeValue::Unknown
1436 );
1437 }
1438}