use super::encoded_words::decode_rfc2047;
use super::envelope_fetch::envelope;
#[allow(clippy::wildcard_imports)]
use super::primitives::*;
#[allow(clippy::wildcard_imports)]
use super::*;
use crate::types::rfc2231::decode_rfc2231_params;
const MAX_BODY_NESTING_DEPTH: u32 = 64;
pub(super) fn body_structure(
input: &[u8],
utf8_mode: bool,
depth: u32,
) -> IResult<&[u8], BodyStructure> {
if depth > MAX_BODY_NESTING_DEPTH {
return Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::TooLarge,
)));
}
let (input, _) = char('(')(input)?;
let (input, _) = take_while(|b: u8| b == b' ')(input)?;
if input.first() == Some(&b'(') {
body_type_mpart(input, utf8_mode, depth)
} else {
body_type_single(input, utf8_mode, depth)
}
}
fn body_type_single(input: &[u8], utf8_mode: bool, depth: u32) -> IResult<&[u8], BodyStructure> {
let (input, media_type_raw) = astring(input)?;
let media_type = String::from_utf8_lossy(&media_type_raw).to_ascii_lowercase();
let (input, _) = sp(input)?;
let (input, media_subtype_raw) = astring(input)?;
let media_subtype = String::from_utf8_lossy(&media_subtype_raw).to_ascii_lowercase();
let (input, _) = sp(input)?;
let (input, params) = body_params(input)?;
let (input, _) = sp(input)?;
let (input, id) = nstring_utf8(input)?;
let (input, _) = sp(input)?;
let (input, description_raw) = nstring(input)?;
let description = description_raw.map(|v| decode_rfc2047(&v));
let (input, _) = sp(input)?;
let (input, encoding_raw) = astring(input)?;
let encoding = String::from_utf8_lossy(&encoding_raw).to_ascii_lowercase();
let (input, _) = sp(input)?;
let (input, size) = number64(input)?;
if media_type == "text" {
let (input, _) = sp(input)?;
let (input, lines) = number64(input)?;
let (input, ext) = body_ext_1part(input)?;
let (input, _) = char(')')(input)?;
Ok((
input,
BodyStructure::Text {
media_subtype,
params,
id,
description,
encoding,
size,
lines,
md5: ext.0,
disposition: ext.1,
language: ext.2,
location: ext.3,
},
))
} else if media_type == "message" && (media_subtype == "rfc822" || media_subtype == "global") {
let (input, _) = sp(input)?;
let (input, env) = envelope(input, utf8_mode)?;
let (input, _) = sp(input)?;
let (input, body) = body_structure(input, utf8_mode, depth + 1)?;
let (input, _) = sp(input)?;
let (input, lines) = number64(input)?;
let (input, ext) = body_ext_1part(input)?;
let (input, _) = char(')')(input)?;
Ok((
input,
BodyStructure::Message {
media_subtype,
params,
id,
description,
encoding,
size,
envelope: Box::new(env),
body: Box::new(body),
lines,
md5: ext.0,
disposition: ext.1,
language: ext.2,
location: ext.3,
},
))
} else {
let (input, ext) = body_ext_1part(input)?;
let (input, _) = char(')')(input)?;
Ok((
input,
BodyStructure::Basic {
media_type,
media_subtype,
params,
id,
description,
encoding,
size,
md5: ext.0,
disposition: ext.1,
language: ext.2,
location: ext.3,
},
))
}
}
pub(super) fn body_type_mpart(
input: &[u8],
utf8_mode: bool,
depth: u32,
) -> IResult<&[u8], BodyStructure> {
let mut bodies = Vec::new();
let mut input = input;
while input.first() == Some(&b'(') {
let (rest, bs) = body_structure(input, utf8_mode, depth + 1)?;
bodies.push(bs);
input = rest;
let trimmed = input
.iter()
.position(|&b| b != b' ' && b != b'\t')
.map_or(&[][..], |pos| &input[pos..]);
if trimmed.first() == Some(&b'(') {
input = trimmed;
}
}
if bodies.is_empty() {
return Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::Many1,
)));
}
let (input, _) = take_while1(|b: u8| b == b' ' || b == b'\t')(input)?;
let (input, subtype_raw) = astring(input)?;
let media_subtype = String::from_utf8_lossy(&subtype_raw).to_ascii_lowercase();
let (input, ext) = body_ext_mpart(input)?;
let (input, _) = char(')')(input)?;
Ok((
input,
BodyStructure::Multipart {
media_subtype,
bodies,
params: ext.0,
disposition: ext.1,
language: ext.2,
location: ext.3,
},
))
}
pub(super) fn body_params(input: &[u8]) -> IResult<&[u8], Vec<(String, String)>> {
alt((
value(vec![], nil_token),
map(
delimited(
char('('),
separated_list0(
take_while1(|b: u8| b == b' '),
map(
tuple((
string_utf8,
preceded(take_while1(|b: u8| b == b' '), string_utf8),
)),
|(k, v)| {
(k.to_ascii_lowercase(), v)
},
),
),
char(')'),
),
|params| decode_rfc2231_params(¶ms),
),
))(input)
}
pub(super) fn body_disposition(input: &[u8]) -> IResult<&[u8], Option<ContentDisposition>> {
alt((
value(None, nil_token),
map(
delimited(
char('('),
tuple((string_utf8, preceded(sp, body_params))),
char(')'),
),
|(disposition_type, params)| {
Some(ContentDisposition {
disposition_type: disposition_type.to_ascii_lowercase(),
params,
})
},
),
))(input)
}
pub(super) fn body_language(input: &[u8]) -> IResult<&[u8], Option<Vec<String>>> {
alt((
value(None, nil_token),
map(
delimited(
char('('),
separated_list0(take_while1(|b: u8| b == b' '), string_utf8),
char(')'),
),
Some,
),
map(string_utf8, |s| Some(vec![s])),
))(input)
}
type BodyExtData = (
Option<String>,
Option<ContentDisposition>,
Option<Vec<String>>,
Option<String>,
);
type MpartExtData = (
Vec<(String, String)>,
Option<ContentDisposition>,
Option<Vec<String>>,
Option<String>,
);
fn at_body_ext_end(input: &[u8]) -> IResult<&[u8], bool> {
let (trimmed, _) = take_while(|b: u8| b == b' ')(input)?;
if trimmed.first() == Some(&b')') {
Ok((trimmed, true))
} else {
Ok((input, false))
}
}
fn body_ext_1part(input: &[u8]) -> IResult<&[u8], BodyExtData> {
let (input, at_end) = at_body_ext_end(input)?;
if at_end {
return Ok((input, (None, None, None, None)));
}
let (input, _) = sp(input)?;
let (input, md5) = nstring_utf8(input)?;
let (input, at_end) = at_body_ext_end(input)?;
if at_end {
return Ok((input, (md5, None, None, None)));
}
let (input, _) = sp(input)?;
let (input, disposition) = body_disposition(input)?;
let (input, at_end) = at_body_ext_end(input)?;
if at_end {
return Ok((input, (md5, disposition, None, None)));
}
let (input, _) = sp(input)?;
let (input, language) = body_language(input)?;
let (input, at_end) = at_body_ext_end(input)?;
if at_end {
return Ok((input, (md5, disposition, language, None)));
}
let (input, _) = sp(input)?;
let (input, location) = nstring_utf8(input)?;
let (input, ()) = skip_balanced_parens(input)?;
Ok((input, (md5, disposition, language, location)))
}
fn body_ext_mpart(input: &[u8]) -> IResult<&[u8], MpartExtData> {
let (input, at_end) = at_body_ext_end(input)?;
if at_end {
return Ok((input, (vec![], None, None, None)));
}
let (input, _) = sp(input)?;
let (input, params) = body_params(input)?;
let (input, at_end) = at_body_ext_end(input)?;
if at_end {
return Ok((input, (params, None, None, None)));
}
let (input, _) = sp(input)?;
let (input, disposition) = body_disposition(input)?;
let (input, at_end) = at_body_ext_end(input)?;
if at_end {
return Ok((input, (params, disposition, None, None)));
}
let (input, _) = sp(input)?;
let (input, language) = body_language(input)?;
let (input, at_end) = at_body_ext_end(input)?;
if at_end {
return Ok((input, (params, disposition, language, None)));
}
let (input, _) = sp(input)?;
let (input, location) = nstring_utf8(input)?;
let (input, ()) = skip_balanced_parens(input)?;
Ok((input, (params, disposition, language, location)))
}