turn smtp into an interfaced service

This commit is contained in:
Clément DOUIN 2021-09-13 15:32:01 +02:00
parent c619f06206
commit 5a9481910f
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
6 changed files with 110 additions and 23 deletions

3
src/domain/mod.rs Normal file
View file

@ -0,0 +1,3 @@
//! Domain-specific modules.
pub mod smtp;

3
src/domain/smtp/mod.rs Normal file
View file

@ -0,0 +1,3 @@
//! Modules related to SMTP.
pub mod service;

View file

@ -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(())
}
}

View file

@ -45,4 +45,5 @@ pub mod output;
/// This module takes care for sending your mails! /// This module takes care for sending your mails!
pub mod smtp; pub mod smtp;
pub mod domain;
pub mod ui; pub mod ui;

View file

@ -9,7 +9,7 @@ use himalaya::{
comp, comp,
config::{cli::config_args, model::Config}, config::{cli::config_args, model::Config},
ctx::Ctx, ctx::Ctx,
flag, imap, mbox, domain, flag, imap, mbox,
msg::{self, cli::msg_matches_mailto}, msg::{self, cli::msg_matches_mailto},
output::{cli::output_args, model::Output}, output::{cli::output_args, model::Output},
}; };
@ -46,7 +46,8 @@ fn main() -> Result<()> {
let arg_matches = ArgMatches::default(); let arg_matches = ArgMatches::default();
let app = Ctx::new(config, account, output, mbox, arg_matches); let app = Ctx::new(config, account, output, mbox, arg_matches);
let url = Url::parse(&raw_args[1])?; 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(); let args = parse_args();
@ -78,10 +79,11 @@ fn main() -> Result<()> {
debug!("begin matching"); debug!("begin matching");
let app = Ctx::new(config, account, output, mbox, arg_matches); 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)? let _matched = mbox::cli::matches(&app)?
|| flag::cli::matches(&app)? || flag::cli::matches(&app)?
|| imap::cli::matches(&app)? || imap::cli::matches(&app)?
|| msg::cli::matches(&app)?; || msg::cli::matches(&app, smtp)?;
Ok(()) Ok(())
} }

View file

@ -19,8 +19,8 @@ use super::{
model::{Msg, MsgSerialized, Msgs}, model::{Msg, MsgSerialized, Msgs},
}; };
use crate::{ use crate::{
ctx::Ctx, flag::model::Flags, imap::model::ImapConnector, input, mbox::cli::mbox_target_arg, ctx::Ctx, domain::smtp, flag::model::Flags, imap::model::ImapConnector, input,
smtp, mbox::cli::mbox_target_arg,
}; };
pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> { pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
@ -123,19 +123,22 @@ pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
] ]
} }
pub fn matches(ctx: &Ctx) -> Result<bool> { pub fn matches<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
ctx: &Ctx,
smtp: SMTP,
) -> Result<bool> {
match ctx.arg_matches.subcommand() { match ctx.arg_matches.subcommand() {
("attachments", Some(matches)) => msg_matches_attachments(ctx, matches), ("attachments", Some(matches)) => msg_matches_attachments(ctx, matches),
("copy", Some(matches)) => msg_matches_copy(ctx, matches), ("copy", Some(matches)) => msg_matches_copy(ctx, matches),
("delete", Some(matches)) => msg_matches_delete(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), ("move", Some(matches)) => msg_matches_move(ctx, matches),
("read", Some(matches)) => msg_matches_read(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), ("save", Some(matches)) => msg_matches_save(ctx, matches),
("search", Some(matches)) => msg_matches_search(ctx, matches), ("search", Some(matches)) => msg_matches_search(ctx, matches),
("send", Some(matches)) => msg_matches_send(ctx, matches), ("send", Some(matches)) => msg_matches_send(ctx, matches, smtp),
("write", Some(matches)) => msg_matches_write(ctx, matches), ("write", Some(matches)) => msg_matches_write(ctx, matches, smtp),
("template", Some(matches)) => Ok(msg_matches_tpl(ctx, matches)?), ("template", Some(matches)) => Ok(msg_matches_tpl(ctx, matches)?),
("list", opt_matches) => msg_matches_list(ctx, opt_matches), ("list", opt_matches) => msg_matches_list(ctx, opt_matches),
@ -236,7 +239,6 @@ fn tpl_args<'a>() -> Vec<clap::Arg<'a, 'a>> {
] ]
} }
// == Match functions ==
fn msg_matches_list(ctx: &Ctx, opt_matches: Option<&clap::ArgMatches>) -> Result<bool> { fn msg_matches_list(ctx: &Ctx, opt_matches: Option<&clap::ArgMatches>) -> Result<bool> {
debug!("list command matched"); debug!("list command matched");
@ -388,7 +390,11 @@ fn msg_matches_attachments(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool
Ok(true) Ok(true)
} }
fn msg_matches_write(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> { fn msg_matches_write<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
ctx: &Ctx,
matches: &clap::ArgMatches,
smtp: SMTP,
) -> Result<bool> {
debug!("write command matched"); debug!("write command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?; let mut imap_conn = ImapConnector::new(&ctx.account)?;
@ -414,7 +420,7 @@ fn msg_matches_write(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
.iter() .iter()
.for_each(|path| msg.add_attachment(path)); .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 // let's be nice to the server and say "bye" to the server
imap_conn.logout(); imap_conn.logout();
@ -422,7 +428,11 @@ fn msg_matches_write(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true) Ok(true)
} }
fn msg_matches_reply(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> { fn msg_matches_reply<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
ctx: &Ctx,
matches: &clap::ArgMatches,
smtp: SMTP,
) -> Result<bool> {
debug!("reply command matched"); debug!("reply command matched");
// -- Preparations -- // -- Preparations --
@ -446,13 +456,17 @@ fn msg_matches_reply(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
debug!("found {} attachments", attachments.len()); debug!("found {} attachments", attachments.len());
trace!("attachments: {:?}", attachments); trace!("attachments: {:?}", attachments);
msg_interaction(&ctx, &mut msg, &mut imap_conn)?; msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?;
imap_conn.logout(); imap_conn.logout();
Ok(true) 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"); debug!("mailto command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?; 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); let mut msg = Msg::new_with_headers(&ctx, headers);
msg.body = Body::new_with_text(body); 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(); imap_conn.logout();
Ok(()) Ok(())
} }
fn msg_matches_forward(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> { fn msg_matches_forward<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
ctx: &Ctx,
matches: &clap::ArgMatches,
smtp: SMTP,
) -> Result<bool> {
debug!("forward command matched"); debug!("forward command matched");
// fetch the msg // fetch the msg
@ -523,7 +541,7 @@ fn msg_matches_forward(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
trace!("attachments: {:?}", attachments); trace!("attachments: {:?}", attachments);
// apply changes // apply changes
msg_interaction(&ctx, &mut msg, &mut imap_conn)?; msg_interaction(&ctx, &mut msg, &mut imap_conn, smtp)?;
imap_conn.logout(); imap_conn.logout();
@ -608,7 +626,11 @@ fn msg_matches_delete(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
Ok(true) Ok(true)
} }
fn msg_matches_send(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> { fn msg_matches_send<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
ctx: &Ctx,
matches: &clap::ArgMatches,
smtp: SMTP,
) -> Result<bool> {
debug!("send command matched"); debug!("send command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?; let mut imap_conn = ImapConnector::new(&ctx.account)?;
@ -633,7 +655,7 @@ fn msg_matches_send(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
// send the message/msg // send the message/msg
let sendable = msg.to_sendable_msg()?; let sendable = msg.to_sendable_msg()?;
smtp::send(&ctx.account, &sendable)?; smtp.send(&sendable)?;
debug!("message sent!"); debug!("message sent!");
// add the message/msg to the Sent-Mailbox of the user // 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<bool> {
/// This function opens the prompt to do some actions to the msg like sending, editing it again and /// This function opens the prompt to do some actions to the msg like sending, editing it again and
/// so on. /// so on.
fn msg_interaction(ctx: &Ctx, msg: &mut Msg, imap_conn: &mut ImapConnector) -> Result<bool> { fn msg_interaction<'a, SMTP: smtp::service::SMTPServiceInterface<'a>>(
ctx: &Ctx,
msg: &mut Msg,
imap_conn: &mut ImapConnector,
smtp: SMTP,
) -> Result<bool> {
// let the user change the body a little bit first, before opening the prompt // let the user change the body a little bit first, before opening the prompt
msg.edit_body()?; msg.edit_body()?;
@ -836,7 +863,7 @@ fn msg_interaction(ctx: &Ctx, msg: &mut Msg, imap_conn: &mut ImapConnector) -> R
continue; continue;
} }
}; };
smtp::send(&ctx.account, &sendable)?; smtp.send(&sendable)?;
// TODO: Gmail sent mailboxes are called `[Gmail]/Sent` // TODO: Gmail sent mailboxes are called `[Gmail]/Sent`
// which creates a conflict, fix this! // which creates a conflict, fix this!