diff --git a/examples/simple.rs b/examples/simple.rs index 44bdc69..1918a21 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -10,11 +10,11 @@ This is the plain text body of the message. Note the blank line between the header information and the body of the message."#; // if you are only interested in email metadata/headers - let (_, header) = eml_codec::imf(input).unwrap(); + let (_, imf) = eml_codec::imf(input).unwrap(); println!( "{} just sent you an email with subject \"{}\"", - header.imf.from[0].to_string(), - header.imf.subject.unwrap().to_string(), + imf.from[0].to_string(), + imf.subject.unwrap().to_string(), ); // if you like to also parse the body/content diff --git a/src/header.rs b/src/header.rs index 861103e..213dcbb 100644 --- a/src/header.rs +++ b/src/header.rs @@ -18,8 +18,8 @@ pub enum CompField<'a, T> { Bad(&'a [u8]), } -#[derive(Debug, PartialEq)] -pub struct Kv<'a>(&'a [u8], Unstructured<'a>); +#[derive(Debug, PartialEq, Clone)] +pub struct Kv<'a>(pub &'a [u8], pub Unstructured<'a>); pub fn header<'a, T>( diff --git a/src/imf/field.rs b/src/imf/field.rs index 61873bc..c9ea2bd 100644 --- a/src/imf/field.rs +++ b/src/imf/field.rs @@ -6,7 +6,7 @@ use nom::{ IResult, }; -use crate::header::{field_name, header, self}; +use crate::header::{field_name, header}; 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}; @@ -80,18 +80,12 @@ pub fn field(input: &[u8]) -> IResult<&[u8], Field> { )(input) } -#[derive(Debug, PartialEq)] -pub struct Header<'a> { - pub imf: Imf<'a>, - pub ext: Vec>, - 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 +pub fn imf(input: &[u8]) -> IResult<&[u8], Imf> { + map(header(field), |(known, unknown, bad)| { + let mut imf = Imf::from_iter(known); + imf.header_ext = unknown; + imf.header_bad = bad; + imf })(input) } @@ -117,7 +111,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."[..], - Header { bad: vec![], ext: vec![], imf: 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, @@ -141,7 +135,7 @@ between the header information and the body of the message."; UnstrToken::Plain(&b"message"[..]), ])), ..Imf::default() - }} + } )), ) } diff --git a/src/imf/mod.rs b/src/imf/mod.rs index 8aad350..7817ac7 100644 --- a/src/imf/mod.rs +++ b/src/imf/mod.rs @@ -14,6 +14,7 @@ use crate::imf::identification::MessageID; use crate::imf::mailbox::{AddrSpec, MailboxRef}; use crate::imf::mime::Version; use crate::imf::trace::ReceivedLog; +use crate::header; use crate::text::misc_token::{PhraseList, Unstructured}; use chrono::{DateTime, FixedOffset}; @@ -49,6 +50,19 @@ pub struct Imf<'a> { // MIME pub mime_version: Option, + + // Junk + pub header_ext: Vec>, + pub header_bad: Vec<&'a [u8]>, +} + +impl<'a> Imf<'a> { + pub fn with_opt(mut self, opt: Vec>) -> Self { + self.header_ext = opt; self + } + pub fn with_bad(mut self, bad: Vec<&'a [u8]>) -> Self { + self.header_bad = bad; self + } } //@FIXME min and max limits are not enforced, diff --git a/src/lib.rs b/src/lib.rs index 118086d..ce05109 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,7 @@ use nom::IResult; /// ); /// ``` pub fn email(input: &[u8]) -> IResult<&[u8], part::composite::Message> { - part::composite::message(mime::Message::default())(input) + part::composite::message(mime::MIME::::default())(input) } /// Only extract the headers of the email that are part of the Internet Message Format spec @@ -87,13 +87,13 @@ pub fn email(input: &[u8]) -> IResult<&[u8], part::composite::Message> { /// This is the plain text body of the message. Note the blank line /// between the header information and the body of the message."#; /// -/// let (_, header) = eml_codec::imf(input).unwrap(); +/// let (_, imf) = eml_codec::imf(input).unwrap(); /// println!( /// "{} just sent you an email with subject \"{}\"", -/// header.imf.from[0].to_string(), -/// header.imf.subject.unwrap().to_string(), +/// imf.from[0].to_string(), +/// imf.subject.unwrap().to_string(), /// ); /// ``` -pub fn imf(input: &[u8]) -> IResult<&[u8], imf::field::Header> { +pub fn imf(input: &[u8]) -> IResult<&[u8], imf::Imf> { imf::field::imf(input) } diff --git a/src/mime/field.rs b/src/mime/field.rs index b672618..fa8b23c 100644 --- a/src/mime/field.rs +++ b/src/mime/field.rs @@ -8,7 +8,6 @@ use nom::{ use crate::header::{field_name}; use crate::imf::identification::{msg_id, MessageID}; use crate::mime::mechanism::{mechanism, Mechanism}; -use crate::mime::{AnyMIMEWithDefault, WithDefaultType}; use crate::mime::r#type::{naive_type, NaiveType}; use crate::text::misc_token::{unstructured, Unstructured}; use crate::text::whitespace::obs_crlf; @@ -48,14 +47,10 @@ impl<'a> Content<'a> { } } -/*impl<'a> CompFieldList<'a, Content<'a>> { - pub fn to_mime (self) -> AnyMIMEWithDefault<'a, T> { - self.known().into_iter().collect::>() - } -}*/ +/* pub fn to_mime<'a, T: WithDefaultType>(list: Vec>) -> AnyMIMEWithDefault<'a, T> { list.into_iter().collect::>() -} +}*/ pub fn content(input: &[u8]) -> IResult<&[u8], Content> { terminated( diff --git a/src/mime/mod.rs b/src/mime/mod.rs index a4c625b..3b549d0 100644 --- a/src/mime/mod.rs +++ b/src/mime/mod.rs @@ -15,38 +15,38 @@ use std::marker::PhantomData; use crate::imf::identification::MessageID; use crate::mime::field::Content; use crate::mime::mechanism::Mechanism; -use crate::mime::r#type::{self as ctype, AnyType}; +use crate::mime::r#type::{AnyType, NaiveType}; +use crate::header; use crate::text::misc_token::Unstructured; //Multipart, Message, Text, Binary}; #[derive(Debug, PartialEq, Clone)] -pub struct Multipart<'a>(pub ctype::Multipart, pub Generic<'a>); - -#[derive(Debug, PartialEq, Clone, Default)] -pub struct Message<'a>(pub ctype::Message, pub Generic<'a>); - -#[derive(Debug, PartialEq, Clone, Default)] -pub struct Text<'a>(pub ctype::Text, pub Generic<'a>); - -#[derive(Debug, PartialEq, Clone)] -pub struct Binary<'a>(pub ctype::Binary, pub Generic<'a>); +pub struct MIME<'a, T> { + pub interpreted: T, + pub parsed: NaiveMIME<'a> +} +impl<'a> Default for MIME<'a, r#type::Text> { + fn default() -> Self { + Self { + interpreted: r#type::Text::default(), + parsed: NaiveMIME::default(), + } + } +} +impl<'a> Default for MIME<'a, r#type::Message> { + fn default() -> Self { + Self { + interpreted: r#type::Message::default(), + parsed: NaiveMIME::default(), + } + } +} #[derive(Debug, PartialEq, Clone)] pub enum AnyMIME<'a> { - Mult(Multipart<'a>), - Msg(Message<'a>), - Txt(Text<'a>), - Bin(Binary<'a>), -} - -impl<'a> AnyMIME<'a> { - pub fn from_pair(at: AnyType, gen: Generic<'a>) -> Self { - match at { - AnyType::Multipart(m) => AnyMIME::Mult(Multipart(m, gen)), - AnyType::Message(m) => AnyMIME::Msg(Message(m, gen)), - AnyType::Text(m) => AnyMIME::Txt(Text(m, gen)), - AnyType::Binary(m) => AnyMIME::Bin(Binary(m, gen)), - } - } + Mult(MIME<'a, r#type::Multipart>), + Msg(MIME<'a, r#type::Message>), + Txt(MIME<'a, r#type::Text>), + Bin(MIME<'a, r#type::Binary>), } impl<'a, T: WithDefaultType> From> for AnyMIME<'a> { @@ -56,12 +56,46 @@ impl<'a, T: WithDefaultType> From> for AnyMIME<'a> { } #[derive(Debug, PartialEq, Default, Clone)] -pub struct Generic<'a> { +pub struct NaiveMIME<'a> { + pub ctype: Option>, pub transfer_encoding: Mechanism<'a>, pub id: Option>, pub description: Option>, + pub header_ext: Vec>, + pub header_bad: Vec<&'a [u8]>, } +impl<'a> FromIterator> for NaiveMIME<'a> { + fn from_iter>>(it: I) -> Self { + it.into_iter().fold( + NaiveMIME::default(), + |mut section, field| { + match field { + Content::Type(v) => section.ctype = Some(v), + Content::TransferEncoding(v) => section.transfer_encoding = v, + Content::ID(v) => section.id = Some(v), + Content::Description(v) => section.description = Some(v), + }; + section + }, + ) + } +} + +impl<'a> NaiveMIME<'a> { + pub fn with_opt(mut self, opt: Vec>) -> Self { + self.header_ext = opt; self + } + pub fn with_bad(mut self, bad: Vec<&'a [u8]>) -> Self { + self.header_bad = bad; self + } + pub fn to_interpreted(self) -> AnyMIME<'a> { + self.ctype.as_ref().map(|c| c.to_type()).unwrap_or(T::default_type()).to_mime(self).into() + } +} + + + pub trait WithDefaultType { fn default_type() -> AnyType; } @@ -82,21 +116,8 @@ impl WithDefaultType for WithDigestDefault { #[derive(Debug, PartialEq)] pub struct AnyMIMEWithDefault<'a, T: WithDefaultType>(pub AnyMIME<'a>, PhantomData); -impl<'a, T: WithDefaultType> FromIterator> for AnyMIMEWithDefault<'a, T> { - fn from_iter>>(it: I) -> Self { - let (at, gen) = it.into_iter().fold( - (T::default_type(), 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(AnyMIME::from_pair(at, gen), PhantomData) +impl<'a, T: WithDefaultType> Default for AnyMIMEWithDefault<'a, T> { + fn default() -> Self { + Self(T::default_type().to_mime(NaiveMIME::default()), PhantomData) } } diff --git a/src/mime/type.rs b/src/mime/type.rs index 51934bb..5eaf837 100644 --- a/src/mime/type.rs +++ b/src/mime/type.rs @@ -9,9 +9,10 @@ use nom::{ use crate::mime::charset::EmailCharset; use crate::text::misc_token::{mime_word, MIMEWord}; use crate::text::words::mime_atom; +use crate::mime::{AnyMIME, MIME, NaiveMIME}; // --------- NAIVE TYPE -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct NaiveType<'a> { pub main: &'a [u8], pub sub: &'a [u8], @@ -29,7 +30,7 @@ pub fn naive_type(input: &[u8]) -> IResult<&[u8], NaiveType> { )(input) } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Parameter<'a> { pub name: &'a [u8], pub value: MIMEWord<'a>, @@ -70,6 +71,17 @@ impl<'a> From<&'a NaiveType<'a>> for AnyType { } } +impl<'a> AnyType { + pub fn to_mime(self, parsed: NaiveMIME<'a>) -> AnyMIME<'a> { + match self { + Self::Multipart(interpreted) => AnyMIME::Mult(MIME:: { interpreted, parsed }), + Self::Message(interpreted) => AnyMIME::Msg(MIME:: { interpreted, parsed }), + Self::Text(interpreted) => AnyMIME::Txt(MIME:: { interpreted, parsed }), + Self::Binary(interpreted) => AnyMIME::Bin(MIME:: { interpreted, parsed }), + } + } +} + #[derive(Debug, PartialEq, Clone)] pub struct Multipart { pub subtype: MultipartSubtype, diff --git a/src/part/composite.rs b/src/part/composite.rs index f6a2cfd..75a3c90 100644 --- a/src/part/composite.rs +++ b/src/part/composite.rs @@ -9,7 +9,7 @@ use crate::text::boundary::{boundary, Delimiter}; //--- Multipart #[derive(Debug, PartialEq)] pub struct Multipart<'a> { - pub interpreted: mime::Multipart<'a>, + pub interpreted: mime::MIME<'a, mime::r#type::Multipart>, pub children: Vec>, pub preamble: &'a [u8], pub epilogue: &'a [u8], @@ -22,12 +22,12 @@ impl<'a> Multipart<'a> { } pub fn multipart<'a>( - m: mime::Multipart<'a>, + m: mime::MIME<'a, mime::r#type::Multipart>, ) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Multipart<'a>> { let m = m.clone(); move |input| { - let bound = m.0.boundary.as_bytes(); + let bound = m.interpreted.boundary.as_bytes(); let (mut input_loop, preamble) = part::part_raw(bound)(input)?; let mut mparts: Vec = vec![]; loop { @@ -57,11 +57,16 @@ pub fn multipart<'a>( Ok((inp, Delimiter::Next)) => inp, }; - // parse mime headers - let (input, (known, unknown, bad)) = header(mime::field::content)(input)?; - let mime = match m.0.subtype { - mime::r#type::MultipartSubtype::Digest => mime::field::to_mime::(known).into(), - _ => mime::field::to_mime::(known).into(), + // parse mime headers, otherwise pick default mime + let (input, naive_mime) = match header(mime::field::content)(input) { + Ok((input, (known, unknown, bad))) => (input, known.into_iter().collect::().with_opt(unknown).with_bad(bad)), + Err(_) => (input, mime::NaiveMIME::default()), + }; + + // interpret mime according to context + let mime = match m.interpreted.subtype { + mime::r#type::MultipartSubtype::Digest => naive_mime.to_interpreted::().into(), + _ => naive_mime.to_interpreted::().into(), }; // parse raw part @@ -70,6 +75,7 @@ pub fn multipart<'a>( // parse mime body mparts.push(part::to_anypart(mime, rpart)); + input_loop = input; } } @@ -79,7 +85,7 @@ pub fn multipart<'a>( #[derive(Debug, PartialEq)] pub struct Message<'a> { - pub interpreted: mime::Message<'a>, + pub interpreted: mime::MIME<'a, mime::r#type::Message>, pub imf: imf::Imf<'a>, pub child: Box>, pub epilogue: &'a [u8], @@ -92,13 +98,23 @@ impl<'a> Message<'a> { } pub fn message<'a>( - m: mime::Message<'a>, + m: mime::MIME<'a, mime::r#type::Message>, ) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], Message<'a>> { move |input: &[u8]| { + // parse header fields let (input, (known, unknown, bad)): (_, (Vec::, Vec, Vec<&[u8]>)) = header(part::field::mixed_field)(input)?; - let (in_mime, imf) = part::field::sections::(known); + // aggregate header fields + let (naive_mime, imf) = part::field::sections(known); + + // attach bad headers to imf + let imf = imf.with_opt(unknown).with_bad(bad); + + // interpret headers to choose a mime type + let in_mime = naive_mime.to_interpreted::().into(); + + // parse this mimetype let part = part::to_anypart(in_mime, input); Ok(( @@ -119,18 +135,19 @@ mod tests { use crate::part::discrete::Text; use crate::part::AnyPart; use crate::text::encoding::{Base64Word, EncodedWord, QuotedChunk, QuotedWord}; - use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word}; + use crate::text::misc_token::{Phrase, UnstrToken, Unstructured, Word, MIMEWord}; + use crate::text::quoted::QuotedString; use chrono::{FixedOffset, TimeZone}; #[test] fn test_multipart() { - let base_mime = mime::Multipart( - mime::r#type::Multipart { + let base_mime = mime::MIME { + interpreted: mime::r#type::Multipart { subtype: mime::r#type::MultipartSubtype::Alternative, boundary: "simple boundary".to_string(), }, - mime::Generic::default(), - ); + parsed: mime::NaiveMIME::default(), + }; assert_eq!( multipart(base_mime.clone())(b"This is the preamble. It is to be ignored, though it @@ -158,23 +175,35 @@ This is the epilogue. It is also to be ignored. epilogue: &b""[..], children: vec![ AnyPart::Txt(Text { - interpreted: mime::Text( - mime::r#type::Text { + interpreted: mime::MIME { + interpreted: mime::r#type::Text { subtype: mime::r#type::TextSubtype::Plain, charset: mime::charset::EmailCharset::US_ASCII, }, - mime::Generic::default(), - ), + parsed: mime::NaiveMIME::default(), + }, body: &b"This is implicitly typed plain US-ASCII text.\nIt does NOT end with a linebreak."[..], }), AnyPart::Txt(Text { - interpreted: mime::Text( - mime::r#type::Text { + interpreted: mime::MIME { + interpreted: mime::r#type::Text { subtype: mime::r#type::TextSubtype::Plain, charset: mime::charset::EmailCharset::US_ASCII, }, - mime::Generic::default(), - ), + parsed: mime::NaiveMIME { + ctype: Some(mime::r#type::NaiveType { + main: &b"text"[..], + sub: &b"plain"[..], + params: vec![ + mime::r#type::Parameter { + name: &b"charset"[..], + value: MIMEWord::Atom(&b"us-ascii"[..]), + } + ] + }), + ..mime::NaiveMIME::default() + }, + }, body: &b"This is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..], }), ], @@ -230,7 +259,7 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
"# .as_bytes(); - let base_mime = mime::Message::default(); + let base_mime = mime::MIME::::default(); assert_eq!( message(base_mime.clone())(fullmail), Ok(( @@ -301,40 +330,84 @@ OoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO
right: &b"www.grrrndzero.org"[..], }), mime_version: Some(imf::mime::Version { major: 1, minor: 0}), + header_ext: vec![ + header::Kv(&b"X-Unknown"[..], Unstructured(vec![ + UnstrToken::Plain(&b"something"[..]), + UnstrToken::Plain(&b"something"[..]), + ])) + ], + header_bad: vec![ + &b"Bad entry\n on multiple lines\n"[..], + ], ..imf::Imf::default() }, child: Box::new(AnyPart::Mult(Multipart { - interpreted: mime::Multipart( - mime::r#type::Multipart { + interpreted: mime::MIME { + interpreted: mime::r#type::Multipart { subtype: mime::r#type::MultipartSubtype::Alternative, boundary: "b1_e376dc71bafc953c0b0fdeb9983a9956".to_string(), }, - mime::Generic::default(), - ), + parsed: mime::NaiveMIME { + ctype: Some(mime::r#type::NaiveType { + main: &b"multipart"[..], + sub: &b"alternative"[..], + params: vec![ + mime::r#type::Parameter { + name: &b"boundary"[..], + value: MIMEWord::Quoted(QuotedString(vec![&b"b1_e376dc71bafc953c0b0fdeb9983a9956"[..]])), + } + ] + }), + ..mime::NaiveMIME::default() + }, + }, preamble: &b"This is a multi-part message in MIME format.\n"[..], epilogue: &b""[..], children: vec![ AnyPart::Txt(Text { - interpreted: mime::Text( - mime::r#type::Text { + interpreted: mime::MIME { + interpreted: mime::r#type::Text { subtype: mime::r#type::TextSubtype::Plain, charset: mime::charset::EmailCharset::UTF_8, }, - mime::Generic { + parsed: mime::NaiveMIME { + ctype: Some(mime::r#type::NaiveType { + main: &b"text"[..], + sub: &b"plain"[..], + params: vec![ + mime::r#type::Parameter { + name: &b"charset"[..], + value: MIMEWord::Atom(&b"utf-8"[..]), + } + ] + }), transfer_encoding: mime::mechanism::Mechanism::QuotedPrintable, - ..mime::Generic::default() + ..mime::NaiveMIME::default() } - ), + }, body: &b"GZ\nOoOoO\noOoOoOoOo\noOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOo\noOoOoOoOoOoOoOoOoOoOoOoOoOoOo\nOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO\n"[..], }), AnyPart::Txt(Text { - interpreted: mime::Text( - mime::r#type::Text { + interpreted: mime::MIME { + interpreted: mime::r#type::Text { subtype: mime::r#type::TextSubtype::Html, charset: mime::charset::EmailCharset::US_ASCII, }, - mime::Generic::default(), - ), + + parsed: mime::NaiveMIME { + ctype: Some(mime::r#type::NaiveType { + main: &b"text"[..], + sub: &b"html"[..], + params: vec![ + mime::r#type::Parameter { + name: &b"charset"[..], + value: MIMEWord::Atom(&b"us-ascii"[..]), + } + ] + }), + ..mime::NaiveMIME::default() + }, + }, body: &br#"
GZ
OoOoO
oOoOoOoOo
diff --git a/src/part/discrete.rs b/src/part/discrete.rs index c252af9..cdaa5f9 100644 --- a/src/part/discrete.rs +++ b/src/part/discrete.rs @@ -4,7 +4,7 @@ use crate::mime; #[derive(PartialEq)] pub struct Text<'a> { - pub interpreted: mime::Text<'a>, + pub interpreted: mime::MIME<'a, mime::r#type::Text>, pub body: &'a [u8], } @@ -22,7 +22,7 @@ impl<'a> fmt::Debug for Text<'a> { #[derive(PartialEq)] pub struct Binary<'a> { - pub interpreted: mime::Binary<'a>, + pub interpreted: mime::MIME<'a, mime::r#type::Binary>, pub body: &'a [u8], } diff --git a/src/part/field.rs b/src/part/field.rs index 0046085..3f4a6dc 100644 --- a/src/part/field.rs +++ b/src/part/field.rs @@ -35,11 +35,11 @@ impl<'a> MixedField<'a> { } } -pub fn sections<'a, T: mime::WithDefaultType>(list: Vec>) -> (mime::AnyMIME<'a>, imf::Imf<'a>) { +pub fn sections<'a>(list: Vec>) -> (mime::NaiveMIME<'a>, imf::Imf<'a>) { let (v1, v2): (Vec, Vec<_>) = list.into_iter().partition(|v| v.mime().is_some()); - let mime = v1.into_iter().flat_map(MixedField::to_mime).collect::>(); + let mime = v1.into_iter().flat_map(MixedField::to_mime).collect::(); let imf = v2.into_iter().flat_map(MixedField::to_imf).collect::(); - (mime.into(), imf) + (mime, imf) } pub fn mixed_field(input: &[u8]) -> IResult<&[u8], MixedField> { diff --git a/src/part/mod.rs b/src/part/mod.rs index 977b39b..f692b81 100644 --- a/src/part/mod.rs +++ b/src/part/mod.rs @@ -65,13 +65,13 @@ pub fn to_anypart<'a>(m: AnyMIME<'a>, rpart: &'a [u8]) -> AnyPart<'a> { AnyMIME::Mult(a) => multipart(a)(rpart) .map(|(rest, multi)| AnyPart::Mult(multi.with_epilogue(rest))) .unwrap_or(AnyPart::Txt(Text { - interpreted: mime::Text::default(), + interpreted: mime::MIME::::default(), body: rpart, })), AnyMIME::Msg(a) => message(a)(rpart) .map(|(rest, msg)| AnyPart::Msg(msg.with_epilogue(rest))) .unwrap_or(AnyPart::Txt(Text { - interpreted: mime::Text::default(), + interpreted: mime::MIME::::default(), body: rpart, })), AnyMIME::Txt(a) => AnyPart::Txt(Text { diff --git a/src/text/misc_token.rs b/src/text/misc_token.rs index 29e3318..a4c1c13 100644 --- a/src/text/misc_token.rs +++ b/src/text/misc_token.rs @@ -23,7 +23,7 @@ pub fn phrase_list(input: &[u8]) -> IResult<&[u8], PhraseList> { map(separated_list1(tag(","), phrase), PhraseList)(input) } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum MIMEWord<'a> { Quoted(QuotedString<'a>), Atom(&'a [u8]), diff --git a/src/text/quoted.rs b/src/text/quoted.rs index f3b0dcb..eae66fb 100644 --- a/src/text/quoted.rs +++ b/src/text/quoted.rs @@ -10,7 +10,7 @@ use nom::{ use crate::text::ascii; use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl}; -#[derive(Debug, PartialEq, Default)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct QuotedString<'a>(pub Vec<&'a [u8]>); impl<'a> QuotedString<'a> {