introduce --header arg for read command (#338)

This commit is contained in:
Clément DOUIN
2022-03-12 13:05:57 +01:00
parent eb6f51456b
commit 86b41e4914
7 changed files with 129 additions and 19 deletions
+2 -2
View File
@@ -221,8 +221,8 @@ fn main() -> Result<()> {
Some(msg_args::Cmd::Move(seq, mbox_dst)) => {
return msg_handlers::move_(seq, mbox, mbox_dst, &mut printer, backend);
}
Some(msg_args::Cmd::Read(seq, text_mime, raw)) => {
return msg_handlers::read(seq, text_mime, raw, mbox, &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);
}
Some(msg_args::Cmd::Reply(seq, all, attachment_paths, encrypt)) => {
return msg_handlers::reply(
+21 -7
View File
@@ -25,6 +25,7 @@ type AttachmentPaths<'a> = Vec<&'a str>;
type MaxTableWidth = Option<usize>;
type Encrypt = bool;
type Criteria = String;
type Headers<'a> = Vec<&'a str>;
/// Message commands.
#[derive(Debug, PartialEq, Eq)]
@@ -35,7 +36,7 @@ pub enum Cmd<'a> {
Forward(Seq<'a>, AttachmentPaths<'a>, Encrypt),
List(MaxTableWidth, Option<PageSize>, Page),
Move(Seq<'a>, Mbox<'a>),
Read(Seq<'a>, TextMime<'a>, Raw),
Read(Seq<'a>, TextMime<'a>, Raw, Headers<'a>),
Reply(Seq<'a>, All, AttachmentPaths<'a>, Encrypt),
Save(RawMsg<'a>),
Search(Query, MaxTableWidth, Option<PageSize>, Page),
@@ -121,7 +122,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Cmd<'a>>> {
debug!("text mime: {}", mime);
let raw = m.is_present("raw");
debug!("raw: {}", raw);
return Ok(Some(Cmd::Read(seq, mime, raw)));
let headers: Vec<&str> = m.values_of("headers").unwrap_or_default().collect();
debug!("headers: {:?}", headers);
return Ok(Some(Cmd::Read(seq, mime, raw, headers)));
}
if let Some(m) = m.subcommand_matches("reply") {
@@ -318,7 +321,7 @@ fn page_arg<'a>() -> Arg<'a, 'a> {
}
/// Message attachment argument.
pub fn attachment_arg<'a>() -> Arg<'a, 'a> {
pub fn attachments_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("attachments")
.help("Adds attachment to the message")
.short("a")
@@ -327,6 +330,16 @@ pub fn attachment_arg<'a>() -> Arg<'a, 'a> {
.multiple(true)
}
/// Represents the message headers argument.
pub fn headers_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("headers")
.help("Shows additional headers with the message")
.short("h")
.long("header")
.value_name("STR")
.multiple(true)
}
/// Message encrypt argument.
pub fn encrypt_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("encrypt")
@@ -399,7 +412,7 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
),
SubCommand::with_name("write")
.about("Writes a new message")
.arg(attachment_arg())
.arg(attachments_arg())
.arg(encrypt_arg()),
SubCommand::with_name("send")
.about("Sends a raw message")
@@ -424,19 +437,20 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
.help("Reads raw message")
.long("raw")
.short("r"),
),
)
.arg(headers_arg()),
SubCommand::with_name("reply")
.aliases(&["rep", "r"])
.about("Answers to a message")
.arg(seq_arg())
.arg(reply_all_arg())
.arg(attachment_arg())
.arg(attachments_arg())
.arg(encrypt_arg()),
SubCommand::with_name("forward")
.aliases(&["fwd", "f"])
.about("Forwards a message")
.arg(seq_arg())
.arg(attachment_arg())
.arg(attachments_arg())
.arg(encrypt_arg()),
SubCommand::with_name("copy")
.aliases(&["cp", "c"])
+91 -3
View File
@@ -1,11 +1,19 @@
use ammonia;
use anyhow::{anyhow, Context, Error, Result};
use chrono::{DateTime, FixedOffset};
use convert_case::{Case, Casing};
use html_escape;
use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart};
use log::{debug, info, trace, warn};
use regex::Regex;
use std::{collections::HashSet, convert::TryInto, env::temp_dir, fmt::Debug, fs, path::PathBuf};
use std::{
collections::{HashMap, HashSet},
convert::TryInto,
env::temp_dir,
fmt::Debug,
fs,
path::PathBuf,
};
use uuid::Uuid;
use crate::{
@@ -41,6 +49,7 @@ pub struct Msg {
pub bcc: Option<Addrs>,
pub in_reply_to: Option<String>,
pub message_id: Option<String>,
pub headers: HashMap<String, String>,
/// The internal date of the message.
///
@@ -665,9 +674,11 @@ impl Msg {
"message-id" => msg.message_id = Some(val),
"in-reply-to" => msg.in_reply_to = Some(val),
"subject" => {
msg.subject = val;
msg.subject = rfc2047_decoder::decode(val.as_bytes())?;
}
"date" => {
// TODO: use date format instead
// https://github.com/jonhoo/rust-imap/blob/afbc5118f251da4e3f6a1e560e749c0700020b54/src/types/fetch.rs#L16
msg.date = DateTime::parse_from_rfc2822(
val.split_at(val.find(" (").unwrap_or_else(|| val.len())).0,
)
@@ -697,7 +708,12 @@ impl Msg {
msg.bcc = from_slice_to_addrs(val)
.context(format!("cannot parse header {:?}", key))?
}
_ => (),
key => {
msg.headers.insert(
key.to_owned(),
rfc2047_decoder::decode(val.as_bytes()).unwrap_or(val),
);
}
}
}
@@ -708,6 +724,78 @@ impl Msg {
info!("end: building message from parsed mail");
Ok(msg)
}
/// Transforms a message into a readable string. A readable
/// 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<String> {
let mut readable_msg = String::new();
for h in headers {
match h.to_lowercase().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));
}
_ => (),
},
"in-reply-to" => match self.in_reply_to {
Some(ref in_reply_to) if !in_reply_to.is_empty() => {
readable_msg.push_str(&format!("In-Reply-To: {}\n", in_reply_to));
}
_ => (),
},
"subject" => {
readable_msg.push_str(&format!("Subject: {}\n", self.subject));
}
"date" => {
if let Some(ref date) = self.date {
readable_msg.push_str(&format!("Date: {}\n", date));
}
}
"from" => match self.from {
Some(ref addrs) if !addrs.is_empty() => {
readable_msg.push_str(&format!("From: {}\n", addrs));
}
_ => (),
},
"to" => match self.to {
Some(ref addrs) if !addrs.is_empty() => {
readable_msg.push_str(&format!("To: {}\n", addrs));
}
_ => (),
},
"reply-to" => match self.reply_to {
Some(ref addrs) if !addrs.is_empty() => {
readable_msg.push_str(&format!("Reply-To: {}\n", addrs));
}
_ => (),
},
"cc" => match self.cc {
Some(ref addrs) if !addrs.is_empty() => {
readable_msg.push_str(&format!("Cc: {}\n", addrs));
}
_ => (),
},
"bcc" => match self.bcc {
Some(ref addrs) if !addrs.is_empty() => {
readable_msg.push_str(&format!("Bcc: {}\n", addrs));
}
_ => (),
},
key => match self.headers.get(key) {
Some(ref val) if !val.is_empty() => {
readable_msg.push_str(&format!("{}: {}\n", key.to_case(Case::Pascal), val));
}
_ => (),
},
};
}
readable_msg.push_str("\n");
readable_msg.push_str(&self.fold_text_parts(text_mime));
Ok(readable_msg)
}
}
impl TryInto<lettre::address::Envelope> for Msg {
+5 -5
View File
@@ -207,19 +207,19 @@ pub fn read<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
seq: &str,
text_mime: &str,
raw: bool,
headers: Vec<&str>,
mbox: &str,
printer: &mut P,
backend: Box<&'a mut B>,
) -> Result<()> {
let msg = backend.get_msg(mbox, seq)?;
let msg = if raw {
printer.print_struct(if raw {
// Emails don't always have valid utf8. Using "lossy" to display what we can.
String::from_utf8_lossy(&msg.raw).into_owned()
} else {
msg.fold_text_parts(text_mime)
};
printer.print_struct(msg)
msg.to_readable_string(text_mime, headers)?
})
}
/// Reply to the given message UID.
+2 -2
View File
@@ -183,13 +183,13 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
.subcommand(
SubCommand::with_name("save")
.about("Saves a message based on the given template")
.arg(&msg_args::attachment_arg())
.arg(&msg_args::attachments_arg())
.arg(Arg::with_name("template").raw(true)),
)
.subcommand(
SubCommand::with_name("send")
.about("Sends a message based on the given template")
.arg(&msg_args::attachment_arg())
.arg(&msg_args::attachments_arg())
.arg(Arg::with_name("template").raw(true)),
)]
}