diff --git a/src/mime/field.rs b/src/mime/field.rs index 3b17bcb..0f9ec2d 100644 --- a/src/mime/field.rs +++ b/src/mime/field.rs @@ -37,6 +37,7 @@ 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; diff --git a/src/mime/mechanism.rs b/src/mime/mechanism.rs index f22f277..5cf8dbe 100644 --- a/src/mime/mechanism.rs +++ b/src/mime/mechanism.rs @@ -8,8 +8,9 @@ use nom::{ use crate::text::whitespace::cfws; use crate::text::words::mime_atom as token; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub enum Mechanism<'a> { + #[default] _7Bit, _8Bit, Binary, diff --git a/src/mime/mime.rs b/src/mime/mime.rs index a7deb11..b9bdcf4 100644 --- a/src/mime/mime.rs +++ b/src/mime/mime.rs @@ -3,13 +3,14 @@ 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; #[derive(Debug, PartialEq, Default)] pub struct MIME<'a> { - pub content_type: Option>, - pub content_transfer_encoding: Option>, - pub content_id: Option>, - pub content_description: Option>, + pub part_type: Type, + pub transfer_encoding: Mechanism<'a>, + pub id: Option>, + pub description: Option>, } impl<'a> FromIterator> for MIME<'a> { @@ -18,13 +19,133 @@ impl<'a> FromIterator> for MIME<'a> { MIME::default(), |mut section, field| { match field { - Content::Type(v) => section.content_type = Some(v), - Content::TransferEncoding(v) => section.content_transfer_encoding = Some(v), - Content::ID(v) => section.content_id = Some(v), - Content::Description(v) => section.content_description = Some(v), + 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 2ac51b6..a1cc1dc 100644 --- a/src/mime/type.rs +++ b/src/mime/type.rs @@ -6,9 +6,9 @@ use nom::{ IResult, }; -use crate::mime::charset::EmailCharset; use crate::text::misc_token::{MIMEWord, mime_word}; use crate::text::words::{mime_atom}; +use crate::mime::mime::{Type}; // --------- NAIVE TYPE #[derive(Debug, PartialEq)] @@ -39,130 +39,13 @@ pub fn parameter_list(input: &[u8]) -> IResult<&[u8], Vec> { many0(preceded(tag(";"), parameter))(input) } -// -------- 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, - } - } -} #[cfg(test)] mod tests { use super::*; + use crate::mime::charset::EmailCharset; use crate::text::quoted::QuotedString; + use crate::mime::mime::*; #[test] fn test_parameter() { diff --git a/src/part/part.rs b/src/part/part.rs index e86554d..e5c6447 100644 --- a/src/part/part.rs +++ b/src/part/part.rs @@ -19,7 +19,7 @@ pub struct Part<'a> { pub fn message() -> IResult<&[u8], Part> { } -pub fn multipart<'a>(ctype: Type) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Part<'a>> { +pub fn multipart<'a>(ctype: Multipart) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Part<'a>> { move |input: &[u8]| { let (mut input_loop, _) = preamble(ctype.boundary)(input)?; let mut parts: Vec = vec![]; @@ -31,7 +31,9 @@ pub fn multipart<'a>(ctype: Type) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Part }; // parse mime headers - header(content)(input)?; + let (input, fields) = header_in_boundaries(ctype.boundary, content)(input)?; + let mime = fields.to_mime(); + match mime. // based on headers, parse part @@ -72,33 +74,6 @@ pub fn preamble<'a>(bound: &'a [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], & // FIXME parse email here - -// Returns Ok even if an error is encountered while parsing -// the different mimes. -pub fn multipart<'a>(bound: &'a [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Vec<&'a [u8]>> { - move |input: &[u8]| { - let (mut input_loop, _) = preamble(bound)(input)?; - let mut parts: Vec<&[u8]> = vec![]; - loop { - let input = match boundary(bound)(input_loop) { - Err(_) => return Ok((input_loop, parts)), - Ok((inp, Delimiter::Last)) => return Ok((inp, parts)), - Ok((inp, Delimiter::Next)) => inp, - }; - - let input = match part(bound)(input) { - Err(_) => return Ok((input, parts)), - Ok((inp, part)) => { - parts.push(part); - inp - } - }; - - input_loop = input; - } - } -} - #[cfg(test)] mod tests { use super::*;