From 00d36fd49892ea6f01a1e97bc57b7a4826c62c21 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Sat, 22 Jul 2023 20:52:35 +0200 Subject: [PATCH] new approach to build mimeheader --- src/mime/field.rs | 24 +++++-- src/mime/mime.rs | 178 ++++++++++------------------------------------ src/mime/type.rs | 135 +++++++++++++++++++++++++++++++++-- src/part/part.rs | 31 +++++--- 4 files changed, 208 insertions(+), 160 deletions(-) diff --git a/src/mime/field.rs b/src/mime/field.rs index 0f9ec2d..703e937 100644 --- a/src/mime/field.rs +++ b/src/mime/field.rs @@ -11,7 +11,7 @@ use crate::rfc5322::identification::{MessageID, msg_id}; use crate::header::{field_name, CompFieldList}; use crate::mime::r#type::{NaiveType, naive_type}; use crate::mime::mechanism::{Mechanism, mechanism}; -use crate::mime::mime::MIME; +use crate::mime::mime::AnyMIME; #[derive(Debug, PartialEq)] pub enum Content<'a> { @@ -20,8 +20,25 @@ pub enum Content<'a> { ID(MessageID<'a>), Description(Unstructured<'a>), } +impl<'a> Content<'a> { + pub fn ctype(&'a self) -> Option<&'a NaiveType<'a>> { + match self { Content::Type(v) => Some(v), _ => None } + } + pub fn transfer_encoding(&'a self) -> Option<&'a Mechanism<'a>> { + match self { Content::TransferEncoding(v) => Some(v), _ => None } + } + pub fn id(&'a self) -> Option<&'a MessageID<'a>> { + match self { Content::ID(v) => Some(v), _ => None } + } + pub fn description(&'a self) -> Option<&'a Unstructured<'a>> { + match self { Content::Description(v) => Some(v), _ => None } + } +} + impl<'a> CompFieldList<'a, Content<'a>> { - pub fn to_mime(self) -> MIME<'a> { self.known().into_iter().collect::() } + pub fn to_mime(self) -> AnyMIME<'a> { + self.known().into_iter().collect::() + } } pub fn content(input: &[u8]) -> IResult<&[u8], Content> { @@ -37,7 +54,6 @@ pub fn content(input: &[u8]) -> IResult<&[u8], Content> { mod tests { use super::*; use crate::mime::r#type::*; - use crate::mime::mime::*; use crate::mime::charset::EmailCharset; use crate::text::misc_token::MIMEWord; use crate::text::quoted::QuotedString; @@ -51,7 +67,7 @@ mod tests { if let Content::Type(nt) = content { assert_eq!( nt.to_type(), - Type::Text(Text { + AnyType::Text(Text { charset: EmailCharset::UTF_8, subtype: TextSubtype::Plain, }), diff --git a/src/mime/mime.rs b/src/mime/mime.rs index b9bdcf4..b8e1f38 100644 --- a/src/mime/mime.rs +++ b/src/mime/mime.rs @@ -1,151 +1,49 @@ -use crate::mime::r#type::NaiveType; use crate::mime::mechanism::Mechanism; use crate::rfc5322::identification::MessageID; use crate::text::misc_token::Unstructured; use crate::mime::field::Content; -use crate::mime::charset::EmailCharset; +use crate::mime::r#type::{AnyType, Multipart, Message, Text, Binary}; + +pub enum AnyMIME<'a> { + Multipart(Multipart, Generic<'a>), + Message(Message, Generic<'a>), + Text(Text, Generic<'a>), + Binary(Binary, Generic<'a>), +} +impl<'a> AnyMIME<'a> { + pub fn from_pair(at: AnyType, gen: Generic<'a>) -> Self { + match at { + AnyType::Multipart(m) => AnyMIME::Multipart(m, gen), + AnyType::Message(m) => AnyMIME::Message(m, gen), + AnyType::Text(m) => AnyMIME::Text(m, gen), + AnyType::Binary(m) => AnyMIME::Binary(m, gen), + } + } +} + +impl<'a> FromIterator> for AnyMIME<'a> { + fn from_iter>>(it: I) -> Self { + let (at, gen) = it.into_iter().fold( + (AnyType::default(), 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::from_pair(at, gen) + } +} #[derive(Debug, PartialEq, Default)] -pub struct MIME<'a> { - pub part_type: Type, +pub struct Generic<'a> { pub transfer_encoding: Mechanism<'a>, pub id: Option>, pub description: Option>, } -impl<'a> FromIterator> for MIME<'a> { - fn from_iter>>(source: I) -> Self { - source.into_iter().fold( - MIME::default(), - |mut section, field| { - match field { - Content::Type(v) => section.part_type = v.to_type(), - Content::TransferEncoding(v) => section.transfer_encoding = v, - Content::ID(v) => section.id = Some(v), - Content::Description(v) => section.description = Some(v), - }; - section - } - ) - } -} - -// -------- TYPE -#[derive(Debug, PartialEq)] -pub enum Type { - // Composite types - Multipart(Multipart), - Message(Message), - - // Discrete types - Text(Text), - Binary, -} -impl Default for Type { - fn default() -> Self { - Self::Text(Text::default()) - } -} -impl<'a> From<&'a NaiveType<'a>> for Type { - fn from(nt: &'a NaiveType<'a>) -> Self { - match nt.main.to_ascii_lowercase().as_slice() { - b"multipart" => Multipart::try_from(nt).map(Self::Multipart).unwrap_or(Self::default()), - b"message" => Self::Message(Message::from(nt)), - b"text" => Self::Text(Text::from(nt)), - _ => Self::Binary, - } - } -} - -#[derive(Debug, PartialEq)] -pub struct Multipart { - pub subtype: MultipartSubtype, - pub boundary: String, -} -impl<'a> TryFrom<&'a NaiveType<'a>> for Multipart { - type Error = (); - - fn try_from(nt: &'a NaiveType<'a>) -> Result { - nt.params.iter() - .find(|x| x.name.to_ascii_lowercase().as_slice() == b"boundary") - .map(|boundary| Multipart { - subtype: MultipartSubtype::from(nt), - boundary: boundary.value.to_string(), - }) - .ok_or(()) - } -} - -#[derive(Debug, PartialEq)] -pub enum MultipartSubtype { - Alternative, - Mixed, - Digest, - Parallel, - Report, - Unknown, -} -impl<'a> From<&NaiveType<'a>> for MultipartSubtype { - fn from(nt: &NaiveType<'a>) -> Self { - match nt.sub.to_ascii_lowercase().as_slice() { - b"alternative" => Self::Alternative, - b"mixed" => Self::Mixed, - b"digest" => Self::Digest, - b"parallel" => Self::Parallel, - b"report" => Self::Report, - _ => Self::Unknown, - } - } -} - -#[derive(Debug, PartialEq)] -pub enum Message { - RFC822, - Partial, - External, - Unknown, -} -impl<'a> From<&NaiveType<'a>> for Message { - fn from(nt: &NaiveType<'a>) -> Self { - match nt.sub.to_ascii_lowercase().as_slice() { - b"rfc822" => Self::RFC822, - b"partial" => Self::Partial, - b"external" => Self::External, - _ => Self::Unknown, - } - } -} - -#[derive(Debug, PartialEq, Default)] -pub struct Text { - pub subtype: TextSubtype, - pub charset: EmailCharset, -} -impl<'a> From<&NaiveType<'a>> for Text { - fn from(nt: &NaiveType<'a>) -> Self { - Self { - subtype: TextSubtype::from(nt), - charset: nt.params.iter() - .find(|x| x.name.to_ascii_lowercase().as_slice() == b"charset") - .map(|x| EmailCharset::from(x.value.to_string().as_bytes())) - .unwrap_or(EmailCharset::US_ASCII), - } - } -} - -#[derive(Debug, PartialEq, Default)] -pub enum TextSubtype { - #[default] - Plain, - Html, - Unknown, -} -impl<'a> From<&NaiveType<'a>> for TextSubtype { - fn from(nt: &NaiveType<'a>) -> Self { - match nt.sub.to_ascii_lowercase().as_slice() { - b"plain" => Self::Plain, - b"html" => Self::Html, - _ => Self::Unknown, - } - } -} diff --git a/src/mime/type.rs b/src/mime/type.rs index a1cc1dc..fbb622a 100644 --- a/src/mime/type.rs +++ b/src/mime/type.rs @@ -8,7 +8,7 @@ use nom::{ use crate::text::misc_token::{MIMEWord, mime_word}; use crate::text::words::{mime_atom}; -use crate::mime::mime::{Type}; +use crate::mime::charset::EmailCharset; // --------- NAIVE TYPE #[derive(Debug, PartialEq)] @@ -18,7 +18,7 @@ pub struct NaiveType<'a> { pub params: Vec>, } impl<'a> NaiveType<'a> { - pub fn to_type(&self) -> Type { self.into() } + pub fn to_type(&self) -> AnyType { self.into() } } pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> { map( @@ -39,13 +39,136 @@ pub fn parameter_list(input: &[u8]) -> IResult<&[u8], Vec> { many0(preceded(tag(";"), parameter))(input) } +// MIME TYPES TRANSLATED TO RUST TYPING SYSTEM + +#[derive(Debug, PartialEq)] +pub enum AnyType { + // Composite types + Multipart(Multipart), + Message(Message), + + // Discrete types + Text(Text), + Binary(Binary), +} + +impl Default for AnyType { + fn default() -> Self { + Self::Text(Text::default()) + } +} +impl<'a> From<&'a NaiveType<'a>> for AnyType { + fn from(nt: &'a NaiveType<'a>) -> Self { + match nt.main.to_ascii_lowercase().as_slice() { + b"multipart" => Multipart::try_from(nt).map(Self::Multipart).unwrap_or(Self::default()), + b"message" => Self::Message(Message::from(nt)), + b"text" => Self::Text(Text::from(nt)), + _ => Self::Binary(Binary::default()), + } + } +} + +#[derive(Debug, PartialEq)] +pub struct Multipart { + pub subtype: MultipartSubtype, + pub boundary: String, +} +impl<'a> TryFrom<&'a NaiveType<'a>> for Multipart { + type Error = (); + + fn try_from(nt: &'a NaiveType<'a>) -> Result { + nt.params.iter() + .find(|x| x.name.to_ascii_lowercase().as_slice() == b"boundary") + .map(|boundary| Multipart { + subtype: MultipartSubtype::from(nt), + boundary: boundary.value.to_string(), + }) + .ok_or(()) + } +} + +#[derive(Debug, PartialEq)] +pub enum MultipartSubtype { + Alternative, + Mixed, + Digest, + Parallel, + Report, + Unknown, +} +impl<'a> From<&NaiveType<'a>> for MultipartSubtype { + fn from(nt: &NaiveType<'a>) -> Self { + match nt.sub.to_ascii_lowercase().as_slice() { + b"alternative" => Self::Alternative, + b"mixed" => Self::Mixed, + b"digest" => Self::Digest, + b"parallel" => Self::Parallel, + b"report" => Self::Report, + _ => Self::Unknown, + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Message { + RFC822, + Partial, + External, + Unknown, +} +impl<'a> From<&NaiveType<'a>> for Message { + fn from(nt: &NaiveType<'a>) -> Self { + match nt.sub.to_ascii_lowercase().as_slice() { + b"rfc822" => Self::RFC822, + b"partial" => Self::Partial, + b"external" => Self::External, + _ => Self::Unknown, + } + } +} + +#[derive(Debug, PartialEq, Default)] +pub struct Text { + pub subtype: TextSubtype, + pub charset: EmailCharset, +} +impl<'a> From<&NaiveType<'a>> for Text { + fn from(nt: &NaiveType<'a>) -> Self { + Self { + subtype: TextSubtype::from(nt), + charset: nt.params.iter() + .find(|x| x.name.to_ascii_lowercase().as_slice() == b"charset") + .map(|x| EmailCharset::from(x.value.to_string().as_bytes())) + .unwrap_or(EmailCharset::US_ASCII), + } + } +} + +#[derive(Debug, PartialEq, Default)] +pub enum TextSubtype { + #[default] + Plain, + Html, + Unknown, +} +impl<'a> From<&NaiveType<'a>> for TextSubtype { + fn from(nt: &NaiveType<'a>) -> Self { + match nt.sub.to_ascii_lowercase().as_slice() { + b"plain" => Self::Plain, + b"html" => Self::Html, + _ => Self::Unknown, + } + } +} + +#[derive(Debug, PartialEq, Default)] +pub struct Binary {} #[cfg(test)] mod tests { use super::*; use crate::mime::charset::EmailCharset; use crate::text::quoted::QuotedString; - use crate::mime::mime::*; #[test] fn test_parameter() { @@ -72,7 +195,7 @@ mod tests { assert_eq!( nt.to_type(), - Type::Text(Text { + AnyType::Text(Text { charset: EmailCharset::UTF_8, subtype: TextSubtype::Plain, }) @@ -86,7 +209,7 @@ mod tests { assert_eq!(rest, &[]); assert_eq!( nt.to_type(), - Type::Multipart(Multipart { + AnyType::Multipart(Multipart { subtype: MultipartSubtype::Mixed, boundary: "--==_mimepart_64a3f2c69114f_2a13d020975fe".into(), }) @@ -100,7 +223,7 @@ mod tests { assert_eq!( nt.to_type(), - Type::Message(Message::RFC822), + AnyType::Message(Message::RFC822), ); } diff --git a/src/part/part.rs b/src/part/part.rs index e5c6447..43a89d7 100644 --- a/src/part/part.rs +++ b/src/part/part.rs @@ -10,30 +10,42 @@ use nom::{ use crate::mime::r#type; pub struct Part<'a> { - Multipart(r#type::Multipart, Vec>), - Message(r#type::Message, Message, Part<'a>), - Text(r#type::Text, &'a [u8]), - Binary(&'a [u8]), + Multipart(Multipart, Vec>), + Message(MIME, Message, Part<'a>), + Text(MIME, &'a [u8]), + Binary(MIME, &'a [u8]), +} + +pub struct Part<'a> { + List(Vec>), + Single(Part<'a>), + Leaf(&'a [u8]), } pub fn message() -> IResult<&[u8], Part> { } -pub fn multipart<'a>(ctype: Multipart) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Part<'a>> { +pub fn multipart<'a>(m: Multipart) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Part<'a>> { move |input: &[u8]| { - let (mut input_loop, _) = preamble(ctype.boundary)(input)?; + let (mut input_loop, _) = preamble(m.ctype.boundary)(input)?; let mut parts: Vec = vec![]; loop { - let input = match boundary(ctype.boundary)(input_loop) { + let input = match boundary(m.ctype.boundary)(input_loop) { Err(_) => return Ok((input_loop, parts)), - Ok((inp, Delimiter::Last)) => return Ok((inp, Part::Multipart(ctype, parts))), + Ok((inp, Delimiter::Last)) => return Ok((inp, Part::List(parts))), Ok((inp, Delimiter::Next)) => inp, }; // parse mime headers let (input, fields) = header_in_boundaries(ctype.boundary, content)(input)?; let mime = fields.to_mime(); - match mime. + + // parse mime body + match mime.part_type { + Type::Multipart(m) => multipart(m), + Type::Message(m) => message(m), + Type::Text(t) | Type::Binary + } // based on headers, parse part @@ -48,7 +60,6 @@ pub fn multipart<'a>(ctype: Multipart) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], input_loop = input; } } - } pub fn discrete() -> IResult<&[u8], Part> {