From 59f550b439b5e244dfc06d31911ba1fd4a4d8fe1 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Sat, 22 Jul 2023 13:51:19 +0200 Subject: [PATCH] generic header support --- src/header.rs | 29 +++++++++++++++++++------- src/mime/field.rs | 49 +++++++++++++++++++++++++++++++++++++++++--- src/mime/type.rs | 10 ++++----- src/rfc5322/field.rs | 13 ++---------- 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/header.rs b/src/header.rs index 7ae4841..9836f0d 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,9 +1,13 @@ use nom::{ IResult, + branch::alt, bytes::complete::{tag_no_case, tag, take_while1}, character::complete::space0, + combinator::map, + multi::many0, sequence::{pair, terminated, tuple}, }; +use crate::text::whitespace::{foldable_line, obs_crlf}; use crate::text::misc_token::{Unstructured, unstructured}; #[derive(Debug, PartialEq)] @@ -24,6 +28,16 @@ impl<'a, T> CompFieldList<'a, T> { } } +pub fn header<'a, T>(fx: impl Fn(&'a [u8]) -> IResult<&[u8], T> + Copy) + -> impl Fn(&'a [u8]) -> IResult<&[u8], CompFieldList> +{ + move |input| map(terminated(many0(alt(( + map(fx, CompField::Known), + map(opt_field, |(k,v)| CompField::Unknown(k,v)), + map(foldable_line, CompField::Bad), + ))), obs_crlf), CompFieldList)(input) +} + pub fn field_name<'a>(name: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> { move |input| { terminated( @@ -43,13 +57,14 @@ pub fn field_name<'a>(name: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [ /// ; ":". /// ``` pub fn opt_field(input: &[u8]) -> IResult<&[u8], (&[u8], Unstructured)> { - pair( - terminated( - take_while1(|c| c >= 0x21 && c <= 0x7E && c != 0x3A), - tuple((space0, tag(b":"), space0)), - ), - unstructured, - )(input) + terminated( + pair( + terminated( + take_while1(|c| c >= 0x21 && c <= 0x7E && c != 0x3A), + tuple((space0, tag(b":"), space0)), + ), + unstructured, + ), obs_crlf)(input) } diff --git a/src/mime/field.rs b/src/mime/field.rs index 9d39ef3..4be6223 100644 --- a/src/mime/field.rs +++ b/src/mime/field.rs @@ -8,9 +8,10 @@ use nom::{ use crate::text::whitespace::obs_crlf; use crate::text::misc_token::{Unstructured, unstructured}; use crate::rfc5322::identification::{MessageID, msg_id}; -use crate::header::field_name; +use crate::header::{field_name, header, CompFieldList}; use crate::mime::r#type::{NaiveType, naive_type}; use crate::mime::mechanism::{Mechanism, mechanism}; +//use crate::mime::mime::MIME; #[derive(Debug, PartialEq)] pub enum Content<'a> { @@ -19,8 +20,11 @@ pub enum Content<'a> { ID(MessageID<'a>), Description(Unstructured<'a>), } +/*impl<'a> CompFieldList> { + pub fn to_mime(&self) -> MIME { self.into() } +}*/ -fn field(input: &[u8]) -> IResult<&[u8], Content> { +fn content(input: &[u8]) -> IResult<&[u8], Content> { terminated(alt(( preceded(field_name(b"content-type"), map(naive_type, Content::Type)), preceded(field_name(b"content-transfer-encoding"), map(mechanism, Content::TransferEncoding)), @@ -34,10 +38,12 @@ mod tests { use super::*; use crate::mime::r#type::*; use crate::mime::charset::EmailCharset; + use crate::text::misc_token::MIMEWord; + use crate::text::quoted::QuotedString; #[test] fn test_content_type() { - let (rest, content) = field(b"Content-Type: text/plain; charset=UTF-8; format=flowed\r\n").unwrap(); + let (rest, content) = content(b"Content-Type: text/plain; charset=UTF-8; format=flowed\r\n").unwrap(); assert_eq!(&b""[..], rest); if let Content::Type(nt) = content { @@ -52,4 +58,41 @@ mod tests { panic!("Expected Content::Type, got {:?}", content); } } + + #[test] + fn test_header() { + let fullmail: &[u8] = r#"Date: Sat, 8 Jul 2023 07:14:29 +0200 +From: Grrrnd Zero +To: John Doe +Subject: Re: Saying Hello +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="b1_e376dc71bafc953c0b0fdeb9983a9956" +Content-Transfer-Encoding: 7bit + +This is a multipart message. + +"#.as_bytes(); + + assert_eq!( + map(header(content), CompFieldList::known)(fullmail), + Ok(( + &b"This is a multipart message.\n\n"[..], + vec![ + Content::Type(NaiveType { + main: &b"multipart"[..], + sub: &b"alternative"[..], + params: vec![ + Parameter { + name: &b"boundary"[..], + value: MIMEWord::Quoted(QuotedString(vec![&b"b1_e376dc71bafc953c0b0fdeb9983a9956"[..]])), + } + ] + }), + Content::TransferEncoding(Mechanism::_7Bit), + ], + )), + ); + } } diff --git a/src/mime/type.rs b/src/mime/type.rs index 7c533e7..37d1cae 100644 --- a/src/mime/type.rs +++ b/src/mime/type.rs @@ -13,9 +13,9 @@ use crate::text::words::{mime_atom}; // --------- NAIVE TYPE #[derive(Debug, PartialEq)] pub struct NaiveType<'a> { - main: &'a [u8], - sub: &'a [u8], - params: Vec>, + pub main: &'a [u8], + pub sub: &'a [u8], + pub params: Vec>, } impl<'a> NaiveType<'a> { pub fn to_type(&self) -> Type { self.into() } @@ -29,8 +29,8 @@ pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> { #[derive(Debug, PartialEq)] pub struct Parameter<'a> { - name: &'a [u8], - value: MIMEWord<'a>, + pub name: &'a [u8], + pub value: MIMEWord<'a>, } pub fn parameter(input: &[u8]) -> IResult<&[u8], Parameter> { map(tuple((mime_atom, tag(b"="), mime_word)), |(name, _, value)| Parameter { name, value })(input) diff --git a/src/rfc5322/field.rs b/src/rfc5322/field.rs index fa4142a..1423ce4 100644 --- a/src/rfc5322/field.rs +++ b/src/rfc5322/field.rs @@ -15,7 +15,7 @@ use crate::rfc5322::identification::{MessageID, MessageIDList, msg_id, msg_list} use crate::rfc5322::trace::{ReceivedLog, return_path, received_log}; use crate::rfc5322::mime::{Version, version}; use crate::rfc5322::message::Message; -use crate::header::*; +use crate::header::{header, field_name, CompFieldList}; use crate::text::misc_token::{Unstructured, PhraseList, unstructured, phrase_list}; #[derive(Debug, PartialEq)] @@ -87,15 +87,6 @@ pub fn field(input: &[u8]) -> IResult<&[u8], Field> { )), obs_crlf)(input) } -pub fn header(input: &[u8]) -> IResult<&[u8], CompFieldList> { - map(terminated(many0(alt(( - map(field, CompField::Known), - map(opt_field, |(k,v)| CompField::Unknown(k,v)), - map(foldable_line, CompField::Bad), - ))), obs_crlf), CompFieldList)(input) -} - - #[cfg(test)] mod tests { use super::*; @@ -115,7 +106,7 @@ This is the plain text body of the message. Note the blank line between the header information and the body of the message."; assert_eq!( - map(header, |v| FieldList(v.known()).message())(fullmail), + map(header(field), |v| FieldList(v.known()).message())(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."[..], Message {