diff --git a/README.md b/README.md index 8517f7f..2f3a205 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Todo: | 🔴 |2049 | ↳ Multipurpose Internet Mail Extensions (MIME) Part Five: Conformance Criteria and Examples | | | | **Headers extensions** | | 🔴 |2183 | ↳ Communicating Presentation Information in Internet Messages: The Content-Disposition Header Field | -| 🟩 |6532 | ↳ Internationalized Email Headers | +| 🔴 |6532 | ↳ Internationalized Email Headers | | 🔴 |9228 | ↳ Delivered-To Email Header Field | | | | **MIME extensions** | | 🔴 |1847 | ↳ Security Multiparts for MIME: Multipart/Signed and Multipart/Encrypted | diff --git a/src/text/buffer.rs b/src/text/buffer.rs index bd9cbb8..f2973a5 100644 --- a/src/text/buffer.rs +++ b/src/text/buffer.rs @@ -1,12 +1,10 @@ -use encoding_rs::Encoding; - #[derive(Debug, PartialEq, Default)] pub struct Text<'a> { parts: Vec<&'a [u8]>, } impl<'a> Text<'a> { - pub fn push(&mut self, e: &[u8]) { + pub fn push(&mut self, e: &'a [u8]) { self.parts.push(e) } diff --git a/src/text/encoding.rs b/src/text/encoding.rs index 84a2c0c..610c127 100644 --- a/src/text/encoding.rs +++ b/src/text/encoding.rs @@ -48,6 +48,14 @@ pub enum EncodedWord<'a> { Quoted(QuotedWord<'a>), Base64(Base64Word<'a>), } +impl<'a> EncodedWord<'a> { + pub fn to_string(&self) -> String { + match self { + EncodedWord::Quoted(v) => v.to_string(), + EncodedWord::Base64(v) => v.to_string(), + } + } +} #[derive(PartialEq,Debug)] pub struct Base64Word<'a> { diff --git a/src/text/misc_token.rs b/src/text/misc_token.rs index 35869fe..cb3120a 100644 --- a/src/text/misc_token.rs +++ b/src/text/misc_token.rs @@ -2,12 +2,11 @@ use nom::{ branch::alt, bytes::complete::take_while1, character::complete::space0, - combinator::{into, map, opt}, + combinator::{map, opt}, multi::{many0, many1}, - sequence::{preceded, tuple}, + sequence::{preceded}, IResult, }; -use std::borrow::Cow; use crate::text::{ quoted::quoted_string, @@ -52,7 +51,7 @@ impl<'a> Word<'a> { match self { Word::Quoted(v) => v.to_string(), Word::Encoded(v) => v.to_string(), - Word::Atom(v) => v.to_string(), + Word::Atom(v) => encoding_rs::UTF_8.decode_without_bom_handling(v).0.to_string(), } } } @@ -73,7 +72,7 @@ pub fn word(input: &[u8]) -> IResult<&[u8], Word> { pub struct Phrase<'a>(pub Vec>); impl<'a> Phrase<'a> { pub fn to_string(&self) -> String { - self.0.join(" ") + self.0.iter().map(|v| v.to_string()).collect::>().join(" ") } } @@ -96,7 +95,7 @@ fn is_unstructured(c: u8) -> bool { is_vchar(c) || is_obs_no_ws_ctl(c) || c == ascii::NULL } -enum UnstrToken<'a> { +pub enum UnstrToken<'a> { Init, Encoded(encoding::EncodedWord<'a>), Plain(&'a [u8]), @@ -106,7 +105,7 @@ impl<'a> UnstrToken<'a> { match self { UnstrToken::Init => "".into(), UnstrToken::Encoded(e) => e.to_string(), - UnstrToken::Plain(e) => encoding_rs::UTF_8.decode_without_bom_handling(e).into_owned(), + UnstrToken::Plain(e) => encoding_rs::UTF_8.decode_without_bom_handling(e).0.into_owned(), } } } @@ -116,19 +115,19 @@ impl<'a> Unstructured<'a> { pub fn to_string(&self) -> String { self.0.iter().fold( (&UnstrToken::Init, String::new()), - |(prev_token, result), current_token| { + |(prev_token, mut result), current_token| { match (prev_token, current_token) { (UnstrToken::Init, v) => result.push_str(v.to_string().as_ref()), - (UnstrToken::EncodedWord(_), UnstrToken::EncodedWord(v)) => result.push_str(v.to_string()).as_ref(), + (UnstrToken::Encoded(_), UnstrToken::Encoded(v)) => result.push_str(v.to_string().as_ref()), (_, v) => { result.push(' '); result.push_str(v.to_string().as_ref()) }, }; - result + (current_token, result) } - ) + ).1 } } diff --git a/src/text/quoted.rs b/src/text/quoted.rs index 78ef7a3..a36066a 100644 --- a/src/text/quoted.rs +++ b/src/text/quoted.rs @@ -1,8 +1,7 @@ use nom::{ branch::alt, - bytes::complete::{take_while1, tag}, - character::complete::anychar, - combinator::{recognize, opt}, + bytes::complete::{take_while1, take, tag}, + combinator::{opt}, multi::many0, sequence::{pair, preceded}, IResult, @@ -18,10 +17,12 @@ use crate::text::buffer; /// quoted-pair = ("\" (VCHAR / WSP)) / obs-qp /// obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR) /// ``` -pub fn quoted_pair(input: &[u8]) -> IResult<&[u8], u8> { - preceded(tag(&[ascii::SLASH]), anychar)(input) +pub fn quoted_pair(input: &[u8]) -> IResult<&[u8], &[u8]> { + preceded(tag(&[ascii::SLASH]), take(1usize))(input) } + + /// Allowed characters in quote /// /// ```abnf @@ -43,8 +44,8 @@ fn is_qtext(c: u8) -> bool { /// ```abnf /// qcontent = qtext / quoted-pair /// ``` -fn qcontent(input: &u8) -> IResult<&[u8], &[u8]> { - alt((take_while1(is_qtext), recognize(quoted_pair)))(input) +fn qcontent(input: &[u8]) -> IResult<&[u8], &[u8]> { + alt((take_while1(is_qtext), quoted_pair))(input) } /// Quoted string @@ -63,7 +64,7 @@ pub fn quoted_string(input: &[u8]) -> IResult<&[u8], buffer::Text> { let mut qstring = content .iter() .fold(buffer::Text::default(), |mut acc, (maybe_wsp, c)| { - if let Some(wsp) = maybe_wsp { + if let Some(_) = maybe_wsp { acc.push(&[ascii::SP]); } acc.push(c); @@ -71,8 +72,8 @@ pub fn quoted_string(input: &[u8]) -> IResult<&[u8], buffer::Text> { }); let (input, maybe_wsp) = opt(fws)(input)?; - if let Some(wsp) = maybe_wsp { - qstring.push(wsp); + if let Some(_) = maybe_wsp { + qstring.push(&[ascii::SP]); } let (input, _) = tag("\"")(input)?;