From 5a9481910f8a56d5f1a3b7b23771a529948a3e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Mon, 13 Sep 2021 15:32:01 +0200 Subject: [PATCH] turn smtp into an interfaced service --- src/domain/mod.rs | 3 ++ src/domain/smtp/mod.rs | 3 ++ src/domain/smtp/service.rs | 51 +++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 8 +++-- src/msg/cli.rs | 67 ++++++++++++++++++++++++++------------ 6 files changed, 110 insertions(+), 23 deletions(-) create mode 100644 src/domain/mod.rs create mode 100644 src/domain/smtp/mod.rs create mode 100644 src/domain/smtp/service.rs diff --git a/src/domain/mod.rs b/src/domain/mod.rs new file mode 100644 index 0000000..9d5bfdd --- /dev/null +++ b/src/domain/mod.rs @@ -0,0 +1,3 @@ +//! Domain-specific modules. + +pub mod smtp; diff --git a/src/domain/smtp/mod.rs b/src/domain/smtp/mod.rs new file mode 100644 index 0000000..81d6738 --- /dev/null +++ b/src/domain/smtp/mod.rs @@ -0,0 +1,3 @@ +//! Modules related to SMTP. + +pub mod service; diff --git a/src/domain/smtp/service.rs b/src/domain/smtp/service.rs new file mode 100644 index 0000000..4730fcd --- /dev/null +++ b/src/domain/smtp/service.rs @@ -0,0 +1,51 @@ +use anyhow::Result; +use lettre::{ + self, + transport::{smtp::client::Tls, smtp::client::TlsParameters, smtp::SmtpTransport}, + Transport, +}; + +use crate::config::model::Account; + +pub trait SMTPServiceInterface<'a> { + fn send(&self, msg: &lettre::Message) -> Result<()>; +} + +pub struct SMTPService<'a> { + account: &'a Account, +} + +impl<'a> SMTPService<'a> { + pub fn init(account: &'a Account) -> Self { + Self { account } + } +} + +impl<'a> SMTPServiceInterface<'a> for SMTPService<'a> { + fn send(&self, msg: &lettre::Message) -> Result<()> { + let smtp_relay = if self.account.smtp_starttls() { + SmtpTransport::starttls_relay + } else { + SmtpTransport::relay + }; + + let tls = TlsParameters::builder(self.account.smtp_host.to_string()) + .dangerous_accept_invalid_hostnames(self.account.smtp_insecure()) + .dangerous_accept_invalid_certs(self.account.smtp_insecure()) + .build()?; + let tls = if self.account.smtp_starttls() { + Tls::Required(tls) + } else { + Tls::Wrapper(tls) + }; + + smtp_relay(&self.account.smtp_host)? + .port(self.account.smtp_port) + .tls(tls) + .credentials(self.account.smtp_creds()?) + .build() + .send(msg)?; + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9e301bb..8769e08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,4 +45,5 @@ pub mod output; /// This module takes care for sending your mails! pub mod smtp; +pub mod domain; pub mod ui; diff --git a/src/main.rs b/src/main.rs index 6f13a54..8f29a03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use himalaya::{ comp, config::{cli::config_args, model::Config}, ctx::Ctx, - flag, imap, mbox, + domain, flag, imap, mbox, msg::{self, cli::msg_matches_mailto}, output::{cli::output_args, model::Output}, }; @@ -46,7 +46,8 @@ fn main() -> Result<()> { let arg_matches = ArgMatches::default(); let app = Ctx::new(config, account, output, mbox, arg_matches); let url = Url::parse(&raw_args[1])?; - return Ok(msg_matches_mailto(&app, &url)?); + let smtp = domain::smtp::service::SMTPService::init(&app.account); + return Ok(msg_matches_mailto(&app, &url, smtp)?); } let args = parse_args(); @@ -78,10 +79,11 @@ fn main() -> Result<()> { debug!("begin matching"); let app = Ctx::new(config, account, output, mbox, arg_matches); + let smtp = domain::smtp::service::SMTPService::init(&app.account); let _matched = mbox::cli::matches(&app)? || flag::cli::matches(&app)? || imap::cli::matches(&app)? - || msg::cli::matches(&app)?; + || msg::cli::matches(&app, smtp)?; Ok(()) } diff --git a/src/msg/cli.rs b/src/msg/cli.rs index 9eebf37..298e31a 100644 --- a/src/msg/cli.rs +++ b/src/msg/cli.rs @@ -19,8 +19,8 @@ use super::{ model::{Msg, MsgSerialized, Msgs}, }; use crate::{ - ctx::Ctx, flag::model::Flags, imap::model::ImapConnector, input, mbox::cli::mbox_target_arg, - smtp, + ctx::Ctx, domain::smtp, flag::model::Flags, imap::model::ImapConnector, input, + mbox::cli::mbox_target_arg, }; pub fn subcmds<'a>() -> Vec> { @@ -123,19 +123,22 @@ pub fn subcmds<'a>() -> Vec> { ] } -pub fn matches(ctx: &Ctx) -> Result { +pub fn matches<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>( + ctx: &Ctx, + smtp: SMTP, +) -> Result { match ctx.arg_matches.subcommand() { ("attachments", Some(matches)) => msg_matches_attachments(ctx, matches), ("copy", Some(matches)) => msg_matches_copy(ctx, matches), ("delete", Some(matches)) => msg_matches_delete(ctx, matches), - ("forward", Some(matches)) => msg_matches_forward(ctx, matches), + ("forward", Some(matches)) => msg_matches_forward(ctx, matches, smtp), ("move", Some(matches)) => msg_matches_move(ctx, matches), ("read", Some(matches)) => msg_matches_read(ctx, matches), - ("reply", Some(matches)) => msg_matches_reply(ctx, matches), + ("reply", Some(matches)) => msg_matches_reply(ctx, matches, smtp), ("save", Some(matches)) => msg_matches_save(ctx, matches), ("search", Some(matches)) => msg_matches_search(ctx, matches), - ("send", Some(matches)) => msg_matches_send(ctx, matches), - ("write", Some(matches)) => msg_matches_write(ctx, matches), + ("send", Some(matches)) => msg_matches_send(ctx, matches, smtp), + ("write", Some(matches)) => msg_matches_write(ctx, matches, smtp), ("template", Some(matches)) => Ok(msg_matches_tpl(ctx, matches)?), ("list", opt_matches) => msg_matches_list(ctx, opt_matches), @@ -236,7 +239,6 @@ fn tpl_args<'a>() -> Vec> { ] } -// == Match functions == fn msg_matches_list(ctx: &Ctx, opt_matches: Option<&clap::ArgMatches>) -> Result { debug!("list command matched"); @@ -388,7 +390,11 @@ fn msg_matches_attachments(ctx: &Ctx, matches: &clap::ArgMatches) -> Result Result { +fn msg_matches_write<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>( + ctx: &Ctx, + matches: &clap::ArgMatches, + smtp: SMTP, +) -> Result { debug!("write command matched"); let mut imap_conn = ImapConnector::new(&ctx.account)?; @@ -414,7 +420,7 @@ fn msg_matches_write(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { .iter() .for_each(|path| msg.add_attachment(path)); - msg_interaction(&ctx, &mut msg, &mut imap_conn)?; + msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?; // let's be nice to the server and say "bye" to the server imap_conn.logout(); @@ -422,7 +428,11 @@ fn msg_matches_write(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { Ok(true) } -fn msg_matches_reply(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { +fn msg_matches_reply<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>( + ctx: &Ctx, + matches: &clap::ArgMatches, + smtp: SMTP, +) -> Result { debug!("reply command matched"); // -- Preparations -- @@ -446,13 +456,17 @@ fn msg_matches_reply(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { debug!("found {} attachments", attachments.len()); trace!("attachments: {:?}", attachments); - msg_interaction(&ctx, &mut msg, &mut imap_conn)?; + msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?; imap_conn.logout(); Ok(true) } -pub fn msg_matches_mailto(ctx: &Ctx, url: &Url) -> Result<()> { +pub fn msg_matches_mailto<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>( + ctx: &Ctx, + url: &Url, + smtp: SMTP, +) -> Result<()> { debug!("mailto command matched"); let mut imap_conn = ImapConnector::new(&ctx.account)?; @@ -493,13 +507,17 @@ pub fn msg_matches_mailto(ctx: &Ctx, url: &Url) -> Result<()> { let mut msg = Msg::new_with_headers(&ctx, headers); msg.body = Body::new_with_text(body); - msg_interaction(&ctx, &mut msg, &mut imap_conn)?; + msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?; imap_conn.logout(); Ok(()) } -fn msg_matches_forward(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { +fn msg_matches_forward<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>( + ctx: &Ctx, + matches: &clap::ArgMatches, + smtp: SMTP, +) -> Result { debug!("forward command matched"); // fetch the msg @@ -523,7 +541,7 @@ fn msg_matches_forward(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { trace!("attachments: {:?}", attachments); // apply changes - msg_interaction(&ctx, &mut msg, &mut imap_conn)?; + msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?; imap_conn.logout(); @@ -608,7 +626,11 @@ fn msg_matches_delete(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { Ok(true) } -fn msg_matches_send(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { +fn msg_matches_send<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>( + ctx: &Ctx, + matches: &clap::ArgMatches, + smtp: SMTP, +) -> Result { debug!("send command matched"); let mut imap_conn = ImapConnector::new(&ctx.account)?; @@ -633,7 +655,7 @@ fn msg_matches_send(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { // send the message/msg let sendable = msg.to_sendable_msg()?; - smtp::send(&ctx.account, &sendable)?; + smtp.send(&sendable)?; debug!("message sent!"); // add the message/msg to the Sent-Mailbox of the user @@ -813,7 +835,12 @@ fn tpl_matches_forward(ctx: &Ctx, matches: &clap::ArgMatches) -> Result { /// This function opens the prompt to do some actions to the msg like sending, editing it again and /// so on. -fn msg_interaction(ctx: &Ctx, msg: &mut Msg, imap_conn: &mut ImapConnector) -> Result { +fn msg_interaction<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>( + ctx: &Ctx, + msg: &mut Msg, + imap_conn: &mut ImapConnector, + smtp: SMTP, +) -> Result { // let the user change the body a little bit first, before opening the prompt msg.edit_body()?; @@ -836,7 +863,7 @@ fn msg_interaction(ctx: &Ctx, msg: &mut Msg, imap_conn: &mut ImapConnector) -> R continue; } }; - smtp::send(&ctx.account, &sendable)?; + smtp.send(&sendable)?; // TODO: Gmail sent mailboxes are called `[Gmail]/Sent` // which creates a conflict, fix this!