1use abnf_core::streaming::sp;
2use imap_types::{command::CommandBody, core::NonEmptyVec, search::SearchKey};
3use nom::{
4 branch::alt,
5 bytes::streaming::{tag, tag_no_case},
6 combinator::{map, map_opt, opt, value},
7 multi::{many1, separated_list1},
8 sequence::{delimited, preceded, tuple},
9};
10
11use crate::{
12 core::{astring, atom, charset, number},
13 datetime::date,
14 decode::{IMAPErrorKind, IMAPParseError, IMAPResult},
15 fetch::header_fld_name,
16 sequence::sequence_set,
17};
18
19pub(crate) fn search(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
25 let mut parser = tuple((
26 tag_no_case(b"SEARCH"),
27 opt(map(
28 tuple((sp, tag_no_case(b"CHARSET"), sp, charset)),
29 |(_, _, _, charset)| charset,
30 )),
31 many1(preceded(sp, search_key(9))),
32 ));
33
34 let (remaining, (_, charset, mut criteria)) = parser(input)?;
35
36 let criteria = match criteria.len() {
37 0 => unreachable!(),
38 1 => criteria.pop().unwrap(),
39 _ => SearchKey::And(NonEmptyVec::unvalidated(criteria)),
40 };
41
42 Ok((
43 remaining,
44 CommandBody::Search {
45 charset,
46 criteria,
47 uid: false,
48 },
49 ))
50}
51
52pub(crate) fn search_key(
94 remaining_recursions: usize,
95) -> impl Fn(&[u8]) -> IMAPResult<&[u8], SearchKey> {
96 move |input: &[u8]| search_key_limited(input, remaining_recursions)
97}
98
99fn search_key_limited<'a>(
100 input: &'a [u8],
101 remaining_recursion: usize,
102) -> IMAPResult<&'a [u8], SearchKey> {
103 if remaining_recursion == 0 {
104 return Err(nom::Err::Failure(IMAPParseError {
105 input,
106 kind: IMAPErrorKind::RecursionLimitExceeded,
107 }));
108 }
109
110 let search_key =
111 move |input: &'a [u8]| search_key_limited(input, remaining_recursion.saturating_sub(1));
112
113 alt((
114 alt((
115 value(SearchKey::All, tag_no_case(b"ALL")),
116 value(SearchKey::Answered, tag_no_case(b"ANSWERED")),
117 map(tuple((tag_no_case(b"BCC"), sp, astring)), |(_, _, val)| {
118 SearchKey::Bcc(val)
119 }),
120 map(
121 tuple((tag_no_case(b"BEFORE"), sp, map_opt(date, |date| date))),
122 |(_, _, date)| SearchKey::Before(date),
123 ),
124 map(tuple((tag_no_case(b"BODY"), sp, astring)), |(_, _, val)| {
125 SearchKey::Body(val)
126 }),
127 map(tuple((tag_no_case(b"CC"), sp, astring)), |(_, _, val)| {
128 SearchKey::Cc(val)
129 }),
130 value(SearchKey::Deleted, tag_no_case(b"DELETED")),
131 value(SearchKey::Flagged, tag_no_case(b"FLAGGED")),
132 map(tuple((tag_no_case(b"FROM"), sp, astring)), |(_, _, val)| {
133 SearchKey::From(val)
134 }),
135 map(
136 tuple((tag_no_case(b"KEYWORD"), sp, atom)),
140 |(_, _, val)| SearchKey::Keyword(val),
141 ),
142 value(SearchKey::New, tag_no_case(b"NEW")),
143 value(SearchKey::Old, tag_no_case(b"OLD")),
144 map(
145 tuple((tag_no_case(b"ON"), sp, map_opt(date, |date| date))),
146 |(_, _, date)| SearchKey::On(date),
147 ),
148 value(SearchKey::Recent, tag_no_case(b"RECENT")),
149 value(SearchKey::Seen, tag_no_case(b"SEEN")),
150 map(
151 tuple((tag_no_case(b"SINCE"), sp, map_opt(date, |date| date))),
152 |(_, _, date)| SearchKey::Since(date),
153 ),
154 map(
155 tuple((tag_no_case(b"SUBJECT"), sp, astring)),
156 |(_, _, val)| SearchKey::Subject(val),
157 ),
158 map(tuple((tag_no_case(b"TEXT"), sp, astring)), |(_, _, val)| {
159 SearchKey::Text(val)
160 }),
161 map(tuple((tag_no_case(b"TO"), sp, astring)), |(_, _, val)| {
162 SearchKey::To(val)
163 }),
164 )),
165 alt((
166 value(SearchKey::Unanswered, tag_no_case(b"UNANSWERED")),
167 value(SearchKey::Undeleted, tag_no_case(b"UNDELETED")),
168 value(SearchKey::Unflagged, tag_no_case(b"UNFLAGGED")),
169 map(
170 tuple((tag_no_case(b"UNKEYWORD"), sp, atom)),
174 |(_, _, val)| SearchKey::Unkeyword(val),
175 ),
176 value(SearchKey::Unseen, tag_no_case(b"UNSEEN")),
177 value(SearchKey::Draft, tag_no_case(b"DRAFT")),
178 map(
179 tuple((tag_no_case(b"HEADER"), sp, header_fld_name, sp, astring)),
180 |(_, _, key, _, val)| SearchKey::Header(key, val),
181 ),
182 map(
183 tuple((tag_no_case(b"LARGER"), sp, number)),
184 |(_, _, val)| SearchKey::Larger(val),
185 ),
186 map(
187 tuple((tag_no_case(b"NOT"), sp, search_key)),
188 |(_, _, val)| SearchKey::Not(Box::new(val)),
189 ),
190 map(
191 tuple((tag_no_case(b"OR"), sp, search_key, sp, search_key)),
192 |(_, _, alt1, _, alt2)| SearchKey::Or(Box::new(alt1), Box::new(alt2)),
193 ),
194 map(
195 tuple((tag_no_case(b"SENTBEFORE"), sp, map_opt(date, |date| date))),
196 |(_, _, date)| SearchKey::SentBefore(date),
197 ),
198 map(
199 tuple((tag_no_case(b"SENTON"), sp, map_opt(date, |date| date))),
200 |(_, _, date)| SearchKey::SentOn(date),
201 ),
202 map(
203 tuple((tag_no_case(b"SENTSINCE"), sp, map_opt(date, |date| date))),
204 |(_, _, date)| SearchKey::SentSince(date),
205 ),
206 map(
207 tuple((tag_no_case(b"SMALLER"), sp, number)),
208 |(_, _, val)| SearchKey::Smaller(val),
209 ),
210 map(
211 tuple((tag_no_case(b"UID"), sp, sequence_set)),
212 |(_, _, val)| SearchKey::Uid(val),
213 ),
214 value(SearchKey::Undraft, tag_no_case(b"UNDRAFT")),
215 map(sequence_set, SearchKey::SequenceSet),
216 map(
217 delimited(tag(b"("), separated_list1(sp, search_key), tag(b")")),
218 |val| SearchKey::And(NonEmptyVec::unvalidated(val)),
219 ),
220 )),
221 ))(input)
222}
223
224#[cfg(test)]
225mod tests {
226 use imap_types::{
227 core::{AString, Atom},
228 datetime::NaiveDate,
229 sequence::{Sequence, SequenceSet},
230 };
231
232 use super::*;
233 use crate::testing::known_answer_test_encode;
234
235 #[test]
236 fn test_parse_search() {
237 use imap_types::{
238 search::SearchKey::*,
239 sequence::{SeqOrUid::Value, Sequence::*, SequenceSet as SequenceSetData},
240 };
241
242 let (_rem, val) = search(b"search (uid 5)???").unwrap();
243 assert_eq!(
244 val,
245 CommandBody::Search {
246 charset: None,
247 criteria: And(NonEmptyVec::from(Uid(SequenceSetData(
248 vec![Single(Value(5.try_into().unwrap()))]
249 .try_into()
250 .unwrap()
251 )))),
252 uid: false,
253 }
254 );
255
256 let (_rem, val) = search(b"search (uid 5 or uid 5 (uid 1 uid 2) not uid 5)???").unwrap();
257 let expected = CommandBody::Search {
258 charset: None,
259 criteria: And(vec![
260 Uid(SequenceSetData(
261 vec![Single(Value(5.try_into().unwrap()))]
262 .try_into()
263 .unwrap(),
264 )),
265 Or(
266 Box::new(Uid(SequenceSetData(
267 vec![Single(Value(5.try_into().unwrap()))]
268 .try_into()
269 .unwrap(),
270 ))),
271 Box::new(And(vec![
272 Uid(SequenceSetData(
273 vec![Single(Value(1.try_into().unwrap()))]
274 .try_into()
275 .unwrap(),
276 )),
277 Uid(SequenceSetData(
278 vec![Single(Value(2.try_into().unwrap()))]
279 .try_into()
280 .unwrap(),
281 )),
282 ]
283 .try_into()
284 .unwrap())),
285 ),
286 Not(Box::new(Uid(SequenceSetData(
287 vec![Single(Value(5.try_into().unwrap()))]
288 .try_into()
289 .unwrap(),
290 )))),
291 ]
292 .try_into()
293 .unwrap()),
294 uid: false,
295 };
296 assert_eq!(val, expected);
297 }
298
299 #[test]
300 fn test_parse_search_key() {
301 assert!(search_key(1)(b"1:5|").is_ok());
302 assert!(search_key(1)(b"(1:5)|").is_err());
303 assert!(search_key(2)(b"(1:5)|").is_ok());
304 assert!(search_key(2)(b"((1:5))|").is_err());
305 }
306
307 #[test]
308 fn test_encode_search_key() {
309 let tests = [
310 (
311 SearchKey::And(NonEmptyVec::try_from(vec![SearchKey::Answered]).unwrap()),
312 b"(ANSWERED)".as_ref(),
313 ),
314 (
315 SearchKey::And(
316 NonEmptyVec::try_from(vec![SearchKey::Answered, SearchKey::Seen]).unwrap(),
317 ),
318 b"(ANSWERED SEEN)".as_ref(),
319 ),
320 (
321 SearchKey::SequenceSet(SequenceSet::try_from(1).unwrap()),
322 b"1",
323 ),
324 (SearchKey::All, b"ALL"),
325 (SearchKey::Answered, b"ANSWERED"),
326 (SearchKey::Bcc(AString::try_from("A").unwrap()), b"BCC A"),
327 (
328 SearchKey::Before(
329 NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 4, 12).unwrap())
330 .unwrap(),
331 ),
332 b"BEFORE \"12-Apr-2023\"",
333 ),
334 (SearchKey::Body(AString::try_from("A").unwrap()), b"BODY A"),
335 (SearchKey::Cc(AString::try_from("A").unwrap()), b"CC A"),
336 (SearchKey::Deleted, b"DELETED"),
337 (SearchKey::Draft, b"DRAFT"),
338 (SearchKey::Flagged, b"FLAGGED"),
339 (SearchKey::From(AString::try_from("A").unwrap()), b"FROM A"),
340 (
341 SearchKey::Header(
342 AString::try_from("A").unwrap(),
343 AString::try_from("B").unwrap(),
344 ),
345 b"HEADER A B",
346 ),
347 (
348 SearchKey::Keyword(Atom::try_from("A").unwrap()),
349 b"KEYWORD A",
350 ),
351 (SearchKey::Larger(42), b"LARGER 42"),
352 (SearchKey::New, b"NEW"),
353 (SearchKey::Not(Box::new(SearchKey::New)), b"NOT NEW"),
354 (SearchKey::Old, b"OLD"),
355 (
356 SearchKey::On(
357 NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 4, 12).unwrap())
358 .unwrap(),
359 ),
360 b"ON \"12-Apr-2023\"",
361 ),
362 (
363 SearchKey::Or(Box::new(SearchKey::New), Box::new(SearchKey::Recent)),
364 b"OR NEW RECENT",
365 ),
366 (SearchKey::Recent, b"RECENT"),
367 (SearchKey::Seen, b"SEEN"),
368 (
369 SearchKey::SentBefore(
370 NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 4, 12).unwrap())
371 .unwrap(),
372 ),
373 b"SENTBEFORE \"12-Apr-2023\"",
374 ),
375 (
376 SearchKey::SentOn(
377 NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 4, 12).unwrap())
378 .unwrap(),
379 ),
380 b"SENTON \"12-Apr-2023\"",
381 ),
382 (
383 SearchKey::SentSince(
384 NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 4, 12).unwrap())
385 .unwrap(),
386 ),
387 b"SENTSINCE \"12-Apr-2023\"",
388 ),
389 (
390 SearchKey::Since(
391 NaiveDate::try_from(chrono::NaiveDate::from_ymd_opt(2023, 4, 12).unwrap())
392 .unwrap(),
393 ),
394 b"SINCE \"12-Apr-2023\"",
395 ),
396 (SearchKey::Smaller(1337), b"SMALLER 1337"),
397 (
398 SearchKey::Subject(AString::try_from("A").unwrap()),
399 b"SUBJECT A",
400 ),
401 (SearchKey::Text(AString::try_from("A").unwrap()), b"TEXT A"),
402 (SearchKey::To(AString::try_from("A").unwrap()), b"TO A"),
403 (
404 SearchKey::Uid(SequenceSet::try_from(Sequence::try_from(1..).unwrap()).unwrap()),
405 b"UID 1:*",
406 ),
407 (SearchKey::Unanswered, b"UNANSWERED"),
408 (SearchKey::Undeleted, b"UNDELETED"),
409 (SearchKey::Undraft, b"UNDRAFT"),
410 (SearchKey::Unflagged, b"UNFLAGGED"),
411 (
412 SearchKey::Unkeyword(Atom::try_from("A").unwrap()),
413 b"UNKEYWORD A",
414 ),
415 (SearchKey::Unseen, b"UNSEEN"),
416 ];
417
418 for test in tests {
419 known_answer_test_encode(test);
420 }
421 }
422}