summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2020-08-25 12:25:26 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2020-08-26 00:54:06 +0300
commit8d50e83a3312d9d6037b340c55c22c9a128c9b28 (patch)
treed0e4d940ed29864e887afa4806fa012633187565
parent0f3bf858a3abb14a6300cef965d5d6c2e9c9e35e (diff)
melib/email: add case-insensitive Header struct
- HeaderName is either 32 or less inlined bytes or heap-allocated vec for more than that. - Equality and hashing is case-insensitive - A HeaderMap is a hashmap from HeaderName to Strings that can be indexed with &str, case insensitive. Insertion order is also preserved
-rw-r--r--melib/src/email.rs81
-rw-r--r--melib/src/email/compose.rs101
-rw-r--r--melib/src/email/headers.rs288
-rw-r--r--src/components/mail/compose.rs68
-rw-r--r--src/components/mail/view.rs4
-rw-r--r--src/components/utilities.rs4
-rw-r--r--tests/generated.mail2
-rw-r--r--tests/generating_email.rs20
8 files changed, 409 insertions, 159 deletions
diff --git a/melib/src/email.rs b/melib/src/email.rs
index 092603c1..9f26575a 100644
--- a/melib/src/email.rs
+++ b/melib/src/email.rs
@@ -22,7 +22,7 @@
/*!
* Email parsing, handling, sending etc.
*/
-use std::collections::HashMap;
+use std::convert::TryInto;
mod compose;
pub use self::compose::*;
@@ -37,7 +37,9 @@ mod address;
pub mod parser;
use crate::parser::BytesExt;
pub use address::*;
+mod headers;
pub mod signatures;
+pub use headers::*;
use crate::backends::BackendOp;
use crate::datetime::UnixTimestamp;
@@ -139,7 +141,7 @@ pub struct Envelope {
message_id: MessageID,
in_reply_to: Option<MessageID>,
pub references: Option<References>,
- other_headers: HashMap<String, String>,
+ other_headers: HeaderMap,
timestamp: UnixTimestamp,
thread: ThreadNodeHash,
@@ -153,15 +155,16 @@ pub struct Envelope {
impl fmt::Debug for Envelope {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "Envelope {{\n\tsubject: {}\n\tdate: {},\n\tfrom:{:#?},\n\tto {:#?},\n\tmessage_id: {},\n\tin_reply_to: {:?}\n\treferences: {:#?},\n\thash: {}\n}}",
- self.subject(),
- self.date,
- self.from,
- self.to,
- self.message_id_display(),
- self.in_reply_to_display(),
- self.references,
- self.hash)
+ f.debug_struct("Envelope")
+ .field("Subject", &self.subject())
+ .field("Date", &self.date)
+ .field("From", &self.from)
+ .field("To", &self.to)
+ .field("Message-ID", &self.message_id_display())
+ .field("In-Reply-To", &self.in_reply_to_display())
+ .field("References", &self.references)
+ .field("Hash", &self.hash)
+ .finish()
}
}
@@ -183,17 +186,7 @@ impl Envelope {
message_id: MessageID::default(),
in_reply_to: None,
references: None,
- other_headers: [
- ("From".to_string(), String::new()),
- ("To".to_string(), String::new()),
- ("Subject".to_string(), String::new()),
- ("Date".to_string(), String::new()),
- ("Cc".to_string(), String::new()),
- ("Bcc".to_string(), String::new()),
- ]
- .iter()
- .cloned()
- .collect(),
+ other_headers: Default::default(),
timestamp: 0,
@@ -253,49 +246,40 @@ impl Envelope {
let mut in_reply_to = None;
for (name, value) in headers {
- self.other_headers.insert(
- String::from_utf8(name.to_vec())
- .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into()),
- parser::encodings::phrase(value, false)
- .map(|(_, value)| {
- String::from_utf8(value)
- .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into())
- })
- .unwrap_or_else(|_| String::from_utf8_lossy(value).into()),
- );
- if name.eq_ignore_ascii_case(b"to") {
+ let name: HeaderName = name.try_into()?;
+ if name == "to" {
let parse_result = parser::address::rfc2822address_list(value);
if parse_result.is_ok() {
let value = parse_result.unwrap().1;
self.set_to(value);
};
- } else if name.eq_ignore_ascii_case(b"cc") {
+ } else if name == "cc" {
let parse_result = parser::address::rfc2822address_list(value);
if parse_result.is_ok() {
let value = parse_result.unwrap().1;
self.set_cc(value);
};
- } else if name.eq_ignore_ascii_case(b"bcc") {
+ } else if name == "bcc" {
let parse_result = parser::address::rfc2822address_list(value);
if parse_result.is_ok() {
let value = parse_result.unwrap().1;
self.set_bcc(value.to_vec());
};
- } else if name.eq_ignore_ascii_case(b"from") {
+ } else if name == "from" {
let parse_result = parser::address::rfc2822address_list(value);
if parse_result.is_ok() {
let value = parse_result.unwrap().1;
self.set_from(value);
}
- } else if name.eq_ignore_ascii_case(b"subject") {
+ } else if name == "subject" {
let parse_result = parser::encodings::phrase(value.trim(), false);
if parse_result.is_ok() {
let value = parse_result.unwrap().1;
self.set_subject(value);
};
- } else if name.eq_ignore_ascii_case(b"message-id") {
+ } else if name == "message-id" {
self.set_message_id(value);
- } else if name.eq_ignore_ascii_case(b"references") {
+ } else if name == "references" {
{
let parse_result = parser::address::references(value);
if parse_result.is_ok() {
@@ -305,10 +289,10 @@ impl Envelope {
}
}
self.set_references(value);
- } else if name.eq_ignore_ascii_case(b"in-reply-to") {
+ } else if name == "in-reply-to" {
self.set_in_reply_to(value);
in_reply_to = Some(value);
- } else if name.eq_ignore_ascii_case(b"date") {
+ } else if name == "date" {
let parse_result = parser::encodings::phrase(value, false);
if parse_result.is_ok() {
let value = parse_result.unwrap().1;
@@ -316,7 +300,7 @@ impl Envelope {
} else {
self.set_date(value);
}
- } else if name.eq_ignore_ascii_case(b"content-type") {
+ } else if name == "content-type" {
match parser::attachments::content_type(value) {
Ok((_, (ct, cst, ref params)))
if ct.eq_ignore_ascii_case(b"multipart")
@@ -341,6 +325,15 @@ impl Envelope {
_ => {}
}
}
+ self.other_headers.insert(
+ name,
+ parser::encodings::phrase(value, false)
+ .map(|(_, value)| {
+ String::from_utf8(value)
+ .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into())
+ })
+ .unwrap_or_else(|_| String::from_utf8_lossy(value).into()),
+ );
}
/*
* https://tools.ietf.org/html/rfc5322#section-3.6.4
@@ -644,11 +637,11 @@ impl Envelope {
}
}
- pub fn other_headers(&self) -> &HashMap<String, String> {
+ pub fn other_headers(&self) -> &HeaderMap {
&self.other_headers
}
- pub fn other_headers_mut(&mut self) -> &mut HashMap<String, String> {
+ pub fn other_headers_mut(&mut self) -> &mut HeaderMap {
&mut self.other_headers
}
diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs
index 9984499e..0d0ca99e 100644
--- a/melib/src/email/compose.rs
+++ b/melib/src/email/compose.rs
@@ -20,11 +20,9 @@
*/
use super::*;
-use crate::backends::BackendOp;
use crate::email::attachments::AttachmentBuilder;
use crate::shellexpand::ShellExpandTrait;
use data_encoding::BASE64_MIME;
-use indexmap::IndexMap;
use std::ffi::OsStr;
use std::io::Read;
use std::path::{Path, PathBuf};
@@ -39,7 +37,7 @@ use super::parser;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Draft {
- pub headers: IndexMap<String, String>,
+ pub headers: HeaderMap,
pub body: String,
pub attachments: Vec<AttachmentBuilder>,
@@ -47,17 +45,17 @@ pub struct Draft {
impl Default for Draft {
fn default() -> Self {
- let mut headers = IndexMap::with_capacity_and_hasher(8, Default::default());
+ let mut headers = HeaderMap::default();
headers.insert(
- "Date".into(),
+ HeaderName::new_unchecked("Date"),
crate::datetime::timestamp_to_string(crate::datetime::now(), None),
);
- headers.insert("From".into(), "".into());
- headers.insert("To".into(), "".into());
- headers.insert("Cc".into(), "".into());
- headers.insert("Bcc".into(), "".into());
+ headers.insert(HeaderName::new_unchecked("From"), "".into());
+ headers.insert(HeaderName::new_unchecked("To"), "".into());
+ headers.insert(HeaderName::new_unchecked("Cc"), "".into());
+ headers.insert(HeaderName::new_unchecked("Bcc"), "".into());
+ headers.insert(HeaderName::new_unchecked("Subject"), "".into());
- headers.insert("Subject".into(), "".into());
Draft {
headers,
body: String::new(),
@@ -78,20 +76,9 @@ impl str::FromStr for Draft {
let mut ret = Draft::default();
for (k, v) in headers {
- ret.headers.insert(
- String::from_utf8(k.to_vec())?,
- String::from_utf8(v.to_vec())?,
- );
- }
- if ret.headers.contains_key("From") && !ret.headers.contains_key("Message-ID") {
- if let Ok((_, addr)) = super::parser::address::mailbox(ret.headers["From"].as_bytes()) {
- if let Some(fqdn) = addr.get_fqdn() {
- ret.headers
- .insert("Message-ID".into(), random::gen_message_id(&fqdn));
- }
- }
+ ret.headers
+ .insert(k.try_into()?, String::from_utf8(v.to_vec())?);
}
-
let body = Envelope::new(0).body_bytes(s.as_bytes());
ret.body = String::from_utf8(decode(&body, None))?;
@@ -101,30 +88,28 @@ impl str::FromStr for Draft {
}
impl Draft {
- pub fn edit(envelope: &Envelope, mut op: Box<dyn BackendOp>) -> Result<Self> {
+ pub fn edit(envelope: &Envelope, bytes: &[u8]) -> Result<Self> {
let mut ret = Draft::default();
//TODO: Inform user if error
- {
- let bytes = futures::executor::block_on(op.as_bytes()?)?;
- for (k, v) in envelope.headers(&bytes).unwrap_or_else(|_| Vec::new()) {
- ret.headers.insert(k.into(), v.into());
- }
+ for (k, v) in envelope.headers(&bytes).unwrap_or_else(|_| Vec::new()) {
+ ret.headers.insert(k.try_into()?, v.into());
}
- ret.body = envelope.body(op)?.text();
+ ret.body = envelope.body_bytes(bytes).text();
Ok(ret)
}
pub fn set_header(&mut self, header: &str, value: String) -> &mut Self {
- self.headers.insert(header.to_string(), value);
+ self.headers
+ .insert(HeaderName::new_unchecked(header), value);
self
}
pub fn new_reply(envelope: &Envelope, bytes: &[u8], reply_to_all: bool) -> Self {
let mut ret = Draft::default();
ret.headers_mut().insert(
- "References".into(),
+ HeaderName::new_unchecked("References"),
format!(
"{} {}",
envelope
@@ -140,35 +125,47 @@ impl Draft {
envelope.message_id_display()
),
);
- ret.headers_mut()
- .insert("In-Reply-To".into(), envelope.message_id_display().into());
+ ret.headers_mut().insert(
+ HeaderName::new_unchecked("In-Reply-To"),
+ envelope.message_id_display().into(),
+ );
// "Mail-Followup-To/(To+Cc+(Mail-Reply-To/Reply-To/From)) for follow-up,
// Mail-Reply-To/Reply-To/From for reply-to-author."
// source: https://cr.yp.to/proto/replyto.html
if reply_to_all {
if let Some(reply_to) = envelope.other_headers().get("Mail-Followup-To") {
- ret.headers_mut().insert("To".into(), reply_to.to_string());
+ ret.headers_mut()
+ .insert(HeaderName::new_unchecked("To"), reply_to.to_string());
} else {
if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
- ret.headers_mut().insert("To".into(), reply_to.to_string());
- } else {
ret.headers_mut()
- .insert("To".into(), envelope.field_from_to_string());
+ .insert(HeaderName::new_unchecked("To"), reply_to.to_string());
+ } else {
+ ret.headers_mut().insert(
+ HeaderName::new_unchecked("To"),
+ envelope.field_from_to_string(),
+ );
}
// FIXME: add To/Cc
}
} else {
if let Some(reply_to) = envelope.other_headers().get("Mail-Reply-To") {
- ret.headers_mut().insert("To".into(), reply_to.to_string());
+ ret.headers_mut()
+ .insert(HeaderName::new_unchecked("To"), reply_to.to_string());
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
- ret.headers_mut().insert("To".into(), reply_to.to_string());
- } else {
ret.headers_mut()
- .insert("To".into(), envelope.field_from_to_string());
+ .insert(HeaderName::new_unchecked("To"), reply_to.to_string());
+ } else {
+ ret.headers_mut().insert(
+ HeaderName::new_unchecked("To"),
+ envelope.field_from_to_string(),
+ );
}
}
- ret.headers_mut()
- .insert("Cc".into(), envelope.field_cc_to_string());
+ ret.headers_mut().insert(
+ HeaderName::new_unchecked("Cc"),
+ envelope.field_cc_to_string(),
+ );
let body = envelope.body_bytes(bytes);
ret.body = {
let reply_body_bytes = decode_rec(&body, None);
@@ -177,7 +174,7 @@ impl Draft {
let mut ret = format!(
"On {} {} wrote:\n",
envelope.date_as_str(),
- ret.headers()["To"]
+ &ret.headers()["To"]
);
for l in lines {
ret.push('>');
@@ -191,11 +188,11 @@ impl Draft {
ret
}
- pub fn headers_mut(&mut self) -> &mut IndexMap<String, String> {
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
- pub fn headers(&self) -> &IndexMap<String, String> {
+ pub fn headers(&self) -> &HeaderMap {
&self.headers
}
@@ -219,7 +216,7 @@ impl Draft {
pub fn to_string(&self) -> Result<String> {
let mut ret = String::new();
- for (k, v) in &self.headers {
+ for (k, v) in self.headers.deref() {
ret.extend(format!("{}: {}\n", k, v).chars());
}
@@ -236,12 +233,14 @@ impl Draft {
if let Ok((_, addr)) = super::parser::address::mailbox(self.headers["From"].as_bytes())
{
if let Some(fqdn) = addr.get_fqdn() {
- self.headers
- .insert("Message-ID".into(), random::gen_message_id(&fqdn));
+ self.headers.insert(
+ HeaderName::new_unchecked("Message-ID"),
+ random::gen_message_id(&fqdn),
+ );
}
}
}
- for (k, v) in &self.headers {
+ for (k, v) in self.headers.deref() {
if v.is_ascii() {
ret.extend(format!("{}: {}\n", k, v).chars());
} else {
diff --git a/melib/src/email/headers.rs b/melib/src/email/headers.rs
new file mode 100644
index 00000000..4dbe9899
--- /dev/null
+++ b/melib/src/email/headers.rs
@@ -0,0 +1,288 @@
+/*
+ * meli - headers
+ *
+ * Copyright 2020 Manos Pitsidianakis
+ *
+ * This file is part of meli.
+ *
+ * meli is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * meli is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with meli. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use crate::error::MeliError;
+use indexmap::IndexMap;
+use smallvec::SmallVec;
+use std::borrow::Borrow;
+use std::cmp::{Eq, PartialEq};
+use std::convert::TryFrom;
+use std::fmt;
+use std::hash::{Hash, Hasher};
+use std::ops::{Deref, DerefMut};
+
+#[derive(Clone, Copy, Serialize, Deserialize)]
+pub struct HeaderNameType<S>(S);
+
+///Case insensitive wrapper for a header name. As of `RFC5322` it's guaranteened to be ASCII.
+pub type HeaderName = HeaderNameType<SmallVec<[u8; 32]>>;
+
+impl HeaderName {
+ pub fn new_unchecked(from: &str) -> Self {
+ HeaderNameType(from.as_bytes().into())
+ }
+}
+
+impl<S: AsRef<[u8]>> fmt::Display for HeaderNameType<S> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.normalize())
+ }
+}
+
+impl<S: AsRef<[u8]>> fmt::Debug for HeaderNameType<S> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.as_str())
+ }
+}
+
+impl<S: AsRef<[u8]>> PartialEq<[u8]> for HeaderNameType<S> {
+ fn eq(&self, other: &[u8]) -> bool {
+ self.0.as_ref().eq_ignore_ascii_case(other)
+ }
+}
+
+impl<S: AsRef<[u8]>> PartialEq<&str> for HeaderNameType<S> {
+ fn eq(&self, other: &&str) -> bool {
+ self.0.as_ref().eq_ignore_ascii_case(other.as_bytes())
+ }
+}
+
+impl<S1: AsRef<[u8]>, S2: AsRef<[u8]>> PartialEq<HeaderNameType<S2>> for HeaderNameType<S1> {
+ fn eq(&self, other: &HeaderNameType<S2>) -> bool {
+ self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
+ }
+}
+
+impl<S: AsRef<[u8]>> Eq for HeaderNameType<S> {}
+
+impl<S: AsRef<[u8]>> Hash for HeaderNameType<S> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ for b in self.0.as_ref().iter() {
+ b.to_ascii_lowercase().hash(state);
+ }
+ }
+}
+
+impl TryFrom<&[u8]> for HeaderName {
+ type Error = MeliError;
+
+ fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
+ if value.is_ascii() {
+ Ok(HeaderNameType(value.into()))
+ } else {
+ Err(MeliError::new(format!(
+ "Header value is not ascii: {:?}",
+ value
+ )))
+ }
+ }
+}
+
+impl TryFrom<&str> for HeaderName {
+ type Error = MeliError;
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ if value.is_ascii() {
+ Ok(HeaderNameType(value.as_bytes().into()))
+ } else {
+ Err(MeliError::new(format!(
+ "Header value is not ascii: {:?}",
+ value
+ )))
+ }
+ }
+}
+
+trait HeaderKey {
+ fn to_key(&self) -> &[u8];
+}
+
+impl Hash for dyn HeaderKey + '_ {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ for b in self.to_key().iter() {
+ b.to_ascii_lowercase().hash(state);
+ }
+ }
+}
+
+impl PartialEq for dyn HeaderKey + '_ {
+ fn eq(&self, other: &Self) -> bool {
+ self.to_key().eq_ignore_ascii_case(other.to_key())
+ }
+}
+
+impl Eq for dyn HeaderKey + '_ {}
+
+impl<S: AsRef<[u8]>> HeaderKey for HeaderNameType<S> {
+ fn to_key(&self) -> &[u8] {
+ self.0.as_ref()
+ }
+}
+
+//Implement Borrow for all the lookup types as returning our trait object:
+
+impl<'a> Borrow<dyn HeaderKey + 'a> for HeaderName {
+ fn borrow(&self) -> &(dyn HeaderKey + 'a) {
+ self
+ }
+}
+
+impl<S: AsRef<[u8]>> HeaderNameType<S> {
+ pub fn as_str(&self) -> &str {
+ //HeadersType are ascii so valid utf8
+ unsafe { std::str::from_utf8_unchecked(self.0.as_ref()) }
+ }
+
+ pub fn normalize(&self) -> &str {
+ if self == &b"subject"[..] {
+ "Subject"
+ } else if self == &b"from"[..] {
+ "From"
+ } else if self == &b"to"[..] {
+ "To"
+ } else if self == &b"cc"[..] {
+ "Cc"
+ } else if self == &b"bcc"[..] {
+ "Bcc"
+ } else if self == &b"reply-to"[..] {
+ "Reply-To"
+ } else if self == &b"in-reply-to"[..] {
+ "In-Reply-To"
+ } else if self == &b"references"[..] {
+ "References"
+ } else if self == &b"sender"[..] {
+ "Sender"
+ } else if self == &b"mail-reply-to"[..] {
+ "Mail-Reply-To"
+ } else if self == &b"mail-followup-to"[..] {
+ "Mail-Followup-To"
+ } else if self == &b"mime-version"[..] {
+ "MIME-Version"
+ } else if self == &b"content-disposition"[..] {
+ "Content-Disposition"
+ } else if self == &b"content-transfer-encoding"[..] {
+ "Content-Transfer-Encoding"
+ } else if self == &b"content-type"[..] {
+ "Content-Type"
+ } else if self == &b"content-id"[..] {
+ "Content-ID"
+ } else if self == &b"content-description"[..] {
+ "Content-Description"
+ } else if self == &b"authentication-results"[..] {
+ "Authentication-Results"
+ } else if self == &b"dkim-signature"[..] {
+ "DKIM-Signature"
+ } else if self == &b"delivered-to"[..] {
+ "Delivered-To"
+ } else if self == &b"message-id"[..] {
+ "Message-ID"
+ } else if self == &b"comments"[..] {
+ "Comments"
+ } else if self == &b"keywords"[..] {
+ "Keywords"
+ } else if self == &b"resent-from"[..] {
+ "Resent-From"
+ } else if self == &b"resent-sender"[..] {
+ "Resent-Sender"
+ } else if self == &b"resent-to"[..] {
+ "Resent-To"
+ } else if self == &b"resent-cc"[..] {
+ "Resent-Cc"
+ } else if self == &b"resent-bcc"[..] {
+ "Resent-Bcc"
+ } else if self == &b"resent-date"[..] {
+ "Resent-Date"
+ } else if self == &b"resent-message-id"[..] {
+ "Resent-Message-ID"
+ } else if self == &b"resent-reply-to"[..] {
+ "Resent-Reply-To"
+ } else if self == &b"return-path"[..] {
+ "Return-Path"
+ } else if self == &b"received"[..] {
+ "Received"
+ } else {
+ self.as_str()
+ }
+ }
+}
+
+#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct HeaderMap(indexmap::IndexMap<HeaderName, String>);
+
+impl std::ops::Index<&[u8]> for HeaderMap {
+ type Output = str;
+ fn index(&self, k: &[u8]) -> &Self::Output {
+ (self.0)[HeaderNameType(k).borrow() as &dyn HeaderKey].as_str()
+ }
+}
+
+impl std::ops::Index<&str> for HeaderMap {
+ type Output = str;
+ fn index(&self, k: &str) -> &Self::Output {
+ (self.0)[HeaderNameType(k).borrow() as &dyn HeaderKey].as_str()
+ }
+}
+
+impl HeaderMap {
+ pub fn get_mut(&mut self, key: &str) -> Option<&mut String> {
+ (self.0).get_mut(HeaderNameType(key).borrow() as &dyn HeaderKey)
+ }
+
+ pub fn get(&self, key: &str) -> Option<&String> {
+ (self.0).get(HeaderNameType(key).borrow() as &dyn HeaderKey)
+ }
+
+ pub fn contains_key(&self, key: &str) -> bool {
+ (self.0).contains_key(HeaderNameType(key).borrow() as &dyn HeaderKey)
+ }
+
+ pub fn remove(&mut self, key: &str) -> Option<String> {
+ (self.0).remove(HeaderNameType(key).borrow() as &dyn HeaderKey)
+ }
+}
+
+impl Deref for HeaderMap {
+ type Target = IndexMap<HeaderName, String>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for HeaderMap {
+ fn deref_mut(&mut self) -> &mut IndexMap<HeaderName, String> {
+ &mut self.0
+ }
+}
+
+#[test]
+fn test_headers_case_sensitivity() {
+ use std::convert::TryInto;
+ let mut headers = HeaderMap::default();
+ headers.insert("from".try_into().unwrap(), "Myself <a@b.c>".into());
+ assert_eq!(&headers["From"], "Myself <a@b.c>");
+ assert_eq!(&headers["From"], &headers["from"]);
+ assert_eq!(&headers["fROm"], &headers["from"]);
+ headers.get_mut("from").unwrap().pop();
+ assert_eq!(&headers["From"], "Myself <a@b.c");
+ headers.insert("frOM".try_into().unwrap(), "nada".into());
+ assert_eq!(&headers["fROm"], "nada");
+}
diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs
index 78f3dfa1..eed9afba 100644
--- a/src/components/mail/compose.rs
+++ b/src/components/mail/compose.rs
@@ -137,7 +137,7 @@ impl fmt::Display for Composer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO display subject/info
if self.reply_context.is_some() {
- write!(f, "reply: {:8}", self.draft.headers()["Subject"])
+ write!(f, "reply: {:8}", &self.draft.headers()["Subject"])
} else {
write!(f, "composing")
}
@@ -158,21 +158,11 @@ impl Composer {
if v.is_empty() {
continue;
}
- if let Some(k) = ret
- .draft
- .headers()
- .keys()
- .find(|k| k.eq_ignore_ascii_case(h))
- {
- let _k = k.clone();
- ret.draft.headers_mut().insert(_k, v.into());
- } else {
- ret.draft.set_header(h, v.into());
- }
+ ret.draft.set_header(h, v.into());
}
if *mailbox_acc_settings!(context[account_hash].composing.insert_user_agent) {
ret.draft.set_header(
- "User-Agent".into(),
+ "User-Agent",
format!("meli {}", option_env!("CARGO_PKG_VERSION").unwrap_or("0.0")),
);
}
@@ -181,14 +171,18 @@ impl Composer {
ret
}
- pub fn edit(new_account_hash: AccountHash, h: EnvelopeHash, context: &Context) -> Result<Self> {
+ pub fn edit(
+ account_hash: AccountHash,
+ env_hash: EnvelopeHash,
+ bytes: &[u8],
+ context: &Context,
+ ) -> Result<Self> {
let mut ret = Composer::default();
- let op = context.accounts[&new_account_hash].operation(h)?;
- let envelope: EnvelopeRef = context.accounts[&new_account_hash].collection.get_env(h);
+ let envelope: EnvelopeRef = context.accounts[&account_hash].collection.get_env(env_hash);
- ret.draft = Draft::edit(&envelope, op)?;
+ ret.draft = Draft::edit(&envelope, bytes)?;
- ret.account_hash = new_account_hash;
+ ret.account_hash = account_hash;
Ok(ret)
}
@@ -202,16 +196,16 @@ impl Composer {
let account = &context.accounts[&coordinates.0];
let envelope = account.collection.get_env(coordinates.2);
let subject = envelope.subject();
- ret.draft.headers_mut().insert(
- "Subject".into(),
+ ret.draft.set_header(
+ "Subject",
if !subject.starts_with("Re: ") {
format!("Re: {}", subject)
} else {
subject.into()
},
);
- ret.draft.headers_mut().insert(
- "References".into(),
+ ret.draft.set_header(
+ "References",
format!(
"{} {}",
envelope
@@ -228,8 +222,7 @@ impl Composer {
),
);
ret.draft
- .headers_mut()
- .insert("In-Reply-To".into(), envelope.message_id_display().into());
+ .set_header("In-Reply-To", envelope.message_id_display().into());
// "Mail-Followup-To/(To+Cc+(Mail-Reply-To/Reply-To/From)) for follow-up,
// Mail-Reply-To/Reply-To/From for reply-to-author."
@@ -274,7 +267,7 @@ impl Composer {
{
to.remove(&ours);
}
- ret.draft.headers_mut().insert("To".into(), {
+ ret.draft.set_header("To", {
let mut ret: String =
to.into_iter()
.fold(String::new(), |mut s: String, n: Address| {
@@ -286,22 +279,14 @@ impl Composer {
ret.pop();
ret
});
- ret.draft
- .headers_mut()
- .insert("Cc".into(), envelope.field_cc_to_string());
+ ret.draft.set_header("Cc", envelope.field_cc_to_string());
} else {
if let Some(reply_to) = envelope.other_headers().get("Mail-Reply-To") {
- ret.draft
- .headers_mut()
- .insert("To".into(), reply_to.to_string());
+ ret.draft.set_header("To", reply_to.to_string());
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
- ret.draft
- .headers_mut()
- .insert("To".into(), reply_to.to_string());
+ ret.draft.set_header("To", reply_to.to_string());
} else {
- ret.draft
- .headers_mut()
- .insert("To".into(), envelope.field_from_to_string());
+ ret.draft.set_header("To", envelope.field_from_to_string());
}
}
let body = envelope.body_bytes(bytes);
@@ -399,7 +384,7 @@ impl Composer {
let header_values = self.form.values_mut();
let draft_header_map = self.draft.headers_mut();
for (k, v) in draft_header_map.iter_mut() {
- if let Some(ref vn) = header_values.get(k) {
+ if let Some(ref vn) = header_values.get(k.as_str()) {
*v = vn.as_str().to_string();
}
}
@@ -537,8 +522,8 @@ impl Component for Composer {
}
if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty()
{
- self.draft.headers_mut().insert(
- "From".into(),
+ self.draft.set_header(
+ "From",
crate::components::mail::get_display_name(context, self.account_hash),
);
}
@@ -798,8 +783,7 @@ impl Component for Composer {
) if selector.id() == *id => {
if let Some(to_val) = result.downcast_mut::<String>() {
self.draft
- .headers_mut()
- .insert("To".to_string(), std::mem::replace(to_val, String::new()));
+ .set_header("To", std::mem::replace(to_val, String::new()));
self.update_form();
}
self.mode = ViewMode::Edit;
diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs
index ca61fa6d..f39301a3 100644
--- a/src/components/mail/view.rs
+++ b/src/components/mail/view.rs
@@ -1563,8 +1563,8 @@ impl Component for MailView {
list_management::ListAction::Email(email) => {
if let Ok(mailto) = Mailto::try_from(*email) {
let mut draft: Draft = mailto.into();
- draft.headers_mut().insert(
- "From".into(),
+ draft.set_header(
+ "From",
crate::components::mail::get_display_name(
context,
self.coordinates.0,
diff --git a/src/components/utilities.rs b/src/components/utilities.rs
index f7f03e99..be1b8a52 100644
--- a/src/components/utilities.rs
+++ b/src/components/utilities.rs
@@ -1675,7 +1675,8 @@ impl Component for Tabbed {
self.help_curr_views = children_maps;
return true;
}
- UIEvent::Action(Tab(Edit(account_hash, msg))) => {
+ UIEvent::Action(Tab(Edit(_, _))) => {
+ /* FIXME
let composer = match Composer::edit(*account_hash, *msg, context) {
Ok(c) => c,
Err(e) => {
@@ -1704,6 +1705,7 @@ impl Component for Tabbed {
let mut children_maps = self.children[self.cursor_pos].get_shortcuts(context);
children_maps.extend(self.get_shortcuts(context));
self.help_curr_views = children_maps;
+ */
return true;
}
UIEvent::Action(Tab(New(ref mut e))) if e.is_some() => {
diff --git a/tests/generated.mail b/tests/generated.mail
index 267c23a0..20f23ff0 100644
--- a/tests/generated.mail
+++ b/tests/generated.mail
@@ -1,8 +1,8 @@
+Subject:
From:
To:
Cc:
Bcc:
-Subject:
MIME-Version: 1.0
Content-Type: multipart/mixed; charset="utf-8"; boundary="bzz_bzz__bzz__"
diff --git a/tests/generating_email.rs b/tests/generating_email.rs
index c0acd498..04b94a9d 100644
--- a/tests/generating_email.rs
+++ b/tests/generating_email.rs
@@ -15,24 +15,8 @@ fn build_draft() {
_ => {}
}
}