mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-25 04:20:22 +00:00
improve errors management
This commit is contained in:
parent
b023704d31
commit
2709faf30a
9 changed files with 365 additions and 137 deletions
115
Cargo.lock
generated
115
Cargo.lock
generated
|
@ -170,6 +170,27 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.26"
|
||||
|
@ -200,6 +221,16 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
|
@ -217,6 +248,17 @@ dependencies = [
|
|||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4060f4657be78b8e766215b02b18a2e862d83745545de804638e2b545e81aee6"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.17"
|
||||
|
@ -236,6 +278,7 @@ dependencies = [
|
|||
"mailparse",
|
||||
"native-tls",
|
||||
"rfc2047-decoder",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
|
@ -468,6 +511,18 @@ dependencies = [
|
|||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
|
@ -568,7 +623,7 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -638,7 +693,7 @@ version = "0.7.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.15",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
|
@ -661,7 +716,7 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -679,6 +734,25 @@ version = "0.1.57"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||
dependencies = [
|
||||
"getrandom 0.2.1",
|
||||
"redox_syscall 0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.2"
|
||||
|
@ -717,6 +791,27 @@ dependencies = [
|
|||
"quoted_printable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8227301bfc717136f0ecbd3d064ba8199e44497a0bdd46bb01ede4387cfd2cec"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
"dirs-next",
|
||||
"fs2",
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix",
|
||||
"scopeguard",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
|
@ -846,7 +941,7 @@ dependencies = [
|
|||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"rand",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -931,6 +1026,12 @@ dependencies = [
|
|||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
|
@ -943,6 +1044,12 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.1"
|
||||
|
|
|
@ -12,5 +12,6 @@ lettre = "0.10.0-alpha.4"
|
|||
mailparse = "0.13.1"
|
||||
native-tls = "0.2"
|
||||
rfc2047-decoder = "0.1.2"
|
||||
rustyline = "7.1.0"
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
toml = "0.5.8"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use lettre::transport::smtp::authentication::Credentials;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
env, fmt,
|
||||
|
@ -66,6 +67,10 @@ impl ServerInfo {
|
|||
pub fn get_addr(&self) -> (&str, u16) {
|
||||
(&self.host, self.port)
|
||||
}
|
||||
|
||||
pub fn to_smtp_creds(&self) -> Credentials {
|
||||
Credentials::new(self.login.to_owned(), self.password.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
use std::env::temp_dir;
|
||||
use std::fs::{remove_file, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::process::Command;
|
||||
use std::{fmt, result};
|
||||
|
||||
// Error wrapper
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
IoError(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::IoError(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Result wrapper
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Editor utils
|
||||
|
||||
fn open_with_template(template: &[u8]) -> Result<String> {
|
||||
// Create temporary draft
|
||||
let mut draft_path = temp_dir();
|
||||
draft_path.push("himalaya-draft.mail");
|
||||
File::create(&draft_path)?.write(template)?;
|
||||
|
||||
// Open editor and save user input to draft
|
||||
Command::new(env!("EDITOR")).arg(&draft_path).status()?;
|
||||
|
||||
// Read draft
|
||||
let mut draft = String::new();
|
||||
File::open(&draft_path)?.read_to_string(&mut draft)?;
|
||||
remove_file(&draft_path)?;
|
||||
|
||||
Ok(draft)
|
||||
}
|
||||
|
||||
pub fn open_with_new_template() -> Result<String> {
|
||||
let template = ["To: ", "Subject: ", ""].join("\r\n");
|
||||
open_with_template(template.as_bytes())
|
||||
}
|
|
@ -6,6 +6,7 @@ use std::{fmt, net::TcpStream, result};
|
|||
use crate::config;
|
||||
use crate::email::{self, Email};
|
||||
use crate::mailbox::Mailbox;
|
||||
use crate::msg::Msg;
|
||||
|
||||
// Error wrapper
|
||||
|
||||
|
@ -76,7 +77,7 @@ impl ImapConnector {
|
|||
Ok(Self { config, sess })
|
||||
}
|
||||
|
||||
pub fn list_mailboxes(&mut self) -> Result<Vec<Mailbox<'_>>> {
|
||||
pub fn list_mboxes(&mut self) -> Result<Vec<Mailbox<'_>>> {
|
||||
let mboxes = self
|
||||
.sess
|
||||
.list(Some(""), Some("*"))?
|
||||
|
@ -131,4 +132,10 @@ impl ImapConnector {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_msg(&mut self, mbox: &str, msg: &Msg) -> Result<()> {
|
||||
use imap::types::Flag::*;
|
||||
self.sess.append_with_flags(mbox, msg.to_vec(), &[Seen])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
81
src/input.rs
Normal file
81
src/input.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use std::{
|
||||
env::temp_dir,
|
||||
fmt,
|
||||
fs::{remove_file, File},
|
||||
io::{self, Read, Write},
|
||||
process::Command,
|
||||
result,
|
||||
};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
// Error wrapper
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
IoError(io::Error),
|
||||
AskForSendingConfirmationError,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "(input): ")?;
|
||||
match self {
|
||||
Error::IoError(err) => err.fmt(f),
|
||||
Error::AskForSendingConfirmationError => write!(f, "action cancelled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Result wrapper
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Utils
|
||||
|
||||
fn open_editor_with_tpl(tpl: &[u8]) -> Result<String> {
|
||||
// Creates draft file
|
||||
let mut draft_path = temp_dir();
|
||||
draft_path.push("himalaya-draft.mail");
|
||||
File::create(&draft_path)?.write(tpl)?;
|
||||
|
||||
// Opens editor and saves user input to draft file
|
||||
Command::new(env!("EDITOR")).arg(&draft_path).status()?;
|
||||
|
||||
// Extracts draft file content
|
||||
let mut draft = String::new();
|
||||
File::open(&draft_path)?.read_to_string(&mut draft)?;
|
||||
remove_file(&draft_path)?;
|
||||
|
||||
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<()> {
|
||||
print!("{} (y/n) ", prompt);
|
||||
io::stdout().flush()?;
|
||||
|
||||
match io::stdin()
|
||||
.bytes()
|
||||
.next()
|
||||
.and_then(|res| res.ok())
|
||||
.map(|bytes| bytes as char)
|
||||
{
|
||||
Some('y') | Some('Y') => Ok(()),
|
||||
_ => Err(Error::AskForSendingConfirmationError),
|
||||
}
|
||||
}
|
71
src/main.rs
71
src/main.rs
|
@ -1,8 +1,9 @@
|
|||
mod config;
|
||||
mod editor;
|
||||
mod email;
|
||||
mod imap;
|
||||
mod input;
|
||||
mod mailbox;
|
||||
mod msg;
|
||||
mod smtp;
|
||||
mod table;
|
||||
|
||||
|
@ -11,21 +12,26 @@ use std::{fmt, process::exit, result};
|
|||
|
||||
use crate::config::Config;
|
||||
use crate::imap::ImapConnector;
|
||||
use crate::msg::Msg;
|
||||
use crate::table::DisplayTable;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ConfigError(config::Error),
|
||||
InputError(input::Error),
|
||||
MsgError(msg::Error),
|
||||
ImapError(imap::Error),
|
||||
EditorError(editor::Error),
|
||||
SmtpError(smtp::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::ConfigError(err) => err.fmt(f),
|
||||
Error::InputError(err) => err.fmt(f),
|
||||
Error::MsgError(err) => err.fmt(f),
|
||||
Error::ImapError(err) => err.fmt(f),
|
||||
Error::EditorError(err) => err.fmt(f),
|
||||
Error::SmtpError(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,15 +42,27 @@ impl From<config::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<input::Error> for Error {
|
||||
fn from(err: input::Error) -> Error {
|
||||
Error::InputError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<msg::Error> for Error {
|
||||
fn from(err: msg::Error) -> Error {
|
||||
Error::MsgError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<imap::Error> for Error {
|
||||
fn from(err: imap::Error) -> Error {
|
||||
Error::ImapError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<editor::Error> for Error {
|
||||
fn from(err: editor::Error) -> Error {
|
||||
Error::EditorError(err)
|
||||
impl From<smtp::Error> for Error {
|
||||
fn from(err: smtp::Error) -> Error {
|
||||
Error::SmtpError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +78,6 @@ fn mailbox_arg() -> Arg<'static, 'static> {
|
|||
.long("mailbox")
|
||||
.help("Name of the targeted mailbox")
|
||||
.value_name("STRING")
|
||||
.default_value("INBOX")
|
||||
}
|
||||
|
||||
fn uid_arg() -> Arg<'static, 'static> {
|
||||
|
@ -80,7 +97,7 @@ fn run() -> Result<()> {
|
|||
.subcommand(
|
||||
SubCommand::with_name("search")
|
||||
.about("Lists emails matching the given IMAP query")
|
||||
.arg(mailbox_arg())
|
||||
.arg(mailbox_arg().default_value("INBOX"))
|
||||
.arg(
|
||||
Arg::with_name("query")
|
||||
.help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)")
|
||||
|
@ -93,7 +110,7 @@ fn run() -> Result<()> {
|
|||
SubCommand::with_name("read")
|
||||
.about("Reads an email by its UID")
|
||||
.arg(uid_arg())
|
||||
.arg(mailbox_arg())
|
||||
.arg(mailbox_arg().default_value("INBOX"))
|
||||
.arg(
|
||||
Arg::with_name("mime-type")
|
||||
.help("MIME type to use")
|
||||
|
@ -104,16 +121,12 @@ fn run() -> Result<()> {
|
|||
.default_value("text/plain"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("write")
|
||||
.about("Writes a new email")
|
||||
.arg(mailbox_arg()),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("write").about("Writes a new email"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("reply")
|
||||
.about("Replies to an email by its UID")
|
||||
.arg(uid_arg())
|
||||
.arg(mailbox_arg())
|
||||
.arg(mailbox_arg().default_value("INBOX"))
|
||||
.arg(
|
||||
Arg::with_name("reply all")
|
||||
.help("Replies to all recipients")
|
||||
|
@ -125,20 +138,13 @@ fn run() -> Result<()> {
|
|||
SubCommand::with_name("forward")
|
||||
.about("Forwards an email by its UID")
|
||||
.arg(uid_arg())
|
||||
.arg(mailbox_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("send")
|
||||
.about("Send a draft by its UID")
|
||||
.arg(uid_arg()),
|
||||
.arg(mailbox_arg().default_value("INBOX")),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("list") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mboxes = ImapConnector::new(config.imap)?
|
||||
.list_mailboxes()?
|
||||
.to_table();
|
||||
let mboxes = ImapConnector::new(config.imap)?.list_mboxes()?.to_table();
|
||||
|
||||
println!("{}", mboxes);
|
||||
}
|
||||
|
@ -191,12 +197,15 @@ fn run() -> Result<()> {
|
|||
|
||||
if let Some(_) = matches.subcommand_matches("write") {
|
||||
let config = Config::new_from_file()?;
|
||||
let draft = editor::open_with_new_template()?;
|
||||
let content = input::open_editor_with_new_tpl(&config)?;
|
||||
let msg = Msg::from_raw(content.as_bytes())?;
|
||||
|
||||
// TODO: save as draft instead (IMAP)
|
||||
println!("Sending ...");
|
||||
smtp::send(&config, draft.as_bytes());
|
||||
println!("Done!");
|
||||
input::ask_for_confirmation("Would you like to send this email?")?;
|
||||
|
||||
println!("Sending …");
|
||||
smtp::send(&config.smtp, &msg)?;
|
||||
ImapConnector::new(config.imap)?.append_msg("Sent", &msg)?;
|
||||
println!("Sent!");
|
||||
}
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("reply") {
|
||||
|
@ -207,10 +216,6 @@ fn run() -> Result<()> {
|
|||
// TODO
|
||||
}
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("send") {
|
||||
// TODO
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
85
src/msg.rs
Normal file
85
src/msg.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use lettre;
|
||||
use mailparse;
|
||||
use std::{fmt, result};
|
||||
|
||||
// Error wrapper
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ParseMsgError(mailparse::MailParseError),
|
||||
BuildEmailError(lettre::error::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "(msg): ")?;
|
||||
match self {
|
||||
Error::ParseMsgError(err) => err.fmt(f),
|
||||
Error::BuildEmailError(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mailparse::MailParseError> for Error {
|
||||
fn from(err: mailparse::MailParseError) -> Error {
|
||||
Error::ParseMsgError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lettre::error::Error> for Error {
|
||||
fn from(err: lettre::error::Error) -> Error {
|
||||
Error::BuildEmailError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Result wrapper
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Wrapper around mailparse::ParsedMail and lettre::Message
|
||||
|
||||
pub struct Msg(lettre::Message);
|
||||
|
||||
impl Msg {
|
||||
pub fn from_raw(bytes: &[u8]) -> Result<Msg> {
|
||||
use lettre::message::header::{ContentTransferEncoding, ContentType};
|
||||
use lettre::message::{Message, SinglePart};
|
||||
|
||||
let parsed_msg = mailparse::parse_mail(bytes)?;
|
||||
let built_msg = parsed_msg
|
||||
.headers
|
||||
.iter()
|
||||
.fold(Message::builder(), |msg, h| {
|
||||
match h.get_key().to_lowercase().as_str() {
|
||||
"from" => msg.from(h.get_value().parse().unwrap()),
|
||||
"to" => msg.to(h.get_value().parse().unwrap()),
|
||||
"cc" => match h.get_value().parse() {
|
||||
Err(_) => msg,
|
||||
Ok(addr) => msg.cc(addr),
|
||||
},
|
||||
"bcc" => match h.get_value().parse() {
|
||||
Err(_) => msg,
|
||||
Ok(addr) => msg.bcc(addr),
|
||||
},
|
||||
"subject" => msg.subject(h.get_value()),
|
||||
_ => msg,
|
||||
}
|
||||
})
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType("text/plain; charset=utf-8".parse().unwrap()))
|
||||
.header(ContentTransferEncoding::Base64)
|
||||
.body(parsed_msg.get_body_raw()?),
|
||||
)?;
|
||||
|
||||
Ok(Msg(built_msg))
|
||||
}
|
||||
|
||||
pub fn as_sendable_msg(&self) -> &lettre::Message {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
self.0.formatted()
|
||||
}
|
||||
}
|
81
src/smtp.rs
81
src/smtp.rs
|
@ -1,52 +1,43 @@
|
|||
use lettre::{
|
||||
message::{header, Message, SinglePart},
|
||||
transport::smtp::{authentication::Credentials, SmtpTransport},
|
||||
Transport,
|
||||
};
|
||||
use mailparse;
|
||||
use lettre;
|
||||
use std::{fmt, result};
|
||||
|
||||
use crate::config;
|
||||
use crate::msg::Msg;
|
||||
|
||||
// TODO: improve error management
|
||||
pub fn send(config: &config::Config, bytes: &[u8]) {
|
||||
let email_origin = mailparse::parse_mail(bytes).unwrap();
|
||||
let email = email_origin
|
||||
.headers
|
||||
.iter()
|
||||
.fold(Message::builder(), |msg, h| {
|
||||
match h.get_key().to_lowercase().as_str() {
|
||||
"to" => msg.to(h.get_value().parse().unwrap()),
|
||||
"cc" => match h.get_value().parse() {
|
||||
Err(_) => msg,
|
||||
Ok(addr) => msg.cc(addr),
|
||||
},
|
||||
"bcc" => match h.get_value().parse() {
|
||||
Err(_) => msg,
|
||||
Ok(addr) => msg.bcc(addr),
|
||||
},
|
||||
"subject" => msg.subject(h.get_value()),
|
||||
_ => msg,
|
||||
}
|
||||
})
|
||||
.from(config.email_full().parse().unwrap())
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(header::ContentType(
|
||||
"text/plain; charset=utf-8".parse().unwrap(),
|
||||
))
|
||||
.header(header::ContentTransferEncoding::Base64)
|
||||
.body(email_origin.get_body_raw().unwrap()),
|
||||
)
|
||||
.unwrap();
|
||||
// Error wrapper
|
||||
|
||||
let creds = Credentials::new(config.smtp.login.clone(), config.smtp.password.clone());
|
||||
let mailer = SmtpTransport::relay(&config.smtp.host)
|
||||
.unwrap()
|
||||
.credentials(creds)
|
||||
.build();
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
TransportError(lettre::transport::smtp::Error),
|
||||
}
|
||||
|
||||
match mailer.send(&email) {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("Could not send email: {:?}", e),
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "(smtp): ")?;
|
||||
match self {
|
||||
Error::TransportError(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lettre::transport::smtp::Error> for Error {
|
||||
fn from(err: lettre::transport::smtp::Error) -> Error {
|
||||
Error::TransportError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Result wrapper
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Utils
|
||||
|
||||
pub fn send(config: &config::ServerInfo, msg: &Msg) -> Result<()> {
|
||||
use lettre::Transport;
|
||||
|
||||
lettre::transport::smtp::SmtpTransport::relay(&config.host)?
|
||||
.credentials(config.to_smtp_creds())
|
||||
.build()
|
||||
.send(msg.as_sendable_msg())
|
||||
.map(|_| Ok(()))?
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue