From f9bed5f3c221ae8f6ed59b5eebc213727aec3fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sat, 12 Mar 2022 15:25:35 +0100 Subject: [PATCH] introduce read_headers in account config (#338) --- CHANGELOG.md | 2 + src/config/account_config.rs | 4 ++ src/config/deserialized_account_config.rs | 7 +- src/main.rs | 11 ++- src/msg/msg_entity.rs | 84 ++++++++++++++++------- src/msg/msg_handlers.rs | 3 +- 6 files changed, 82 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76a52aa..f76f862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - SMTP pre-send hook [#178] +- Customize headers to show at the top of a read message [#338] ### Changed @@ -503,3 +504,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#329]: https://github.com/soywod/himalaya/issues/329 [#331]: https://github.com/soywod/himalaya/issues/331 [#335]: https://github.com/soywod/himalaya/issues/335 +[#338]: https://github.com/soywod/himalaya/issues/338 diff --git a/src/config/account_config.rs b/src/config/account_config.rs index 9a73e8f..0d32a19 100644 --- a/src/config/account_config.rs +++ b/src/config/account_config.rs @@ -32,6 +32,9 @@ pub struct AccountConfig { /// Represents the text/plain format as defined in the /// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) pub format: Format, + /// Overrides the default headers displayed at the top of + /// the read message. + pub read_headers: Vec, /// Represents mailbox aliases. pub mailboxes: HashMap, @@ -157,6 +160,7 @@ impl<'a> AccountConfig { .unwrap_or(&vec![]) .to_owned(), format: base_account.format.unwrap_or_default(), + read_headers: base_account.read_headers, mailboxes: base_account.mailboxes.clone(), hooks: base_account.hooks.unwrap_or_default(), default: base_account.default.unwrap_or_default(), diff --git a/src/config/deserialized_account_config.rs b/src/config/deserialized_account_config.rs index 84e7dff..626b86d 100644 --- a/src/config/deserialized_account_config.rs +++ b/src/config/deserialized_account_config.rs @@ -45,7 +45,7 @@ macro_rules! make_account_config { pub signature: Option, /// Overrides the signature delimiter for this account. pub signature_delimiter: Option, - /// Overrides the default page size for this account. + /// Overrides the default page size for this account. pub default_page_size: Option, /// Overrides the notify command for this account. pub notify_cmd: Option, @@ -56,6 +56,10 @@ macro_rules! make_account_config { /// Represents the text/plain format as defined in the /// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) pub format: Option, + /// Overrides the default headers displayed at the top of + /// the read message. + #[serde(default)] + pub read_headers: Vec, /// Makes this account the default one. pub default: Option, @@ -102,6 +106,7 @@ macro_rules! make_account_config { notify_query: self.notify_query.clone(), watch_cmds: self.watch_cmds.clone(), format: self.format.clone(), + read_headers: self.read_headers.clone(), default: self.default.clone(), email: self.email.clone(), diff --git a/src/main.rs b/src/main.rs index 9c0b8af..61c647d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -222,7 +222,16 @@ fn main() -> Result<()> { return msg_handlers::move_(seq, mbox, mbox_dst, &mut printer, backend); } Some(msg_args::Cmd::Read(seq, text_mime, raw, headers)) => { - return msg_handlers::read(seq, text_mime, raw, headers, mbox, &mut printer, backend); + return msg_handlers::read( + seq, + text_mime, + raw, + headers, + mbox, + &account_config, + &mut printer, + backend, + ); } Some(msg_args::Cmd::Reply(seq, all, attachment_paths, encrypt)) => { return msg_handlers::reply( diff --git a/src/msg/msg_entity.rs b/src/msg/msg_entity.rs index 4a2855a..3c1c60e 100644 --- a/src/msg/msg_entity.rs +++ b/src/msg/msg_entity.rs @@ -729,11 +729,29 @@ impl Msg { /// message is like a template, except that: /// - headers part is customizable (can be omitted if empty filter given in argument) /// - body type is customizable (plain or html) - pub fn to_readable_string(&self, text_mime: &str, headers: Vec<&str>) -> Result { - let mut readable_msg = String::new(); + pub fn to_readable_string( + &self, + text_mime: &str, + headers: Vec<&str>, + config: &AccountConfig, + ) -> Result { + let mut all_headers = vec![]; + for h in config.read_headers.iter() { + let h = h.to_lowercase(); + if !all_headers.contains(&h) { + all_headers.push(h) + } + } + for h in headers.iter() { + let h = h.to_lowercase(); + if !all_headers.contains(&h) { + all_headers.push(h) + } + } - for h in headers { - match h.to_lowercase().as_str() { + let mut readable_msg = String::new(); + for h in all_headers { + match h.as_str() { "message-id" => match self.message_id { Some(ref message_id) if !message_id.is_empty() => { readable_msg.push_str(&format!("Message-Id: {}\n", message_id)); @@ -833,9 +851,8 @@ impl TryInto for &Msg { #[cfg(test)] mod tests { - use std::iter::FromIterator; - use mailparse::SingleInfo; + use std::iter::FromIterator; use crate::msg::Addr; @@ -982,6 +999,7 @@ mod tests { #[test] fn test_to_readable() { + let config = AccountConfig::default(); let msg = Msg { parts: Parts(vec![Part::TextPlain(TextPlainPart { content: String::from("hello, world!"), @@ -989,21 +1007,22 @@ mod tests { ..Msg::default() }; - // empty msg, empty headers + // empty msg headers, empty headers, empty config assert_eq!( "hello, world!", - msg.to_readable_string("plain", vec![]).unwrap() + msg.to_readable_string("plain", vec![], &config).unwrap() ); - // empty msg, basic headers + // empty msg headers, basic headers assert_eq!( "hello, world!", - msg.to_readable_string("plain", vec!["from", "date", "custom-header"]) + msg.to_readable_string("plain", vec!["From", "DATE", "custom-hEader"], &config) .unwrap() ); - // empty msg, subject header + // empty msg headers, multiple subject headers assert_eq!( "Subject: \n\nhello, world!", - msg.to_readable_string("plain", vec!["subject"]).unwrap() + msg.to_readable_string("plain", vec!["subject", "Subject", "SUBJECT"], &config) + .unwrap() ); let msg = Msg { @@ -1023,26 +1042,39 @@ mod tests { ..Msg::default() }; - // header present in msg headers + // header present in msg headers, empty config assert_eq!( "From: \"Test\" \n\nhello, world!", - msg.to_readable_string("plain", vec!["from"]).unwrap() - ); - // header present but empty in msg headers - assert_eq!( - "hello, world!", - msg.to_readable_string("plain", vec!["cc"]).unwrap() - ); - // custom header present in msg headers - assert_eq!( - "Custom-Header: custom value\n\nhello, world!", - msg.to_readable_string("plain", vec!["custom-header"]) + msg.to_readable_string("plain", vec!["from"], &config) .unwrap() ); - // custom header present in msg headers (case insensitivity) + // header present but empty in msg headers, empty config + assert_eq!( + "hello, world!", + msg.to_readable_string("plain", vec!["cc"], &config) + .unwrap() + ); + // multiple same custom headers present in msg headers, empty + // config assert_eq!( "Custom-Header: custom value\n\nhello, world!", - msg.to_readable_string("plain", vec!["CUSTOM-hEaDer"]) + msg.to_readable_string("plain", vec!["custom-header", "cuSTom-HeaDer"], &config) + .unwrap() + ); + + let config = AccountConfig { + read_headers: vec![ + "CusTOM-heaDER".into(), + "Subject".into(), + "from".into(), + "cc".into(), + ], + ..AccountConfig::default() + }; + // header present but empty in msg headers, empty config + assert_eq!( + "Custom-Header: custom value\nSubject: \nFrom: \"Test\" \nMessage-Id: \n\nhello, world!", + msg.to_readable_string("plain", vec!["cc", "message-ID"], &config) .unwrap() ); } diff --git a/src/msg/msg_handlers.rs b/src/msg/msg_handlers.rs index 4728565..80520c5 100644 --- a/src/msg/msg_handlers.rs +++ b/src/msg/msg_handlers.rs @@ -209,6 +209,7 @@ pub fn read<'a, P: PrinterService, B: Backend<'a> + ?Sized>( raw: bool, headers: Vec<&str>, mbox: &str, + config: &AccountConfig, printer: &mut P, backend: Box<&'a mut B>, ) -> Result<()> { @@ -218,7 +219,7 @@ pub fn read<'a, P: PrinterService, B: Backend<'a> + ?Sized>( // Emails don't always have valid utf8. Using "lossy" to display what we can. String::from_utf8_lossy(&msg.raw).into_owned() } else { - msg.to_readable_string(text_mime, headers)? + msg.to_readable_string(text_mime, headers, config)? }) }