1use std::num::NonZeroU32;
2
3use abnf_core::streaming::sp;
4use imap_types::{
5 core::{AString, NonEmptyVec},
6 fetch::{MessageDataItem, MessageDataItemName, Part, PartSpecifier, Section},
7};
8use nom::{
9 branch::alt,
10 bytes::streaming::{tag, tag_no_case},
11 combinator::{map, opt, value},
12 multi::separated_list1,
13 sequence::{delimited, tuple},
14};
15
16use crate::{
17 body::body,
18 core::{astring, nstring, number, nz_number},
19 datetime::date_time,
20 decode::IMAPResult,
21 envelope::envelope,
22 flag::flag_fetch,
23};
24
25pub(crate) fn fetch_att(input: &[u8]) -> IMAPResult<&[u8], MessageDataItemName> {
34 alt((
35 value(MessageDataItemName::Envelope, tag_no_case(b"ENVELOPE")),
36 value(MessageDataItemName::Flags, tag_no_case(b"FLAGS")),
37 value(
38 MessageDataItemName::InternalDate,
39 tag_no_case(b"INTERNALDATE"),
40 ),
41 value(
42 MessageDataItemName::BodyStructure,
43 tag_no_case(b"BODYSTRUCTURE"),
44 ),
45 map(
46 tuple((
47 tag_no_case(b"BODY.PEEK"),
48 section,
49 opt(delimited(
50 tag(b"<"),
51 tuple((number, tag(b"."), nz_number)),
52 tag(b">"),
53 )),
54 )),
55 |(_, section, byterange)| MessageDataItemName::BodyExt {
56 section,
57 partial: byterange.map(|(start, _, end)| (start, end)),
58 peek: true,
59 },
60 ),
61 map(
62 tuple((
63 tag_no_case(b"BODY"),
64 section,
65 opt(delimited(
66 tag(b"<"),
67 tuple((number, tag(b"."), nz_number)),
68 tag(b">"),
69 )),
70 )),
71 |(_, section, byterange)| MessageDataItemName::BodyExt {
72 section,
73 partial: byterange.map(|(start, _, end)| (start, end)),
74 peek: false,
75 },
76 ),
77 value(MessageDataItemName::Body, tag_no_case(b"BODY")),
78 value(MessageDataItemName::Uid, tag_no_case(b"UID")),
79 value(
80 MessageDataItemName::Rfc822Header,
81 tag_no_case(b"RFC822.HEADER"),
82 ),
83 value(MessageDataItemName::Rfc822Size, tag_no_case(b"RFC822.SIZE")),
84 value(MessageDataItemName::Rfc822Text, tag_no_case(b"RFC822.TEXT")),
85 value(MessageDataItemName::Rfc822, tag_no_case(b"RFC822")),
86 ))(input)
87}
88
89pub(crate) fn msg_att(input: &[u8]) -> IMAPResult<&[u8], NonEmptyVec<MessageDataItem>> {
93 delimited(
94 tag(b"("),
95 map(
96 separated_list1(sp, alt((msg_att_dynamic, msg_att_static))),
97 NonEmptyVec::unvalidated,
98 ),
99 tag(b")"),
100 )(input)
101}
102
103pub(crate) fn msg_att_dynamic(input: &[u8]) -> IMAPResult<&[u8], MessageDataItem> {
107 let mut parser = tuple((
108 tag_no_case(b"FLAGS"),
109 sp,
110 delimited(tag(b"("), opt(separated_list1(sp, flag_fetch)), tag(b")")),
111 ));
112
113 let (remaining, (_, _, flags)) = parser(input)?;
114
115 Ok((remaining, MessageDataItem::Flags(flags.unwrap_or_default())))
116}
117
118pub(crate) fn msg_att_static(input: &[u8]) -> IMAPResult<&[u8], MessageDataItem> {
128 alt((
129 map(
130 tuple((tag_no_case(b"ENVELOPE"), sp, envelope)),
131 |(_, _, envelope)| MessageDataItem::Envelope(envelope),
132 ),
133 map(
134 tuple((tag_no_case(b"INTERNALDATE"), sp, date_time)),
135 |(_, _, date_time)| MessageDataItem::InternalDate(date_time),
136 ),
137 map(
138 tuple((tag_no_case(b"RFC822.HEADER"), sp, nstring)),
139 |(_, _, nstring)| MessageDataItem::Rfc822Header(nstring),
140 ),
141 map(
142 tuple((tag_no_case(b"RFC822.TEXT"), sp, nstring)),
143 |(_, _, nstring)| MessageDataItem::Rfc822Text(nstring),
144 ),
145 map(
146 tuple((tag_no_case(b"RFC822.SIZE"), sp, number)),
147 |(_, _, num)| MessageDataItem::Rfc822Size(num),
148 ),
149 map(
150 tuple((tag_no_case(b"RFC822"), sp, nstring)),
151 |(_, _, nstring)| MessageDataItem::Rfc822(nstring),
152 ),
153 map(
154 tuple((tag_no_case(b"BODYSTRUCTURE"), sp, body(8))),
155 |(_, _, body)| MessageDataItem::BodyStructure(body),
156 ),
157 map(
158 tuple((tag_no_case(b"BODY"), sp, body(8))),
159 |(_, _, body)| MessageDataItem::Body(body),
160 ),
161 map(
162 tuple((
163 tag_no_case(b"BODY"),
164 section,
165 opt(delimited(tag(b"<"), number, tag(b">"))),
166 sp,
167 nstring,
168 )),
169 |(_, section, origin, _, data)| MessageDataItem::BodyExt {
170 section,
171 origin,
172 data,
173 },
174 ),
175 map(tuple((tag_no_case(b"UID"), sp, uniqueid)), |(_, _, uid)| {
176 MessageDataItem::Uid(uid)
177 }),
178 ))(input)
179}
180
181#[inline]
182pub(crate) fn uniqueid(input: &[u8]) -> IMAPResult<&[u8], NonZeroU32> {
186 nz_number(input)
187}
188
189pub(crate) fn section(input: &[u8]) -> IMAPResult<&[u8], Option<Section>> {
191 delimited(tag(b"["), opt(section_spec), tag(b"]"))(input)
192}
193
194pub(crate) fn section_spec(input: &[u8]) -> IMAPResult<&[u8], Section> {
196 alt((
197 map(section_msgtext, |part_specifier| match part_specifier {
198 PartSpecifier::PartNumber(_) => unreachable!(),
199 PartSpecifier::Header => Section::Header(None),
200 PartSpecifier::HeaderFields(fields) => Section::HeaderFields(None, fields),
201 PartSpecifier::HeaderFieldsNot(fields) => Section::HeaderFieldsNot(None, fields),
202 PartSpecifier::Text => Section::Text(None),
203 PartSpecifier::Mime => unreachable!(),
204 }),
205 map(
206 tuple((section_part, opt(tuple((tag(b"."), section_text))))),
207 |(part_number, maybe_part_specifier)| {
208 if let Some((_, part_specifier)) = maybe_part_specifier {
209 match part_specifier {
210 PartSpecifier::PartNumber(_) => unreachable!(),
211 PartSpecifier::Header => Section::Header(Some(Part(part_number))),
212 PartSpecifier::HeaderFields(fields) => {
213 Section::HeaderFields(Some(Part(part_number)), fields)
214 }
215 PartSpecifier::HeaderFieldsNot(fields) => {
216 Section::HeaderFieldsNot(Some(Part(part_number)), fields)
217 }
218 PartSpecifier::Text => Section::Text(Some(Part(part_number))),
219 PartSpecifier::Mime => Section::Mime(Part(part_number)),
220 }
221 } else {
222 Section::Part(Part(part_number))
223 }
224 },
225 ),
226 ))(input)
227}
228
229pub(crate) fn section_msgtext(input: &[u8]) -> IMAPResult<&[u8], PartSpecifier> {
233 alt((
234 map(
235 tuple((tag_no_case(b"HEADER.FIELDS.NOT"), sp, header_list)),
236 |(_, _, header_list)| PartSpecifier::HeaderFieldsNot(header_list),
237 ),
238 map(
239 tuple((tag_no_case(b"HEADER.FIELDS"), sp, header_list)),
240 |(_, _, header_list)| PartSpecifier::HeaderFields(header_list),
241 ),
242 value(PartSpecifier::Header, tag_no_case(b"HEADER")),
243 value(PartSpecifier::Text, tag_no_case(b"TEXT")),
244 ))(input)
245}
246
247#[inline]
248pub(crate) fn section_part(input: &[u8]) -> IMAPResult<&[u8], NonEmptyVec<NonZeroU32>> {
252 map(
253 separated_list1(tag(b"."), nz_number),
254 NonEmptyVec::unvalidated,
255 )(input)
256}
257
258pub(crate) fn section_text(input: &[u8]) -> IMAPResult<&[u8], PartSpecifier> {
262 alt((
263 section_msgtext,
264 value(PartSpecifier::Mime, tag_no_case(b"MIME")),
265 ))(input)
266}
267
268pub(crate) fn header_list(input: &[u8]) -> IMAPResult<&[u8], NonEmptyVec<AString>> {
270 map(
271 delimited(tag(b"("), separated_list1(sp, header_fld_name), tag(b")")),
272 NonEmptyVec::unvalidated,
273 )(input)
274}
275
276#[inline]
277pub(crate) fn header_fld_name(input: &[u8]) -> IMAPResult<&[u8], AString> {
279 astring(input)
280}
281
282#[cfg(test)]
283mod tests {
284 use imap_types::{
285 body::{BasicFields, Body, BodyStructure, SpecificFields},
286 core::{IString, NString},
287 datetime::DateTime,
288 envelope::Envelope,
289 };
290
291 use super::*;
292 use crate::testing::known_answer_test_encode;
293
294 #[test]
295 fn test_encode_message_data_item_name() {
296 let tests = [
297 (MessageDataItemName::Body, b"BODY".as_ref()),
298 (
299 MessageDataItemName::BodyExt {
300 section: None,
301 partial: None,
302 peek: false,
303 },
304 b"BODY[]",
305 ),
306 (MessageDataItemName::BodyStructure, b"BODYSTRUCTURE"),
307 (MessageDataItemName::Envelope, b"ENVELOPE"),
308 (MessageDataItemName::Flags, b"FLAGS"),
309 (MessageDataItemName::InternalDate, b"INTERNALDATE"),
310 (MessageDataItemName::Rfc822, b"RFC822"),
311 (MessageDataItemName::Rfc822Header, b"RFC822.HEADER"),
312 (MessageDataItemName::Rfc822Size, b"RFC822.SIZE"),
313 (MessageDataItemName::Rfc822Text, b"RFC822.TEXT"),
314 (MessageDataItemName::Uid, b"UID"),
315 ];
316
317 for test in tests {
318 known_answer_test_encode(test);
319 }
320 }
321
322 #[test]
323 fn test_encode_message_data_item() {
324 let tests = [
325 (
326 MessageDataItem::Body(BodyStructure::Single {
327 body: Body {
328 basic: BasicFields {
329 parameter_list: vec![],
330 id: NString(None),
331 description: NString(None),
332 content_transfer_encoding: IString::try_from("base64").unwrap(),
333 size: 42,
334 },
335 specific: SpecificFields::Text {
336 subtype: IString::try_from("foo").unwrap(),
337 number_of_lines: 1337,
338 },
339 },
340 extension_data: None,
341 }),
342 b"BODY (\"TEXT\" \"foo\" NIL NIL NIL \"base64\" 42 1337)".as_ref(),
343 ),
344 (
345 MessageDataItem::BodyExt {
346 section: None,
347 origin: None,
348 data: NString(None),
349 },
350 b"BODY[] NIL",
351 ),
352 (
353 MessageDataItem::BodyExt {
354 section: None,
355 origin: Some(123),
356 data: NString(None),
357 },
358 b"BODY[]<123> NIL",
359 ),
360 (
361 MessageDataItem::BodyStructure(BodyStructure::Single {
362 body: Body {
363 basic: BasicFields {
364 parameter_list: vec![],
365 id: NString(None),
366 description: NString(None),
367 content_transfer_encoding: IString::try_from("base64").unwrap(),
368 size: 213,
369 },
370 specific: SpecificFields::Text {
371 subtype: IString::try_from("").unwrap(),
372 number_of_lines: 224,
373 },
374 },
375 extension_data: None,
376 }),
377 b"BODYSTRUCTURE (\"TEXT\" \"\" NIL NIL NIL \"base64\" 213 224)",
378 ),
379 (
380 MessageDataItem::Envelope(Envelope {
381 date: NString(None),
382 subject: NString(None),
383 from: vec![],
384 sender: vec![],
385 reply_to: vec![],
386 to: vec![],
387 cc: vec![],
388 bcc: vec![],
389 in_reply_to: NString(None),
390 message_id: NString(None),
391 }),
392 b"ENVELOPE (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)",
393 ),
394 (MessageDataItem::Flags(vec![]), b"FLAGS ()"),
395 (
396 MessageDataItem::InternalDate(
397 DateTime::try_from(
398 chrono::DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")
399 .unwrap(),
400 )
401 .unwrap(),
402 ),
403 b"INTERNALDATE \"01-Jul-2003 10:52:37 +0200\"",
404 ),
405 (MessageDataItem::Rfc822(NString(None)), b"RFC822 NIL"),
406 (
407 MessageDataItem::Rfc822Header(NString(None)),
408 b"RFC822.HEADER NIL",
409 ),
410 (MessageDataItem::Rfc822Size(3456), b"RFC822.SIZE 3456"),
411 (
412 MessageDataItem::Rfc822Text(NString(None)),
413 b"RFC822.TEXT NIL",
414 ),
415 (
416 MessageDataItem::Uid(NonZeroU32::try_from(u32::MAX).unwrap()),
417 b"UID 4294967295",
418 ),
419 ];
420
421 for test in tests {
422 known_answer_test_encode(test);
423 }
424 }
425
426 #[test]
427 fn test_encode_section() {
428 let tests = [
429 (
430 Section::Part(Part(NonEmptyVec::from(NonZeroU32::try_from(1).unwrap()))),
431 b"1".as_ref(),
432 ),
433 (Section::Header(None), b"HEADER"),
434 (
435 Section::Header(Some(Part(NonEmptyVec::from(
436 NonZeroU32::try_from(1).unwrap(),
437 )))),
438 b"1.HEADER",
439 ),
440 (
441 Section::HeaderFields(None, NonEmptyVec::from(AString::try_from("").unwrap())),
442 b"HEADER.FIELDS (\"\")",
443 ),
444 (
445 Section::HeaderFields(
446 Some(Part(NonEmptyVec::from(NonZeroU32::try_from(1).unwrap()))),
447 NonEmptyVec::from(AString::try_from("").unwrap()),
448 ),
449 b"1.HEADER.FIELDS (\"\")",
450 ),
451 (
452 Section::HeaderFieldsNot(None, NonEmptyVec::from(AString::try_from("").unwrap())),
453 b"HEADER.FIELDS.NOT (\"\")",
454 ),
455 (
456 Section::HeaderFieldsNot(
457 Some(Part(NonEmptyVec::from(NonZeroU32::try_from(1).unwrap()))),
458 NonEmptyVec::from(AString::try_from("").unwrap()),
459 ),
460 b"1.HEADER.FIELDS.NOT (\"\")",
461 ),
462 (Section::Text(None), b"TEXT"),
463 (
464 Section::Text(Some(Part(NonEmptyVec::from(
465 NonZeroU32::try_from(1).unwrap(),
466 )))),
467 b"1.TEXT",
468 ),
469 (
470 Section::Mime(Part(NonEmptyVec::from(NonZeroU32::try_from(1).unwrap()))),
471 b"1.MIME",
472 ),
473 ];
474
475 for test in tests {
476 known_answer_test_encode(test)
477 }
478 }
479}