report all parsed data
This commit is contained in:
parent
07f969d109
commit
6ce280151b
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."#;
|
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
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
|
@ -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()
|
||||||
}}
|
}
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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> {
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
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::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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
transfer_encoding: mime::mechanism::Mechanism::QuotedPrintable,
|
ctype: Some(mime::r#type::NaiveType {
|
||||||
..mime::Generic::default()
|
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::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 />
|
||||||
|
|
|
@ -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],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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]),
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
Loading…
Reference in a new issue