1use abnf_core::streaming::sp;
2use imap_types::{
3 body::{
4 BasicFields, Body, BodyExtension, BodyStructure, Disposition, Language, Location,
5 MultiPartExtensionData, SinglePartExtensionData, SpecificFields,
6 },
7 core::{IString, NString, NonEmptyVec},
8};
9use nom::{
10 branch::alt,
11 bytes::streaming::{tag, tag_no_case},
12 combinator::{map, opt},
13 multi::{many0, many1, separated_list1},
14 sequence::{delimited, preceded, tuple},
15};
16
17use crate::{
18 core::{nil, nstring, number, string},
19 decode::{IMAPErrorKind, IMAPParseError, IMAPResult},
20 envelope::envelope,
21};
22
23pub(crate) fn body(
28 remaining_recursions: usize,
29) -> impl Fn(&[u8]) -> IMAPResult<&[u8], BodyStructure> {
30 move |input: &[u8]| body_limited(input, remaining_recursions)
31}
32
33fn body_limited<'a>(
34 input: &'a [u8],
35 remaining_recursions: usize,
36) -> IMAPResult<&'a [u8], BodyStructure> {
37 if remaining_recursions == 0 {
38 return Err(nom::Err::Failure(IMAPParseError {
39 input,
40 kind: IMAPErrorKind::RecursionLimitExceeded,
41 }));
42 }
43
44 let body_type_1part = move |input: &'a [u8]| {
45 body_type_1part_limited(input, remaining_recursions.saturating_sub(1))
46 };
47 let body_type_mpart = move |input: &'a [u8]| {
48 body_type_mpart_limited(input, remaining_recursions.saturating_sub(1))
49 };
50
51 delimited(
52 tag(b"("),
53 alt((body_type_1part, body_type_mpart)),
54 tag(b")"),
55 )(input)
56}
57
58fn body_type_1part_limited<'a>(
68 input: &'a [u8],
69 remaining_recursions: usize,
70) -> IMAPResult<&'a [u8], BodyStructure> {
71 if remaining_recursions == 0 {
72 return Err(nom::Err::Failure(IMAPParseError {
73 input,
74 kind: IMAPErrorKind::RecursionLimitExceeded,
75 }));
76 }
77
78 let body_type_msg = move |input: &'a [u8]| body_type_msg_limited(input, 8);
79
80 let mut parser = tuple((
81 alt((body_type_msg, body_type_text, body_type_basic)),
82 opt(preceded(sp, body_ext_1part)),
83 ));
84
85 let (remaining, ((basic, specific), extension_data)) = parser(input)?;
86
87 Ok((
88 remaining,
89 BodyStructure::Single {
90 body: Body { basic, specific },
91 extension_data,
92 },
93 ))
94}
95
96pub(crate) fn body_type_basic(input: &[u8]) -> IMAPResult<&[u8], (BasicFields, SpecificFields)> {
100 let mut parser = tuple((media_basic, sp, body_fields));
101
102 let (remaining, ((type_, subtype), _, basic)) = parser(input)?;
103
104 Ok((
105 remaining,
106 (
107 basic,
108 SpecificFields::Basic {
109 r#type: type_,
110 subtype,
111 },
112 ),
113 ))
114}
115
116fn body_type_msg_limited<'a>(
125 input: &'a [u8],
126 remaining_recursions: usize,
127) -> IMAPResult<&'a [u8], (BasicFields, SpecificFields)> {
128 if remaining_recursions == 0 {
129 return Err(nom::Err::Failure(IMAPParseError {
130 input,
131 kind: IMAPErrorKind::RecursionLimitExceeded,
132 }));
133 }
134
135 let body = move |input: &'a [u8]| body_limited(input, remaining_recursions.saturating_sub(1));
136
137 let mut parser = tuple((
138 media_message,
139 sp,
140 body_fields,
141 sp,
142 envelope,
143 sp,
144 body,
145 sp,
146 body_fld_lines,
147 ));
148
149 let (remaining, (_, _, basic, _, envelope, _, body_structure, _, number_of_lines)) =
150 parser(input)?;
151
152 Ok((
153 remaining,
154 (
155 basic,
156 SpecificFields::Message {
157 envelope: Box::new(envelope),
158 body_structure: Box::new(body_structure),
159 number_of_lines,
160 },
161 ),
162 ))
163}
164
165pub(crate) fn body_type_text(input: &[u8]) -> IMAPResult<&[u8], (BasicFields, SpecificFields)> {
169 let mut parser = tuple((media_text, sp, body_fields, sp, body_fld_lines));
170
171 let (remaining, (subtype, _, basic, _, number_of_lines)) = parser(input)?;
172
173 Ok((
174 remaining,
175 (
176 basic,
177 SpecificFields::Text {
178 subtype,
179 number_of_lines,
180 },
181 ),
182 ))
183}
184
185pub(crate) fn body_fields(input: &[u8]) -> IMAPResult<&[u8], BasicFields> {
191 let mut parser = tuple((
192 body_fld_param,
193 sp,
194 body_fld_id,
195 sp,
196 body_fld_desc,
197 sp,
198 body_fld_enc,
199 sp,
200 body_fld_octets,
201 ));
202
203 let (remaining, (parameter_list, _, id, _, description, _, content_transfer_encoding, _, size)) =
204 parser(input)?;
205
206 Ok((
207 remaining,
208 BasicFields {
209 parameter_list,
210 id,
211 description,
212 content_transfer_encoding,
213 size,
214 },
215 ))
216}
217
218pub(crate) fn body_fld_param(input: &[u8]) -> IMAPResult<&[u8], Vec<(IString, IString)>> {
223 let mut parser = alt((
224 delimited(
225 tag(b"("),
226 separated_list1(
227 sp,
228 map(tuple((string, sp, string)), |(key, _, value)| (key, value)),
229 ),
230 tag(b")"),
231 ),
232 map(nil, |_| vec![]),
233 ));
234
235 let (remaining, parsed_body_fld_param) = parser(input)?;
236
237 Ok((remaining, parsed_body_fld_param))
238}
239
240#[inline]
241pub(crate) fn body_fld_id(input: &[u8]) -> IMAPResult<&[u8], NString> {
243 nstring(input)
244}
245
246#[inline]
247pub(crate) fn body_fld_desc(input: &[u8]) -> IMAPResult<&[u8], NString> {
249 nstring(input)
250}
251
252#[inline]
253pub(crate) fn body_fld_enc(input: &[u8]) -> IMAPResult<&[u8], IString> {
269 string(input)
270}
271
272#[inline]
273#[allow(clippy::needless_return)]
281pub(crate) fn body_fld_octets(input: &[u8]) -> IMAPResult<&[u8], u32> {
282 #[cfg(not(feature = "quirk_rectify_numbers"))]
283 return number(input);
284
285 #[cfg(feature = "quirk_rectify_numbers")]
286 {
287 return alt((
288 number,
289 map(tuple((tag("-"), number)), |(_, _)| {
290 log::warn!("Rectified negative number to 0");
291 0
292 }),
293 ))(input);
294 }
295}
296
297#[inline]
298pub(crate) fn body_fld_lines(input: &[u8]) -> IMAPResult<&[u8], u32> {
300 number(input)
301}
302
303pub(crate) fn body_ext_1part(input: &[u8]) -> IMAPResult<&[u8], SinglePartExtensionData> {
314 map(
315 tuple((
316 body_fld_md5,
317 opt(map(
318 tuple((
319 preceded(sp, body_fld_dsp),
320 opt(map(
321 tuple((
322 preceded(sp, body_fld_lang),
323 opt(map(
324 tuple((
325 preceded(sp, body_fld_loc),
326 many0(preceded(sp, body_extension(8))),
327 )),
328 |(location, extensions)| Location {
329 location,
330 extensions,
331 },
332 )),
333 )),
334 |(language, tail)| Language { language, tail },
335 )),
336 )),
337 |(disposition, tail)| Disposition { disposition, tail },
338 )),
339 )),
340 |(md5, tail)| SinglePartExtensionData { md5, tail },
341 )(input)
342}
343
344#[inline]
345pub(crate) fn body_fld_md5(input: &[u8]) -> IMAPResult<&[u8], NString> {
347 nstring(input)
348}
349
350#[allow(clippy::type_complexity)]
352pub(crate) fn body_fld_dsp(
353 input: &[u8],
354) -> IMAPResult<&[u8], Option<(IString, Vec<(IString, IString)>)>> {
355 alt((
356 delimited(
357 tag(b"("),
358 map(
359 tuple((string, sp, body_fld_param)),
360 |(string, _, body_fld_param)| Some((string, body_fld_param)),
361 ),
362 tag(b")"),
363 ),
364 map(nil, |_| None),
365 ))(input)
366}
367
368pub(crate) fn body_fld_lang(input: &[u8]) -> IMAPResult<&[u8], Vec<IString>> {
370 alt((
371 map(nstring, |nstring| match nstring.0 {
372 Some(item) => vec![item],
373 None => vec![],
374 }),
375 delimited(tag(b"("), separated_list1(sp, string), tag(b")")),
376 ))(input)
377}
378
379#[inline]
380pub(crate) fn body_fld_loc(input: &[u8]) -> IMAPResult<&[u8], NString> {
382 nstring(input)
383}
384
385pub(crate) fn body_extension(
400 remaining_recursions: usize,
401) -> impl Fn(&[u8]) -> IMAPResult<&[u8], BodyExtension> {
402 move |input: &[u8]| body_extension_limited(input, remaining_recursions)
403}
404
405fn body_extension_limited<'a>(
406 input: &'a [u8],
407 remaining_recursion: usize,
408) -> IMAPResult<&'a [u8], BodyExtension> {
409 if remaining_recursion == 0 {
410 return Err(nom::Err::Failure(IMAPParseError {
411 input,
412 kind: IMAPErrorKind::RecursionLimitExceeded,
413 }));
414 }
415
416 let body_extension =
417 move |input: &'a [u8]| body_extension_limited(input, remaining_recursion.saturating_sub(1));
418
419 alt((
420 map(nstring, BodyExtension::NString),
421 map(number, BodyExtension::Number),
422 map(
423 delimited(tag(b"("), separated_list1(sp, body_extension), tag(b")")),
424 |body_extensions| BodyExtension::List(NonEmptyVec::unvalidated(body_extensions)),
425 ),
426 ))(input)
427}
428
429fn body_type_mpart_limited(
436 input: &[u8],
437 remaining_recursion: usize,
438) -> IMAPResult<&[u8], BodyStructure> {
439 if remaining_recursion == 0 {
440 return Err(nom::Err::Failure(IMAPParseError {
441 input,
442 kind: IMAPErrorKind::RecursionLimitExceeded,
443 }));
444 }
445
446 let mut parser = tuple((
447 many1(body(remaining_recursion)),
448 sp,
449 media_subtype,
450 opt(preceded(sp, body_ext_mpart)),
451 ));
452
453 let (remaining, (bodies, _, subtype, extension_data)) = parser(input)?;
454
455 Ok((
456 remaining,
457 BodyStructure::Multi {
458 bodies: NonEmptyVec::try_from(bodies).unwrap(),
460 subtype,
461 extension_data,
462 },
463 ))
464}
465
466pub(crate) fn body_ext_mpart(input: &[u8]) -> IMAPResult<&[u8], MultiPartExtensionData> {
477 map(
478 tuple((
479 body_fld_param,
480 opt(map(
481 tuple((
482 preceded(sp, body_fld_dsp),
483 opt(map(
484 tuple((
485 preceded(sp, body_fld_lang),
486 opt(map(
487 tuple((
488 preceded(sp, body_fld_loc),
489 many0(preceded(sp, body_extension(8))),
490 )),
491 |(location, extensions)| Location {
492 location,
493 extensions,
494 },
495 )),
496 )),
497 |(language, tail)| Language { language, tail },
498 )),
499 )),
500 |(disposition, tail)| Disposition { disposition, tail },
501 )),
502 )),
503 |(parameter_list, tail)| MultiPartExtensionData {
504 parameter_list,
505 tail,
506 },
507 )(input)
508}
509
510pub(crate) fn media_basic(input: &[u8]) -> IMAPResult<&[u8], (IString, IString)> {
532 let mut parser = tuple((string, sp, media_subtype));
533
534 let (remaining, (type_, _, subtype)) = parser(input)?;
535
536 Ok((remaining, (type_, subtype)))
537}
538
539#[inline]
540pub(crate) fn media_subtype(input: &[u8]) -> IMAPResult<&[u8], IString> {
544 string(input)
545}
546
547#[inline]
548pub(crate) fn media_message(input: &[u8]) -> IMAPResult<&[u8], &[u8]> {
559 tag_no_case(b"\"MESSAGE\" \"RFC822\"")(input)
560}
561
562pub(crate) fn media_text(input: &[u8]) -> IMAPResult<&[u8], IString> {
568 let mut parser = preceded(tag_no_case(b"\"TEXT\" "), media_subtype);
569
570 let (remaining, media_subtype) = parser(input)?;
571
572 Ok((remaining, media_subtype))
573}
574
575#[cfg(test)]
576mod tests {
577 use std::num::NonZeroU32;
578
579 use imap_types::{
580 core::{Literal, Quoted},
581 fetch::MessageDataItem,
582 response::{Data, Response},
583 };
584
585 use super::*;
586 use crate::testing::{kat_inverse_response, known_answer_test_encode};
587
588 #[test]
589 fn test_parse_media_basic() {
590 media_basic(b"\"application\" \"xxx\"").unwrap();
591 media_basic(b"\"unknown\" \"test\"").unwrap();
592 media_basic(b"\"x\" \"xxx\"").unwrap();
593 }
594
595 #[test]
596 fn test_parse_media_message() {
597 media_message(b"\"message\" \"rfc822\"").unwrap();
598 }
599
600 #[test]
601 fn test_parse_media_text() {
602 media_text(b"\"text\" \"html\"").unwrap();
603 }
604
605 #[test]
606 fn test_parse_body_ext_1part() {
607 for test in [
608 b"nil|xxx".as_ref(),
609 b"\"md5\"|xxx".as_ref(),
610 b"\"md5\" nil|xxx".as_ref(),
611 b"\"md5\" (\"dsp\" nil)|xxx".as_ref(),
612 b"\"md5\" (\"dsp\" (\"key\" \"value\")) nil|xxx".as_ref(),
613 b"\"md5\" (\"dsp\" (\"key\" \"value\")) \"swedish\"|xxx".as_ref(),
614 b"\"md5\" (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\")|xxx".as_ref(),
615 b"\"md5\" (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") nil|xxx".as_ref(),
616 b"\"md5\" (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\"|xxx".as_ref(),
617 b"\"md5\" (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\" (1 \"2\" (nil 4))|xxx".as_ref(),
618 b"\"AABB\" NIL NIL NIL 1337|xxx",
619 b"\"AABB\" NIL NIL NIL (1337)|xxx",
620 b"\"AABB\" NIL NIL NIL (1337 1337)|xxx",
621 b"\"AABB\" NIL NIL NIL (1337 (1337 (1337 \"FOO\" {0}\r\n)))|xxx",
622 ]
623 .iter()
624 {
625 let (rem, out) = body_ext_1part(test).unwrap();
626 println!("{:?}", out);
627 assert_eq!(rem, b"|xxx");
628 }
629 }
630
631 #[test]
632 fn test_body_rec() {
633 let _ = body(8)(str::repeat("(", 1_000_000).as_bytes());
634 }
635
636 #[test]
637 fn test_parse_body_ext_mpart() {
638 for test in [
639 b"nil|xxx".as_ref(),
640 b"(\"key\" \"value\")|xxx".as_ref(),
641 b"(\"key\" \"value\") nil|xxx".as_ref(),
642 b"(\"key\" \"value\") (\"dsp\" nil)|xxx".as_ref(),
643 b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) nil|xxx".as_ref(),
644 b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) \"swedish\"|xxx".as_ref(),
645 b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\")|xxx".as_ref(),
646 b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") nil|xxx".as_ref(),
647 b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\"|xxx".as_ref(),
648 b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\" (1 \"2\" (nil 4))|xxx".as_ref(),
649 b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\" (1 \"2\" (nil 4) {0}\r\n)|xxx".as_ref(),
650 b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\" {0}\r\n {0}\r\n|xxx".as_ref(),
651 ]
652 .iter()
653 {
654 let (rem, out) = body_ext_mpart(test).unwrap();
655 println!("{:?}", out);
656 assert_eq!(rem, b"|xxx");
657 }
658 }
659
660 #[test]
661 fn test_parse_body() {
662 dbg!(body(9)(b"((((((({0}\r\n {0}\r\n NIL NIL NIL {0}\r\n 0 \"FOO\" NIL NIL \"LOCATION\" 1337) \"mixed\") \"mixed\") \"mixed\") \"mixed\") \"mixed\") \"mixed\")|xxx").unwrap());
663 }
664
665 #[test]
666 fn test_kat_inverse_response_data() {
667 kat_inverse_response(&[(
668 b"* 3372220415 FETCH (BODYSTRUCTURE ((((((({0}\r\n {0}\r\n NIL NIL NIL {0}\r\n 0 \"FOO\" NIL NIL \"LOCATION\" 1337) \"mixed\") \"mixed\") \"mixed\") \"mixed\") \"mixed\") \"mixed\"))\r\n".as_ref(),
669 b"".as_ref(),
670 Response::Data(Data::Fetch {
671 seq: NonZeroU32::try_from(3372220415).unwrap(),
672 items: NonEmptyVec::from(MessageDataItem::BodyStructure(
673 BodyStructure::Multi {
674 bodies: NonEmptyVec::from(BodyStructure::Multi {
675 bodies: NonEmptyVec::from(BodyStructure::Multi {
676 bodies: NonEmptyVec::from(BodyStructure::Multi {
677 bodies: NonEmptyVec::from(BodyStructure::Multi {
678 bodies: NonEmptyVec::from(BodyStructure::Multi {
679 bodies: NonEmptyVec::from(BodyStructure::Single {
680 body: Body {
681 basic: BasicFields {
682 parameter_list: vec![],
683 id: NString(None),
684 description: NString(None),
685 content_transfer_encoding: IString::from(
686 Literal::try_from(b"".as_ref())
687 .unwrap(),
688 ),
689 size: 0,
690 },
691 specific: SpecificFields::Basic {
692 r#type: IString::from(
693 Literal::try_from(b"".as_ref())
694 .unwrap(),
695 ),
696 subtype: IString::from(
697 Literal::try_from(b"".as_ref())
698 .unwrap(),
699 ),
700 },
701 },
702 extension_data: Some(SinglePartExtensionData {
703 md5: NString::try_from("FOO").unwrap(),
704 tail: Some(Disposition{
705 disposition: None,
706 tail: Some(Language {
707 language: vec![],
708 tail: Some(Location{
709 location: NString::try_from("LOCATION").unwrap(),
710 extensions: vec![BodyExtension::Number(1337)],
711 })
712 })
713 })
714 }),
715 }),
716 subtype: IString::try_from("mixed").unwrap(),
717 extension_data: None,
718 }),
719 subtype: IString::try_from("mixed").unwrap(),
720 extension_data: None,
721 }),
722 subtype: IString::try_from("mixed").unwrap(),
723 extension_data: None,
724 }),
725 subtype: IString::try_from("mixed").unwrap(),
726 extension_data: None,
727 }),
728 subtype: IString::try_from("mixed").unwrap(),
729 extension_data: None,
730 }),
731 subtype: IString::try_from("mixed").unwrap(),
732 extension_data: None,
733 },
734 )),
735 }),
736 )]);
737 }
738
739 #[test]
740 fn test_encode_single_part_extension_data() {
741 let tests = [(
742 SinglePartExtensionData {
743 md5: NString(None),
744 tail: Some(Disposition {
745 disposition: None,
746 tail: Some(Language {
747 language: vec![],
748 tail: Some(Location {
749 location: NString::from(Quoted::try_from("").unwrap()),
750 extensions: vec![],
751 }),
752 }),
753 }),
754 },
755 b"NIL NIL NIL \"\"".as_ref(),
756 )];
757
758 for test in tests {
759 known_answer_test_encode(test);
760 }
761 }
762
763 #[test]
764 fn test_number_quirk() {
765 assert_eq!(body_fld_octets(b"0)").unwrap().1, 0);
766 assert_eq!(body_fld_octets(b"1)").unwrap().1, 1);
767
768 #[cfg(not(feature = "quirk_rectify_numbers"))]
769 {
770 assert!(dbg!(body_fld_octets(b"-0)")).is_err());
771 assert!(body_fld_octets(b"-1)").is_err());
772 assert!(body_fld_octets(b"-999999)").is_err());
773 }
774
775 #[cfg(feature = "quirk_rectify_numbers")]
776 {
777 assert_eq!(body_fld_octets(b"-0)").unwrap().1, 0);
778 assert_eq!(body_fld_octets(b"-1)").unwrap().1, 0);
779 assert_eq!(body_fld_octets(b"-999999)").unwrap().1, 0);
780 }
781 }
782}