use abnf_core::streaming::sp;
use imap_types::{
body::{
BasicFields, Body, BodyExtension, BodyStructure, Disposition, Language, Location,
MultiPartExtensionData, SinglePartExtensionData, SpecificFields,
},
core::{IString, NString, NonEmptyVec},
};
use nom::{
branch::alt,
bytes::streaming::{tag, tag_no_case},
combinator::{map, opt},
multi::{many0, many1, separated_list1},
sequence::{delimited, preceded, tuple},
};
use crate::{
core::{nil, nstring, number, string},
decode::{IMAPErrorKind, IMAPParseError, IMAPResult},
envelope::envelope,
};
pub(crate) fn body(
remaining_recursions: usize,
) -> impl Fn(&[u8]) -> IMAPResult<&[u8], BodyStructure> {
move |input: &[u8]| body_limited(input, remaining_recursions)
}
fn body_limited<'a>(
input: &'a [u8],
remaining_recursions: usize,
) -> IMAPResult<&'a [u8], BodyStructure> {
if remaining_recursions == 0 {
return Err(nom::Err::Failure(IMAPParseError {
input,
kind: IMAPErrorKind::RecursionLimitExceeded,
}));
}
let body_type_1part = move |input: &'a [u8]| {
body_type_1part_limited(input, remaining_recursions.saturating_sub(1))
};
let body_type_mpart = move |input: &'a [u8]| {
body_type_mpart_limited(input, remaining_recursions.saturating_sub(1))
};
delimited(
tag(b"("),
alt((body_type_1part, body_type_mpart)),
tag(b")"),
)(input)
}
fn body_type_1part_limited<'a>(
input: &'a [u8],
remaining_recursions: usize,
) -> IMAPResult<&'a [u8], BodyStructure> {
if remaining_recursions == 0 {
return Err(nom::Err::Failure(IMAPParseError {
input,
kind: IMAPErrorKind::RecursionLimitExceeded,
}));
}
let body_type_msg = move |input: &'a [u8]| body_type_msg_limited(input, 8);
let mut parser = tuple((
alt((body_type_msg, body_type_text, body_type_basic)),
opt(preceded(sp, body_ext_1part)),
));
let (remaining, ((basic, specific), extension_data)) = parser(input)?;
Ok((
remaining,
BodyStructure::Single {
body: Body { basic, specific },
extension_data,
},
))
}
pub(crate) fn body_type_basic(input: &[u8]) -> IMAPResult<&[u8], (BasicFields, SpecificFields)> {
let mut parser = tuple((media_basic, sp, body_fields));
let (remaining, ((type_, subtype), _, basic)) = parser(input)?;
Ok((
remaining,
(
basic,
SpecificFields::Basic {
r#type: type_,
subtype,
},
),
))
}
fn body_type_msg_limited<'a>(
input: &'a [u8],
remaining_recursions: usize,
) -> IMAPResult<&'a [u8], (BasicFields, SpecificFields)> {
if remaining_recursions == 0 {
return Err(nom::Err::Failure(IMAPParseError {
input,
kind: IMAPErrorKind::RecursionLimitExceeded,
}));
}
let body = move |input: &'a [u8]| body_limited(input, remaining_recursions.saturating_sub(1));
let mut parser = tuple((
media_message,
sp,
body_fields,
sp,
envelope,
sp,
body,
sp,
body_fld_lines,
));
let (remaining, (_, _, basic, _, envelope, _, body_structure, _, number_of_lines)) =
parser(input)?;
Ok((
remaining,
(
basic,
SpecificFields::Message {
envelope: Box::new(envelope),
body_structure: Box::new(body_structure),
number_of_lines,
},
),
))
}
pub(crate) fn body_type_text(input: &[u8]) -> IMAPResult<&[u8], (BasicFields, SpecificFields)> {
let mut parser = tuple((media_text, sp, body_fields, sp, body_fld_lines));
let (remaining, (subtype, _, basic, _, number_of_lines)) = parser(input)?;
Ok((
remaining,
(
basic,
SpecificFields::Text {
subtype,
number_of_lines,
},
),
))
}
pub(crate) fn body_fields(input: &[u8]) -> IMAPResult<&[u8], BasicFields> {
let mut parser = tuple((
body_fld_param,
sp,
body_fld_id,
sp,
body_fld_desc,
sp,
body_fld_enc,
sp,
body_fld_octets,
));
let (remaining, (parameter_list, _, id, _, description, _, content_transfer_encoding, _, size)) =
parser(input)?;
Ok((
remaining,
BasicFields {
parameter_list,
id,
description,
content_transfer_encoding,
size,
},
))
}
pub(crate) fn body_fld_param(input: &[u8]) -> IMAPResult<&[u8], Vec<(IString, IString)>> {
let mut parser = alt((
delimited(
tag(b"("),
separated_list1(
sp,
map(tuple((string, sp, string)), |(key, _, value)| (key, value)),
),
tag(b")"),
),
map(nil, |_| vec![]),
));
let (remaining, parsed_body_fld_param) = parser(input)?;
Ok((remaining, parsed_body_fld_param))
}
#[inline]
pub(crate) fn body_fld_id(input: &[u8]) -> IMAPResult<&[u8], NString> {
nstring(input)
}
#[inline]
pub(crate) fn body_fld_desc(input: &[u8]) -> IMAPResult<&[u8], NString> {
nstring(input)
}
#[inline]
pub(crate) fn body_fld_enc(input: &[u8]) -> IMAPResult<&[u8], IString> {
string(input)
}
#[inline]
#[allow(clippy::needless_return)]
pub(crate) fn body_fld_octets(input: &[u8]) -> IMAPResult<&[u8], u32> {
#[cfg(not(feature = "quirk_rectify_numbers"))]
return number(input);
#[cfg(feature = "quirk_rectify_numbers")]
{
return alt((
number,
map(tuple((tag("-"), number)), |(_, _)| {
log::warn!("Rectified negative number to 0");
0
}),
))(input);
}
}
#[inline]
pub(crate) fn body_fld_lines(input: &[u8]) -> IMAPResult<&[u8], u32> {
number(input)
}
pub(crate) fn body_ext_1part(input: &[u8]) -> IMAPResult<&[u8], SinglePartExtensionData> {
map(
tuple((
body_fld_md5,
opt(map(
tuple((
preceded(sp, body_fld_dsp),
opt(map(
tuple((
preceded(sp, body_fld_lang),
opt(map(
tuple((
preceded(sp, body_fld_loc),
many0(preceded(sp, body_extension(8))),
)),
|(location, extensions)| Location {
location,
extensions,
},
)),
)),
|(language, tail)| Language { language, tail },
)),
)),
|(disposition, tail)| Disposition { disposition, tail },
)),
)),
|(md5, tail)| SinglePartExtensionData { md5, tail },
)(input)
}
#[inline]
pub(crate) fn body_fld_md5(input: &[u8]) -> IMAPResult<&[u8], NString> {
nstring(input)
}
#[allow(clippy::type_complexity)]
pub(crate) fn body_fld_dsp(
input: &[u8],
) -> IMAPResult<&[u8], Option<(IString, Vec<(IString, IString)>)>> {
alt((
delimited(
tag(b"("),
map(
tuple((string, sp, body_fld_param)),
|(string, _, body_fld_param)| Some((string, body_fld_param)),
),
tag(b")"),
),
map(nil, |_| None),
))(input)
}
pub(crate) fn body_fld_lang(input: &[u8]) -> IMAPResult<&[u8], Vec<IString>> {
alt((
map(nstring, |nstring| match nstring.0 {
Some(item) => vec![item],
None => vec![],
}),
delimited(tag(b"("), separated_list1(sp, string), tag(b")")),
))(input)
}
#[inline]
pub(crate) fn body_fld_loc(input: &[u8]) -> IMAPResult<&[u8], NString> {
nstring(input)
}
pub(crate) fn body_extension(
remaining_recursions: usize,
) -> impl Fn(&[u8]) -> IMAPResult<&[u8], BodyExtension> {
move |input: &[u8]| body_extension_limited(input, remaining_recursions)
}
fn body_extension_limited<'a>(
input: &'a [u8],
remaining_recursion: usize,
) -> IMAPResult<&'a [u8], BodyExtension> {
if remaining_recursion == 0 {
return Err(nom::Err::Failure(IMAPParseError {
input,
kind: IMAPErrorKind::RecursionLimitExceeded,
}));
}
let body_extension =
move |input: &'a [u8]| body_extension_limited(input, remaining_recursion.saturating_sub(1));
alt((
map(nstring, BodyExtension::NString),
map(number, BodyExtension::Number),
map(
delimited(tag(b"("), separated_list1(sp, body_extension), tag(b")")),
|body_extensions| BodyExtension::List(NonEmptyVec::unvalidated(body_extensions)),
),
))(input)
}
fn body_type_mpart_limited(
input: &[u8],
remaining_recursion: usize,
) -> IMAPResult<&[u8], BodyStructure> {
if remaining_recursion == 0 {
return Err(nom::Err::Failure(IMAPParseError {
input,
kind: IMAPErrorKind::RecursionLimitExceeded,
}));
}
let mut parser = tuple((
many1(body(remaining_recursion)),
sp,
media_subtype,
opt(preceded(sp, body_ext_mpart)),
));
let (remaining, (bodies, _, subtype, extension_data)) = parser(input)?;
Ok((
remaining,
BodyStructure::Multi {
bodies: NonEmptyVec::try_from(bodies).unwrap(),
subtype,
extension_data,
},
))
}
pub(crate) fn body_ext_mpart(input: &[u8]) -> IMAPResult<&[u8], MultiPartExtensionData> {
map(
tuple((
body_fld_param,
opt(map(
tuple((
preceded(sp, body_fld_dsp),
opt(map(
tuple((
preceded(sp, body_fld_lang),
opt(map(
tuple((
preceded(sp, body_fld_loc),
many0(preceded(sp, body_extension(8))),
)),
|(location, extensions)| Location {
location,
extensions,
},
)),
)),
|(language, tail)| Language { language, tail },
)),
)),
|(disposition, tail)| Disposition { disposition, tail },
)),
)),
|(parameter_list, tail)| MultiPartExtensionData {
parameter_list,
tail,
},
)(input)
}
pub(crate) fn media_basic(input: &[u8]) -> IMAPResult<&[u8], (IString, IString)> {
let mut parser = tuple((string, sp, media_subtype));
let (remaining, (type_, _, subtype)) = parser(input)?;
Ok((remaining, (type_, subtype)))
}
#[inline]
pub(crate) fn media_subtype(input: &[u8]) -> IMAPResult<&[u8], IString> {
string(input)
}
#[inline]
pub(crate) fn media_message(input: &[u8]) -> IMAPResult<&[u8], &[u8]> {
tag_no_case(b"\"MESSAGE\" \"RFC822\"")(input)
}
pub(crate) fn media_text(input: &[u8]) -> IMAPResult<&[u8], IString> {
let mut parser = preceded(tag_no_case(b"\"TEXT\" "), media_subtype);
let (remaining, media_subtype) = parser(input)?;
Ok((remaining, media_subtype))
}
#[cfg(test)]
mod tests {
use std::num::NonZeroU32;
use imap_types::{
core::{Literal, Quoted},
fetch::MessageDataItem,
response::{Data, Response},
};
use super::*;
use crate::testing::{kat_inverse_response, known_answer_test_encode};
#[test]
fn test_parse_media_basic() {
media_basic(b"\"application\" \"xxx\"").unwrap();
media_basic(b"\"unknown\" \"test\"").unwrap();
media_basic(b"\"x\" \"xxx\"").unwrap();
}
#[test]
fn test_parse_media_message() {
media_message(b"\"message\" \"rfc822\"").unwrap();
}
#[test]
fn test_parse_media_text() {
media_text(b"\"text\" \"html\"").unwrap();
}
#[test]
fn test_parse_body_ext_1part() {
for test in [
b"nil|xxx".as_ref(),
b"\"md5\"|xxx".as_ref(),
b"\"md5\" nil|xxx".as_ref(),
b"\"md5\" (\"dsp\" nil)|xxx".as_ref(),
b"\"md5\" (\"dsp\" (\"key\" \"value\")) nil|xxx".as_ref(),
b"\"md5\" (\"dsp\" (\"key\" \"value\")) \"swedish\"|xxx".as_ref(),
b"\"md5\" (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\")|xxx".as_ref(),
b"\"md5\" (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") nil|xxx".as_ref(),
b"\"md5\" (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\"|xxx".as_ref(),
b"\"md5\" (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\" (1 \"2\" (nil 4))|xxx".as_ref(),
b"\"AABB\" NIL NIL NIL 1337|xxx",
b"\"AABB\" NIL NIL NIL (1337)|xxx",
b"\"AABB\" NIL NIL NIL (1337 1337)|xxx",
b"\"AABB\" NIL NIL NIL (1337 (1337 (1337 \"FOO\" {0}\r\n)))|xxx",
]
.iter()
{
let (rem, out) = body_ext_1part(test).unwrap();
println!("{:?}", out);
assert_eq!(rem, b"|xxx");
}
}
#[test]
fn test_body_rec() {
let _ = body(8)(str::repeat("(", 1_000_000).as_bytes());
}
#[test]
fn test_parse_body_ext_mpart() {
for test in [
b"nil|xxx".as_ref(),
b"(\"key\" \"value\")|xxx".as_ref(),
b"(\"key\" \"value\") nil|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" nil)|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) nil|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) \"swedish\"|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\")|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") nil|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\"|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\" (1 \"2\" (nil 4))|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\" (1 \"2\" (nil 4) {0}\r\n)|xxx".as_ref(),
b"(\"key\" \"value\") (\"dsp\" (\"key\" \"value\")) (\"german\" \"russian\") \"loc\" {0}\r\n {0}\r\n|xxx".as_ref(),
]
.iter()
{
let (rem, out) = body_ext_mpart(test).unwrap();
println!("{:?}", out);
assert_eq!(rem, b"|xxx");
}
}
#[test]
fn test_parse_body() {
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());
}
#[test]
fn test_kat_inverse_response_data() {
kat_inverse_response(&[(
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(),
b"".as_ref(),
Response::Data(Data::Fetch {
seq: NonZeroU32::try_from(3372220415).unwrap(),
items: NonEmptyVec::from(MessageDataItem::BodyStructure(
BodyStructure::Multi {
bodies: NonEmptyVec::from(BodyStructure::Multi {
bodies: NonEmptyVec::from(BodyStructure::Multi {
bodies: NonEmptyVec::from(BodyStructure::Multi {
bodies: NonEmptyVec::from(BodyStructure::Multi {
bodies: NonEmptyVec::from(BodyStructure::Multi {
bodies: NonEmptyVec::from(BodyStructure::Single {
body: Body {
basic: BasicFields {
parameter_list: vec![],
id: NString(None),
description: NString(None),
content_transfer_encoding: IString::from(
Literal::try_from(b"".as_ref())
.unwrap(),
),
size: 0,
},
specific: SpecificFields::Basic {
r#type: IString::from(
Literal::try_from(b"".as_ref())
.unwrap(),
),
subtype: IString::from(
Literal::try_from(b"".as_ref())
.unwrap(),
),
},
},
extension_data: Some(SinglePartExtensionData {
md5: NString::try_from("FOO").unwrap(),
tail: Some(Disposition{
disposition: None,
tail: Some(Language {
language: vec![],
tail: Some(Location{
location: NString::try_from("LOCATION").unwrap(),
extensions: vec![BodyExtension::Number(1337)],
})
})
})
}),
}),
subtype: IString::try_from("mixed").unwrap(),
extension_data: None,
}),
subtype: IString::try_from("mixed").unwrap(),
extension_data: None,
}),
subtype: IString::try_from("mixed").unwrap(),
extension_data: None,
}),
subtype: IString::try_from("mixed").unwrap(),
extension_data: None,
}),
subtype: IString::try_from("mixed").unwrap(),
extension_data: None,
}),
subtype: IString::try_from("mixed").unwrap(),
extension_data: None,
},
)),
}),
)]);
}
#[test]
fn test_encode_single_part_extension_data() {
let tests = [(
SinglePartExtensionData {
md5: NString(None),
tail: Some(Disposition {
disposition: None,
tail: Some(Language {
language: vec![],
tail: Some(Location {
location: NString::from(Quoted::try_from("").unwrap()),
extensions: vec![],
}),
}),
}),
},
b"NIL NIL NIL \"\"".as_ref(),
)];
for test in tests {
known_answer_test_encode(test);
}
}
#[test]
fn test_number_quirk() {
assert_eq!(body_fld_octets(b"0)").unwrap().1, 0);
assert_eq!(body_fld_octets(b"1)").unwrap().1, 1);
#[cfg(not(feature = "quirk_rectify_numbers"))]
{
assert!(dbg!(body_fld_octets(b"-0)")).is_err());
assert!(body_fld_octets(b"-1)").is_err());
assert!(body_fld_octets(b"-999999)").is_err());
}
#[cfg(feature = "quirk_rectify_numbers")]
{
assert_eq!(body_fld_octets(b"-0)").unwrap().1, 0);
assert_eq!(body_fld_octets(b"-1)").unwrap().1, 0);
assert_eq!(body_fld_octets(b"-999999)").unwrap().1, 0);
}
}
}