Refactor how headers are handled

This commit is contained in:
Quentin 2023-07-24 19:19:49 +02:00
parent 7db49875ea
commit 07f969d109
Signed by: quentin
GPG key ID: E9602264D639FF68
8 changed files with 65 additions and 71 deletions

View file

@ -13,8 +13,8 @@ between the header information and the body of the message."#;
let (_, header) = eml_codec::imf(input).unwrap();
println!(
"{} just sent you an email with subject \"{}\"",
header.from[0].to_string(),
header.subject.unwrap().to_string(),
header.imf.from[0].to_string(),
header.imf.subject.unwrap().to_string(),
);
// if you like to also parse the body/content

View file

@ -6,7 +6,7 @@ use nom::{
bytes::complete::{tag, tag_no_case, take_while1},
character::complete::space0,
combinator::map,
multi::many0,
multi::{fold_many0},
sequence::{pair, terminated, tuple},
IResult,
};
@ -14,38 +14,36 @@ use nom::{
#[derive(Debug, PartialEq)]
pub enum CompField<'a, T> {
Known(T),
Unknown(&'a [u8], Unstructured<'a>),
Unknown(Kv<'a>),
Bad(&'a [u8]),
}
#[derive(Debug, PartialEq)]
pub struct CompFieldList<'a, T>(pub Vec<CompField<'a, T>>);
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 struct Kv<'a>(&'a [u8], Unstructured<'a>);
pub fn header<'a, T>(
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| {
map(
terminated(
many0(alt((
fold_many0(
alt((
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),
))),
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)
}
}

View file

@ -6,7 +6,7 @@ use nom::{
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::datetime::section as date;
use crate::imf::identification::{msg_id, msg_list, MessageID, MessageIDList};
@ -50,14 +50,6 @@ pub enum Field<'a> {
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> {
terminated(
alt((
@ -88,8 +80,19 @@ pub fn field(input: &[u8]) -> IResult<&[u8], Field> {
)(input)
}
pub fn imf(input: &[u8]) -> IResult<&[u8], Imf> {
map(header(field), |v| FieldList(v.known()).imf())(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
})(input)
}
#[cfg(test)]
@ -114,7 +117,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."[..],
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()),
from: vec![MailboxRef {
name: None,
@ -138,7 +141,7 @@ between the header information and the body of the message.";
UnstrToken::Plain(&b"message"[..]),
])),
..Imf::default()
}
}}
)),
)
}

View file

@ -90,10 +90,10 @@ pub fn email(input: &[u8]) -> IResult<&[u8], part::composite::Message> {
/// let (_, header) = eml_codec::imf(input).unwrap();
/// println!(
/// "{} just sent you an email with subject \"{}\"",
/// header.from[0].to_string(),
/// header.subject.unwrap().to_string(),
/// header.imf.from[0].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)
}

View file

@ -5,7 +5,7 @@ use nom::{
IResult,
};
use crate::header::{field_name, CompFieldList};
use crate::header::{field_name};
use crate::imf::identification::{msg_id, MessageID};
use crate::mime::mechanism::{mechanism, Mechanism};
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> {
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> {
@ -75,7 +78,7 @@ pub fn content(input: &[u8]) -> IResult<&[u8], Content> {
#[cfg(test)]
mod tests {
use super::*;
use crate::header::{header, CompFieldList};
use crate::header::{header};
use crate::mime::charset::EmailCharset;
use crate::mime::r#type::*;
use crate::text::misc_token::MIMEWord;
@ -118,7 +121,7 @@ This is a multipart message.
.as_bytes();
assert_eq!(
map(header(content), CompFieldList::known)(fullmail),
map(header(content), |(k, _, _)| k)(fullmail),
Ok((
&b"This is a multipart message.\n\n"[..],
vec![

View file

@ -1,9 +1,9 @@
use nom::IResult;
use crate::header::{header, CompFieldList};
use crate::imf::{self as imf};
use crate::header::{header, self};
use crate::imf;
use crate::mime;
use crate::part::{self, AnyPart};
use crate::part::{self, AnyPart, field::MixedField};
use crate::text::boundary::{boundary, Delimiter};
//--- Multipart
@ -58,10 +58,10 @@ pub fn multipart<'a>(
};
// 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 {
mime::r#type::MultipartSubtype::Digest => fields.to_mime::<mime::WithDigestDefault>().into(),
_ => fields.to_mime::<mime::WithGenericDefault>().into(),
mime::r#type::MultipartSubtype::Digest => mime::field::to_mime::<mime::WithDigestDefault>(known).into(),
_ => mime::field::to_mime::<mime::WithGenericDefault>(known).into(),
};
// parse raw part
@ -95,9 +95,9 @@ pub fn message<'a>(
m: mime::Message<'a>,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> {
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)?;
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);

View file

@ -2,7 +2,6 @@ use nom::{branch::alt, combinator::map, IResult};
use crate::imf;
use crate::mime;
use crate::part::CompFieldList;
pub enum MixedField<'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>) {
let k = self.known();
let (v1, v2): (Vec<MixedField>, Vec<MixedField>) =
k.into_iter().partition(|v| v.mime().is_some());
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>();
pub fn sections<'a, T: mime::WithDefaultType>(list: Vec<MixedField<'a>>) -> (mime::AnyMIME<'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 imf = v2.into_iter().flat_map(MixedField::to_imf).collect::<imf::Imf>();
(mime.into(), imf)
}
}
pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> {
alt((
map(mime::field::content, MixedField::MIME),

View file

@ -16,7 +16,6 @@ use nom::{
IResult,
};
use crate::header::CompFieldList;
use crate::mime;
use crate::mime::AnyMIME;
use crate::part::{