From 5cfa06dbf8b1a5696855efa4d367a001ef4b0116 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jul 2023 12:26:53 +0200 Subject: [PATCH] split part mod in multiple files --- README.md | 4 +- examples/simple.rs | 4 +- src/lib.rs | 4 +- src/parse.rs | 4 +- src/part/{part.rs => composite.rs} | 265 +++++++---------------------- src/part/discrete.rs | 35 ++++ src/part/field.rs | 65 +++++++ src/part/mod.rs | 96 ++++++++++- 8 files changed, 261 insertions(+), 216 deletions(-) rename src/part/{part.rs => composite.rs} (60%) create mode 100644 src/part/discrete.rs diff --git a/README.md b/README.md index 001b433..b57c78e 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ between the header information and the body of the message."#; let email = eml_codec::email(input).unwrap(); println!( "{} just sent you an email with subject \"{}\"", - email.1.from[0].to_string(), - email.1.subject.unwrap().to_string(), + email.imf.from[0].to_string(), + email.imf.subject.unwrap().to_string(), ); ``` diff --git a/examples/simple.rs b/examples/simple.rs index 9886d8f..0cd76a7 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -12,7 +12,7 @@ between the header information and the body of the message."#; let email = eml_codec::email(input).unwrap(); println!( "{} just sent you an email with subject \"{}\"", - email.1.from[0].to_string(), - email.1.subject.unwrap().to_string(), + email.imf.from[0].to_string(), + email.imf.subject.unwrap().to_string(), ); } diff --git a/src/lib.rs b/src/lib.rs index 9825acb..a94898e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,8 @@ mod part; mod imf; mod text; -pub fn email(input: &[u8]) -> Result { - part::part::message(mime::mime::Message::default())(input) +pub fn email(input: &[u8]) -> Result { + part::composite::message(mime::mime::Message::default())(input) .map(|(_, v)| v) .map_err(error::EMLError::ParseError) } diff --git a/src/parse.rs b/src/parse.rs index 834964c..821f0d6 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -10,6 +10,6 @@ fn main() { let eml = eml_codec::email(&rawmail).unwrap(); println!("{:#?}", eml); - assert!(eml.1.date.is_some()); - assert!(!eml.1.from.is_empty()); + assert!(eml.imf.date.is_some()); + assert!(!eml.imf.from.is_empty()); } diff --git a/src/part/part.rs b/src/part/composite.rs similarity index 60% rename from src/part/part.rs rename to src/part/composite.rs index 082e793..032c3b5 100644 --- a/src/part/part.rs +++ b/src/part/composite.rs @@ -1,128 +1,16 @@ -use std::fmt; -use nom::{ - branch::alt, - bytes::complete::is_not, - combinator::{map, not, recognize}, - multi::many0, - sequence::pair, - IResult, -}; +use nom::IResult; use crate::header::{header, CompFieldList}; use crate::mime; -use crate::mime::mime::AnyMIME; use crate::imf::{self as imf}; -use crate::text::ascii::CRLF; use crate::text::boundary::{boundary, Delimiter}; -use crate::text::whitespace::obs_crlf; +use crate::part::{AnyPart, self}; +//--- Multipart #[derive(Debug, PartialEq)] -pub struct Multipart<'a>(pub mime::mime::Multipart<'a>, pub Vec>); - -#[derive(Debug, PartialEq)] -pub struct Message<'a>( - pub mime::mime::Message<'a>, - pub imf::Imf<'a>, - pub Box>, -); - -#[derive(PartialEq)] -pub struct Text<'a>(pub mime::mime::Text<'a>, pub &'a [u8]); -impl<'a> fmt::Debug for Text<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt - .debug_struct("part::Text") - .field("mime", &self.0) - .field("body", &format_args!("\"{}\"", String::from_utf8_lossy(self.1))) - .finish() - } -} - -#[derive(PartialEq)] -pub struct Binary<'a>(pub mime::mime::Binary<'a>, pub &'a [u8]); -impl<'a> fmt::Debug for Binary<'a> { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt - .debug_struct("part::Binary") - .field("mime", &self.0) - .field("body", &format_args!("\"{}\"", String::from_utf8_lossy(self.1))) - .finish() - } -} - -#[derive(Debug, PartialEq)] -pub enum AnyPart<'a> { - Mult(Multipart<'a>), - Msg(Message<'a>), - Txt(Text<'a>), - Bin(Binary<'a>), -} - -pub enum MixedField<'a> { - MIME(mime::field::Content<'a>), - IMF(imf::field::Field<'a>), -} -#[allow(dead_code)] -impl<'a> MixedField<'a> { - pub fn mime(&self) -> Option<&mime::field::Content<'a>> { - match self { - Self::MIME(v) => Some(v), - _ => None, - } - } - pub fn to_mime(self) -> Option> { - match self { - Self::MIME(v) => Some(v), - _ => None, - } - } - pub fn imf(&self) -> Option<&imf::field::Field<'a>> { - match self { - Self::IMF(v) => Some(v), - _ => None, - } - } - pub fn to_imf(self) -> Option> { - match self { - Self::IMF(v) => Some(v), - _ => None, - } - } -} -impl<'a> CompFieldList<'a, MixedField<'a>> { - pub fn sections(self) -> (mime::mime::AnyMIME<'a>, imf::Imf<'a>) { - let k = self.known(); - let (v1, v2): (Vec, Vec) = - k.into_iter().partition(|v| v.mime().is_some()); - let mime = v1 - .into_iter() - .filter_map(|v| v.to_mime()) - .collect::(); - let imf = v2 - .into_iter() - .filter_map(|v| v.to_imf()) - .collect::(); - (mime, imf) - } -} -pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> { - alt(( - map(mime::field::content, MixedField::MIME), - map(imf::field::field, MixedField::IMF), - ))(input) -} - -pub fn message<'a>( - m: mime::mime::Message<'a>, -) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> { - move |input: &[u8]| { - let (input, fields) = header(mixed_field)(input)?; - let (in_mime, imf) = fields.sections(); - - let part = to_anypart(in_mime, input); - - Ok((&[], Message(m.clone(), imf, Box::new(part)))) - } +pub struct Multipart<'a> { + pub interpreted: mime::mime::Multipart<'a>, + pub children: Vec> } pub fn multipart<'a>( @@ -132,12 +20,12 @@ pub fn multipart<'a>( move |input| { let bound = m.0.boundary.as_bytes(); - let (mut input_loop, _) = part_raw(bound)(input)?; + let (mut input_loop, _) = part::part_raw(bound)(input)?; let mut mparts: Vec = vec![]; loop { let input = match boundary(bound)(input_loop) { - Err(_) => return Ok((input_loop, Multipart(m.clone(), mparts))), - Ok((inp, Delimiter::Last)) => return Ok((inp, Multipart(m.clone(), mparts))), + Err(_) => return Ok((input_loop, Multipart { interpreted: m.clone(), children: mparts })), + Ok((inp, Delimiter::Last)) => return Ok((inp, Multipart{ interpreted: m.clone(), children: mparts})), Ok((inp, Delimiter::Next)) => inp, }; @@ -146,35 +34,35 @@ pub fn multipart<'a>( let mime = fields.to_mime(); // parse raw part - let (input, rpart) = part_raw(bound)(input)?; + let (input, rpart) = part::part_raw(bound)(input)?; // parse mime body - mparts.push(to_anypart(mime, rpart)); + mparts.push(part::to_anypart(mime, rpart)); input_loop = input; } } } -pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> { - match m { - AnyMIME::Mult(a) => map(multipart(a), AnyPart::Mult)(rpart) - .map(|v| v.1) - .unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))), - AnyMIME::Msg(a) => map(message(a), AnyPart::Msg)(rpart) - .map(|v| v.1) - .unwrap_or(AnyPart::Txt(Text(mime::mime::Text::default(), rpart))), - AnyMIME::Txt(a) => AnyPart::Txt(Text(a, rpart)), - AnyMIME::Bin(a) => AnyPart::Bin(Binary(a, rpart)), - } +//--- Message + +#[derive(Debug, PartialEq)] +pub struct Message<'a> { + pub interpreted: mime::mime::Message<'a>, + pub imf: imf::Imf<'a>, + pub child: Box>, } -pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> + '_ { - move |input| { - recognize(many0(pair( - not(boundary(bound)), - alt((is_not(CRLF), obs_crlf)), - )))(input) +pub fn message<'a>( + m: mime::mime::Message<'a>, +) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> { + move |input: &[u8]| { + let (input, fields): (_, CompFieldList) = header(part::field::mixed_field)(input)?; + let (in_mime, imf) = fields.sections(); + + let part = part::to_anypart(in_mime, input); + + Ok((&[], Message { interpreted: m.clone(), imf, child: Box::new(part) })) } } @@ -183,47 +71,10 @@ mod tests { use super::*; use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord}; use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word}; + use crate::part::discrete::Text; + use crate::part::AnyPart; use chrono::{FixedOffset, TimeZone}; - #[test] - fn test_preamble() { - assert_eq!( - part_raw(b"hello")( - b"blip -bloup - -blip -bloup-- ---bim ---bim-- - ---hello -Field: Body -" - ), - Ok(( - &b"\n--hello\nField: Body\n"[..], - &b"blip\nbloup\n\nblip\nbloup--\n--bim\n--bim--\n"[..], - )) - ); - } - - #[test] - fn test_part_raw() { - assert_eq!( - part_raw(b"simple boundary")(b"Content-type: text/plain; charset=us-ascii - -This is explicitly typed plain US-ASCII text. -It DOES end with a linebreak. - ---simple boundary-- -"), - Ok(( - &b"\n--simple boundary--\n"[..], - &b"Content-type: text/plain; charset=us-ascii\n\nThis is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..], - )) - ); - } #[test] fn test_multipart() { @@ -255,31 +106,31 @@ It DOES end with a linebreak. This is the epilogue. It is also to be ignored. "), Ok((&b"\nThis is the epilogue. It is also to be ignored.\n"[..], - Multipart( - base_mime, - vec![ - AnyPart::Txt(Text( - mime::mime::Text( + Multipart { + interpreted: base_mime, + children: vec![ + AnyPart::Txt(Text { + interpreted: mime::mime::Text( mime::r#type::Text { subtype: mime::r#type::TextSubtype::Plain, charset: mime::charset::EmailCharset::US_ASCII, }, mime::mime::Generic::default(), ), - &b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..], - )), - AnyPart::Txt(Text( - mime::mime::Text( + body: &b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..], + }), + AnyPart::Txt(Text { + interpreted: mime::mime::Text( mime::r#type::Text { subtype: mime::r#type::TextSubtype::Plain, charset: mime::charset::EmailCharset::US_ASCII, }, mime::mime::Generic::default(), ), - &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"[..], + }), ], - ), + }, )) ); } @@ -336,9 +187,9 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
message(base_mime.clone())(fullmail), Ok(( &[][..], - Message ( - base_mime, - imf::Imf { + Message { + interpreted: base_mime, + imf: imf::Imf { date: Some(FixedOffset::east_opt(2 * 3600) .unwrap() .with_ymd_and_hms(2023, 07, 8, 7, 14, 29) @@ -403,17 +254,17 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
mime_version: Some(imf::mime::Version { major: 1, minor: 0}), ..imf::Imf::default() }, - Box::new(AnyPart::Mult(Multipart ( - mime::mime::Multipart( + child: Box::new(AnyPart::Mult(Multipart { + interpreted: mime::mime::Multipart( mime::r#type::Multipart { subtype: mime::r#type::MultipartSubtype::Alternative, boundary: "b1_e376dc71bafc953c0b0fdeb9983a9956".to_string(), }, mime::mime::Generic::default(), ), - vec![ - AnyPart::Txt(Text( - mime::mime::Text( + children: vec![ + AnyPart::Txt(Text { + interpreted: mime::mime::Text( mime::r#type::Text { subtype: mime::r#type::TextSubtype::Plain, charset: mime::charset::EmailCharset::UTF_8, @@ -423,17 +274,17 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
..mime::mime::Generic::default() } ), - &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..], - )), - AnyPart::Txt(Text( - mime::mime::Text( + body: &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..], + }), + AnyPart::Txt(Text { + interpreted: mime::mime::Text( mime::r#type::Text { subtype: mime::r#type::TextSubtype::Html, charset: mime::charset::EmailCharset::US_ASCII, }, mime::mime::Generic::default(), ), - &br#"
GZ
+ body: &br#"
GZ
OoOoO
oOoOoOoOo
oOoOoOoOoOoOoOoOo
@@ -442,10 +293,10 @@ oOoOoOoOoOoOoOoOoOoOoOoOoOoOo
OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
"#[..], - )), + }), ], - ))), - ), + })), + }, )) ); } diff --git a/src/part/discrete.rs b/src/part/discrete.rs new file mode 100644 index 0000000..41e5d02 --- /dev/null +++ b/src/part/discrete.rs @@ -0,0 +1,35 @@ +use std::fmt; + +use crate::mime; + +#[derive(PartialEq)] +pub struct Text<'a> { + pub interpreted: mime::mime::Text<'a>, + pub body: &'a [u8] +} + +impl<'a> fmt::Debug for Text<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt + .debug_struct("part::Text") + .field("mime", &self.interpreted) + .field("body", &format_args!("\"{}\"", String::from_utf8_lossy(self.body))) + .finish() + } +} + +#[derive(PartialEq)] +pub struct Binary<'a> { + pub interpreted: mime::mime::Binary<'a>, + pub body: &'a [u8] +} + +impl<'a> fmt::Debug for Binary<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt + .debug_struct("part::Binary") + .field("mime", &self.interpreted) + .field("body", &format_args!("\"{}\"", String::from_utf8_lossy(self.body))) + .finish() + } +} diff --git a/src/part/field.rs b/src/part/field.rs index e69de29..b33f3b6 100644 --- a/src/part/field.rs +++ b/src/part/field.rs @@ -0,0 +1,65 @@ +use nom::{ + IResult, + branch::alt, + combinator::map, +}; + +use crate::mime; +use crate::imf; +use crate::part::CompFieldList; + +pub enum MixedField<'a> { + MIME(mime::field::Content<'a>), + IMF(imf::field::Field<'a>), +} +#[allow(dead_code)] +impl<'a> MixedField<'a> { + pub fn mime(&self) -> Option<&mime::field::Content<'a>> { + match self { + Self::MIME(v) => Some(v), + _ => None, + } + } + pub fn to_mime(self) -> Option> { + match self { + Self::MIME(v) => Some(v), + _ => None, + } + } + pub fn imf(&self) -> Option<&imf::field::Field<'a>> { + match self { + Self::IMF(v) => Some(v), + _ => None, + } + } + pub fn to_imf(self) -> Option> { + match self { + Self::IMF(v) => Some(v), + _ => None, + } + } +} +impl<'a> CompFieldList<'a, MixedField<'a>> { + pub fn sections(self) -> (mime::mime::AnyMIME<'a>, imf::Imf<'a>) { + let k = self.known(); + let (v1, v2): (Vec, Vec) = + k.into_iter().partition(|v| v.mime().is_some()); + let mime = v1 + .into_iter() + .filter_map(|v| v.to_mime()) + .collect::(); + let imf = v2 + .into_iter() + .filter_map(|v| v.to_imf()) + .collect::(); + (mime, imf) + } +} +pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> { + alt(( + map(mime::field::content, MixedField::MIME), + map(imf::field::field, MixedField::IMF), + ))(input) +} + + diff --git a/src/part/mod.rs b/src/part/mod.rs index 257b2da..fb54023 100644 --- a/src/part/mod.rs +++ b/src/part/mod.rs @@ -1 +1,95 @@ -pub mod part; +pub mod field; +pub mod composite; +pub mod discrete; + +use nom::{ + branch::alt, + bytes::complete::is_not, + combinator::{map, not, recognize}, + multi::many0, + sequence::pair, + IResult, +}; + +use crate::header::CompFieldList; +use crate::mime; +use crate::mime::mime::AnyMIME; +use crate::text::ascii::CRLF; +use crate::text::boundary::boundary; +use crate::text::whitespace::obs_crlf; +use crate::part::{composite::{Multipart, Message, multipart, message}, discrete::{Text, Binary}}; + +#[derive(Debug, PartialEq)] +pub enum AnyPart<'a> { + Mult(Multipart<'a>), + Msg(Message<'a>), + Txt(Text<'a>), + Bin(Binary<'a>), +} + +pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> { + match m { + AnyMIME::Mult(a) => map(multipart(a), AnyPart::Mult)(rpart) + .map(|v| v.1) + .unwrap_or(AnyPart::Txt(Text { interpreted: mime::mime::Text::default(), body: rpart })), + AnyMIME::Msg(a) => map(message(a), AnyPart::Msg)(rpart) + .map(|v| v.1) + .unwrap_or(AnyPart::Txt(Text { interpreted: mime::mime::Text::default(), body: rpart })), + AnyMIME::Txt(a) => AnyPart::Txt(Text { interpreted: a, body: rpart}), + AnyMIME::Bin(a) => AnyPart::Bin(Binary{ interpreted: a, body: rpart }), + } +} + +pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> + '_ { + move |input| { + recognize(many0(pair( + not(boundary(bound)), + alt((is_not(CRLF), obs_crlf)), + )))(input) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_preamble() { + assert_eq!( + part_raw(b"hello")( + b"blip +bloup + +blip +bloup-- +--bim +--bim-- + +--hello +Field: Body +" + ), + Ok(( + &b"\n--hello\nField: Body\n"[..], + &b"blip\nbloup\n\nblip\nbloup--\n--bim\n--bim--\n"[..], + )) + ); + } + + #[test] + fn test_part_raw() { + assert_eq!( + part_raw(b"simple boundary")(b"Content-type: text/plain; charset=us-ascii + +This is explicitly typed plain US-ASCII text. +It DOES end with a linebreak. + +--simple boundary-- +"), + Ok(( + &b"\n--simple boundary--\n"[..], + &b"Content-type: text/plain; charset=us-ascii\n\nThis is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..], + )) + ); + } +}