From e91f22cb4f1c2324847e7895d25e6a5446929cd0 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 4 Aug 2018 20:40:20 +0300 Subject: [PATCH] Remove some string conversions in parsing --- melib/src/mailbox/email/attachments.rs | 95 +++++++++------- melib/src/mailbox/email/mod.rs | 123 ++++++++++---------- melib/src/mailbox/email/parser.rs | 151 +++++++++++++++---------- melib/src/mailbox/thread.rs | 38 ++++--- ui/src/components/utilities.rs | 2 +- 5 files changed, 226 insertions(+), 183 deletions(-) diff --git a/melib/src/mailbox/email/attachments.rs b/melib/src/mailbox/email/attachments.rs index 66a2d6aa..639db8c8 100644 --- a/melib/src/mailbox/email/attachments.rs +++ b/melib/src/mailbox/email/attachments.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ use mailbox::email::parser; +use mailbox::email::parser::BytesExt; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::str; @@ -28,7 +29,7 @@ use data_encoding::BASE64_MIME; /* * * Data - * Text { content: String } + * Text { content: Vec } * Multipart */ @@ -37,7 +38,7 @@ pub enum MultipartType { Mixed, Alternative, Digest, - Unsupported { tag: String }, + Unsupported { tag: Vec }, } impl Display for MultipartType { @@ -46,7 +47,7 @@ impl Display for MultipartType { MultipartType::Mixed => write!(f, "multipart/mixed"), MultipartType::Alternative => write!(f, "multipart/alternative"), MultipartType::Digest => write!(f, "multipart/digest"), - MultipartType::Unsupported { tag: ref t } => write!(f, "multipart/{}", t), + MultipartType::Unsupported { tag: ref t } => write!(f, "multipart/{}", String::from_utf8_lossy(t)), } } } @@ -54,10 +55,10 @@ impl Display for MultipartType { #[derive(Clone, Debug)] pub enum AttachmentType { Data { - tag: String, + tag: Vec, }, Text { - content: String, + content: Vec, }, Multipart { of_type: MultipartType, @@ -68,8 +69,8 @@ pub enum AttachmentType { impl Display for AttachmentType { fn fmt(&self, f: &mut Formatter) -> FmtResult { match self { - AttachmentType::Data { tag: ref t } => write!(f, "{}", t), - AttachmentType::Text { content: ref c } => write!(f, "{}", c), + AttachmentType::Data { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)), + AttachmentType::Text { content: ref c } => write!(f, "{}", String::from_utf8_lossy(c)), AttachmentType::Multipart { of_type: ref t, .. } => write!(f, "{}", t), } } @@ -77,8 +78,8 @@ impl Display for AttachmentType { #[derive(Clone, Debug)] pub enum ContentType { Text, - Multipart { boundary: String }, - Unsupported { tag: String }, + Multipart { boundary: Vec }, + Unsupported { tag: Vec }, } impl Display for ContentType { @@ -86,20 +87,20 @@ impl Display for ContentType { match *self { ContentType::Text => write!(f, "text"), ContentType::Multipart { .. } => write!(f, "multipart"), - ContentType::Unsupported { tag: ref t } => write!(f, "{}", t), + ContentType::Unsupported { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)), } } } #[derive(Clone, Debug, PartialEq)] pub enum ContentSubType { Plain, - Other { tag: String }, + Other { tag: Vec }, } impl Display for ContentSubType { fn fmt(&self, f: &mut Formatter) -> FmtResult { match *self { ContentSubType::Plain => write!(f, "plain"), - ContentSubType::Other { tag: ref t } => write!(f, "{}", t), + ContentSubType::Other { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)), } } } @@ -109,7 +110,7 @@ pub enum ContentTransferEncoding { _7Bit, Base64, QuotedPrintable, - Other { tag: String }, + Other { tag: Vec }, } /// TODO: Add example. @@ -129,13 +130,17 @@ impl AttachmentBuilder { raw: content.to_vec(), } } - pub fn content_type(&mut self, value: &str) -> &Self { - match parser::content_type(value.as_bytes()).to_full_result() { - Ok((ct, cst, params)) => if ct.eq_ignore_ascii_case("multipart") { + pub fn content_type(&mut self, value: &[u8]) -> &Self { + match parser::content_type(value).to_full_result() { + Ok((ct, cst, params)) => if ct.eq_ignore_ascii_case(b"multipart") { let mut boundary = None; for (n, v) in params { - if n.eq_ignore_ascii_case("boundary") { - boundary = Some(format!("--{}--", v).to_string()); + if n.eq_ignore_ascii_case(b"boundary") { + let mut vec: Vec = Vec::with_capacity(v.len()+4); + vec.extend_from_slice(b"--"); + vec.extend(v); + vec.extend_from_slice(b"--"); + boundary = Some(vec); break; } } @@ -144,11 +149,11 @@ impl AttachmentBuilder { boundary: boundary.unwrap(), }; self.content_type.1 = ContentSubType::Other { - tag: cst.to_string(), + tag: cst.into(), }; - } else if ct.eq_ignore_ascii_case("text") { + } else if ct.eq_ignore_ascii_case(b"text") { self.content_type.0 = ContentType::Text; - if !cst.eq_ignore_ascii_case("plain") { + if !cst.eq_ignore_ascii_case(b"plain") { self.content_type.1 = ContentSubType::Other { tag: cst.to_ascii_lowercase(), }; @@ -167,14 +172,14 @@ impl AttachmentBuilder { } self } - pub fn content_transfer_encoding(&mut self, value: &str) -> &Self { - self.content_transfer_encoding = if value.eq_ignore_ascii_case("base64") { + pub fn content_transfer_encoding(&mut self, value: &[u8]) -> &Self { + self.content_transfer_encoding = if value.eq_ignore_ascii_case(b"base64") { ContentTransferEncoding::Base64 - } else if value.eq_ignore_ascii_case("7bit") { + } else if value.eq_ignore_ascii_case(b"7bit") { ContentTransferEncoding::_7Bit - } else if value.eq_ignore_ascii_case("8bit") { + } else if value.eq_ignore_ascii_case(b"8bit") { ContentTransferEncoding::_8Bit - } else if value.eq_ignore_ascii_case("quoted-printable") { + } else if value.eq_ignore_ascii_case(b"quoted-printable") { ContentTransferEncoding::QuotedPrintable } else { ContentTransferEncoding::Other { @@ -183,7 +188,7 @@ impl AttachmentBuilder { }; self } - fn decode(&self) -> String { + fn decode(&self) -> Vec { // TODO: Use charset for decoding match self.content_transfer_encoding { ContentTransferEncoding::Base64 => match BASE64_MIME.decode( @@ -197,15 +202,17 @@ impl AttachmentBuilder { }) .as_bytes(), ) { - Ok(ref v) => { - let s = String::from_utf8_lossy(v); - if s.find("\r\n").is_some() { - s.replace("\r\n", "\n") - } else { - s.into_owned() + Ok(ref s) => { + let s:Vec = s.clone(); + { + let slice = &s[..]; + if slice.find(b"\r\n").is_some() { + s.replace(b"\r\n", b"\n"); } + } + s } - _ => String::from_utf8_lossy(&self.raw).into_owned(), + _ => self.raw.clone() }, ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw) .to_full_result() @@ -213,7 +220,7 @@ impl AttachmentBuilder { ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit | ContentTransferEncoding::Other { .. } => { - String::from_utf8_lossy(&self.raw).into_owned() + self.raw.clone() } } } @@ -224,11 +231,11 @@ impl AttachmentBuilder { }, ContentType::Multipart { boundary: ref b } => { let multipart_type = match self.content_type.1 { - ContentSubType::Other { ref tag } => match tag.as_ref() { - "mixed" => MultipartType::Mixed, - "alternative" => MultipartType::Alternative, - "digest" => MultipartType::Digest, - t => MultipartType::Unsupported { tag: t.to_string() }, + ContentSubType::Other { ref tag } => match &tag[..] { + b"mixed" => MultipartType::Mixed, + b"alternative" => MultipartType::Alternative, + b"digest" => MultipartType::Digest, + _ => MultipartType::Unsupported { tag:tag.clone() }, }, _ => panic!(), }; @@ -247,7 +254,7 @@ impl AttachmentBuilder { } } - pub fn subattachments(raw: &[u8], boundary: &str) -> Vec { + pub fn subattachments(raw: &[u8], boundary: &[u8]) -> Vec { let boundary_length = boundary.len(); match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result() { @@ -267,9 +274,9 @@ impl AttachmentBuilder { }; let mut builder = AttachmentBuilder::new(body); for (name, value) in headers { - if name.eq_ignore_ascii_case("content-type") { + if name.eq_ignore_ascii_case(b"content-type") { builder.content_type(value); - } else if name.eq_ignore_ascii_case("content-transfer-encoding") { + } else if name.eq_ignore_ascii_case(b"content-transfer-encoding") { builder.content_transfer_encoding(value); } } @@ -333,7 +340,7 @@ impl Attachment { //text.push_str(&format!("Data attachment of type {}", self.mime_type())); } AttachmentType::Text { content: ref t } => { - text.push_str(t); + text.push_str(&String::from_utf8_lossy(t)); } AttachmentType::Multipart { of_type: ref multipart_type, diff --git a/melib/src/mailbox/email/mod.rs b/melib/src/mailbox/email/mod.rs index 0a21ddfa..fe9eced3 100644 --- a/melib/src/mailbox/email/mod.rs +++ b/melib/src/mailbox/email/mod.rs @@ -22,6 +22,7 @@ pub mod attachments; pub mod parser; +use parser::BytesExt; pub use self::attachments::*; use error::{MeliError, Result}; use mailbox::backends::BackendOpGenerator; @@ -31,20 +32,21 @@ use std::fmt; use std::option::Option; use std::string::String; use std::sync::Arc; +use std::borrow::Cow; use chrono; use chrono::TimeZone; #[derive(Clone, Debug)] pub struct GroupAddress { - raw: String, + raw: Vec, display_name: StrBuilder, mailbox_list: Vec
, } #[derive(Clone, Debug)] pub struct MailboxAddress { - raw: String, + raw: Vec, display_name: StrBuilder, address_spec: StrBuilder, } @@ -110,42 +112,42 @@ struct StrBuilder { /// Structs implementing this trait must contain a `StrBuilder` field. pub trait StrBuild { /// Create a new `Self` out of a string and a slice - fn new(string: &str, slice: &str) -> Self; + fn new(string: &[u8], slice: &[u8]) -> Self; /// Get the slice part of the string - fn raw(&self) -> &str; + fn raw(&self) -> &[u8]; /// Get the entire string as a slice - fn val(&self) -> &str; + fn val(&self) -> &[u8]; } impl StrBuilder { - fn display<'a>(&self, s: &'a str) -> &'a str { + fn display<'a>(&self, s: &'a [u8]) -> String { let offset = self.offset; let length = self.length; - &s[offset..offset + length] + String::from_utf8(s[offset..offset + length].to_vec()).unwrap() } } /// `MessageID` is accessed through the `StrBuild` trait. #[derive(Clone)] -pub struct MessageID(String, StrBuilder); +pub struct MessageID(Vec, StrBuilder); impl StrBuild for MessageID { - fn new(string: &str, slice: &str) -> Self { + fn new(string: &[u8], slice: &[u8]) -> Self { let offset = string.find(slice).unwrap(); MessageID( - string.to_string(), + string.to_owned(), StrBuilder { offset: offset, length: slice.len() + 1, }, ) } - fn raw(&self) -> &str { + fn raw(&self) -> &[u8] { let offset = self.1.offset; let length = self.1.length; &self.0[offset..offset + length - 1] } - fn val(&self) -> &str { + fn val(&self) -> &[u8] { &self.0 } } @@ -173,13 +175,13 @@ impl PartialEq for MessageID { } impl fmt::Debug for MessageID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.raw()) + write!(f, "{}", String::from_utf8(self.raw().to_vec()).unwrap()) } } #[derive(Clone, Debug)] struct References { - raw: String, + raw: Vec, refs: Vec, } @@ -208,7 +210,7 @@ pub struct Envelope { from: Vec
, to: Vec
, body: Option, - subject: Option, + subject: Option>, message_id: Option, in_reply_to: Option, references: Option, @@ -268,48 +270,48 @@ impl Envelope { if value.len() == 1 && value.is_empty() { continue; } - if name.eq_ignore_ascii_case("to") { - let parse_result = parser::rfc2822address_list(value.as_bytes()); + if name.eq_ignore_ascii_case(b"to") { + let parse_result = parser::rfc2822address_list(value); let value = if parse_result.is_done() { parse_result.to_full_result().unwrap() } else { Vec::new() }; self.set_to(value); - } else if name.eq_ignore_ascii_case("from") { - let parse_result = parser::rfc2822address_list(value.as_bytes()); + } else if name.eq_ignore_ascii_case(b"from") { + let parse_result = parser::rfc2822address_list(value); let value = if parse_result.is_done() { parse_result.to_full_result().unwrap() } else { Vec::new() }; self.set_from(value); - } else if name.eq_ignore_ascii_case("subject") { - let parse_result = parser::phrase(value.trim().as_bytes()); + } else if name.eq_ignore_ascii_case(b"subject") { + let parse_result = parser::phrase(value.trim()); let value = if parse_result.is_done() { parse_result.to_full_result().unwrap() } else { - "".to_string() + "".into() }; self.set_subject(value); - } else if name.eq_ignore_ascii_case("message-id") { + } else if name.eq_ignore_ascii_case(b"message-id") { self.set_message_id(value); - } else if name.eq_ignore_ascii_case("references") { + } else if name.eq_ignore_ascii_case(b"references") { { - let parse_result = parser::references(value.as_bytes()); + let parse_result = parser::references(value); if parse_result.is_done() { for v in parse_result.to_full_result().unwrap() { self.push_references(v); } } } - self.set_references(value.to_string()); - } else if name.eq_ignore_ascii_case("in-reply-to") { + self.set_references(value); + } else if name.eq_ignore_ascii_case(b"in-reply-to") { self.set_in_reply_to(value); in_reply_to = Some(value); - } else if name.eq_ignore_ascii_case("date") { - self.set_date(value.to_string()); - datetime = Some(value.to_string()); + } else if name.eq_ignore_ascii_case(b"date") { + self.set_date(value); + datetime = Some(value); } } /* @@ -374,46 +376,46 @@ impl Envelope { if value.len() == 1 && value.is_empty() { continue; } - if name.eq_ignore_ascii_case("content-transfer-encoding") { + if name.eq_ignore_ascii_case(b"content-transfer-encoding") { builder.content_transfer_encoding(value); - } else if name.eq_ignore_ascii_case("content-type") { + } else if name.eq_ignore_ascii_case(b"content-type") { builder.content_type(value); } } builder.build() } - pub fn subject(&self) -> &str { + pub fn subject(&self) -> Cow { match self.subject { - Some(ref s) => s, - _ => "", + Some(ref s) => String::from_utf8_lossy(s), + _ => Cow::from(String::new()), } } - pub fn in_reply_to(&self) -> &str { + pub fn in_reply_to(&self) -> Cow { match self.in_reply_to { - Some(ref s) => s.val(), - _ => "", + Some(ref s) => String::from_utf8_lossy(s.val()), + _ => Cow::from(String::new()), } } - pub fn in_reply_to_raw(&self) -> &str { + pub fn in_reply_to_raw(&self) -> Cow { match self.in_reply_to { - Some(ref s) => s.raw(), - _ => "", + Some(ref s) => String::from_utf8_lossy(s.raw()).into(), + _ => Cow::from(String::new()), } } - pub fn message_id(&self) -> &str { + pub fn message_id(&self) -> Cow { match self.message_id { - Some(ref s) => s.val(), - _ => "", + Some(ref s) => String::from_utf8_lossy(s.val()), + _ => Cow::from(String::new()), } } - pub fn message_id_raw(&self) -> &str { + pub fn message_id_raw(&self) -> Cow { match self.message_id { - Some(ref s) => s.raw(), - _ => "", + Some(ref s) => String::from_utf8_lossy(s.raw()), + _ => Cow::from(String::new()), } } - fn set_date(&mut self, new_val: String) -> () { - self.date = new_val; + fn set_date(&mut self, new_val: &[u8]) -> () { + self.date = String::from_utf8_lossy(new_val).into_owned(); } fn set_from(&mut self, new_val: Vec
) -> () { self.from = new_val; @@ -421,8 +423,8 @@ impl Envelope { fn set_to(&mut self, new_val: Vec
) -> () { self.to = new_val; } - fn set_in_reply_to(&mut self, new_val: &str) -> () { - let slice = match parser::message_id(new_val.as_bytes()).to_full_result() { + fn set_in_reply_to(&mut self, new_val: &[u8]) -> () { + let slice = match parser::message_id(new_val).to_full_result() { Ok(v) => v, Err(_) => { self.in_reply_to = None; @@ -431,11 +433,11 @@ impl Envelope { }; self.in_reply_to = Some(MessageID::new(new_val, slice)); } - fn set_subject(&mut self, new_val: String) -> () { + fn set_subject(&mut self, new_val: Vec) -> () { self.subject = Some(new_val); } - fn set_message_id(&mut self, new_val: &str) -> () { - let slice = match parser::message_id(new_val.as_bytes()).to_full_result() { + fn set_message_id(&mut self, new_val: &[u8]) -> () { + let slice = match parser::message_id(new_val).to_full_result() { Ok(v) => v, Err(_) => { self.message_id = None; @@ -444,8 +446,8 @@ impl Envelope { }; self.message_id = Some(MessageID::new(new_val, slice)); } - fn push_references(&mut self, new_val: &str) -> () { - let slice = match parser::message_id(new_val.as_bytes()).to_full_result() { + fn push_references(&mut self, new_val: &[u8]) -> () { + let slice = match parser::message_id(new_val).to_full_result() { Ok(v) => v, Err(_) => { return; @@ -471,21 +473,22 @@ impl Envelope { let mut v = Vec::new(); v.push(new_ref); self.references = Some(References { - raw: "".to_string(), + raw: "".into(), refs: v, }); } } } - fn set_references(&mut self, new_val: String) -> () { + // TODO: Check what references should be like again. + fn set_references(&mut self, new_val: &[u8]) -> () { match self.references { Some(ref mut s) => { - s.raw = new_val; + s.raw = new_val.into(); } None => { let v = Vec::new(); self.references = Some(References { - raw: new_val, + raw: new_val.into(), refs: v, }); } diff --git a/melib/src/mailbox/email/parser.rs b/melib/src/mailbox/email/parser.rs index 80f7ff30..08baa6ed 100644 --- a/melib/src/mailbox/email/parser.rs +++ b/melib/src/mailbox/email/parser.rs @@ -28,6 +28,45 @@ use nom::{ErrorKind, IResult, Needed}; use std; use std::str::from_utf8; +pub trait BytesExt { + fn trim(&self) -> &Self; + fn find(&self, needle: &[u8]) -> Option; + fn replace(&self, from: &[u8], to: &[u8]) -> Vec; +} + +impl BytesExt for [u8] { + fn trim(&self) -> &[u8] { + fn is_whitespace(c: &u8) -> bool { + *c == b'\t' || *c == b' ' + } + + fn is_not_whitespace(c: &u8) -> bool { + !is_whitespace(c) + } + + if let Some(first) = self.iter().position(is_not_whitespace) { + if let Some(last) = self.iter().rposition(is_not_whitespace) { + &self[first..last + 1] + } else { + unreachable!(); + } + } else { + &[] + } + } + // https://stackoverflow.com/a/35907071 + fn find(&self, needle: &[u8]) -> Option { + self.windows(needle.len()).position(|window| window == needle) + } + fn replace(&self, from: &[u8], to: &[u8]) -> Vec { + let mut ret = self.to_vec(); + if let Some(idx) = self.find(from) { + ret.splice(idx..(idx + from.len()), to.iter().cloned()); + } + ret + } +} + macro_rules! is_whitespace { ($var:ident) => { $var == b' ' && $var == b'\t' && $var == b'\n' && $var == b'\r' @@ -71,7 +110,7 @@ fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> { * Tue, 5 Jan 2016 21:30:44 +0100 (CET) */ -fn header_value(input: &[u8]) -> IResult<&[u8], &str> { +fn header_value(input: &[u8]) -> IResult<&[u8], &[u8]> { if input.is_empty() || input[0] == b'\n' { IResult::Incomplete(Needed::Unknown) } else { @@ -79,15 +118,9 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> { for (i, x) in input.iter().enumerate() { if *x == b'\n' { if (i + 1) < input_len && input[i + 1] != b' ' && input[i + 1] != b'\t' { - return match from_utf8(&input[0..i]) { - Ok(v) => IResult::Done(&input[(i + 1)..], v), - Err(_) => IResult::Error(error_code!(ErrorKind::Custom(43))), - }; + return IResult::Done(&input[(i + 1)..], &input[0..i]); } else if i + 1 == input_len { - return match from_utf8(input) { - Ok(v) => IResult::Done(&input[(i + 1)..], v), - Err(_) => IResult::Error(error_code!(ErrorKind::Custom(43))), - }; + return IResult::Done(&input[(i + 1)..], &input[0..i]); } } } @@ -96,15 +129,15 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> { } /* Parse the name part of the header -> &str */ -named!(name<&str>, map_res!(is_not!(":\n"), from_utf8)); +named!(name<&[u8]>, is_not!(":\n")); /* Parse a single header as a tuple -> (&str, Vec<&str>) */ named!( - header<(&str, &str)>, - separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value)) + header<(&[u8], &[u8])>, + separated_pair!(complete!(name), ws!(tag!(b":")), complete!(header_value)) ); /* Parse all headers -> Vec<(&str, Vec<&str>)> */ -named!(pub headers>, +named!(pub headers>, many1!(complete!(header))); //named!(pub headers_raw<&[u8]>, @@ -128,9 +161,9 @@ named!(pub body_raw<&[u8]>, body: take_while!(call!(|_| true)) >> ( { body } ))); -named!(pub mail<(std::vec::Vec<(&str, &str)>, &[u8])>, - separated_pair!(headers, tag!("\n"), take_while!(call!(|_| true)))); -named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>, +named!(pub mail<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>, + separated_pair!(headers, tag!(b"\n"), take_while!(call!(|_| true)))); +named!(pub attachment<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>, do_parse!( opt!(is_a!(" \n\t\r")) >> pair: pair!(many0!(complete!(header)), take_while!(call!(|_| true))) >> @@ -252,7 +285,7 @@ named!( ); named!( - encoded_word_list, + encoded_word_list>, ws!(do_parse!( list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >> ({ let list_len = list.iter().fold(0, |mut acc, x| { @@ -264,17 +297,17 @@ named!( acc.append(&mut x.clone()); acc }); - String::from_utf8_lossy(&bytes).into_owned() + bytes }) )) ); named!( - ascii_token, + ascii_token>, do_parse!( word: alt!( terminated!(take_until1!("=?"), peek!(tag_no_case!("=?UTF-8?"))) | take_while!(call!(|_| true)) - ) >> ({ String::from_utf8_lossy(word).into_owned() }) + ) >> ({ word.into() }) ) ); @@ -317,7 +350,7 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> { IResult::Error(e) => IResult::Error(e), IResult::Incomplete(i) => IResult::Incomplete(i), IResult::Done(rest, raw) => { - display_name.length = raw.find('<').unwrap(); + display_name.length = raw.find(b"<").unwrap(); address_spec.offset = display_name.length + 1; address_spec.length = raw.len() - display_name.length - 2; IResult::Done( @@ -357,7 +390,7 @@ fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> { IResult::Done( &input[end..], Address::Mailbox(MailboxAddress { - raw: String::from_utf8_lossy(&input[0..end + 1]).to_string(), + raw: input[0..end + 1].into(), display_name: StrBuilder { offset: 0, length: 0, @@ -446,7 +479,7 @@ fn group(input: &[u8]) -> IResult<&[u8], Address> { return IResult::Done( rest, Address::Group(GroupAddress { - raw: String::from_utf8(input[0..size].to_vec()).unwrap(), + raw: input[0..size].into(), display_name: StrBuilder { offset: 0, length: dlength, @@ -480,13 +513,13 @@ named!(pub rfc2822address_list>, ws!( named!(pub address_list, ws!(do_parse!( list: alt_complete!( encoded_word_list | ascii_token) >> ( { - let list: Vec<&str> = list.split(',').collect(); + let list: Vec<&[u8]> = list.split(|c| *c == b',').collect(); let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.trim().len(); acc }) + list.len() - 1; let list_len = list.len(); let mut i = 0; list.iter().fold(String::with_capacity(string_len), |acc, x| { - let mut acc = acc + &x.replace("\n", "").replace("\t", " ").trim(); + let mut acc = acc + &String::from_utf8_lossy(x.replace(b"\n", b"").replace(b"\t", b" ").trim()); if i != list_len - 1 { acc.push_str(" "); i+=1; @@ -497,20 +530,20 @@ named!(pub address_list, ws!(do_parse!( ))); -named!(pub phrase, ws!(do_parse!( +named!(pub phrase>, ws!(do_parse!( list: many0!(alt_complete!( encoded_word_list | ascii_token)) >> ( { if list.len() == 0 { - String::new() + Vec::new() } else { let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }) + list.len() - 1; let list_len = list.len(); let mut i = 0; - list.iter().fold(String::with_capacity(string_len), - |acc, x| { - let mut acc = acc + &x.replace("\n", "").replace("\t", " "); + list.iter().fold(Vec::with_capacity(string_len), + |mut acc, x| { + acc.extend(x.replace(b"\n", b"").replace(b"\t", b" ")); if i != list_len - 1 { - acc.push_str(" "); + acc.push(b' '); i+=1; } acc @@ -578,21 +611,21 @@ fn test_phrase() { phrase(phrase_s).unwrap() ); } -fn eat_comments(input: &str) -> String { +fn eat_comments(input: &[u8]) -> Vec { let mut in_comment = false; input - .chars() - .fold(String::with_capacity(input.len()), |mut acc, x| { - if x == '(' && !in_comment { + .iter() + .fold(Vec::with_capacity(input.len()), |mut acc, x| { + if *x == b'(' && !in_comment { in_comment = true; acc - } else if x == ')' && in_comment { + } else if *x == b')' && in_comment { in_comment = false; acc } else if in_comment { acc } else { - acc.push(x); + acc.push(*x); acc } }) @@ -610,12 +643,12 @@ fn test_eat_comments() { * right now we expect input will have no extra spaces in between tokens * * We should use a custom parser here*/ -pub fn date(input: &str) -> Option> { - let parsed_result = phrase(eat_comments(input).as_bytes()) +pub fn date(input: &[u8]) -> Option> { + let parsed_result = phrase(&eat_comments(input)) .to_full_result() .unwrap() - .replace("-", "+"); - chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok() + .replace(b"-",b"+"); + chrono::DateTime::parse_from_rfc2822(String::from_utf8_lossy(parsed_result.trim()).as_ref()).ok() } #[test] @@ -627,11 +660,11 @@ fn test_date() { assert_eq!(date(_s).unwrap(), date(__s).unwrap()); } -named!(pub message_id<&str>, - map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8) +named!(pub message_id<&[u8]>, + complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))) ); -fn message_id_peek(input: &[u8]) -> IResult<&[u8], &str> { +fn message_id_peek(input: &[u8]) -> IResult<&[u8], &[u8]> { let input_length = input.len(); if input.is_empty() { IResult::Incomplete(Needed::Size(1)) @@ -640,16 +673,16 @@ fn message_id_peek(input: &[u8]) -> IResult<&[u8], &str> { } else { for (i, &x) in input.iter().take(input_length).enumerate().skip(1) { if x == b'>' { - return IResult::Done(&input[i + 1..], from_utf8(&input[0..i + 1]).unwrap()); + return IResult::Done(&input[i + 1..], &input[0..i + 1]); } } IResult::Incomplete(Needed::Unknown) } } -named!(pub references>, separated_list!(complete!(is_a!(" \n\t\r")), message_id_peek)); +named!(pub references>, separated_list!(complete!(is_a!(" \n\t\r")), message_id_peek)); -named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >, +named_args!(pub attachments<'a>(boundary: &'a [u8], boundary_end: &'a [u8]) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >, alt_complete!(do_parse!( take_until!(boundary) >> vecs: many0!(complete!(do_parse!( @@ -688,31 +721,27 @@ fn test_attachments() { } named!( - content_type_parameter<(&str, &str)>, + content_type_parameter<(&[u8], &[u8])>, do_parse!( - tag!(";") >> name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) - >> value: - map_res!( - ws!(alt_complete!( - delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";") - )), - from_utf8 - ) >> ({ (name, value) }) + tag!(";") >> + name: terminated!(ws!(take_until!("=")) , tag!("=")) >> + value: ws!(alt_complete!( delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";"))) >> + ({ (name, value) }) ) ); -named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >, +named!(pub content_type< (&[u8], &[u8], Vec<(&[u8], &[u8])>) >, do_parse!( - _type: map_res!(take_until!("/"), from_utf8) >> + _type: take_until!("/") >> tag!("/") >> - _subtype: map_res!(is_not!(";"), from_utf8) >> + _subtype: is_not!(";") >> parameters: many0!(complete!(content_type_parameter)) >> ( { (_type, _subtype, parameters) } ) )); -named!(pub quoted_printable_text, +named!(pub quoted_printable_text>, do_parse!( bytes: many0!(alt_complete!( preceded!(tag!("=\n"), quoted_printable_byte) | @@ -720,7 +749,7 @@ named!(pub quoted_printable_text, quoted_printable_byte | le_u8)) >> ( { - String::from_utf8_lossy(&bytes).into_owned() + bytes } ) ) ); diff --git a/melib/src/mailbox/thread.rs b/melib/src/mailbox/thread.rs index f8b0344e..2803886d 100644 --- a/melib/src/mailbox/thread.rs +++ b/melib/src/mailbox/thread.rs @@ -25,7 +25,7 @@ use mailbox::Mailbox; extern crate fnv; use self::fnv::FnvHashMap; -use std; +use std::borrow::Cow; type UnixTimestamp = u64; @@ -115,12 +115,13 @@ impl PartialEq for Container { fn build_collection( threads: &mut Vec, - id_table: &mut FnvHashMap, + id_table: &mut FnvHashMap, usize>, collection: &mut [Envelope], ) -> () { for (i, x) in collection.iter_mut().enumerate() { let x_index; /* x's index in threads */ - let m_id = x.message_id_raw().to_string(); + let m_id = x.message_id_raw().into_owned(); + let m_id = Cow::from(m_id); /* TODO: Check for missing Message-ID. * Solutions: generate a hidden one */ @@ -170,8 +171,9 @@ fn build_collection( continue; } iasf += 1; - let parent_id = if id_table.contains_key(r.raw()) { - let p = id_table[r.raw()]; + let r = String::from_utf8_lossy(r.raw()); + let parent_id = if id_table.contains_key(&r) { + let p = id_table[r.as_ref()]; if !(threads[p].is_descendant(threads, &threads[curr_ref]) || threads[curr_ref].is_descendant(threads, &threads[p])) { @@ -204,7 +206,8 @@ fn build_collection( if threads[curr_ref].parent.is_none() { threads[curr_ref].parent = Some(idx); } - id_table.insert(r.raw().to_string(), idx); + /* Can't avoid copy here since we have different lifetimes */ + id_table.insert(Cow::from(r.into_owned()), idx); idx }; /* update thread date */ @@ -238,7 +241,7 @@ pub fn build_threads( /* a vector to hold thread members */ let mut threads: Vec = Vec::with_capacity((collection.len() as f64 * 1.2) as usize); /* A hash table of Message IDs */ - let mut id_table: FnvHashMap = + let mut id_table: FnvHashMap, usize> = FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); /* Add each message to id_table and threads, and link them together according to the @@ -261,13 +264,14 @@ pub fn build_threads( let sent_mailbox = sent_mailbox.unwrap(); for x in &sent_mailbox.collection { - if id_table.contains_key(x.message_id_raw()) + let m_id = x.message_id_raw(); + if id_table.contains_key(&m_id) || (!x.in_reply_to_raw().is_empty() - && id_table.contains_key(x.in_reply_to_raw())) + && id_table.contains_key(&x.in_reply_to_raw())) { let mut x: Envelope = (*x).clone(); - if id_table.contains_key(x.message_id_raw()) { - let c = id_table[x.message_id_raw()]; + if id_table.contains_key(&m_id) { + let c = id_table[&m_id]; if threads[c].message.is_some() { /* skip duplicate message-id, but this should be handled instead */ continue; @@ -277,11 +281,11 @@ pub fn build_threads( threads[c].date = x.date(); x.set_thread(c); } else if !x.in_reply_to_raw().is_empty() - && id_table.contains_key(x.in_reply_to_raw()) + && id_table.contains_key(&x.in_reply_to_raw()) { - let p = id_table[x.in_reply_to_raw()]; - let c = if id_table.contains_key(x.message_id_raw()) { - id_table[x.message_id_raw()] + let p = id_table[&m_id]; + let c = if id_table.contains_key(&m_id) { + id_table[&m_id] } else { threads.push(Container { message: Some(idx), @@ -293,7 +297,7 @@ pub fn build_threads( indentation: 0, show_subject: true, }); - id_table.insert(x.message_id_raw().to_string(), tidx); + id_table.insert(Cow::from(m_id.into_owned()), tidx); x.set_thread(tidx); tidx += 1; tidx - 1 @@ -376,7 +380,7 @@ pub fn build_threads( if indentation > 0 && thread.has_message() { let subject = collection[thread.message().unwrap()].subject(); if subject == root_subject - || subject.starts_with("Re: ") && subject.ends_with(root_subject) + || subject.starts_with("Re: ") && subject.as_ref().ends_with(root_subject.as_ref()) { threads[i].set_show_subject(false); } diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index fa14985d..43573813 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -421,7 +421,7 @@ impl Component for StatusBar { .as_ref() .unwrap(); self.status = format!( - "{} |Mailbox: {}, Messages: {}, New: {}", + "{} | Mailbox: {}, Messages: {}, New: {}", self.mode, m.folder.name(), m.collection.len(),