report all parsed data

This commit is contained in:
Quentin 2023-07-24 22:08:13 +02:00
parent 07f969d109
commit 6522d82e30
Signed by: quentin
GPG key ID: E9602264D639FF68
14 changed files with 233 additions and 124 deletions

View file

@ -10,11 +10,11 @@ This is the plain text body of the message. Note the blank line
between the header information and the body of the message."#; between the header information and the body of the message."#;
// if you are only interested in email metadata/headers // if you are only interested in email metadata/headers
let (_, header) = eml_codec::imf(input).unwrap(); let (_, imf) = eml_codec::imf(input).unwrap();
println!( println!(
"{} just sent you an email with subject \"{}\"", "{} just sent you an email with subject \"{}\"",
header.imf.from[0].to_string(), imf.from[0].to_string(),
header.imf.subject.unwrap().to_string(), imf.subject.unwrap().to_string(),
); );
// if you like to also parse the body/content // if you like to also parse the body/content

View file

@ -18,8 +18,8 @@ pub enum CompField<'a, T> {
Bad(&'a [u8]), Bad(&'a [u8]),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct Kv<'a>(&'a [u8], Unstructured<'a>); pub struct Kv<'a>(pub &'a [u8], pub Unstructured<'a>);
pub fn header<'a, T>( pub fn header<'a, T>(

View file

@ -6,7 +6,7 @@ use nom::{
IResult, IResult,
}; };
use crate::header::{field_name, header, self}; use crate::header::{field_name, header};
use crate::imf::address::{address_list, mailbox_list, nullable_address_list, AddressList}; use crate::imf::address::{address_list, mailbox_list, nullable_address_list, AddressList};
use crate::imf::datetime::section as date; use crate::imf::datetime::section as date;
use crate::imf::identification::{msg_id, msg_list, MessageID, MessageIDList}; use crate::imf::identification::{msg_id, msg_list, MessageID, MessageIDList};
@ -80,18 +80,12 @@ pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
)(input) )(input)
} }
#[derive(Debug, PartialEq)] pub fn imf(input: &[u8]) -> IResult<&[u8], Imf> {
pub struct Header<'a> { map(header(field), |(known, unknown, bad)| {
pub imf: Imf<'a>, let mut imf = Imf::from_iter(known);
pub ext: Vec<header::Kv<'a>>, imf.header_ext = unknown;
pub bad: Vec<&'a [u8]>, imf.header_bad = bad;
} imf
pub fn imf(input: &[u8]) -> IResult<&[u8], Header> {
map(header(field), |(known, unknown, bad)| Header {
imf: Imf::from_iter(known),
ext: unknown,
bad
})(input) })(input)
} }
@ -117,7 +111,7 @@ between the header information and the body of the message.";
imf(fullmail), imf(fullmail),
Ok(( Ok((
&b"This is the plain text body of the message. Note the blank line\nbetween the header information and the body of the message."[..], &b"This is the plain text body of the message. Note the blank line\nbetween the header information and the body of the message."[..],
Header { bad: vec![], ext: vec![], imf: Imf { Imf {
date: Some(FixedOffset::east_opt(2 * 3600).unwrap().with_ymd_and_hms(2023, 3, 7, 8, 0, 0).unwrap()), date: Some(FixedOffset::east_opt(2 * 3600).unwrap().with_ymd_and_hms(2023, 3, 7, 8, 0, 0).unwrap()),
from: vec![MailboxRef { from: vec![MailboxRef {
name: None, name: None,
@ -141,7 +135,7 @@ between the header information and the body of the message.";
UnstrToken::Plain(&b"message"[..]), UnstrToken::Plain(&b"message"[..]),
])), ])),
..Imf::default() ..Imf::default()
}} }
)), )),
) )
} }

View file

@ -14,6 +14,7 @@ use crate::imf::identification::MessageID;
use crate::imf::mailbox::{AddrSpec, MailboxRef}; use crate::imf::mailbox::{AddrSpec, MailboxRef};
use crate::imf::mime::Version; use crate::imf::mime::Version;
use crate::imf::trace::ReceivedLog; use crate::imf::trace::ReceivedLog;
use crate::header;
use crate::text::misc_token::{PhraseList, Unstructured}; use crate::text::misc_token::{PhraseList, Unstructured};
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
@ -49,6 +50,19 @@ pub struct Imf<'a> {
// MIME // MIME
pub mime_version: Option<Version>, pub mime_version: Option<Version>,
// Junk
pub header_ext: Vec<header::Kv<'a>>,
pub header_bad: Vec<&'a [u8]>,
}
impl<'a> Imf<'a> {
pub fn with_opt(mut self, opt: Vec<header::Kv<'a>>) -> Self {
self.header_ext = opt; self
}
pub fn with_bad(mut self, bad: Vec<&'a [u8]>) -> Self {
self.header_bad = bad; self
}
} }
//@FIXME min and max limits are not enforced, //@FIXME min and max limits are not enforced,

View file

@ -54,7 +54,7 @@ use nom::IResult;
/// ); /// );
/// ``` /// ```
pub fn email(input: &[u8]) -> IResult<&[u8], part::composite::Message> { pub fn email(input: &[u8]) -> IResult<&[u8], part::composite::Message> {
part::composite::message(mime::Message::default())(input) part::composite::message(mime::MIME::<mime::r#type::Message>::default())(input)
} }
/// Only extract the headers of the email that are part of the Internet Message Format spec /// Only extract the headers of the email that are part of the Internet Message Format spec
@ -87,13 +87,13 @@ pub fn email(input: &[u8]) -> IResult<&[u8], part::composite::Message> {
/// This is the plain text body of the message. Note the blank line /// This is the plain text body of the message. Note the blank line
/// between the header information and the body of the message."#; /// between the header information and the body of the message."#;
/// ///
/// let (_, header) = eml_codec::imf(input).unwrap(); /// let (_, imf) = eml_codec::imf(input).unwrap();
/// println!( /// println!(
/// "{} just sent you an email with subject \"{}\"", /// "{} just sent you an email with subject \"{}\"",
/// header.imf.from[0].to_string(), /// imf.from[0].to_string(),
/// header.imf.subject.unwrap().to_string(), /// imf.subject.unwrap().to_string(),
/// ); /// );
/// ``` /// ```
pub fn imf(input: &[u8]) -> IResult<&[u8], imf::field::Header> { pub fn imf(input: &[u8]) -> IResult<&[u8], imf::Imf> {
imf::field::imf(input) imf::field::imf(input)
} }

View file

@ -8,7 +8,6 @@ use nom::{
use crate::header::{field_name}; use crate::header::{field_name};
use crate::imf::identification::{msg_id, MessageID}; use crate::imf::identification::{msg_id, MessageID};
use crate::mime::mechanism::{mechanism, Mechanism}; use crate::mime::mechanism::{mechanism, Mechanism};
use crate::mime::{AnyMIMEWithDefault, WithDefaultType};
use crate::mime::r#type::{naive_type, NaiveType}; use crate::mime::r#type::{naive_type, NaiveType};
use crate::text::misc_token::{unstructured, Unstructured}; use crate::text::misc_token::{unstructured, Unstructured};
use crate::text::whitespace::obs_crlf; use crate::text::whitespace::obs_crlf;
@ -48,14 +47,10 @@ impl<'a> Content<'a> {
} }
} }
/*impl<'a> CompFieldList<'a, Content<'a>> { /*
pub fn to_mime<T: WithDefaultType> (self) -> AnyMIMEWithDefault<'a, T> {
self.known().into_iter().collect::<AnyMIMEWithDefault<T>>()
}
}*/
pub fn to_mime<'a, T: WithDefaultType>(list: Vec<Content<'a>>) -> AnyMIMEWithDefault<'a, T> { pub fn to_mime<'a, T: WithDefaultType>(list: Vec<Content<'a>>) -> AnyMIMEWithDefault<'a, T> {
list.into_iter().collect::<AnyMIMEWithDefault<T>>() list.into_iter().collect::<AnyMIMEWithDefault<T>>()
} }*/
pub fn content(input: &[u8]) -> IResult<&[u8], Content> { pub fn content(input: &[u8]) -> IResult<&[u8], Content> {
terminated( terminated(

View file

@ -15,38 +15,38 @@ use std::marker::PhantomData;
use crate::imf::identification::MessageID; use crate::imf::identification::MessageID;
use crate::mime::field::Content; use crate::mime::field::Content;
use crate::mime::mechanism::Mechanism; use crate::mime::mechanism::Mechanism;
use crate::mime::r#type::{self as ctype, AnyType}; use crate::mime::r#type::{AnyType, NaiveType};
use crate::header;
use crate::text::misc_token::Unstructured; //Multipart, Message, Text, Binary}; use crate::text::misc_token::Unstructured; //Multipart, Message, Text, Binary};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct Multipart<'a>(pub ctype::Multipart, pub Generic<'a>); pub struct MIME<'a, T> {
pub interpreted: T,
#[derive(Debug, PartialEq, Clone, Default)] pub parsed: NaiveMIME<'a>
pub struct Message<'a>(pub ctype::Message, pub Generic<'a>); }
impl<'a> Default for MIME<'a, r#type::Text> {
#[derive(Debug, PartialEq, Clone, Default)] fn default() -> Self {
pub struct Text<'a>(pub ctype::Text, pub Generic<'a>); Self {
interpreted: r#type::Text::default(),
#[derive(Debug, PartialEq, Clone)] parsed: NaiveMIME::default(),
pub struct Binary<'a>(pub ctype::Binary, pub Generic<'a>); }
}
}
impl<'a> Default for MIME<'a, r#type::Message> {
fn default() -> Self {
Self {
interpreted: r#type::Message::default(),
parsed: NaiveMIME::default(),
}
}
}
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum AnyMIME<'a> { pub enum AnyMIME<'a> {
Mult(Multipart<'a>), Mult(MIME<'a, r#type::Multipart>),
Msg(Message<'a>), Msg(MIME<'a, r#type::Message>),
Txt(Text<'a>), Txt(MIME<'a, r#type::Text>),
Bin(Binary<'a>), Bin(MIME<'a, r#type::Binary>),
}
impl<'a> AnyMIME<'a> {
pub fn from_pair(at: AnyType, gen: Generic<'a>) -> Self {
match at {
AnyType::Multipart(m) => AnyMIME::Mult(Multipart(m, gen)),
AnyType::Message(m) => AnyMIME::Msg(Message(m, gen)),
AnyType::Text(m) => AnyMIME::Txt(Text(m, gen)),
AnyType::Binary(m) => AnyMIME::Bin(Binary(m, gen)),
}
}
} }
impl<'a, T: WithDefaultType> From<AnyMIMEWithDefault<'a, T>> for AnyMIME<'a> { impl<'a, T: WithDefaultType> From<AnyMIMEWithDefault<'a, T>> for AnyMIME<'a> {
@ -56,12 +56,46 @@ impl<'a, T: WithDefaultType> From<AnyMIMEWithDefault<'a, T>> for AnyMIME<'a> {
} }
#[derive(Debug, PartialEq, Default, Clone)] #[derive(Debug, PartialEq, Default, Clone)]
pub struct Generic<'a> { pub struct NaiveMIME<'a> {
pub ctype: Option<NaiveType<'a>>,
pub transfer_encoding: Mechanism<'a>, pub transfer_encoding: Mechanism<'a>,
pub id: Option<MessageID<'a>>, pub id: Option<MessageID<'a>>,
pub description: Option<Unstructured<'a>>, pub description: Option<Unstructured<'a>>,
pub header_ext: Vec<header::Kv<'a>>,
pub header_bad: Vec<&'a [u8]>,
} }
impl<'a> FromIterator<Content<'a>> for NaiveMIME<'a> {
fn from_iter<I: IntoIterator<Item = Content<'a>>>(it: I) -> Self {
it.into_iter().fold(
NaiveMIME::default(),
|mut section, field| {
match field {
Content::Type(v) => section.ctype = Some(v),
Content::TransferEncoding(v) => section.transfer_encoding = v,
Content::ID(v) => section.id = Some(v),
Content::Description(v) => section.description = Some(v),
};
section
},
)
}
}
impl<'a> NaiveMIME<'a> {
pub fn with_opt(mut self, opt: Vec<header::Kv<'a>>) -> Self {
self.header_ext = opt; self
}
pub fn with_bad(mut self, bad: Vec<&'a [u8]>) -> Self {
self.header_bad = bad; self
}
pub fn to_interpreted<T: WithDefaultType>(self) -> AnyMIME<'a> {
self.ctype.as_ref().map(|c| c.to_type()).unwrap_or(T::default_type()).to_mime(self).into()
}
}
pub trait WithDefaultType { pub trait WithDefaultType {
fn default_type() -> AnyType; fn default_type() -> AnyType;
} }
@ -82,21 +116,8 @@ impl WithDefaultType for WithDigestDefault {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct AnyMIMEWithDefault<'a, T: WithDefaultType>(pub AnyMIME<'a>, PhantomData<T>); pub struct AnyMIMEWithDefault<'a, T: WithDefaultType>(pub AnyMIME<'a>, PhantomData<T>);
impl<'a, T: WithDefaultType> FromIterator<Content<'a>> for AnyMIMEWithDefault<'a, T> { impl<'a, T: WithDefaultType> Default for AnyMIMEWithDefault<'a, T> {
fn from_iter<I: IntoIterator<Item = Content<'a>>>(it: I) -> Self { fn default() -> Self {
let (at, gen) = it.into_iter().fold( Self(T::default_type().to_mime(NaiveMIME::default()), PhantomData)
(T::default_type(), Generic::default()),
|(mut at, mut section), field| {
match field {
Content::Type(v) => at = v.to_type(),
Content::TransferEncoding(v) => section.transfer_encoding = v,
Content::ID(v) => section.id = Some(v),
Content::Description(v) => section.description = Some(v),
};
(at, section)
},
);
Self(AnyMIME::from_pair(at, gen), PhantomData)
} }
} }

View file

@ -9,9 +9,10 @@ use nom::{
use crate::mime::charset::EmailCharset; use crate::mime::charset::EmailCharset;
use crate::text::misc_token::{mime_word, MIMEWord}; use crate::text::misc_token::{mime_word, MIMEWord};
use crate::text::words::mime_atom; use crate::text::words::mime_atom;
use crate::mime::{AnyMIME, MIME, NaiveMIME};
// --------- NAIVE TYPE // --------- NAIVE TYPE
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct NaiveType<'a> { pub struct NaiveType<'a> {
pub main: &'a [u8], pub main: &'a [u8],
pub sub: &'a [u8], pub sub: &'a [u8],
@ -29,7 +30,7 @@ pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> {
)(input) )(input)
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct Parameter<'a> { pub struct Parameter<'a> {
pub name: &'a [u8], pub name: &'a [u8],
pub value: MIMEWord<'a>, pub value: MIMEWord<'a>,
@ -70,6 +71,17 @@ impl<'a> From<&'a NaiveType<'a>> for AnyType {
} }
} }
impl<'a> AnyType {
pub fn to_mime(self, parsed: NaiveMIME<'a>) -> AnyMIME<'a> {
match self {
Self::Multipart(interpreted) => AnyMIME::Mult(MIME::<Multipart> { interpreted, parsed }),
Self::Message(interpreted) => AnyMIME::Msg(MIME::<Message> { interpreted, parsed }),
Self::Text(interpreted) => AnyMIME::Txt(MIME::<Text> { interpreted, parsed }),
Self::Binary(interpreted) => AnyMIME::Bin(MIME::<Binary> { interpreted, parsed }),
}
}
}
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct Multipart { pub struct Multipart {
pub subtype: MultipartSubtype, pub subtype: MultipartSubtype,

View file

@ -9,7 +9,7 @@ use crate::text::boundary::{boundary, Delimiter};
//--- Multipart //--- Multipart
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Multipart<'a> { pub struct Multipart<'a> {
pub interpreted: mime::Multipart<'a>, pub interpreted: mime::MIME<'a, mime::r#type::Multipart>,
pub children: Vec<AnyPart<'a>>, pub children: Vec<AnyPart<'a>>,
pub preamble: &'a [u8], pub preamble: &'a [u8],
pub epilogue: &'a [u8], pub epilogue: &'a [u8],
@ -22,12 +22,12 @@ impl<'a> Multipart<'a> {
} }
pub fn multipart<'a>( pub fn multipart<'a>(
m: mime::Multipart<'a>, m: mime::MIME<'a, mime::r#type::Multipart>,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Multipart<'a>> { ) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Multipart<'a>> {
let m = m.clone(); let m = m.clone();
move |input| { move |input| {
let bound = m.0.boundary.as_bytes(); let bound = m.interpreted.boundary.as_bytes();
let (mut input_loop, preamble) = part::part_raw(bound)(input)?; let (mut input_loop, preamble) = part::part_raw(bound)(input)?;
let mut mparts: Vec<AnyPart> = vec![]; let mut mparts: Vec<AnyPart> = vec![];
loop { loop {
@ -57,11 +57,16 @@ pub fn multipart<'a>(
Ok((inp, Delimiter::Next)) => inp, Ok((inp, Delimiter::Next)) => inp,
}; };
// parse mime headers // parse mime headers, otherwise pick default mime
let (input, (known, unknown, bad)) = header(mime::field::content)(input)?; let (input, naive_mime) = match header(mime::field::content)(input) {
let mime = match m.0.subtype { Ok((input, (known, unknown, bad))) => (input, known.into_iter().collect::<mime::NaiveMIME>().with_opt(unknown).with_bad(bad)),
mime::r#type::MultipartSubtype::Digest => mime::field::to_mime::<mime::WithDigestDefault>(known).into(), Err(_) => (input, mime::NaiveMIME::default()),
_ => mime::field::to_mime::<mime::WithGenericDefault>(known).into(), };
// interpret mime according to context
let mime = match m.interpreted.subtype {
mime::r#type::MultipartSubtype::Digest => naive_mime.to_interpreted::<mime::WithDigestDefault>().into(),
_ => naive_mime.to_interpreted::<mime::WithGenericDefault>().into(),
}; };
// parse raw part // parse raw part
@ -70,6 +75,7 @@ pub fn multipart<'a>(
// parse mime body // parse mime body
mparts.push(part::to_anypart(mime, rpart)); mparts.push(part::to_anypart(mime, rpart));
input_loop = input; input_loop = input;
} }
} }
@ -79,7 +85,7 @@ pub fn multipart<'a>(
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Message<'a> { pub struct Message<'a> {
pub interpreted: mime::Message<'a>, pub interpreted: mime::MIME<'a, mime::r#type::Message>,
pub imf: imf::Imf<'a>, pub imf: imf::Imf<'a>,
pub child: Box<AnyPart<'a>>, pub child: Box<AnyPart<'a>>,
pub epilogue: &'a [u8], pub epilogue: &'a [u8],
@ -92,13 +98,23 @@ impl<'a> Message<'a> {
} }
pub fn message<'a>( pub fn message<'a>(
m: mime::Message<'a>, m: mime::MIME<'a, mime::r#type::Message>,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> { ) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
move |input: &[u8]| { move |input: &[u8]| {
// parse header fields
let (input, (known, unknown, bad)): (_, (Vec::<MixedField>, Vec<header::Kv>, Vec<&[u8]>)) = let (input, (known, unknown, bad)): (_, (Vec::<MixedField>, Vec<header::Kv>, Vec<&[u8]>)) =
header(part::field::mixed_field)(input)?; header(part::field::mixed_field)(input)?;
let (in_mime, imf) = part::field::sections::<mime::WithGenericDefault>(known);
// aggregate header fields
let (naive_mime, imf) = part::field::sections(known);
// attach bad headers to imf
let imf = imf.with_opt(unknown).with_bad(bad);
// interpret headers to choose a mime type
let in_mime = naive_mime.to_interpreted::<mime::WithGenericDefault>().into();
// parse this mimetype
let part = part::to_anypart(in_mime, input); let part = part::to_anypart(in_mime, input);
Ok(( Ok((
@ -119,18 +135,19 @@ mod tests {
use crate::part::discrete::Text; use crate::part::discrete::Text;
use crate::part::AnyPart; use crate::part::AnyPart;
use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord}; use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord};
use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word}; use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word, MIMEWord};
use crate::text::quoted::QuotedString;
use chrono::{FixedOffset, TimeZone}; use chrono::{FixedOffset, TimeZone};
#[test] #[test]
fn test_multipart() { fn test_multipart() {
let base_mime = mime::Multipart( let base_mime = mime::MIME {
mime::r#type::Multipart { interpreted: mime::r#type::Multipart {
subtype: mime::r#type::MultipartSubtype::Alternative, subtype: mime::r#type::MultipartSubtype::Alternative,
boundary: "simple boundary".to_string(), boundary: "simple boundary".to_string(),
}, },
mime::Generic::default(), parsed: mime::NaiveMIME::default(),
); };
assert_eq!( assert_eq!(
multipart(base_mime.clone())(b"This is the preamble. It is to be ignored, though it multipart(base_mime.clone())(b"This is the preamble. It is to be ignored, though it
@ -158,23 +175,35 @@ This is the epilogue. It is also to be ignored.
epilogue: &b""[..], epilogue: &b""[..],
children: vec![ children: vec![
AnyPart::Txt(Text { AnyPart::Txt(Text {
interpreted: mime::Text( interpreted: mime::MIME {
mime::r#type::Text { interpreted: mime::r#type::Text {
subtype: mime::r#type::TextSubtype::Plain, subtype: mime::r#type::TextSubtype::Plain,
charset: mime::charset::EmailCharset::US_ASCII, charset: mime::charset::EmailCharset::US_ASCII,
}, },
mime::Generic::default(), parsed: mime::NaiveMIME::default(),
), },
body: &b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..], body: &b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..],
}), }),
AnyPart::Txt(Text { AnyPart::Txt(Text {
interpreted: mime::Text( interpreted: mime::MIME {
mime::r#type::Text { interpreted: mime::r#type::Text {
subtype: mime::r#type::TextSubtype::Plain, subtype: mime::r#type::TextSubtype::Plain,
charset: mime::charset::EmailCharset::US_ASCII, charset: mime::charset::EmailCharset::US_ASCII,
}, },
mime::Generic::default(), parsed: mime::NaiveMIME {
), ctype: Some(mime::r#type::NaiveType {
main: &b"text"[..],
sub: &b"plain"[..],
params: vec![
mime::r#type::Parameter {
name: &b"charset"[..],
value: MIMEWord::Atom(&b"us-ascii"[..]),
}
]
}),
..mime::NaiveMIME::default()
},
},
body: &b"This is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..], body: &b"This is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..],
}), }),
], ],
@ -230,7 +259,7 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
"# "#
.as_bytes(); .as_bytes();
let base_mime = mime::Message::default(); let base_mime = mime::MIME::<mime::r#type::Message>::default();
assert_eq!( assert_eq!(
message(base_mime.clone())(fullmail), message(base_mime.clone())(fullmail),
Ok(( Ok((
@ -301,40 +330,84 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
right: &b"www.grrrndzero.org"[..], right: &b"www.grrrndzero.org"[..],
}), }),
mime_version: Some(imf::mime::Version { major: 1, minor: 0}), mime_version: Some(imf::mime::Version { major: 1, minor: 0}),
header_ext: vec![
header::Kv(&b"X-Unknown"[..], Unstructured(vec![
UnstrToken::Plain(&b"something"[..]),
UnstrToken::Plain(&b"something"[..]),
]))
],
header_bad: vec![
&b"Bad entry\n on multiple lines\n"[..],
],
..imf::Imf::default() ..imf::Imf::default()
}, },
child: Box::new(AnyPart::Mult(Multipart { child: Box::new(AnyPart::Mult(Multipart {
interpreted: mime::Multipart( interpreted: mime::MIME {
mime::r#type::Multipart { interpreted: mime::r#type::Multipart {
subtype: mime::r#type::MultipartSubtype::Alternative, subtype: mime::r#type::MultipartSubtype::Alternative,
boundary: "b1_e376dc71bafc953c0b0fdeb9983a9956".to_string(), boundary: "b1_e376dc71bafc953c0b0fdeb9983a9956".to_string(),
}, },
mime::Generic::default(), parsed: mime::NaiveMIME {
), ctype: Some(mime::r#type::NaiveType {
main: &b"multipart"[..],
sub: &b"alternative"[..],
params: vec![
mime::r#type::Parameter {
name: &b"boundary"[..],
value: MIMEWord::Quoted(QuotedString(vec![&b"b1_e376dc71bafc953c0b0fdeb9983a9956"[..]])),
}
]
}),
..mime::NaiveMIME::default()
},
},
preamble: &b"This is a multi-part message in MIME format.\n"[..], preamble: &b"This is a multi-part message in MIME format.\n"[..],
epilogue: &b""[..], epilogue: &b""[..],
children: vec![ children: vec![
AnyPart::Txt(Text { AnyPart::Txt(Text {
interpreted: mime::Text( interpreted: mime::MIME {
mime::r#type::Text { interpreted: mime::r#type::Text {
subtype: mime::r#type::TextSubtype::Plain, subtype: mime::r#type::TextSubtype::Plain,
charset: mime::charset::EmailCharset::UTF_8, charset: mime::charset::EmailCharset::UTF_8,
}, },
mime::Generic { parsed: mime::NaiveMIME {
ctype: Some(mime::r#type::NaiveType {
main: &b"text"[..],
sub: &b"plain"[..],
params: vec![
mime::r#type::Parameter {
name: &b"charset"[..],
value: MIMEWord::Atom(&b"utf-8"[..]),
}
]
}),
transfer_encoding: mime::mechanism::Mechanism::QuotedPrintable, transfer_encoding: mime::mechanism::Mechanism::QuotedPrintable,
..mime::Generic::default() ..mime::NaiveMIME::default()
} }
), },
body: &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..], body: &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..],
}), }),
AnyPart::Txt(Text { AnyPart::Txt(Text {
interpreted: mime::Text( interpreted: mime::MIME {
mime::r#type::Text { interpreted: mime::r#type::Text {
subtype: mime::r#type::TextSubtype::Html, subtype: mime::r#type::TextSubtype::Html,
charset: mime::charset::EmailCharset::US_ASCII, charset: mime::charset::EmailCharset::US_ASCII,
}, },
mime::Generic::default(),
), parsed: mime::NaiveMIME {
ctype: Some(mime::r#type::NaiveType {
main: &b"text"[..],
sub: &b"html"[..],
params: vec![
mime::r#type::Parameter {
name: &b"charset"[..],
value: MIMEWord::Atom(&b"us-ascii"[..]),
}
]
}),
..mime::NaiveMIME::default()
},
},
body: &br#"<div style="text-align: center;"><strong>GZ</strong><br /> body: &br#"<div style="text-align: center;"><strong>GZ</strong><br />
OoOoO<br /> OoOoO<br />
oOoOoOoOo<br /> oOoOoOoOo<br />

View file

@ -4,7 +4,7 @@ use crate::mime;
#[derive(PartialEq)] #[derive(PartialEq)]
pub struct Text<'a> { pub struct Text<'a> {
pub interpreted: mime::Text<'a>, pub interpreted: mime::MIME<'a, mime::r#type::Text>,
pub body: &'a [u8], pub body: &'a [u8],
} }
@ -22,7 +22,7 @@ impl<'a> fmt::Debug for Text<'a> {
#[derive(PartialEq)] #[derive(PartialEq)]
pub struct Binary<'a> { pub struct Binary<'a> {
pub interpreted: mime::Binary<'a>, pub interpreted: mime::MIME<'a, mime::r#type::Binary>,
pub body: &'a [u8], pub body: &'a [u8],
} }

View file

@ -35,11 +35,11 @@ impl<'a> MixedField<'a> {
} }
} }
pub fn sections<'a, T: mime::WithDefaultType>(list: Vec<MixedField<'a>>) -> (mime::AnyMIME<'a>, imf::Imf<'a>) { pub fn sections<'a>(list: Vec<MixedField<'a>>) -> (mime::NaiveMIME<'a>, imf::Imf<'a>) {
let (v1, v2): (Vec<MixedField>, Vec<_>) = list.into_iter().partition(|v| v.mime().is_some()); let (v1, v2): (Vec<MixedField>, Vec<_>) = list.into_iter().partition(|v| v.mime().is_some());
let mime = v1.into_iter().flat_map(MixedField::to_mime).collect::<mime::AnyMIMEWithDefault<T>>(); let mime = v1.into_iter().flat_map(MixedField::to_mime).collect::<mime::NaiveMIME>();
let imf = v2.into_iter().flat_map(MixedField::to_imf).collect::<imf::Imf>(); let imf = v2.into_iter().flat_map(MixedField::to_imf).collect::<imf::Imf>();
(mime.into(), imf) (mime, imf)
} }
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> { pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {

View file

@ -65,13 +65,13 @@ pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> {
AnyMIME::Mult(a) => multipart(a)(rpart) AnyMIME::Mult(a) => multipart(a)(rpart)
.map(|(rest, multi)| AnyPart::Mult(multi.with_epilogue(rest))) .map(|(rest, multi)| AnyPart::Mult(multi.with_epilogue(rest)))
.unwrap_or(AnyPart::Txt(Text { .unwrap_or(AnyPart::Txt(Text {
interpreted: mime::Text::default(), interpreted: mime::MIME::<mime::r#type::Text>::default(),
body: rpart, body: rpart,
})), })),
AnyMIME::Msg(a) => message(a)(rpart) AnyMIME::Msg(a) => message(a)(rpart)
.map(|(rest, msg)| AnyPart::Msg(msg.with_epilogue(rest))) .map(|(rest, msg)| AnyPart::Msg(msg.with_epilogue(rest)))
.unwrap_or(AnyPart::Txt(Text { .unwrap_or(AnyPart::Txt(Text {
interpreted: mime::Text::default(), interpreted: mime::MIME::<mime::r#type::Text>::default(),
body: rpart, body: rpart,
})), })),
AnyMIME::Txt(a) => AnyPart::Txt(Text { AnyMIME::Txt(a) => AnyPart::Txt(Text {

View file

@ -23,7 +23,7 @@ pub fn phrase_list(input: &[u8]) -> IResult<&[u8], PhraseList> {
map(separated_list1(tag(","), phrase), PhraseList)(input) map(separated_list1(tag(","), phrase), PhraseList)(input)
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub enum MIMEWord<'a> { pub enum MIMEWord<'a> {
Quoted(QuotedString<'a>), Quoted(QuotedString<'a>),
Atom(&'a [u8]), Atom(&'a [u8]),

View file

@ -10,7 +10,7 @@ use nom::{
use crate::text::ascii; use crate::text::ascii;
use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl}; use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl};
#[derive(Debug, PartialEq, Default)] #[derive(Debug, PartialEq, Default, Clone)]
pub struct QuotedString<'a>(pub Vec<&'a [u8]>); pub struct QuotedString<'a>(pub Vec<&'a [u8]>);
impl<'a> QuotedString<'a> { impl<'a> QuotedString<'a> {