Refactor how headers are handled
This commit is contained in:
parent
7db49875ea
commit
07f969d109
8 changed files with 65 additions and 71 deletions
|
@ -13,8 +13,8 @@ between the header information and the body of the message."#;
|
||||||
let (_, header) = eml_codec::imf(input).unwrap();
|
let (_, header) = eml_codec::imf(input).unwrap();
|
||||||
println!(
|
println!(
|
||||||
"{} just sent you an email with subject \"{}\"",
|
"{} just sent you an email with subject \"{}\"",
|
||||||
header.from[0].to_string(),
|
header.imf.from[0].to_string(),
|
||||||
header.subject.unwrap().to_string(),
|
header.imf.subject.unwrap().to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// if you like to also parse the body/content
|
// if you like to also parse the body/content
|
||||||
|
|
|
@ -6,7 +6,7 @@ use nom::{
|
||||||
bytes::complete::{tag, tag_no_case, take_while1},
|
bytes::complete::{tag, tag_no_case, take_while1},
|
||||||
character::complete::space0,
|
character::complete::space0,
|
||||||
combinator::map,
|
combinator::map,
|
||||||
multi::many0,
|
multi::{fold_many0},
|
||||||
sequence::{pair, terminated, tuple},
|
sequence::{pair, terminated, tuple},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
@ -14,38 +14,36 @@ use nom::{
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum CompField<'a, T> {
|
pub enum CompField<'a, T> {
|
||||||
Known(T),
|
Known(T),
|
||||||
Unknown(&'a [u8], Unstructured<'a>),
|
Unknown(Kv<'a>),
|
||||||
Bad(&'a [u8]),
|
Bad(&'a [u8]),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct CompFieldList<'a, T>(pub Vec<CompField<'a, T>>);
|
pub struct Kv<'a>(&'a [u8], Unstructured<'a>);
|
||||||
impl<'a, T> CompFieldList<'a, T> {
|
|
||||||
pub fn known(self) -> Vec<T> {
|
|
||||||
self.0
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|v| match v {
|
|
||||||
CompField::Known(f) => Some(f),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn header<'a, T>(
|
pub fn header<'a, T>(
|
||||||
fx: impl Fn(&'a [u8]) -> IResult<&'a [u8], T> + Copy,
|
fx: impl Fn(&'a [u8]) -> IResult<&'a [u8], T> + Copy,
|
||||||
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], CompFieldList<T>> {
|
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], (Vec::<T>, Vec::<Kv>, Vec<&'a [u8]>)> {
|
||||||
move |input| {
|
move |input| {
|
||||||
map(
|
|
||||||
terminated(
|
terminated(
|
||||||
many0(alt((
|
fold_many0(
|
||||||
|
alt((
|
||||||
map(fx, CompField::Known),
|
map(fx, CompField::Known),
|
||||||
map(opt_field, |(k, v)| CompField::Unknown(k, v)),
|
map(opt_field, |(k, v)| CompField::Unknown(Kv(k, v))),
|
||||||
map(foldable_line, CompField::Bad),
|
map(foldable_line, CompField::Bad),
|
||||||
))),
|
)),
|
||||||
obs_crlf,
|
|| (Vec::<T>::new(), Vec::<Kv>::new(), Vec::<&'a [u8]>::new()),
|
||||||
|
|(mut known, mut unknown, mut bad), item| {
|
||||||
|
match item {
|
||||||
|
CompField::Known(v) => known.push(v),
|
||||||
|
CompField::Unknown(v) => unknown.push(v),
|
||||||
|
CompField::Bad(v) => bad.push(v),
|
||||||
|
};
|
||||||
|
(known, unknown, bad)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
CompFieldList,
|
obs_crlf,
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::header::{field_name, header};
|
use crate::header::{field_name, header, self};
|
||||||
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};
|
||||||
|
@ -50,14 +50,6 @@ pub enum Field<'a> {
|
||||||
MIMEVersion(Version),
|
MIMEVersion(Version),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct FieldList<'a>(pub Vec<Field<'a>>);
|
|
||||||
impl<'a> FieldList<'a> {
|
|
||||||
pub fn imf(self) -> Imf<'a> {
|
|
||||||
Imf::from_iter(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
|
pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
|
||||||
terminated(
|
terminated(
|
||||||
alt((
|
alt((
|
||||||
|
@ -88,8 +80,19 @@ pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn imf(input: &[u8]) -> IResult<&[u8], Imf> {
|
#[derive(Debug, PartialEq)]
|
||||||
map(header(field), |v| FieldList(v.known()).imf())(input)
|
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
|
||||||
|
})(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -114,7 +117,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."[..],
|
||||||
Imf {
|
Header { bad: vec![], ext: vec![], 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,
|
||||||
|
@ -138,7 +141,7 @@ between the header information and the body of the message.";
|
||||||
UnstrToken::Plain(&b"message"[..]),
|
UnstrToken::Plain(&b"message"[..]),
|
||||||
])),
|
])),
|
||||||
..Imf::default()
|
..Imf::default()
|
||||||
}
|
}}
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,10 +90,10 @@ pub fn email(input: &[u8]) -> IResult<&[u8], part::composite::Message> {
|
||||||
/// let (_, header) = eml_codec::imf(input).unwrap();
|
/// let (_, header) = eml_codec::imf(input).unwrap();
|
||||||
/// println!(
|
/// println!(
|
||||||
/// "{} just sent you an email with subject \"{}\"",
|
/// "{} just sent you an email with subject \"{}\"",
|
||||||
/// header.from[0].to_string(),
|
/// header.imf.from[0].to_string(),
|
||||||
/// header.subject.unwrap().to_string(),
|
/// header.imf.subject.unwrap().to_string(),
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn imf(input: &[u8]) -> IResult<&[u8], imf::Imf> {
|
pub fn imf(input: &[u8]) -> IResult<&[u8], imf::field::Header> {
|
||||||
imf::field::imf(input)
|
imf::field::imf(input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::header::{field_name, CompFieldList};
|
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::{AnyMIMEWithDefault, WithDefaultType};
|
||||||
|
@ -48,10 +48,13 @@ impl<'a> Content<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompFieldList<'a, Content<'a>> {
|
/*impl<'a> CompFieldList<'a, Content<'a>> {
|
||||||
pub fn to_mime<T: WithDefaultType> (self) -> AnyMIMEWithDefault<'a, T> {
|
pub fn to_mime<T: WithDefaultType> (self) -> AnyMIMEWithDefault<'a, T> {
|
||||||
self.known().into_iter().collect::<AnyMIMEWithDefault<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> {
|
pub fn content(input: &[u8]) -> IResult<&[u8], Content> {
|
||||||
|
@ -75,7 +78,7 @@ pub fn content(input: &[u8]) -> IResult<&[u8], Content> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::header::{header, CompFieldList};
|
use crate::header::{header};
|
||||||
use crate::mime::charset::EmailCharset;
|
use crate::mime::charset::EmailCharset;
|
||||||
use crate::mime::r#type::*;
|
use crate::mime::r#type::*;
|
||||||
use crate::text::misc_token::MIMEWord;
|
use crate::text::misc_token::MIMEWord;
|
||||||
|
@ -118,7 +121,7 @@ This is a multipart message.
|
||||||
.as_bytes();
|
.as_bytes();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map(header(content), CompFieldList::known)(fullmail),
|
map(header(content), |(k, _, _)| k)(fullmail),
|
||||||
Ok((
|
Ok((
|
||||||
&b"This is a multipart message.\n\n"[..],
|
&b"This is a multipart message.\n\n"[..],
|
||||||
vec![
|
vec![
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
|
|
||||||
use crate::header::{header, CompFieldList};
|
use crate::header::{header, self};
|
||||||
use crate::imf::{self as imf};
|
use crate::imf;
|
||||||
use crate::mime;
|
use crate::mime;
|
||||||
use crate::part::{self, AnyPart};
|
use crate::part::{self, AnyPart, field::MixedField};
|
||||||
use crate::text::boundary::{boundary, Delimiter};
|
use crate::text::boundary::{boundary, Delimiter};
|
||||||
|
|
||||||
//--- Multipart
|
//--- Multipart
|
||||||
|
@ -58,10 +58,10 @@ pub fn multipart<'a>(
|
||||||
};
|
};
|
||||||
|
|
||||||
// parse mime headers
|
// parse mime headers
|
||||||
let (input, fields) = header(mime::field::content)(input)?;
|
let (input, (known, unknown, bad)) = header(mime::field::content)(input)?;
|
||||||
let mime = match m.0.subtype {
|
let mime = match m.0.subtype {
|
||||||
mime::r#type::MultipartSubtype::Digest => fields.to_mime::<mime::WithDigestDefault>().into(),
|
mime::r#type::MultipartSubtype::Digest => mime::field::to_mime::<mime::WithDigestDefault>(known).into(),
|
||||||
_ => fields.to_mime::<mime::WithGenericDefault>().into(),
|
_ => mime::field::to_mime::<mime::WithGenericDefault>(known).into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// parse raw part
|
// parse raw part
|
||||||
|
@ -95,9 +95,9 @@ pub fn message<'a>(
|
||||||
m: mime::Message<'a>,
|
m: mime::Message<'a>,
|
||||||
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
|
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
|
||||||
move |input: &[u8]| {
|
move |input: &[u8]| {
|
||||||
let (input, fields): (_, CompFieldList<part::field::MixedField>) =
|
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) = fields.sections::<mime::WithGenericDefault>();
|
let (in_mime, imf) = part::field::sections::<mime::WithGenericDefault>(known);
|
||||||
|
|
||||||
let part = part::to_anypart(in_mime, input);
|
let part = part::to_anypart(in_mime, input);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ use nom::{branch::alt, combinator::map, IResult};
|
||||||
|
|
||||||
use crate::imf;
|
use crate::imf;
|
||||||
use crate::mime;
|
use crate::mime;
|
||||||
use crate::part::CompFieldList;
|
|
||||||
|
|
||||||
pub enum MixedField<'a> {
|
pub enum MixedField<'a> {
|
||||||
MIME(mime::field::Content<'a>),
|
MIME(mime::field::Content<'a>),
|
||||||
|
@ -35,22 +34,14 @@ impl<'a> MixedField<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> CompFieldList<'a, MixedField<'a>> {
|
|
||||||
pub fn sections<T: mime::WithDefaultType>(self) -> (mime::AnyMIME<'a>, imf::Imf<'a>) {
|
pub fn sections<'a, T: mime::WithDefaultType>(list: Vec<MixedField<'a>>) -> (mime::AnyMIME<'a>, imf::Imf<'a>) {
|
||||||
let k = self.known();
|
let (v1, v2): (Vec<MixedField>, Vec<_>) = list.into_iter().partition(|v| v.mime().is_some());
|
||||||
let (v1, v2): (Vec<MixedField>, Vec<MixedField>) =
|
let mime = v1.into_iter().flat_map(MixedField::to_mime).collect::<mime::AnyMIMEWithDefault<T>>();
|
||||||
k.into_iter().partition(|v| v.mime().is_some());
|
let imf = v2.into_iter().flat_map(MixedField::to_imf).collect::<imf::Imf>();
|
||||||
let mime = v1
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|v| v.to_mime())
|
|
||||||
.collect::<mime::AnyMIMEWithDefault<T>>();
|
|
||||||
let imf = v2
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|v| v.to_imf())
|
|
||||||
.collect::<imf::Imf>();
|
|
||||||
(mime.into(), imf)
|
(mime.into(), imf)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
|
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
|
||||||
alt((
|
alt((
|
||||||
map(mime::field::content, MixedField::MIME),
|
map(mime::field::content, MixedField::MIME),
|
||||||
|
|
|
@ -16,7 +16,6 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::header::CompFieldList;
|
|
||||||
use crate::mime;
|
use crate::mime;
|
||||||
use crate::mime::AnyMIME;
|
use crate::mime::AnyMIME;
|
||||||
use crate::part::{
|
use crate::part::{
|
||||||
|
|
Loading…
Reference in a new issue