From 32b4c30feecb43586843900e2a77c094437a18c9 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 26 Jul 2020 16:08:22 +0300 Subject: [PATCH] melib/email.rs: use SmallVec for Address fields --- melib/src/backends/imap/protocol_parser.rs | 20 ++- melib/src/backends/jmap/objects/email.rs | 18 +-- melib/src/email.rs | 134 ++++++++++++++++----- melib/src/email/parser.rs | 70 ++++++++++- 4 files changed, 191 insertions(+), 51 deletions(-) diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index b0f969a6..760397ae 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -29,7 +29,7 @@ use nom::{ character::complete::digit1, character::is_digit, combinator::{map, map_res, opt}, - multi::{length_data, many0, many1, separated_list, separated_nonempty_list}, + multi::{fold_many1, length_data, many0, separated_list, separated_nonempty_list}, sequence::{delimited, preceded}, }; use std::str::FromStr; @@ -1179,7 +1179,7 @@ pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> { } if let Some(bcc) = bcc { - env.set_bcc(bcc); + env.set_bcc(bcc.to_vec()); } if let Some(in_reply_to) = in_reply_to { env.set_in_reply_to(&in_reply_to); @@ -1268,13 +1268,21 @@ macro_rules! str_builder { } // Parse a list of addresses in the format of the ENVELOPE structure -pub fn envelope_addresses<'a>(input: &'a [u8]) -> IResult<&'a [u8], Option>> { +pub fn envelope_addresses<'a>( + input: &'a [u8], +) -> IResult<&'a [u8], Option>> { alt(( map(tag("NIL"), |_| None), - |input: &'a [u8]| -> IResult<&'a [u8], Option>> { + |input: &'a [u8]| -> IResult<&'a [u8], Option>> { let (input, _) = tag("(")(input)?; - let (input, envelopes) = - many1(delimited(tag("("), envelope_address, tag(")")))(input.ltrim())?; + let (input, envelopes) = fold_many1( + delimited(tag("("), envelope_address, tag(")")), + SmallVec::new(), + |mut acc, item| { + acc.push(item); + acc + }, + )(input.ltrim())?; let (input, _) = tag(")")(input)?; Ok((input, Some(envelopes))) }, diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 52c0b12a..ebfab0c0 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -141,17 +141,17 @@ pub struct EmailObject { #[serde(default)] message_id: Vec, #[serde(default)] - to: Vec, + to: SmallVec<[EmailAddress; 1]>, #[serde(default)] bcc: Option>, #[serde(default)] reply_to: Option>, #[serde(default)] - cc: Option>, + cc: Option>, #[serde(default)] sender: Option>, #[serde(default)] - from: Vec, + from: SmallVec<[EmailAddress; 1]>, #[serde(default)] in_reply_to: Option>, #[serde(default)] @@ -269,24 +269,24 @@ impl std::convert::From for crate::Envelope { } env.set_from( - std::mem::replace(&mut t.from, Vec::new()) + std::mem::replace(&mut t.from, SmallVec::new()) .into_iter() .map(|addr| addr.into()) - .collect::>(), + .collect::>(), ); env.set_to( - std::mem::replace(&mut t.to, Vec::new()) + std::mem::replace(&mut t.to, SmallVec::new()) .into_iter() .map(|addr| addr.into()) - .collect::>(), + .collect::>(), ); if let Some(ref mut cc) = t.cc { env.set_cc( - std::mem::replace(cc, Vec::new()) + std::mem::replace(cc, SmallVec::new()) .into_iter() .map(|addr| addr.into()) - .collect::>(), + .collect::>(), ); } diff --git a/melib/src/email.rs b/melib/src/email.rs index 8bf366b4..092603c1 100644 --- a/melib/src/email.rs +++ b/melib/src/email.rs @@ -131,9 +131,9 @@ pub type EnvelopeHash = u64; #[derive(Clone, Serialize, Deserialize)] pub struct Envelope { date: String, - from: Vec
, - to: Vec
, - cc: Vec
, + from: SmallVec<[Address; 1]>, + to: SmallVec<[Address; 1]>, + cc: SmallVec<[Address; 1]>, bcc: Vec
, subject: Option, message_id: MessageID, @@ -175,9 +175,9 @@ impl Envelope { pub fn new(hash: EnvelopeHash) -> Self { Envelope { date: String::new(), - from: Vec::new(), - to: Vec::new(), - cc: Vec::new(), + from: SmallVec::new(), + to: SmallVec::new(), + cc: SmallVec::new(), bcc: Vec::new(), subject: None, message_id: MessageID::default(), @@ -279,7 +279,7 @@ impl Envelope { let parse_result = parser::address::rfc2822address_list(value); if parse_result.is_ok() { let value = parse_result.unwrap().1; - self.set_bcc(value); + self.set_bcc(value.to_vec()); }; } else if name.eq_ignore_ascii_case(b"from") { let parse_result = parser::address::rfc2822address_list(value); @@ -384,31 +384,102 @@ impl Envelope { pub fn date_as_str(&self) -> &str { &self.date } - pub fn from(&self) -> &Vec
{ - &self.from + pub fn from(&self) -> &[Address] { + self.from.as_slice() } pub fn field_bcc_to_string(&self) -> String { - let _strings: Vec = self.bcc.iter().map(|a| format!("{}", a)).collect(); - _strings.join(", ") + if self.bcc.is_empty() { + self.other_headers + .get("Bcc") + .map(|s| s.as_str()) + .unwrap_or_default() + .to_string() + } else { + self.bcc.iter().fold(String::new(), |mut acc, x| { + if !acc.is_empty() { + acc.push_str(", "); + } + acc.push_str(&x.to_string()); + acc + }) + } } pub fn field_cc_to_string(&self) -> String { - let _strings: Vec = self.cc.iter().map(|a| format!("{}", a)).collect(); - _strings.join(", ") + if self.cc.is_empty() { + self.other_headers + .get("Cc") + .map(|s| s.as_str()) + .unwrap_or_default() + .to_string() + } else { + self.cc.iter().fold(String::new(), |mut acc, x| { + if !acc.is_empty() { + acc.push_str(", "); + } + acc.push_str(&x.to_string()); + acc + }) + } } pub fn field_from_to_string(&self) -> String { - let _strings: Vec = self.from().iter().map(|a| format!("{}", a)).collect(); - _strings.join(", ") + if self.from.is_empty() { + self.other_headers + .get("From") + .map(|s| s.as_str()) + .unwrap_or_default() + .to_string() + } else { + self.from.iter().fold(String::new(), |mut acc, x| { + if !acc.is_empty() { + acc.push_str(", "); + } + acc.push_str(&x.to_string()); + acc + }) + } } - pub fn to(&self) -> &Vec
{ - &self.to + pub fn to(&self) -> &[Address] { + self.to.as_slice() } pub fn field_to_to_string(&self) -> String { - let _strings: Vec = self.to.iter().map(|a| format!("{}", a)).collect(); - _strings.join(", ") + if self.to.is_empty() { + self.other_headers + .get("To") + .map(|s| s.as_str()) + .unwrap_or_default() + .to_string() + } else { + self.to + .iter() + .map(|a| format!("{}", a)) + .fold(String::new(), |mut acc, x| { + if !acc.is_empty() { + acc.push_str(", "); + } + acc.push_str(&x); + acc + }) + } } pub fn field_references_to_string(&self) -> String { - let _strings: Vec = self.references().iter().map(|a| a.to_string()).collect(); - _strings.join(", ") + let refs = self.references(); + if refs.is_empty() { + self.other_headers + .get("References") + .map(|s| s.as_str()) + .unwrap_or_default() + .to_string() + } else { + refs.iter() + .map(|a| a.to_string()) + .fold(String::new(), |mut acc, x| { + if !acc.is_empty() { + acc.push_str(", "); + } + acc.push_str(&x); + acc + }) + } } pub fn body_bytes(&self, bytes: &[u8]) -> Attachment { @@ -474,13 +545,13 @@ impl Envelope { pub fn set_bcc(&mut self, new_val: Vec
) { self.bcc = new_val; } - pub fn set_cc(&mut self, new_val: Vec
) { + pub fn set_cc(&mut self, new_val: SmallVec<[Address; 1]>) { self.cc = new_val; } - pub fn set_from(&mut self, new_val: Vec
) { + pub fn set_from(&mut self, new_val: SmallVec<[Address; 1]>) { self.from = new_val; } - pub fn set_to(&mut self, new_val: Vec
) { + pub fn set_to(&mut self, new_val: SmallVec<[Address; 1]>) { self.to = new_val; } pub fn set_in_reply_to(&mut self, new_val: &[u8]) { @@ -563,16 +634,13 @@ impl Envelope { } } } - pub fn references(&self) -> Vec<&MessageID> { + pub fn references(&self) -> SmallVec<[&MessageID; 8]> { match self.references { - Some(ref s) => s - .refs - .iter() - .fold(Vec::with_capacity(s.refs.len()), |mut acc, x| { - acc.push(x); - acc - }), - None => Vec::new(), + Some(ref s) => s.refs.iter().fold(SmallVec::new(), |mut acc, x| { + acc.push(x); + acc + }), + None => SmallVec::new(), } } diff --git a/melib/src/email/parser.rs b/melib/src/email/parser.rs index 8e46fb0e..6f29fb8b 100644 --- a/melib/src/email/parser.rs +++ b/melib/src/email/parser.rs @@ -1348,8 +1348,8 @@ pub mod address { // ws!(alt_complete!(mailbox | group)) } - pub fn rfc2822address_list(input: &[u8]) -> IResult<&[u8], Vec
> { - separated_list(is_a(","), address)(input.ltrim()) + pub fn rfc2822address_list(input: &[u8]) -> IResult<&[u8], SmallVec<[Address; 1]>> { + separated_list_smallvec(is_a(","), address)(input.ltrim()) // ws!( separated_list!(is_a!(","), address)) } @@ -1417,6 +1417,70 @@ pub mod address { separated_list(is_a(" \n\t\r"), message_id_peek)(input) // separated_list!(complete!(is_a!(" \n\t\r")), message_id_peek)); } + + use smallvec::SmallVec; + pub fn separated_list_smallvec( + sep: G, + f: F, + ) -> impl FnMut(I) -> IResult, E> + where + I: Clone + PartialEq, + F: Fn(I) -> IResult, + G: Fn(I) -> IResult, + E: nom::error::ParseError, + { + move |i: I| { + let mut res = SmallVec::new(); + let mut i = i.clone(); + + // Parse the first element + match f(i.clone()) { + Err(e) => return Err(e), + Ok((i1, o)) => { + if i1 == i { + return Err(nom::Err::Error(E::from_error_kind( + i1, + ErrorKind::SeparatedList, + ))); + } + + res.push(o); + i = i1; + } + } + + loop { + match sep(i.clone()) { + Err(nom::Err::Error(_)) => return Ok((i, res)), + Err(e) => return Err(e), + Ok((i1, _)) => { + if i1 == i { + return Err(nom::Err::Error(E::from_error_kind( + i1, + ErrorKind::SeparatedList, + ))); + } + + match f(i1.clone()) { + Err(nom::Err::Error(_)) => return Ok((i, res)), + Err(e) => return Err(e), + Ok((i2, o)) => { + if i2 == i { + return Err(nom::Err::Error(E::from_error_kind( + i2, + ErrorKind::SeparatedList, + ))); + } + + res.push(o); + i = i2; + } + } + } + } + } + } + } } #[cfg(test)] @@ -1497,7 +1561,7 @@ mod tests { assert_eq!( ( &s[0..0], - vec![ + smallvec::smallvec![ make_address!("Obit Oppidum", "user@domain"), make_address!("list", "list@domain.tld"), make_address!("list2", "list2@domain.tld"),