use std::borrow::Cow;
use abnf_core::streaming::SP;
use imap_types::{
body::{
BasicFields, Body, BodyStructure, MultiPartExtensionData, SinglePartExtensionData,
SpecificFields,
},
core::{IString, NString},
};
use nom::{
branch::alt,
bytes::streaming::{tag, tag_no_case},
combinator::{map, opt, recognize},
multi::{many0, many1, separated_list1},
sequence::{delimited, preceded, tuple},
IResult,
};
use crate::rfc3501::{
core::{nil, nstring, number, string},
envelope::envelope,
};
pub fn body(remaining_recursions: usize) -> impl Fn(&[u8]) -> IResult<&[u8], BodyStructure> {
move |input: &[u8]| body_limited(input, remaining_recursions)
}
fn body_limited<'a>(
input: &'a [u8],
remaining_recursions: usize,
) -> IResult<&'a [u8], BodyStructure> {
if remaining_recursions == 0 {
return Err(nom::Err::Failure(nom::error::make_error(
input,
nom::error::ErrorKind::TooLarge,
)));
}
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,
) -> IResult<&'a [u8], BodyStructure> {
if remaining_recursions == 0 {
return Err(nom::Err::Failure(nom::error::make_error(
input,
nom::error::ErrorKind::TooLarge,
)));
}
let body_type_msg =
move |input: &'a [u8]| body_type_msg_limited(input, remaining_recursions.saturating_sub(1));
let mut parser = tuple((
alt((body_type_msg, body_type_text, body_type_basic)),
opt(preceded(SP, body_ext_1part)),
));
let (remaining, ((basic, specific), maybe_extension)) = parser(input)?;
Ok((
remaining,
BodyStructure::Single {
body: Body { basic, specific },
extension: maybe_extension,
},
))
}
pub fn body_type_basic(input: &[u8]) -> IResult<&[u8], (BasicFields, SpecificFields)> {
let mut parser = tuple((media_basic, SP, body_fields));
let (remaining, ((type_, subtype), _, basic)) = parser(input)?;
Ok((remaining, (basic, SpecificFields::Basic { type_, subtype })))
}
fn body_type_msg_limited<'a>(
input: &'a [u8],
remaining_recursions: usize,
) -> IResult<&'a [u8], (BasicFields, SpecificFields)> {
if remaining_recursions == 0 {
return Err(nom::Err::Failure(nom::error::make_error(
input,
nom::error::ErrorKind::TooLarge,
)));
}
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,
body_structure: Box::new(body_structure),
number_of_lines,
},
),
))
}
pub fn body_type_text(input: &[u8]) -> IResult<&[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 fn body_fields(input: &[u8]) -> IResult<&[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 fn body_fld_param(input: &[u8]) -> IResult<&[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 fn body_fld_id(input: &[u8]) -> IResult<&[u8], NString> {
nstring(input)
}
#[inline]
pub fn body_fld_desc(input: &[u8]) -> IResult<&[u8], NString> {
nstring(input)
}
#[inline]
pub fn body_fld_enc(input: &[u8]) -> IResult<&[u8], IString> {
string(input)
}
#[inline]
pub fn body_fld_octets(input: &[u8]) -> IResult<&[u8], u32> {
number(input)
}
#[inline]
pub fn body_fld_lines(input: &[u8]) -> IResult<&[u8], u32> {
number(input)
}
pub fn body_ext_1part(input: &[u8]) -> IResult<&[u8], SinglePartExtensionData> {
let mut disposition = None;
let mut language = None;
let mut location = None;
let mut extension = Cow::Borrowed(&b""[..]);
let (mut rem, md5) = body_fld_md5(input)?;
let (rem_, dsp_) = opt(preceded(SP, body_fld_dsp))(rem)?;
if let Some(dsp_) = dsp_ {
rem = rem_;
disposition = Some(dsp_);
let (rem_, lang_) = opt(preceded(SP, body_fld_lang))(rem)?;
if let Some(lang_) = lang_ {
rem = rem_;
language = Some(lang_);
let (rem_, loc_) = opt(preceded(SP, body_fld_loc))(rem)?;
if let Some(loc_) = loc_ {
rem = rem_;
location = Some(loc_);
let (rem_, ext_) = recognize(many0(preceded(SP, body_extension(8))))(rem)?;
rem = rem_;
extension = Cow::Borrowed(ext_);
}
}
}
Ok((
rem,
SinglePartExtensionData {
md5,
disposition,
language,
location,
extension,
},
))
}
#[inline]
pub fn body_fld_md5(input: &[u8]) -> IResult<&[u8], NString> {
nstring(input)
}
pub fn body_fld_dsp(input: &[u8]) -> IResult<&[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 fn body_fld_lang(input: &[u8]) -> IResult<&[u8], Vec<IString>> {
alt((
map(nstring, |nstring| match nstring.inner {
Some(item) => vec![item],
None => vec![],
}),
delimited(tag(b"("), separated_list1(SP, string), tag(b")")),
))(input)
}
#[inline]
pub fn body_fld_loc(input: &[u8]) -> IResult<&[u8], NString> {
nstring(input)
}
pub fn body_extension(remaining_recursions: usize) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> {
move |input: &[u8]| body_extension_limited(input, remaining_recursions)
}
fn body_extension_limited<'a>(
input: &'a [u8],
remaining_recursion: usize,
) -> IResult<&'a [u8], &[u8]> {
if remaining_recursion == 0 {
return Err(nom::Err::Failure(nom::error::make_error(
input,
nom::error::ErrorKind::TooLarge,
)));
}
let body_extension =
move |input: &'a [u8]| body_extension_limited(input, remaining_recursion.saturating_sub(1));
alt((
recognize(nstring),
recognize(number),
recognize(delimited(
tag(b"("),
separated_list1(SP, body_extension),
tag(b")"),
)),
))(input)
}
fn body_type_mpart_limited(
input: &[u8],
remaining_recursion: usize,
) -> IResult<&[u8], BodyStructure> {
if remaining_recursion == 0 {
return Err(nom::Err::Failure(nom::error::make_error(
input,
nom::error::ErrorKind::TooLarge,
)));
}
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,
subtype,
extension_data,
},
))
}
pub fn body_ext_mpart(input: &[u8]) -> IResult<&[u8], MultiPartExtensionData> {
let mut disposition = None;
let mut language = None;
let mut location = None;
let mut extension = Cow::Borrowed(&b""[..]);
let (mut rem, parameter_list) = body_fld_param(input)?;
let (rem_, dsp_) = opt(preceded(SP, body_fld_dsp))(rem)?;
if let Some(dsp_) = dsp_ {
rem = rem_;
disposition = Some(dsp_);
let (rem_, lang_) = opt(preceded(SP, body_fld_lang))(rem)?;
if let Some(lang_) = lang_ {
rem = rem_;
language = Some(lang_);
let (rem_, loc_) = opt(preceded(SP, body_fld_loc))(rem)?;
if let Some(loc_) = loc_ {
rem = rem_;
location = Some(loc_);
let (rem_, ext_) = recognize(many0(preceded(SP, body_extension(8))))(rem)?;
rem = rem_;
extension = Cow::Borrowed(ext_);
}
}
}
Ok((
rem,
MultiPartExtensionData {
parameter_list,
disposition,
language,
location,
extension,
},
))
}
pub fn media_basic(input: &[u8]) -> IResult<&[u8], (IString, IString)> {
let mut parser = tuple((string, SP, media_subtype));
let (remaining, (type_, _, subtype)) = parser(input)?;
Ok((remaining, (type_, subtype)))
}
#[inline]
pub fn media_subtype(input: &[u8]) -> IResult<&[u8], IString> {
string(input)
}
#[inline]
pub fn media_message(input: &[u8]) -> IResult<&[u8], &[u8]> {
tag_no_case(b"\"MESSAGE\" \"RFC822\"")(input)
}
pub fn media_text(input: &[u8]) -> IResult<&[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 test {
use super::*;
#[test]
fn test_media_basic() {
media_basic(b"\"application\" \"xxx\"").unwrap();
media_basic(b"\"unknown\" \"test\"").unwrap();
media_basic(b"\"x\" \"xxx\"").unwrap();
}
#[test]
fn test_media_message() {
media_message(b"\"message\" \"rfc822\"").unwrap();
}
#[test]
fn test_media_text() {
media_text(b"\"text\" \"html\"").unwrap();
}
#[test]
fn test_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(),
]
.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_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(),
]
.iter()
{
let (rem, out) = body_ext_mpart(test).unwrap();
println!("{:?}", out);
assert_eq!(rem, b"|xxx");
}
}
}