pub mod composite;
pub mod discrete;
pub mod field;
use crate::mime::{AnyMIME, MIME};
use crate::part::{
composite::{message, multipart, Message, Multipart},
discrete::{Binary, Text},
};
use crate::print::{print_seq, Formatter, Print};
use crate::raw_input::RawInput;
#[cfg(feature = "tracing-unsupported")]
use crate::utils::bytes_to_trace_string;
#[cfg(feature = "arbitrary")]
use crate::{
arbitrary_utils::{arbitrary_shuffle, arbitrary_vec_where},
fuzz_eq::FuzzEq,
header, mime,
};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
use bounded_static::ToStatic;
use std::borrow::Cow;
#[cfg(feature = "tracing-unsupported")]
use tracing::warn;
#[derive(Clone, Debug, PartialEq, ToStatic)]
#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
pub struct AnyPart<'a> {
pub entries: Vec<field::EntityEntry<'a>>,
pub mime_body: MimeBody<'a>,
pub raw: RawInput<'a>,
pub raw_headers: RawInput<'a>,
}
impl<'a> AnyPart<'a> {
pub fn field_list(&self) -> Vec<field::EntityField<'a>> {
let mime = self.mime_body.mime();
let mut v = vec![];
for e in &self.entries {
let field = match e {
field::EntityEntry::MIME { e, raw_body } => {
field::EntityField::MIME {
f: mime.get_field(*e).unwrap(),
raw_body: raw_body.clone(),
}
}
field::EntityEntry::Unstructured(u) => field::EntityField::Unstructured(u.clone()),
};
v.push(field);
}
v
}
}
impl Default for AnyPart<'static> {
fn default() -> Self {
Self {
entries: vec![],
mime_body: MimeBody::Txt(discrete::Text {
mime: MIME {
ctype: Default::default(),
fields: Default::default(),
},
body: b"".into(),
raw_body: RawInput(Some(b"")),
}),
raw: RawInput(Some(b"")),
raw_headers: RawInput(Some(b"")),
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for AnyPart<'a> {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mime_body: MimeBody = u.arbitrary()?;
let mut entries: Vec<field::EntityEntry> = mime_body
.mime()
.field_entries()
.into_iter()
.map(|e| field::EntityEntry::MIME {
e,
raw_body: RawInput::none(),
})
.collect();
let unstr: Vec<header::Unstructured> =
arbitrary_vec_where(u, |f: &header::Unstructured| {
!mime::field::is_mime_header(&f.name)
})?;
entries.extend(unstr.into_iter().map(field::EntityEntry::Unstructured));
arbitrary_shuffle(u, &mut entries)?;
Ok(AnyPart {
entries,
mime_body,
raw: RawInput::none(),
raw_headers: RawInput::none(),
})
}
}
#[derive(Clone, Debug, PartialEq, ToStatic)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
pub enum MimeBody<'a> {
Mult(Multipart<'a>),
Msg(Message<'a>),
Txt(Text<'a>),
Bin(Binary<'a>),
}
impl<'a> MimeBody<'a> {
pub fn as_multipart(&self) -> Option<&Multipart<'a>> {
match self {
Self::Mult(x) => Some(x),
_ => None,
}
}
pub fn as_message(&self) -> Option<&Message<'a>> {
match self {
Self::Msg(x) => Some(x),
_ => None,
}
}
pub fn as_text(&self) -> Option<&Text<'a>> {
match self {
Self::Txt(x) => Some(x),
_ => None,
}
}
pub fn as_binary(&self) -> Option<&Binary<'a>> {
match self {
Self::Bin(x) => Some(x),
_ => None,
}
}
pub fn mime(&self) -> AnyMIME<'a> {
match self {
Self::Mult(v) => v.mime.clone().into(),
Self::Msg(v) => v.mime.clone().into(),
Self::Txt(v) => v.mime.clone().into(),
Self::Bin(v) => v.mime.clone().into(),
}
}
pub fn raw_body(&self) -> RawInput<'a> {
match self {
Self::Mult(v) => v.raw_body.clone(),
Self::Msg(v) => v.raw_body.clone(),
Self::Txt(v) => v.raw_body.clone(),
Self::Bin(v) => v.raw_body.clone(),
}
}
pub fn print_body(&self, fmt: &mut impl Formatter) {
match &self {
MimeBody::Mult(multipart) => {
for child in &multipart.children {
fmt.write_bytes(b"--");
fmt.write_current_boundary();
fmt.write_crlf();
child.print(fmt);
fmt.write_crlf();
}
fmt.write_bytes(b"--");
fmt.write_current_boundary();
fmt.write_bytes(b"--");
fmt.write_crlf();
fmt.pop_boundary();
}
MimeBody::Msg(message) => message.child.print(fmt),
MimeBody::Txt(text) => fmt.write_bytes(&text.body),
MimeBody::Bin(binary) => fmt.write_bytes(&binary.body),
}
}
}
impl<'a> From<Multipart<'a>> for MimeBody<'a> {
fn from(m: Multipart<'a>) -> Self {
Self::Mult(m)
}
}
impl<'a> From<Message<'a>> for MimeBody<'a> {
fn from(m: Message<'a>) -> Self {
Self::Msg(m)
}
}
impl<'a> Print for AnyPart<'a> {
fn print(&self, fmt: &mut impl Formatter) {
fmt.begin_line_folding();
print_seq(fmt, &self.field_list(), |_| ());
fmt.end_line_folding();
fmt.write_crlf();
self.mime_body.print_body(fmt);
}
}
pub fn part_body<'a>(m: AnyMIME<'a>) -> impl FnOnce(&'a [u8]) -> MimeBody<'a> {
move |input| {
let part = match m {
AnyMIME::Mult(a) => {
let (_rest, part) = multipart(a)(input);
#[cfg(feature = "tracing-unsupported")]
if !_rest.is_empty() {
warn!(rest = %bytes_to_trace_string(_rest),
"leftover input after multipart parsing")
}
part.into()
}
AnyMIME::Msg(a) => message(a)(input).into(),
AnyMIME::Txt(a) => MimeBody::Txt(Text {
mime: a,
body: Cow::Borrowed(input),
raw_body: input.into(),
}),
AnyMIME::Bin(a) => MimeBody::Bin(Binary {
mime: a,
body: Cow::Borrowed(input),
raw_body: input.into(),
}),
};
part
}
}