introduce read_headers in account config (#338)

This commit is contained in:
Clément DOUIN 2022-03-12 15:25:35 +01:00
parent d3968461e2
commit f9bed5f3c2
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
6 changed files with 82 additions and 29 deletions

View file

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- SMTP pre-send hook [#178] - SMTP pre-send hook [#178]
- Customize headers to show at the top of a read message [#338]
### Changed ### 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 [#329]: https://github.com/soywod/himalaya/issues/329
[#331]: https://github.com/soywod/himalaya/issues/331 [#331]: https://github.com/soywod/himalaya/issues/331
[#335]: https://github.com/soywod/himalaya/issues/335 [#335]: https://github.com/soywod/himalaya/issues/335
[#338]: https://github.com/soywod/himalaya/issues/338

View file

@ -32,6 +32,9 @@ pub struct AccountConfig {
/// Represents the text/plain format as defined in the /// Represents the text/plain format as defined in the
/// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) /// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt)
pub format: Format, pub format: Format,
/// Overrides the default headers displayed at the top of
/// the read message.
pub read_headers: Vec<String>,
/// Represents mailbox aliases. /// Represents mailbox aliases.
pub mailboxes: HashMap<String, String>, pub mailboxes: HashMap<String, String>,
@ -157,6 +160,7 @@ impl<'a> AccountConfig {
.unwrap_or(&vec![]) .unwrap_or(&vec![])
.to_owned(), .to_owned(),
format: base_account.format.unwrap_or_default(), format: base_account.format.unwrap_or_default(),
read_headers: base_account.read_headers,
mailboxes: base_account.mailboxes.clone(), mailboxes: base_account.mailboxes.clone(),
hooks: base_account.hooks.unwrap_or_default(), hooks: base_account.hooks.unwrap_or_default(),
default: base_account.default.unwrap_or_default(), default: base_account.default.unwrap_or_default(),

View file

@ -45,7 +45,7 @@ macro_rules! make_account_config {
pub signature: Option<String>, pub signature: Option<String>,
/// Overrides the signature delimiter for this account. /// Overrides the signature delimiter for this account.
pub signature_delimiter: Option<String>, pub signature_delimiter: Option<String>,
/// Overrides the default page size for this account. /// Overrides the default page size for this account.
pub default_page_size: Option<usize>, pub default_page_size: Option<usize>,
/// Overrides the notify command for this account. /// Overrides the notify command for this account.
pub notify_cmd: Option<String>, pub notify_cmd: Option<String>,
@ -56,6 +56,10 @@ macro_rules! make_account_config {
/// Represents the text/plain format as defined in the /// Represents the text/plain format as defined in the
/// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) /// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt)
pub format: Option<Format>, pub format: Option<Format>,
/// Overrides the default headers displayed at the top of
/// the read message.
#[serde(default)]
pub read_headers: Vec<String>,
/// Makes this account the default one. /// Makes this account the default one.
pub default: Option<bool>, pub default: Option<bool>,
@ -102,6 +106,7 @@ macro_rules! make_account_config {
notify_query: self.notify_query.clone(), notify_query: self.notify_query.clone(),
watch_cmds: self.watch_cmds.clone(), watch_cmds: self.watch_cmds.clone(),
format: self.format.clone(), format: self.format.clone(),
read_headers: self.read_headers.clone(),
default: self.default.clone(), default: self.default.clone(),
email: self.email.clone(), email: self.email.clone(),

View file

@ -222,7 +222,16 @@ fn main() -> Result<()> {
return msg_handlers::move_(seq, mbox, mbox_dst, &mut printer, backend); return msg_handlers::move_(seq, mbox, mbox_dst, &mut printer, backend);
} }
Some(msg_args::Cmd::Read(seq, text_mime, raw, headers)) => { 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)) => { Some(msg_args::Cmd::Reply(seq, all, attachment_paths, encrypt)) => {
return msg_handlers::reply( return msg_handlers::reply(

View file

@ -729,11 +729,29 @@ impl Msg {
/// message is like a template, except that: /// message is like a template, except that:
/// - headers part is customizable (can be omitted if empty filter given in argument) /// - headers part is customizable (can be omitted if empty filter given in argument)
/// - body type is customizable (plain or html) /// - body type is customizable (plain or html)
pub fn to_readable_string(&self, text_mime: &str, headers: Vec<&str>) -> Result<String> { pub fn to_readable_string(
let mut readable_msg = String::new(); &self,
text_mime: &str,
headers: Vec<&str>,
config: &AccountConfig,
) -> Result<String> {
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 { let mut readable_msg = String::new();
match h.to_lowercase().as_str() { for h in all_headers {
match h.as_str() {
"message-id" => match self.message_id { "message-id" => match self.message_id {
Some(ref message_id) if !message_id.is_empty() => { Some(ref message_id) if !message_id.is_empty() => {
readable_msg.push_str(&format!("Message-Id: {}\n", message_id)); readable_msg.push_str(&format!("Message-Id: {}\n", message_id));
@ -833,9 +851,8 @@ impl TryInto<lettre::address::Envelope> for &Msg {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::iter::FromIterator;
use mailparse::SingleInfo; use mailparse::SingleInfo;
use std::iter::FromIterator;
use crate::msg::Addr; use crate::msg::Addr;
@ -982,6 +999,7 @@ mod tests {
#[test] #[test]
fn test_to_readable() { fn test_to_readable() {
let config = AccountConfig::default();
let msg = Msg { let msg = Msg {
parts: Parts(vec![Part::TextPlain(TextPlainPart { parts: Parts(vec![Part::TextPlain(TextPlainPart {
content: String::from("hello, world!"), content: String::from("hello, world!"),
@ -989,21 +1007,22 @@ mod tests {
..Msg::default() ..Msg::default()
}; };
// empty msg, empty headers // empty msg headers, empty headers, empty config
assert_eq!( assert_eq!(
"hello, world!", "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!( assert_eq!(
"hello, world!", "hello, world!",
msg.to_readable_string("plain", vec!["from", "date", "custom-header"]) msg.to_readable_string("plain", vec!["From", "DATE", "custom-hEader"], &config)
.unwrap() .unwrap()
); );
// empty msg, subject header // empty msg headers, multiple subject headers
assert_eq!( assert_eq!(
"Subject: \n\nhello, world!", "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 { let msg = Msg {
@ -1023,26 +1042,39 @@ mod tests {
..Msg::default() ..Msg::default()
}; };
// header present in msg headers // header present in msg headers, empty config
assert_eq!( assert_eq!(
"From: \"Test\" <test@local>\n\nhello, world!", "From: \"Test\" <test@local>\n\nhello, world!",
msg.to_readable_string("plain", vec!["from"]).unwrap() msg.to_readable_string("plain", vec!["from"], &config)
);
// 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"])
.unwrap() .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!( assert_eq!(
"Custom-Header: custom value\n\nhello, world!", "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\" <test@local>\nMessage-Id: <message-id>\n\nhello, world!",
msg.to_readable_string("plain", vec!["cc", "message-ID"], &config)
.unwrap() .unwrap()
); );
} }

View file

@ -209,6 +209,7 @@ pub fn read<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
raw: bool, raw: bool,
headers: Vec<&str>, headers: Vec<&str>,
mbox: &str, mbox: &str,
config: &AccountConfig,
printer: &mut P, printer: &mut P,
backend: Box<&'a mut B>, backend: Box<&'a mut B>,
) -> Result<()> { ) -> 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. // Emails don't always have valid utf8. Using "lossy" to display what we can.
String::from_utf8_lossy(&msg.raw).into_owned() String::from_utf8_lossy(&msg.raw).into_owned()
} else { } else {
msg.to_readable_string(text_mime, headers)? msg.to_readable_string(text_mime, headers, config)?
}) })
} }