mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-08 18:45:13 +00:00
implement reply, reply all and forward features
This commit is contained in:
parent
2709faf30a
commit
43c35532be
23
src/imap.rs
23
src/imap.rs
|
@ -1,12 +1,10 @@
|
||||||
use imap;
|
use imap;
|
||||||
use mailparse;
|
|
||||||
use native_tls::{self, TlsConnector, TlsStream};
|
use native_tls::{self, TlsConnector, TlsStream};
|
||||||
use std::{fmt, net::TcpStream, result};
|
use std::{fmt, net::TcpStream, result};
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::email::{self, Email};
|
use crate::email::{self, Email};
|
||||||
use crate::mailbox::Mailbox;
|
use crate::mailbox::Mailbox;
|
||||||
use crate::msg::Msg;
|
|
||||||
|
|
||||||
// Error wrapper
|
// Error wrapper
|
||||||
|
|
||||||
|
@ -61,13 +59,13 @@ type Result<T> = result::Result<T, Error>;
|
||||||
// Imap connector
|
// Imap connector
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ImapConnector {
|
pub struct ImapConnector<'a> {
|
||||||
pub config: config::ServerInfo,
|
pub config: &'a config::ServerInfo,
|
||||||
pub sess: imap::Session<TlsStream<TcpStream>>,
|
pub sess: imap::Session<TlsStream<TcpStream>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImapConnector {
|
impl<'a> ImapConnector<'a> {
|
||||||
pub fn new(config: config::ServerInfo) -> Result<Self> {
|
pub fn new(config: &'a config::ServerInfo) -> Result<Self> {
|
||||||
let tls = TlsConnector::new()?;
|
let tls = TlsConnector::new()?;
|
||||||
let client = imap::connect(config.get_addr(), &config.host, &tls)?;
|
let client = imap::connect(config.get_addr(), &config.host, &tls)?;
|
||||||
let sess = client
|
let sess = client
|
||||||
|
@ -133,9 +131,18 @@ impl ImapConnector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append_msg(&mut self, mbox: &str, msg: &Msg) -> Result<()> {
|
pub fn read_msg(&mut self, mbox: &str, uid: &str) -> Result<Vec<u8>> {
|
||||||
|
self.sess.select(mbox)?;
|
||||||
|
|
||||||
|
match self.sess.uid_fetch(uid, "BODY[]")?.first() {
|
||||||
|
None => Err(Error::ReadEmailNotFoundError(uid.to_string())),
|
||||||
|
Some(fetch) => Ok(fetch.body().unwrap_or(&[]).to_vec()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_msg(&mut self, mbox: &str, msg: &[u8]) -> Result<()> {
|
||||||
use imap::types::Flag::*;
|
use imap::types::Flag::*;
|
||||||
self.sess.append_with_flags(mbox, msg.to_vec(), &[Seen])?;
|
self.sess.append_with_flags(mbox, msg, &[Seen])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
src/input.rs
13
src/input.rs
|
@ -7,8 +7,6 @@ use std::{
|
||||||
result,
|
result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::config::Config;
|
|
||||||
|
|
||||||
// Error wrapper
|
// Error wrapper
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -39,7 +37,7 @@ type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
fn open_editor_with_tpl(tpl: &[u8]) -> Result<String> {
|
pub fn open_editor_with_tpl(tpl: &[u8]) -> Result<String> {
|
||||||
// Creates draft file
|
// Creates draft file
|
||||||
let mut draft_path = temp_dir();
|
let mut draft_path = temp_dir();
|
||||||
draft_path.push("himalaya-draft.mail");
|
draft_path.push("himalaya-draft.mail");
|
||||||
|
@ -56,15 +54,6 @@ fn open_editor_with_tpl(tpl: &[u8]) -> Result<String> {
|
||||||
Ok(draft)
|
Ok(draft)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_editor_with_new_tpl(config: &Config) -> Result<String> {
|
|
||||||
let from = &format!("From: {}", config.email_full());
|
|
||||||
let to = "To: ";
|
|
||||||
let subject = "Subject: ";
|
|
||||||
let headers = [from, to, subject, ""].join("\r\n");
|
|
||||||
|
|
||||||
Ok(open_editor_with_tpl(headers.as_bytes())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ask_for_confirmation(prompt: &str) -> Result<()> {
|
pub fn ask_for_confirmation(prompt: &str) -> Result<()> {
|
||||||
print!("{} (y/n) ", prompt);
|
print!("{} (y/n) ", prompt);
|
||||||
io::stdout().flush()?;
|
io::stdout().flush()?;
|
||||||
|
|
76
src/main.rs
76
src/main.rs
|
@ -78,6 +78,7 @@ fn mailbox_arg() -> Arg<'static, 'static> {
|
||||||
.long("mailbox")
|
.long("mailbox")
|
||||||
.help("Name of the targeted mailbox")
|
.help("Name of the targeted mailbox")
|
||||||
.value_name("STRING")
|
.value_name("STRING")
|
||||||
|
.default_value("INBOX")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uid_arg() -> Arg<'static, 'static> {
|
fn uid_arg() -> Arg<'static, 'static> {
|
||||||
|
@ -97,7 +98,7 @@ fn run() -> Result<()> {
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("search")
|
SubCommand::with_name("search")
|
||||||
.about("Lists emails matching the given IMAP query")
|
.about("Lists emails matching the given IMAP query")
|
||||||
.arg(mailbox_arg().default_value("INBOX"))
|
.arg(mailbox_arg())
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("query")
|
Arg::with_name("query")
|
||||||
.help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)")
|
.help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)")
|
||||||
|
@ -110,7 +111,7 @@ fn run() -> Result<()> {
|
||||||
SubCommand::with_name("read")
|
SubCommand::with_name("read")
|
||||||
.about("Reads an email by its UID")
|
.about("Reads an email by its UID")
|
||||||
.arg(uid_arg())
|
.arg(uid_arg())
|
||||||
.arg(mailbox_arg().default_value("INBOX"))
|
.arg(mailbox_arg())
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("mime-type")
|
Arg::with_name("mime-type")
|
||||||
.help("MIME type to use")
|
.help("MIME type to use")
|
||||||
|
@ -126,7 +127,7 @@ fn run() -> Result<()> {
|
||||||
SubCommand::with_name("reply")
|
SubCommand::with_name("reply")
|
||||||
.about("Replies to an email by its UID")
|
.about("Replies to an email by its UID")
|
||||||
.arg(uid_arg())
|
.arg(uid_arg())
|
||||||
.arg(mailbox_arg().default_value("INBOX"))
|
.arg(mailbox_arg())
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("reply all")
|
Arg::with_name("reply all")
|
||||||
.help("Replies to all recipients")
|
.help("Replies to all recipients")
|
||||||
|
@ -138,13 +139,13 @@ fn run() -> Result<()> {
|
||||||
SubCommand::with_name("forward")
|
SubCommand::with_name("forward")
|
||||||
.about("Forwards an email by its UID")
|
.about("Forwards an email by its UID")
|
||||||
.arg(uid_arg())
|
.arg(uid_arg())
|
||||||
.arg(mailbox_arg().default_value("INBOX")),
|
.arg(mailbox_arg()),
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("list") {
|
if let Some(_) = matches.subcommand_matches("list") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
let mboxes = ImapConnector::new(config.imap)?.list_mboxes()?.to_table();
|
let mboxes = ImapConnector::new(&config.imap)?.list_mboxes()?.to_table();
|
||||||
|
|
||||||
println!("{}", mboxes);
|
println!("{}", mboxes);
|
||||||
}
|
}
|
||||||
|
@ -177,7 +178,7 @@ fn run() -> Result<()> {
|
||||||
.1
|
.1
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
let emails = ImapConnector::new(config.imap)?
|
let emails = ImapConnector::new(&config.imap)?
|
||||||
.read_emails(&mbox, &query)?
|
.read_emails(&mbox, &query)?
|
||||||
.to_table();
|
.to_table();
|
||||||
|
|
||||||
|
@ -190,30 +191,71 @@ fn run() -> Result<()> {
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let uid = matches.value_of("uid").unwrap();
|
let uid = matches.value_of("uid").unwrap();
|
||||||
let mime = matches.value_of("mime-type").unwrap();
|
let mime = matches.value_of("mime-type").unwrap();
|
||||||
let body = ImapConnector::new(config.imap)?.read_email_body(&mbox, &uid, &mime)?;
|
let body = ImapConnector::new(&config.imap)?.read_email_body(&mbox, &uid, &mime)?;
|
||||||
|
|
||||||
println!("{}", body);
|
println!("{}", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("write") {
|
if let Some(_) = matches.subcommand_matches("write") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
let content = input::open_editor_with_new_tpl(&config)?;
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
let msg = Msg::from_raw(content.as_bytes())?;
|
let tpl = Msg::build_new_tpl(&config)?;
|
||||||
|
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||||
|
let msg = Msg::from(content.as_bytes())?;
|
||||||
|
|
||||||
input::ask_for_confirmation("Would you like to send this email?")?;
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
println!("Sending …");
|
println!("Sending …");
|
||||||
smtp::send(&config.smtp, &msg)?;
|
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||||
ImapConnector::new(config.imap)?.append_msg("Sent", &msg)?;
|
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
println!("Sent!");
|
println!("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("reply") {
|
if let Some(matches) = matches.subcommand_matches("reply") {
|
||||||
// TODO
|
let config = Config::new_from_file()?;
|
||||||
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
let uid = matches.value_of("uid").unwrap();
|
||||||
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
|
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||||
|
let msg = Msg::from(&msg)?;
|
||||||
|
|
||||||
|
let tpl = if matches.is_present("reply all") {
|
||||||
|
msg.build_reply_all_tpl(&config)?
|
||||||
|
} else {
|
||||||
|
msg.build_reply_tpl(&config)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||||
|
let msg = Msg::from(content.as_bytes())?;
|
||||||
|
|
||||||
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
|
println!("Sending …");
|
||||||
|
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||||
|
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
|
println!("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("forward") {
|
if let Some(matches) = matches.subcommand_matches("forward") {
|
||||||
// TODO
|
let config = Config::new_from_file()?;
|
||||||
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
let uid = matches.value_of("uid").unwrap();
|
||||||
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
|
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||||
|
let msg = Msg::from(&msg)?;
|
||||||
|
|
||||||
|
let tpl = msg.build_forward_tpl(&config)?;
|
||||||
|
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||||
|
let msg = Msg::from(content.as_bytes())?;
|
||||||
|
|
||||||
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
|
println!("Sending …");
|
||||||
|
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||||
|
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
|
println!("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
240
src/msg.rs
240
src/msg.rs
|
@ -1,6 +1,8 @@
|
||||||
use lettre;
|
use lettre;
|
||||||
use mailparse;
|
use mailparse::{self, MailHeaderMap};
|
||||||
use std::{fmt, result};
|
use std::{fmt, ops, result};
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
// Error wrapper
|
// Error wrapper
|
||||||
|
|
||||||
|
@ -8,6 +10,7 @@ use std::{fmt, result};
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
ParseMsgError(mailparse::MailParseError),
|
ParseMsgError(mailparse::MailParseError),
|
||||||
BuildEmailError(lettre::error::Error),
|
BuildEmailError(lettre::error::Error),
|
||||||
|
TryError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
|
@ -16,6 +19,7 @@ impl fmt::Display for Error {
|
||||||
match self {
|
match self {
|
||||||
Error::ParseMsgError(err) => err.fmt(f),
|
Error::ParseMsgError(err) => err.fmt(f),
|
||||||
Error::BuildEmailError(err) => err.fmt(f),
|
Error::BuildEmailError(err) => err.fmt(f),
|
||||||
|
Error::TryError => write!(f, "cannot parse"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,30 +42,68 @@ type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Wrapper around mailparse::ParsedMail and lettre::Message
|
// Wrapper around mailparse::ParsedMail and lettre::Message
|
||||||
|
|
||||||
pub struct Msg(lettre::Message);
|
#[derive(Debug)]
|
||||||
|
pub struct Msg<'a>(mailparse::ParsedMail<'a>);
|
||||||
|
|
||||||
impl Msg {
|
impl<'a> ops::Deref for Msg<'a> {
|
||||||
pub fn from_raw(bytes: &[u8]) -> Result<Msg> {
|
type Target = mailparse::ParsedMail<'a>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Msg<'a> {
|
||||||
|
pub fn from(bytes: &'a [u8]) -> Result<Self> {
|
||||||
|
Ok(Self(mailparse::parse_mail(bytes)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> Result<Vec<u8>> {
|
||||||
|
let headers = self.0.get_headers().get_raw_bytes().to_vec();
|
||||||
|
let sep = "\r\n".as_bytes().to_vec();
|
||||||
|
let body = self.0.get_body()?.as_bytes().to_vec();
|
||||||
|
|
||||||
|
Ok(vec![headers, sep, body].concat())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_sendable_msg(&self) -> Result<lettre::Message> {
|
||||||
use lettre::message::header::{ContentTransferEncoding, ContentType};
|
use lettre::message::header::{ContentTransferEncoding, ContentType};
|
||||||
use lettre::message::{Message, SinglePart};
|
use lettre::message::{Message, SinglePart};
|
||||||
|
|
||||||
let parsed_msg = mailparse::parse_mail(bytes)?;
|
let msg = self
|
||||||
let built_msg = parsed_msg
|
.0
|
||||||
.headers
|
.headers
|
||||||
.iter()
|
.iter()
|
||||||
.fold(Message::builder(), |msg, h| {
|
.fold(Message::builder(), |msg, h| {
|
||||||
|
let value = String::from_utf8(h.get_value_raw().to_vec())
|
||||||
|
.unwrap()
|
||||||
|
.replace("\r", "");
|
||||||
|
|
||||||
match h.get_key().to_lowercase().as_str() {
|
match h.get_key().to_lowercase().as_str() {
|
||||||
"from" => msg.from(h.get_value().parse().unwrap()),
|
"in-reply-to" => msg.in_reply_to(value.parse().unwrap()),
|
||||||
"to" => msg.to(h.get_value().parse().unwrap()),
|
"from" => match value.parse() {
|
||||||
"cc" => match h.get_value().parse() {
|
Ok(addr) => msg.from(addr),
|
||||||
Err(_) => msg,
|
Err(_) => msg,
|
||||||
Ok(addr) => msg.cc(addr),
|
|
||||||
},
|
},
|
||||||
"bcc" => match h.get_value().parse() {
|
"to" => value
|
||||||
Err(_) => msg,
|
.split(",")
|
||||||
Ok(addr) => msg.bcc(addr),
|
.fold(msg, |msg, addr| match addr.trim().parse() {
|
||||||
},
|
Ok(addr) => msg.to(addr),
|
||||||
"subject" => msg.subject(h.get_value()),
|
Err(_) => msg,
|
||||||
|
}),
|
||||||
|
"cc" => value
|
||||||
|
.split(",")
|
||||||
|
.fold(msg, |msg, addr| match addr.trim().parse() {
|
||||||
|
Ok(addr) => msg.cc(addr),
|
||||||
|
Err(_) => msg,
|
||||||
|
}),
|
||||||
|
"bcc" => value
|
||||||
|
.split(",")
|
||||||
|
.fold(msg, |msg, addr| match addr.trim().parse() {
|
||||||
|
Ok(addr) => msg.bcc(addr),
|
||||||
|
Err(_) => msg,
|
||||||
|
}),
|
||||||
|
"subject" => msg.subject(value),
|
||||||
_ => msg,
|
_ => msg,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -69,17 +111,171 @@ impl Msg {
|
||||||
SinglePart::builder()
|
SinglePart::builder()
|
||||||
.header(ContentType("text/plain; charset=utf-8".parse().unwrap()))
|
.header(ContentType("text/plain; charset=utf-8".parse().unwrap()))
|
||||||
.header(ContentTransferEncoding::Base64)
|
.header(ContentTransferEncoding::Base64)
|
||||||
.body(parsed_msg.get_body_raw()?),
|
.body(self.0.get_body_raw()?),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(Msg(built_msg))
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_sendable_msg(&self) -> &lettre::Message {
|
pub fn build_new_tpl(config: &Config) -> Result<String> {
|
||||||
&self.0
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
// "From" header
|
||||||
|
tpl.push(format!("From: {}", config.email_full()));
|
||||||
|
|
||||||
|
// "To" header
|
||||||
|
tpl.push("To: ".to_string());
|
||||||
|
|
||||||
|
// "Subject" header
|
||||||
|
tpl.push("Subject: ".to_string());
|
||||||
|
|
||||||
|
Ok(tpl.join("\r\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_vec(&self) -> Vec<u8> {
|
pub fn build_reply_tpl(&self, config: &Config) -> Result<String> {
|
||||||
self.0.formatted()
|
let msg = &self.0;
|
||||||
|
let headers = msg.get_headers();
|
||||||
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
// "From" header
|
||||||
|
tpl.push(format!("From: {}", config.email_full()));
|
||||||
|
|
||||||
|
// "In-Reply-To" header
|
||||||
|
if let Some(msg_id) = headers.get_first_value("message-id") {
|
||||||
|
tpl.push(format!("In-Reply-To: {}", msg_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "To" header
|
||||||
|
let to = headers
|
||||||
|
.get_first_value("reply-to")
|
||||||
|
.or(headers.get_first_value("from"))
|
||||||
|
.unwrap_or(String::new());
|
||||||
|
tpl.push(format!("To: {}", to));
|
||||||
|
|
||||||
|
// "Subject" header
|
||||||
|
let subject = headers.get_first_value("subject").unwrap_or(String::new());
|
||||||
|
tpl.push(format!("Subject: Re: {}", subject));
|
||||||
|
|
||||||
|
// Separator between headers and body
|
||||||
|
tpl.push(String::new());
|
||||||
|
|
||||||
|
// Original msg prepend with ">"
|
||||||
|
let thread = msg
|
||||||
|
.get_body()
|
||||||
|
.unwrap()
|
||||||
|
.split("\r\n")
|
||||||
|
.map(|line| format!(">{}", line))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\r\n");
|
||||||
|
tpl.push(thread);
|
||||||
|
|
||||||
|
Ok(tpl.join("\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_reply_all_tpl(&self, config: &Config) -> Result<String> {
|
||||||
|
let msg = &self.0;
|
||||||
|
let headers = msg.get_headers();
|
||||||
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
// "From" header
|
||||||
|
tpl.push(format!("From: {}", config.email_full()));
|
||||||
|
|
||||||
|
// "In-Reply-To" header
|
||||||
|
if let Some(msg_id) = headers.get_first_value("message-id") {
|
||||||
|
tpl.push(format!("In-Reply-To: {}", msg_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "To" header
|
||||||
|
// All addresses coming from original "To" …
|
||||||
|
let email: lettre::Address = config.email.parse().unwrap();
|
||||||
|
let to = headers
|
||||||
|
.get_all_values("to")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|addrs| addrs.split(","))
|
||||||
|
.fold(vec![], |mut mboxes, addr| {
|
||||||
|
match addr.trim().parse::<lettre::message::Mailbox>() {
|
||||||
|
Err(_) => mboxes,
|
||||||
|
Ok(mbox) => {
|
||||||
|
// … except current user's one (from config) …
|
||||||
|
if mbox.email != email {
|
||||||
|
mboxes.push(mbox.to_string());
|
||||||
|
}
|
||||||
|
mboxes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// … and the ones coming from either "Reply-To" or "From"
|
||||||
|
let reply_to = headers
|
||||||
|
.get_all_values("reply-to")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|addrs| addrs.split(","))
|
||||||
|
.map(|addr| addr.trim().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
let reply_to = if reply_to.is_empty() {
|
||||||
|
headers
|
||||||
|
.get_all_values("from")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|addrs| addrs.split(","))
|
||||||
|
.map(|addr| addr.trim().to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
} else {
|
||||||
|
reply_to
|
||||||
|
};
|
||||||
|
tpl.push(format!("To: {}", vec![reply_to, to].concat().join(", ")));
|
||||||
|
|
||||||
|
// "Cc" header
|
||||||
|
let cc = headers
|
||||||
|
.get_all_values("cc")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|addrs| addrs.split(","))
|
||||||
|
.map(|addr| addr.trim().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
if !cc.is_empty() {
|
||||||
|
tpl.push(format!("Cc: {}", cc.join(", ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Subject" header
|
||||||
|
let subject = headers.get_first_value("subject").unwrap_or(String::new());
|
||||||
|
tpl.push(format!("Subject: Re: {}", subject));
|
||||||
|
|
||||||
|
// Separator between headers and body
|
||||||
|
tpl.push(String::new());
|
||||||
|
|
||||||
|
// Original msg prepend with ">"
|
||||||
|
let thread = msg
|
||||||
|
.get_body()
|
||||||
|
.unwrap()
|
||||||
|
.split("\r\n")
|
||||||
|
.map(|line| format!(">{}", line))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\r\n");
|
||||||
|
tpl.push(thread);
|
||||||
|
|
||||||
|
Ok(tpl.join("\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_forward_tpl(&self, config: &Config) -> Result<String> {
|
||||||
|
let msg = &self.0;
|
||||||
|
let headers = msg.get_headers();
|
||||||
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
// "From" header
|
||||||
|
tpl.push(format!("From: {}", config.email_full()));
|
||||||
|
|
||||||
|
// "To" header
|
||||||
|
tpl.push("To: ".to_string());
|
||||||
|
|
||||||
|
// "Subject" header
|
||||||
|
let subject = headers.get_first_value("subject").unwrap_or(String::new());
|
||||||
|
tpl.push(format!("Subject: Fwd: {}", subject));
|
||||||
|
|
||||||
|
// Separator between headers and body
|
||||||
|
tpl.push(String::new());
|
||||||
|
|
||||||
|
// Original msg
|
||||||
|
tpl.push("-------- Forwarded Message --------".to_string());
|
||||||
|
tpl.push(msg.get_body().unwrap_or(String::new()));
|
||||||
|
|
||||||
|
Ok(tpl.join("\r\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ use lettre;
|
||||||
use std::{fmt, result};
|
use std::{fmt, result};
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::msg::Msg;
|
|
||||||
|
|
||||||
// Error wrapper
|
// Error wrapper
|
||||||
|
|
||||||
|
@ -32,12 +31,12 @@ type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
pub fn send(config: &config::ServerInfo, msg: &Msg) -> Result<()> {
|
pub fn send(config: &config::ServerInfo, msg: &lettre::Message) -> Result<()> {
|
||||||
use lettre::Transport;
|
use lettre::Transport;
|
||||||
|
|
||||||
lettre::transport::smtp::SmtpTransport::relay(&config.host)?
|
lettre::transport::smtp::SmtpTransport::relay(&config.host)?
|
||||||
.credentials(config.to_smtp_creds())
|
.credentials(config.to_smtp_creds())
|
||||||
.build()
|
.build()
|
||||||
.send(msg.as_sendable_msg())
|
.send(msg)
|
||||||
.map(|_| Ok(()))?
|
.map(|_| Ok(()))?
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue