report all parsed data
This commit is contained in:
parent
07f969d109
commit
6522d82e30
14 changed files with 233 additions and 124 deletions
|
@ -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."#;
|
||||
|
||||
// if you are only interested in email metadata/headers
|
||||
let (_, header) = eml_codec::imf(input).unwrap();
|
||||
let (_, imf) = eml_codec::imf(input).unwrap();
|
||||
println!(
|
||||
"{} just sent you an email with subject \"{}\"",
|
||||
header.imf.from[0].to_string(),
|
||||
header.imf.subject.unwrap().to_string(),
|
||||
imf.from[0].to_string(),
|
||||
imf.subject.unwrap().to_string(),
|
||||
);
|
||||
|
||||
// if you like to also parse the body/content
|
||||
|
|
|
@ -18,8 +18,8 @@ pub enum CompField<'a, T> {
|
|||
Bad(&'a [u8]),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Kv<'a>(&'a [u8], Unstructured<'a>);
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Kv<'a>(pub &'a [u8], pub Unstructured<'a>);
|
||||
|
||||
|
||||
pub fn header<'a, T>(
|
||||
|
|
|
@ -6,7 +6,7 @@ use nom::{
|
|||
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::datetime::section as date;
|
||||
use crate::imf::identification::{msg_id, msg_list, MessageID, MessageIDList};
|
||||
|
@ -80,18 +80,12 @@ pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
|
|||
)(input)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Header<'a> {
|
||||
pub imf: Imf<'a>,
|
||||
pub ext: Vec<header::Kv<'a>>,
|
||||
pub bad: Vec<&'a [u8]>,
|
||||
}
|
||||
|
||||
pub fn imf(input: &[u8]) -> IResult<&[u8], Header> {
|
||||
map(header(field), |(known, unknown, bad)| Header {
|
||||
imf: Imf::from_iter(known),
|
||||
ext: unknown,
|
||||
bad
|
||||
pub fn imf(input: &[u8]) -> IResult<&[u8], Imf> {
|
||||
map(header(field), |(known, unknown, bad)| {
|
||||
let mut imf = Imf::from_iter(known);
|
||||
imf.header_ext = unknown;
|
||||
imf.header_bad = bad;
|
||||
imf
|
||||
})(input)
|
||||
}
|
||||
|
||||
|
@ -117,7 +111,7 @@ between the header information and the body of the message.";
|
|||
imf(fullmail),
|
||||
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."[..],
|
||||
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()),
|
||||
from: vec![MailboxRef {
|
||||
name: None,
|
||||
|
@ -141,7 +135,7 @@ between the header information and the body of the message.";
|
|||
UnstrToken::Plain(&b"message"[..]),
|
||||
])),
|
||||
..Imf::default()
|
||||
}}
|
||||
}
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::imf::identification::MessageID;
|
|||
use crate::imf::mailbox::{AddrSpec, MailboxRef};
|
||||
use crate::imf::mime::Version;
|
||||
use crate::imf::trace::ReceivedLog;
|
||||
use crate::header;
|
||||
use crate::text::misc_token::{PhraseList, Unstructured};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
|
||||
|
@ -49,6 +50,19 @@ pub struct Imf<'a> {
|
|||
|
||||
// MIME
|
||||
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,
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -54,7 +54,7 @@ use nom::IResult;
|
|||
/// );
|
||||
/// ```
|
||||
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
|
||||
|
@ -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
|
||||
/// 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!(
|
||||
/// "{} just sent you an email with subject \"{}\"",
|
||||
/// header.imf.from[0].to_string(),
|
||||
/// header.imf.subject.unwrap().to_string(),
|
||||
/// imf.from[0].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)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ use nom::{
|
|||
use crate::header::{field_name};
|
||||
use crate::imf::identification::{msg_id, MessageID};
|
||||
use crate::mime::mechanism::{mechanism, Mechanism};
|
||||
use crate::mime::{AnyMIMEWithDefault, WithDefaultType};
|
||||
use crate::mime::r#type::{naive_type, NaiveType};
|
||||
use crate::text::misc_token::{unstructured, Unstructured};
|
||||
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> {
|
||||
list.into_iter().collect::<AnyMIMEWithDefault<T>>()
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn content(input: &[u8]) -> IResult<&[u8], Content> {
|
||||
terminated(
|
||||
|
|
107
src/mime/mod.rs
107
src/mime/mod.rs
|
@ -15,38 +15,38 @@ use std::marker::PhantomData;
|
|||
use crate::imf::identification::MessageID;
|
||||
use crate::mime::field::Content;
|
||||
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};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Multipart<'a>(pub ctype::Multipart, pub Generic<'a>);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
pub struct Message<'a>(pub ctype::Message, pub Generic<'a>);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
pub struct Text<'a>(pub ctype::Text, pub Generic<'a>);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Binary<'a>(pub ctype::Binary, pub Generic<'a>);
|
||||
pub struct MIME<'a, T> {
|
||||
pub interpreted: T,
|
||||
pub parsed: NaiveMIME<'a>
|
||||
}
|
||||
impl<'a> Default for MIME<'a, r#type::Text> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
interpreted: r#type::Text::default(),
|
||||
parsed: NaiveMIME::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
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)]
|
||||
pub enum AnyMIME<'a> {
|
||||
Mult(Multipart<'a>),
|
||||
Msg(Message<'a>),
|
||||
Txt(Text<'a>),
|
||||
Bin(Binary<'a>),
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
Mult(MIME<'a, r#type::Multipart>),
|
||||
Msg(MIME<'a, r#type::Message>),
|
||||
Txt(MIME<'a, r#type::Text>),
|
||||
Bin(MIME<'a, r#type::Binary>),
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct Generic<'a> {
|
||||
pub struct NaiveMIME<'a> {
|
||||
pub ctype: Option<NaiveType<'a>>,
|
||||
pub transfer_encoding: Mechanism<'a>,
|
||||
pub id: Option<MessageID<'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 {
|
||||
fn default_type() -> AnyType;
|
||||
}
|
||||
|
@ -82,21 +116,8 @@ impl WithDefaultType for WithDigestDefault {
|
|||
#[derive(Debug, PartialEq)]
|
||||
pub struct AnyMIMEWithDefault<'a, T: WithDefaultType>(pub AnyMIME<'a>, PhantomData<T>);
|
||||
|
||||
impl<'a, T: WithDefaultType> FromIterator<Content<'a>> for AnyMIMEWithDefault<'a, T> {
|
||||
fn from_iter<I: IntoIterator<Item = Content<'a>>>(it: I) -> Self {
|
||||
let (at, gen) = it.into_iter().fold(
|
||||
(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)
|
||||
impl<'a, T: WithDefaultType> Default for AnyMIMEWithDefault<'a, T> {
|
||||
fn default() -> Self {
|
||||
Self(T::default_type().to_mime(NaiveMIME::default()), PhantomData)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ use nom::{
|
|||
use crate::mime::charset::EmailCharset;
|
||||
use crate::text::misc_token::{mime_word, MIMEWord};
|
||||
use crate::text::words::mime_atom;
|
||||
use crate::mime::{AnyMIME, MIME, NaiveMIME};
|
||||
|
||||
// --------- NAIVE TYPE
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct NaiveType<'a> {
|
||||
pub main: &'a [u8],
|
||||
pub sub: &'a [u8],
|
||||
|
@ -29,7 +30,7 @@ pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> {
|
|||
)(input)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Parameter<'a> {
|
||||
pub name: &'a [u8],
|
||||
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)]
|
||||
pub struct Multipart {
|
||||
pub subtype: MultipartSubtype,
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::text::boundary::{boundary, Delimiter};
|
|||
//--- Multipart
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Multipart<'a> {
|
||||
pub interpreted: mime::Multipart<'a>,
|
||||
pub interpreted: mime::MIME<'a, mime::r#type::Multipart>,
|
||||
pub children: Vec<AnyPart<'a>>,
|
||||
pub preamble: &'a [u8],
|
||||
pub epilogue: &'a [u8],
|
||||
|
@ -22,12 +22,12 @@ impl<'a> 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>> {
|
||||
let m = m.clone();
|
||||
|
||||
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 mparts: Vec<AnyPart> = vec![];
|
||||
loop {
|
||||
|
@ -57,11 +57,16 @@ pub fn multipart<'a>(
|
|||
Ok((inp, Delimiter::Next)) => inp,
|
||||
};
|
||||
|
||||
// parse mime headers
|
||||
let (input, (known, unknown, bad)) = header(mime::field::content)(input)?;
|
||||
let mime = match m.0.subtype {
|
||||
mime::r#type::MultipartSubtype::Digest => mime::field::to_mime::<mime::WithDigestDefault>(known).into(),
|
||||
_ => mime::field::to_mime::<mime::WithGenericDefault>(known).into(),
|
||||
// parse mime headers, otherwise pick default mime
|
||||
let (input, naive_mime) = match header(mime::field::content)(input) {
|
||||
Ok((input, (known, unknown, bad))) => (input, known.into_iter().collect::<mime::NaiveMIME>().with_opt(unknown).with_bad(bad)),
|
||||
Err(_) => (input, mime::NaiveMIME::default()),
|
||||
};
|
||||
|
||||
// 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
|
||||
|
@ -70,6 +75,7 @@ pub fn multipart<'a>(
|
|||
// parse mime body
|
||||
mparts.push(part::to_anypart(mime, rpart));
|
||||
|
||||
|
||||
input_loop = input;
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +85,7 @@ pub fn multipart<'a>(
|
|||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Message<'a> {
|
||||
pub interpreted: mime::Message<'a>,
|
||||
pub interpreted: mime::MIME<'a, mime::r#type::Message>,
|
||||
pub imf: imf::Imf<'a>,
|
||||
pub child: Box<AnyPart<'a>>,
|
||||
pub epilogue: &'a [u8],
|
||||
|
@ -92,13 +98,23 @@ impl<'a> 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>> {
|
||||
move |input: &[u8]| {
|
||||
// parse header fields
|
||||
let (input, (known, unknown, bad)): (_, (Vec::<MixedField>, Vec<header::Kv>, Vec<&[u8]>)) =
|
||||
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);
|
||||
|
||||
Ok((
|
||||
|
@ -119,18 +135,19 @@ mod tests {
|
|||
use crate::part::discrete::Text;
|
||||
use crate::part::AnyPart;
|
||||
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};
|
||||
|
||||
#[test]
|
||||
fn test_multipart() {
|
||||
let base_mime = mime::Multipart(
|
||||
mime::r#type::Multipart {
|
||||
let base_mime = mime::MIME {
|
||||
interpreted: mime::r#type::Multipart {
|
||||
subtype: mime::r#type::MultipartSubtype::Alternative,
|
||||
boundary: "simple boundary".to_string(),
|
||||
},
|
||||
mime::Generic::default(),
|
||||
);
|
||||
parsed: mime::NaiveMIME::default(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
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""[..],
|
||||
children: vec![
|
||||
AnyPart::Txt(Text {
|
||||
interpreted: mime::Text(
|
||||
mime::r#type::Text {
|
||||
interpreted: mime::MIME {
|
||||
interpreted: mime::r#type::Text {
|
||||
subtype: mime::r#type::TextSubtype::Plain,
|
||||
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."[..],
|
||||
}),
|
||||
AnyPart::Txt(Text {
|
||||
interpreted: mime::Text(
|
||||
mime::r#type::Text {
|
||||
interpreted: mime::MIME {
|
||||
interpreted: mime::r#type::Text {
|
||||
subtype: mime::r#type::TextSubtype::Plain,
|
||||
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"[..],
|
||||
}),
|
||||
],
|
||||
|
@ -230,7 +259,7 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
|
|||
"#
|
||||
.as_bytes();
|
||||
|
||||
let base_mime = mime::Message::default();
|
||||
let base_mime = mime::MIME::<mime::r#type::Message>::default();
|
||||
assert_eq!(
|
||||
message(base_mime.clone())(fullmail),
|
||||
Ok((
|
||||
|
@ -301,40 +330,84 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO<br />
|
|||
right: &b"www.grrrndzero.org"[..],
|
||||
}),
|
||||
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()
|
||||
},
|
||||
child: Box::new(AnyPart::Mult(Multipart {
|
||||
interpreted: mime::Multipart(
|
||||
mime::r#type::Multipart {
|
||||
interpreted: mime::MIME {
|
||||
interpreted: mime::r#type::Multipart {
|
||||
subtype: mime::r#type::MultipartSubtype::Alternative,
|
||||
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"[..],
|
||||
epilogue: &b""[..],
|
||||
children: vec![
|
||||
AnyPart::Txt(Text {
|
||||
interpreted: mime::Text(
|
||||
mime::r#type::Text {
|
||||
interpreted: mime::MIME {
|
||||
interpreted: mime::r#type::Text {
|
||||
subtype: mime::r#type::TextSubtype::Plain,
|
||||
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,
|
||||
..mime::Generic::default()
|
||||
..mime::NaiveMIME::default()
|
||||
}
|
||||
),
|
||||
},
|
||||
body: &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..],
|
||||
}),
|
||||
AnyPart::Txt(Text {
|
||||
interpreted: mime::Text(
|
||||
mime::r#type::Text {
|
||||
interpreted: mime::MIME {
|
||||
interpreted: mime::r#type::Text {
|
||||
subtype: mime::r#type::TextSubtype::Html,
|
||||
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 />
|
||||
OoOoO<br />
|
||||
oOoOoOoOo<br />
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::mime;
|
|||
|
||||
#[derive(PartialEq)]
|
||||
pub struct Text<'a> {
|
||||
pub interpreted: mime::Text<'a>,
|
||||
pub interpreted: mime::MIME<'a, mime::r#type::Text>,
|
||||
pub body: &'a [u8],
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ impl<'a> fmt::Debug for Text<'a> {
|
|||
|
||||
#[derive(PartialEq)]
|
||||
pub struct Binary<'a> {
|
||||
pub interpreted: mime::Binary<'a>,
|
||||
pub interpreted: mime::MIME<'a, mime::r#type::Binary>,
|
||||
pub body: &'a [u8],
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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>();
|
||||
(mime.into(), imf)
|
||||
(mime, imf)
|
||||
}
|
||||
|
||||
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
|
||||
|
|
|
@ -65,13 +65,13 @@ pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> {
|
|||
AnyMIME::Mult(a) => multipart(a)(rpart)
|
||||
.map(|(rest, multi)| AnyPart::Mult(multi.with_epilogue(rest)))
|
||||
.unwrap_or(AnyPart::Txt(Text {
|
||||
interpreted: mime::Text::default(),
|
||||
interpreted: mime::MIME::<mime::r#type::Text>::default(),
|
||||
body: rpart,
|
||||
})),
|
||||
AnyMIME::Msg(a) => message(a)(rpart)
|
||||
.map(|(rest, msg)| AnyPart::Msg(msg.with_epilogue(rest)))
|
||||
.unwrap_or(AnyPart::Txt(Text {
|
||||
interpreted: mime::Text::default(),
|
||||
interpreted: mime::MIME::<mime::r#type::Text>::default(),
|
||||
body: rpart,
|
||||
})),
|
||||
AnyMIME::Txt(a) => AnyPart::Txt(Text {
|
||||
|
|
|
@ -23,7 +23,7 @@ pub fn phrase_list(input: &[u8]) -> IResult<&[u8], PhraseList> {
|
|||
map(separated_list1(tag(","), phrase), PhraseList)(input)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum MIMEWord<'a> {
|
||||
Quoted(QuotedString<'a>),
|
||||
Atom(&'a [u8]),
|
||||
|
|
|
@ -10,7 +10,7 @@ use nom::{
|
|||
use crate::text::ascii;
|
||||
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]>);
|
||||
|
||||
impl<'a> QuotedString<'a> {
|
||||
|
|
Loading…
Reference in a new issue