diff --git a/src/rfc5322/mailbox.rs b/src/rfc5322/mailbox.rs index 125317b..6f9b383 100644 --- a/src/rfc5322/mailbox.rs +++ b/src/rfc5322/mailbox.rs @@ -113,7 +113,7 @@ pub fn addr_spec(input: &[u8]) -> IResult<&[u8], AddrSpec> { obs_local_part, tag(&[ascii::AT]), obs_domain, - many0(pair(tag(&[ascii::AT]), obs_domain)), // for compatibility reasons + many0(pair(tag(&[ascii::AT]), obs_domain)), // for compatibility reasons with ENRON )), |(local_part, _, domain, _)| AddrSpec { local_part, domain }, )(input) @@ -175,7 +175,10 @@ impl<'a> Domain<'a> { pub fn to_string(&self) -> String { match self { Domain::Atoms(v) => v.iter().map(|v| encoding_rs::UTF_8.decode_without_bom_handling(v).0.to_string()).collect::>().join("."), - Domain::Litteral(v) => v.iter().map(|v| encoding_rs::UTF_8.decode_without_bom_handling(v).0.to_string()).collect::>().join(" "), + Domain::Litteral(v) => { + let inner = v.iter().map(|v| encoding_rs::UTF_8.decode_without_bom_handling(v).0.to_string()).collect::>().join(" "); + format!("[{}]", inner) + } } } } @@ -235,39 +238,28 @@ pub fn is_dtext(c: u8) -> bool { #[cfg(test)] mod tests { use super::*; + use crate::text::quoted::QuotedString; #[test] fn test_addr_spec() { assert_eq!( - addr_spec("alice@example.com"), + addr_spec(b"alice@example.com"), Ok(( - "", + &b""[..], AddrSpec { - local_part: "alice".into(), - domain: "example.com".into() + local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"alice"[..]))]), + domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), } )) ); assert_eq!( - addr_spec("jsmith@[192.168.2.1]"), - Ok(( - "", - AddrSpec { - local_part: "jsmith".into(), - domain: "192.168.2.1".into() - } - )) + addr_spec(b"jsmith@[192.168.2.1]").unwrap().1.to_string(), + "jsmith@[192.168.2.1]".to_string(), ); assert_eq!( - addr_spec("jsmith@[IPv6:2001:db8::1]"), - Ok(( - "", - AddrSpec { - local_part: "jsmith".into(), - domain: "IPv6:2001:db8::1".into() - } - )) + addr_spec(b"jsmith@[IPv6:2001:db8::1]").unwrap().1.to_string(), + "jsmith@[IPv6:2001:db8::1]".to_string(), ); // UTF-8 @@ -285,52 +277,42 @@ mod tests { // ASCII Edge cases assert_eq!( - addr_spec("user+mailbox/department=shipping@example.com"), + addr_spec(b"user+mailbox/department=shipping@example.com").unwrap().1.to_string(), + "user+mailbox/department=shipping@example.com".to_string(), + ); + + assert_eq!( + addr_spec(b"!#$%&'*+-/=?^_`.{|}~@example.com").unwrap().1.to_string(), + "!#$%&'*+-/=?^_`.{|}~@example.com".to_string(), + ); + + assert_eq!( + addr_spec(r#""Abc@def"@example.com"#.as_bytes()), Ok(( - "", + &b""[..], AddrSpec { - local_part: "user+mailbox/department=shipping".into(), - domain: "example.com".into() + local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(vec![b"Abc@def"])))]), + domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), } )) ); assert_eq!( - addr_spec("!#$%&'*+-/=?^_`.{|}~@example.com"), + addr_spec(r#""Fred\ Bloggs"@example.com"#.as_bytes()), Ok(( - "", + &b""[..], AddrSpec { - local_part: "!#$%&'*+-/=?^_`.{|}~".into(), - domain: "example.com".into() + local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(vec![b"Fred", b" ", b"Bloggs"])))]), + domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), } )) ); assert_eq!( - addr_spec(r#""Abc@def"@example.com"#), + addr_spec(r#""Joe.\\Blow"@example.com"#.as_bytes()), Ok(( - "", + &b""[..], AddrSpec { - local_part: "Abc@def".into(), - domain: "example.com".into() - } - )) - ); - assert_eq!( - addr_spec(r#""Fred\ Bloggs"@example.com"#), - Ok(( - "", - AddrSpec { - local_part: "Fred Bloggs".into(), - domain: "example.com".into() - } - )) - ); - assert_eq!( - addr_spec(r#""Joe.\\Blow"@example.com"#), - Ok(( - "", - AddrSpec { - local_part: r#"Joe.\Blow"#.into(), - domain: "example.com".into() + local_part: LocalPart(vec![LocalPartToken::Word(Word::Quoted(QuotedString(vec![b"Joe.", &[ascii::BACKSLASH], b"Blow"])))]), + domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), } )) ); @@ -339,84 +321,98 @@ mod tests { #[test] fn test_mailbox() { assert_eq!( - mailbox(r#""Joe Q. Public" "#), + mailbox(r#""Joe Q. Public" "#.as_bytes()), Ok(( - "", + &b""[..], MailboxRef { - name: Some("Joe Q. Public".into()), + name: Some(Phrase(vec![Word::Quoted(QuotedString(vec![&b"Joe"[..], &[ascii::SP], &b"Q."[..], &[ascii::SP], &b"Public"[..]]))])), addrspec: AddrSpec { - local_part: "john.q.public".into(), - domain: "example.com".into(), + local_part: LocalPart(vec![ + LocalPartToken::Word(Word::Atom(&b"john"[..])), + LocalPartToken::Dot, + LocalPartToken::Word(Word::Atom(&b"q"[..])), + LocalPartToken::Dot, + LocalPartToken::Word(Word::Atom(&b"public"[..])), + ]), + domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]), } } )) ); assert_eq!( - mailbox(r#"Mary Smith "#), + mailbox(r#"Mary Smith "#.as_bytes()), Ok(( - "", + &b""[..], MailboxRef { - name: Some("Mary Smith".into()), + name: Some(Phrase(vec![Word::Atom(&b"Mary"[..]), Word::Atom(&b"Smith"[..])])), addrspec: AddrSpec { - local_part: "mary".into(), - domain: "x.test".into(), + local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"mary"[..]))]), + domain: Domain::Atoms(vec![&b"x"[..], &b"test"[..]]), } } )) ); assert_eq!( - mailbox(r#"jdoe@example.org"#), + mailbox(r#"jdoe@example.org"#.as_bytes()), Ok(( - "", + &b""[..], MailboxRef { name: None, addrspec: AddrSpec { - local_part: "jdoe".into(), - domain: "example.org".into(), + local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"jdoe"[..]))]), + domain: Domain::Atoms(vec![&b"example"[..], &b"org"[..]]), } } )) ); assert_eq!( - mailbox(r#"Who? "#), + mailbox(r#"Who? "#.as_bytes()), Ok(( - "", + &b""[..], MailboxRef { - name: Some("Who?".into()), + name: Some(Phrase(vec![Word::Atom(&b"Who?"[..])])), addrspec: AddrSpec { - local_part: "one".into(), - domain: "y.test".into(), + local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"one"[..]))]), + domain: Domain::Atoms(vec![&b"y"[..], &b"test"[..]]), } } )) ); assert_eq!( - mailbox(r#""#), + mailbox(r#""#.as_bytes()), Ok(( - "", + &b""[..], MailboxRef { name: None, addrspec: AddrSpec { - local_part: "boss".into(), - domain: "nil.test".into(), + local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"boss"[..]))]), + domain: Domain::Atoms(vec![&b"nil"[..], &b"test"[..]]), } } )) ); assert_eq!( - mailbox(r#""Giant; \"Big\" Box" "#), + mailbox(r#""Giant; \"Big\" Box" "#.as_bytes()), Ok(( - "", + &b""[..], MailboxRef { - name: Some(r#"Giant; "Big" Box"#.into()), + name: Some(Phrase(vec![Word::Quoted(QuotedString(vec![ + &b"Giant;"[..], + &[ascii::SP], + &[ascii::DQUOTE], + &b"Big"[..], + &[ascii::DQUOTE], + &[ascii::SP], + &b"Box"[..] + ]))])), addrspec: AddrSpec { - local_part: "sysservices".into(), - domain: "example.net".into(), + local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"sysservices"[..]))]), + domain: Domain::Atoms(vec![&b"example"[..], &b"net"[..]]), } } )) @@ -433,17 +429,20 @@ mod tests { @33+4.com,,,, ,,,, (again) - @example.com,@yep.com,@a,@b,,,@c"# + @example.com,@yep.com,@a,@b,,,@c"#.as_bytes() ), Ok(( - "", + &b""[..], vec![ - "33+4.com".into(), - "example.com".into(), - "yep.com".into(), - "a".into(), - "b".into(), - "c".into() + None, + Some(Domain::Atoms(vec![&b"33+4"[..], &b"com"[..]])), + None, None, None, None, None, None, None, + Some(Domain::Atoms(vec![&b"example"[..], &b"com"[..]])), + Some(Domain::Atoms(vec![&b"yep"[..], &b"com"[..]])), + Some(Domain::Atoms(vec![&b"a"[..]])), + Some(Domain::Atoms(vec![&b"b"[..]])), + None, None, + Some(Domain::Atoms(vec![&b"c"[..]])), ] )) ); @@ -452,12 +451,17 @@ mod tests { #[test] fn test_enron1() { assert_eq!( - addr_spec("a..howard@enron.com"), + addr_spec("a..howard@enron.com".as_bytes()), Ok(( - "", + &b""[..], AddrSpec { - local_part: "a..howard".into(), - domain: "enron.com".into(), + local_part: LocalPart(vec![ + LocalPartToken::Word(Word::Atom(&b"a"[..])), + LocalPartToken::Dot, + LocalPartToken::Dot, + LocalPartToken::Word(Word::Atom(&b"howard"[..])), + ]), + domain: Domain::Atoms(vec![&b"enron"[..], &b"com"[..]]), } )) ); @@ -466,12 +470,15 @@ mod tests { #[test] fn test_enron2() { assert_eq!( - addr_spec(".nelson@enron.com"), + addr_spec(".nelson@enron.com".as_bytes()), Ok(( - "", + &b""[..], AddrSpec { - local_part: ".nelson".into(), - domain: "enron.com".into(), + local_part: LocalPart(vec![ + LocalPartToken::Dot, + LocalPartToken::Word(Word::Atom(&b"nelson"[..])), + ]), + domain: Domain::Atoms(vec![&b"enron"[..], &b"com"[..]]), } )) ); @@ -480,12 +487,17 @@ mod tests { #[test] fn test_enron3() { assert_eq!( - addr_spec("ecn2760.conf.@enron.com"), + addr_spec("ecn2760.conf.@enron.com".as_bytes()), Ok(( - "", + &b""[..], AddrSpec { - local_part: "ecn2760.conf.".into(), - domain: "enron.com".into(), + local_part: LocalPart(vec![ + LocalPartToken::Word(Word::Atom(&b"ecn2760"[..])), + LocalPartToken::Dot, + LocalPartToken::Word(Word::Atom(&b"conf"[..])), + LocalPartToken::Dot, + ]), + domain: Domain::Atoms(vec![&b"enron"[..], &b"com"[..]]), } )) ); @@ -494,14 +506,18 @@ mod tests { #[test] fn test_enron4() { assert_eq!( - mailbox(r#"<"mark_kopinski/intl/acim/americancentury"@americancentury.com@enron.com>"#), + mailbox(r#"<"mark_kopinski/intl/acim/americancentury"@americancentury.com@enron.com>"#.as_bytes()), Ok(( - "", + &b""[..], MailboxRef { name: None, addrspec: AddrSpec { - local_part: "mark_kopinski/intl/acim/americancentury".into(), - domain: "americancentury.com".into(), + local_part: LocalPart(vec![ + LocalPartToken::Word(Word::Quoted(QuotedString(vec![ + &b"mark_kopinski/intl/acim/americancentury"[..], + ]))) + ]), + domain: Domain::Atoms(vec![&b"americancentury"[..], &b"com"[..]]), } } )) diff --git a/src/text/buffer.rs b/src/text/buffer.rs deleted file mode 100644 index e1b4c1f..0000000 --- a/src/text/buffer.rs +++ /dev/null @@ -1,42 +0,0 @@ -#[derive(Debug, PartialEq, Default)] -pub struct Text<'a> { - parts: Vec<&'a [u8]>, -} - -impl<'a> Text<'a> { - pub fn push(&mut self, e: &'a [u8]) { - self.parts.push(e) - } - - pub fn to_string(&self) -> String { - let enc = encoding_rs::UTF_8; - let size = self.parts.iter().fold(0, |acc, v| acc + v.len()); - - self.parts.iter().fold( - String::with_capacity(size), - |mut acc, v| { - let (content, _) = enc.decode_without_bom_handling(v); - acc.push_str(content.as_ref()); - acc - }, - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::text::ascii; - - #[test] - fn test_text() { - let mut text = Text::default(); - text.push(b"hello"); - text.push(&[ascii::SP]); - text.push(b"world"); - assert_eq!( - text.to_string(), - "hello world".to_string(), - ); - } -} diff --git a/src/text/misc_token.rs b/src/text/misc_token.rs index 2f43edb..38af493 100644 --- a/src/text/misc_token.rs +++ b/src/text/misc_token.rs @@ -9,11 +9,10 @@ use nom::{ }; use crate::text::{ - quoted::quoted_string, + quoted::{QuotedString, quoted_string}, whitespace::{fws, is_obs_no_ws_ctl}, words::{atom, is_vchar}, encoding::{self, encoded_word}, - buffer, ascii, }; @@ -43,7 +42,7 @@ impl<'a> TryFrom<&'a lazy::PhraseList<'a>> for PhraseList { #[derive(Debug, PartialEq)] pub enum Word<'a> { - Quoted(buffer::Text<'a>), + Quoted(QuotedString<'a>), Encoded(encoding::EncodedWord<'a>), Atom(&'a [u8]), } diff --git a/src/text/mod.rs b/src/text/mod.rs index 6baecdb..a0e5482 100644 --- a/src/text/mod.rs +++ b/src/text/mod.rs @@ -4,4 +4,3 @@ pub mod misc_token; pub mod quoted; pub mod whitespace; pub mod words; -pub mod buffer; diff --git a/src/text/quoted.rs b/src/text/quoted.rs index 2f33688..fe4ff72 100644 --- a/src/text/quoted.rs +++ b/src/text/quoted.rs @@ -9,7 +9,29 @@ use nom::{ use crate::text::whitespace::{cfws, fws, is_obs_no_ws_ctl}; use crate::text::ascii; -use crate::text::buffer; + +#[derive(Debug, PartialEq, Default)] +pub struct QuotedString<'a>(pub Vec<&'a [u8]>); + +impl<'a> QuotedString<'a> { + pub fn push(&mut self, e: &'a [u8]) { + self.0.push(e) + } + + pub fn to_string(&self) -> String { + let enc = encoding_rs::UTF_8; + let size = self.0.iter().fold(0, |acc, v| acc + v.len()); + + self.0.iter().fold( + String::with_capacity(size), + |mut acc, v| { + let (content, _) = enc.decode_without_bom_handling(v); + acc.push_str(content.as_ref()); + acc + }, + ) + } +} /// Quoted pair /// @@ -55,7 +77,7 @@ fn qcontent(input: &[u8]) -> IResult<&[u8], &[u8]> { /// DQUOTE *([FWS] qcontent) [FWS] DQUOTE /// [CFWS] /// ``` -pub fn quoted_string(input: &[u8]) -> IResult<&[u8], buffer::Text> { +pub fn quoted_string(input: &[u8]) -> IResult<&[u8], QuotedString> { let (input, _) = opt(cfws)(input)?; let (input, _) = tag("\"")(input)?; let (input, content) = many0(pair(opt(fws), qcontent))(input)?; @@ -63,7 +85,7 @@ pub fn quoted_string(input: &[u8]) -> IResult<&[u8], buffer::Text> { // Rebuild string let mut qstring = content .iter() - .fold(buffer::Text::default(), |mut acc, (maybe_wsp, c)| { + .fold(QuotedString::default(), |mut acc, (maybe_wsp, c)| { if let Some(_) = maybe_wsp { acc.push(&[ascii::SP]); } @@ -86,23 +108,25 @@ mod tests { use super::*; #[test] - fn test_quoted_string() { - let mut text = buffer::Text::default(); - text.push(b"hello"); - text.push(&[ascii::DQUOTE]); - text.push(b"world"); + fn test_quoted_string_parser() { assert_eq!( - quoted_string(b" \"hello\\\"world\" "), - Ok((&b""[..], text)) + quoted_string(b" \"hello\\\"world\" ").unwrap().1, + QuotedString(vec![b"hello", &[ascii::DQUOTE], b"world"]) ); - let mut text = buffer::Text::default(); - text.push(b"hello"); - text.push(&[ascii::SP]); - text.push(b"world"); assert_eq!( quoted_string(b"\"hello\r\n world\""), - Ok((&b""[..], text)) + Ok((&b""[..], QuotedString(vec![b"hello", &[ascii::SP], b"world"]))), + ); + } + + use crate::text::ascii; + + #[test] + fn test_quoted_string_object() { + assert_eq!( + QuotedString(vec![b"hello", &[ascii::SP], b"world"]).to_string(), + "hello world".to_string(), ); } }