1use abnf_core::streaming::{dquote, sp};
2use imap_types::{
3 core::QuotedChar,
4 flag::FlagNameAttribute,
5 mailbox::{ListCharString, ListMailbox, Mailbox},
6 response::Data,
7 utils::indicators::is_list_char,
8};
9use nom::{
10 branch::alt,
11 bytes::streaming::{tag, tag_no_case, take_while1},
12 combinator::{map, opt, value},
13 multi::many0,
14 sequence::{delimited, preceded, tuple},
15};
16
17use crate::{
18 core::{astring, nil, number, nz_number, quoted_char, string},
19 decode::IMAPResult,
20 extensions::quota::{quota_response, quotaroot_response},
21 flag::{flag_list, mbx_list_flags},
22 status::status_att_list,
23};
24
25pub(crate) fn list_mailbox(input: &[u8]) -> IMAPResult<&[u8], ListMailbox> {
27 alt((
28 map(take_while1(is_list_char), |bytes: &[u8]| {
29 ListMailbox::Token(ListCharString::unvalidated(
35 std::str::from_utf8(bytes).unwrap(),
36 ))
37 }),
38 map(string, ListMailbox::String),
39 ))(input)
40}
41
42pub(crate) fn mailbox(input: &[u8]) -> IMAPResult<&[u8], Mailbox> {
52 map(astring, Mailbox::from)(input)
53}
54
55pub(crate) fn mailbox_data(input: &[u8]) -> IMAPResult<&[u8], Data> {
63 alt((
64 map(
65 tuple((tag_no_case(b"FLAGS"), sp, flag_list)),
66 |(_, _, flags)| Data::Flags(flags),
67 ),
68 map(
69 tuple((tag_no_case(b"LIST"), sp, mailbox_list)),
70 |(_, _, (items, delimiter, mailbox))| Data::List {
71 items: items.unwrap_or_default(),
72 mailbox,
73 delimiter,
74 },
75 ),
76 map(
77 tuple((tag_no_case(b"LSUB"), sp, mailbox_list)),
78 |(_, _, (items, delimiter, mailbox))| Data::Lsub {
79 items: items.unwrap_or_default(),
80 mailbox,
81 delimiter,
82 },
83 ),
84 map(
85 tuple((tag_no_case(b"SEARCH"), many0(preceded(sp, nz_number)))),
86 |(_, nums)| Data::Search(nums),
87 ),
88 map(
89 tuple((
90 tag_no_case(b"STATUS"),
91 sp,
92 mailbox,
93 sp,
94 delimited(tag(b"("), opt(status_att_list), tag(b")")),
95 )),
96 |(_, _, mailbox, _, items)| Data::Status {
97 mailbox,
98 items: items.unwrap_or_default().into(),
99 },
100 ),
101 map(
102 tuple((number, sp, tag_no_case(b"EXISTS"))),
103 |(num, _, _)| Data::Exists(num),
104 ),
105 map(
106 tuple((number, sp, tag_no_case(b"RECENT"))),
107 |(num, _, _)| Data::Recent(num),
108 ),
109 quotaroot_response,
110 quota_response,
111 ))(input)
112}
113
114#[allow(clippy::type_complexity)]
118pub(crate) fn mailbox_list(
119 input: &[u8],
120) -> IMAPResult<&[u8], (Option<Vec<FlagNameAttribute>>, Option<QuotedChar>, Mailbox)> {
121 let mut parser = tuple((
122 delimited(tag(b"("), opt(mbx_list_flags), tag(b")")),
123 sp,
124 alt((
125 map(delimited(dquote, quoted_char, dquote), Option::Some),
126 value(None, nil),
127 )),
128 sp,
129 mailbox,
130 ));
131
132 let (remaining, (mbx_list_flags, _, maybe_delimiter, _, mailbox)) = parser(input)?;
133
134 Ok((remaining, (mbx_list_flags, maybe_delimiter, mailbox)))
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_mailbox() {
143 assert!(mailbox(b"\"iNbOx\"").is_ok());
144 assert!(mailbox(b"{3}\r\naaa\r\n").is_ok());
145 assert!(mailbox(b"inbox ").is_ok());
146 assert!(mailbox(b"inbox.sent ").is_ok());
147 assert!(mailbox(b"aaa").is_err());
148 }
149}