refactor: use imap and smtp streams from toolbox

This commit is contained in:
Clément DOUIN
2026-03-14 14:05:11 +01:00
parent aa23a7a2c8
commit b77a54e5dc
49 changed files with 449 additions and 792 deletions
+3 -3
View File
@@ -5,8 +5,8 @@ use anyhow::Result;
use comfy_table::{presets, ContentArrangement};
use dirs::download_dir;
#[derive(Debug)]
pub struct Account<B> {
#[derive(Clone, Debug)]
pub struct Account<B: Clone> {
pub backend: B,
pub downloads_dir: PathBuf,
@@ -14,7 +14,7 @@ pub struct Account<B> {
pub table_arrangement: ContentArrangement,
}
impl<B> Account<B> {
impl<B: Clone> Account<B> {
pub fn new(config: Config, account_config: AccountConfig, backend: B) -> Result<Self> {
Ok(Self {
backend,
+3 -3
View File
@@ -65,7 +65,7 @@ pub enum BackendCommand {
}
impl BackendCommand {
pub fn exec(
pub fn execute(
self,
printer: &mut impl Printer,
config_paths: &[PathBuf],
@@ -86,7 +86,7 @@ impl BackendCommand {
let account = Account::new(config, account_config, imap_config)?;
cmd.exec(printer, account)
cmd.execute(printer, account)
}
#[cfg(feature = "smtp")]
Self::Smtp(cmd) => {
@@ -99,7 +99,7 @@ impl BackendCommand {
let account = Account::new(config, account_config, smtp_config)?;
cmd.exec(printer, account)
cmd.execute(printer, account)
}
}
}
+71 -65
View File
@@ -1,10 +1,13 @@
use std::{collections::HashMap, fmt, path::PathBuf, process::Command};
use std::{collections::HashMap, path::PathBuf};
use anyhow::{bail, Result};
use comfy_table::ContentArrangement;
use pimalaya_toolbox::config::TomlConfig;
use secrecy::SecretString;
use serde::{de::Visitor, Deserialize, Deserializer};
use pimalaya_toolbox::{
config::{shell_expanded_string, TomlConfig},
sasl::{Sasl, SaslAnonymous, SaslLogin, SaslMechanism, SaslPlain},
secret::{Secret, SecretError},
stream::{Rustls, RustlsCrypto, Tls, TlsProvider},
};
use serde::Deserialize;
use url::Url;
/// Global configuration.
@@ -133,6 +136,32 @@ pub enum RustlsCryptoConfig {
Ring,
}
impl TryFrom<TlsConfig> for Tls {
type Error = SecretError;
fn try_from(config: TlsConfig) -> Result<Self, Self::Error> {
Ok(Tls {
provider: match config.provider {
None => None,
Some(config) => Some(match config {
TlsProviderConfig::Rustls => TlsProvider::Rustls,
TlsProviderConfig::NativeTls => TlsProvider::NativeTls,
}),
},
rustls: Rustls {
crypto: match config.rustls.crypto {
None => None,
Some(config) => Some(match config {
RustlsCryptoConfig::Aws => RustlsCrypto::Aws,
RustlsCryptoConfig::Ring => RustlsCrypto::Ring,
}),
},
},
cert: config.cert,
})
}
}
/// SASL configuration.
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
@@ -163,7 +192,7 @@ pub enum SaslMechanismConfig {
pub struct SaslLoginConfig {
#[serde(deserialize_with = "shell_expanded_string")]
pub username: String,
pub password: SecretConfig,
pub password: Secret,
}
/// SASL PLAIN configuration.
@@ -173,7 +202,7 @@ pub struct SaslPlainConfig {
pub authzid: Option<String>,
#[serde(deserialize_with = "shell_expanded_string")]
pub authcid: String,
pub passwd: SecretConfig,
pub passwd: Secret,
}
/// SASL ANONYMOUS configuration.
@@ -183,64 +212,41 @@ pub struct SaslAnonymousConfig {
pub message: Option<String>,
}
/// Secret configuration.
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SecretConfig {
Raw(SecretString),
Command(Vec<String>),
}
impl TryFrom<SaslConfig> for Sasl {
type Error = SecretError;
impl SecretConfig {
pub fn get(&self) -> Result<SecretString> {
match self {
Self::Raw(secret) => Ok(secret.clone()),
Self::Command(args) => {
let Some((program, args)) = args.split_first() else {
bail!("Secret command cannot be empty")
};
let mut cmd = Command::new(program);
cmd.args(args);
let out = cmd.output()?;
if !out.status.success() {
let err = String::from_utf8_lossy(&out.stderr);
bail!("Cannot read secret from command: {err}");
}
let secret = String::from_utf8_lossy(&out.stdout);
let secret = secret.trim_matches(['\r', '\n']);
let secret = match secret.split_once('\n') {
Some((secret, _)) => secret.trim_matches(['\r', '\n']),
None => secret,
};
Ok(SecretString::from(secret))
}
}
fn try_from(config: SaslConfig) -> Result<Self, Self::Error> {
Ok(Sasl {
mechanisms: config
.mechanisms
.into_iter()
.map(|m| match m {
SaslMechanismConfig::Anonymous => SaslMechanism::Anonymous,
SaslMechanismConfig::Plain => SaslMechanism::Plain,
SaslMechanismConfig::Login => SaslMechanism::Login,
})
.collect(),
anonymous: match config.anonymous {
None => None,
Some(config) => Some(SaslAnonymous {
message: config.message,
}),
},
plain: match config.plain {
None => None,
Some(config) => Some(SaslPlain {
authzid: config.authzid,
authcid: config.authcid,
passwd: config.passwd.get()?,
}),
},
login: match config.login {
None => None,
Some(config) => Some(SaslLogin {
username: config.username,
password: config.password.get()?,
}),
},
})
}
}
struct ShellExpandedStringVisitor;
impl<'de> Visitor<'de> for ShellExpandedStringVisitor {
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an string containing environment variable(s)")
}
fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
match shellexpand::full(&v) {
Ok(v) => Ok(v.to_string()),
Err(_) => Ok(v),
}
}
}
pub fn shell_expanded_string<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<String, D::Error> {
deserializer.deserialize_string(ShellExpandedStringVisitor)
}
+14
View File
@@ -1,3 +1,17 @@
use anyhow::Result;
use pimalaya_toolbox::stream::imap::ImapSession;
use crate::{account::Account, config::ImapConfig};
pub type ImapAccount = Account<ImapConfig>;
impl ImapAccount {
pub fn new_imap_session(&self) -> Result<ImapSession> {
ImapSession::new(
self.backend.url.clone(),
self.backend.tls.clone().try_into()?,
self.backend.starttls,
self.backend.sasl.clone().try_into()?,
)
}
}
+6 -6
View File
@@ -29,14 +29,14 @@ pub enum ImapCommand {
}
impl ImapCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
match self {
Self::Id(cmd) => cmd.exec(printer, account),
Self::Id(cmd) => cmd.execute(printer, account),
Self::Envelopes(cmd) => cmd.exec(printer, account),
Self::Flags(cmd) => cmd.exec(printer, account),
Self::Mailboxes(cmd) => cmd.exec(printer, account),
Self::Messages(cmd) => cmd.exec(printer, account),
Self::Envelopes(cmd) => cmd.execute(printer, account),
Self::Flags(cmd) => cmd.execute(printer, account),
Self::Mailboxes(cmd) => cmd.execute(printer, account),
Self::Messages(cmd) => cmd.execute(printer, account),
}
}
}
+6 -6
View File
@@ -25,13 +25,13 @@ pub enum EnvelopeCommand {
}
impl EnvelopeCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
match self {
Self::Get(cmd) => cmd.exec(printer, account),
Self::List(cmd) => cmd.exec(printer, account),
Self::Search(cmd) => cmd.exec(printer, account),
Self::Sort(cmd) => cmd.exec(printer, account),
Self::Thread(cmd) => cmd.exec(printer, account),
Self::Get(cmd) => cmd.execute(printer, account),
Self::List(cmd) => cmd.execute(printer, account),
Self::Search(cmd) => cmd.execute(printer, account),
Self::Sort(cmd) => cmd.execute(printer, account),
Self::Thread(cmd) => cmd.execute(printer, account),
}
}
}
+7 -9
View File
@@ -18,7 +18,6 @@ use crate::imap::{
account::ImapAccount,
envelope::list::{decode_mime, format_address},
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag},
stream,
};
/// Get a single IMAP envelope.
@@ -42,18 +41,17 @@ pub struct GetEnvelopeCommand {
}
impl GetEnvelopeCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -68,11 +66,11 @@ impl GetEnvelopeCommand {
MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::Envelope]);
let mut arg = None;
let mut coroutine = ImapFetchFirst::new(context, id, item_names, !self.seq);
let mut coroutine = ImapFetchFirst::new(imap.context, id, item_names, !self.seq);
let items = loop {
match coroutine.resume(arg.take()) {
ImapFetchFirstResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapFetchFirstResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapFetchFirstResult::Ok { items, .. } => break items,
ImapFetchFirstResult::Err { err, .. } => bail!(err),
}
+7 -9
View File
@@ -21,7 +21,6 @@ use serde::Serialize;
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalArg, MailboxSelectFlag},
stream,
};
/// List IMAP envelopes from the given mailbox.
@@ -45,18 +44,17 @@ pub struct ListEnvelopesCommand {
}
impl ListEnvelopesCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -68,11 +66,11 @@ impl ListEnvelopesCommand {
MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::Envelope]);
let mut arg = None;
let mut coroutine = ImapFetch::new(context, sequence_set, item_names, !self.seq);
let mut coroutine = ImapFetch::new(imap.context, sequence_set, item_names, !self.seq);
let data = loop {
match coroutine.resume(arg.take()) {
ImapFetchResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapFetchResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapFetchResult::Ok { data, .. } => break data,
ImapFetchResult::Err { err, .. } => bail!(err),
}
+7 -9
View File
@@ -18,7 +18,6 @@ use serde::Serialize;
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag},
stream,
};
/// Search IMAP messages by criteria.
@@ -63,18 +62,17 @@ pub struct SearchEnvelopesCommand {
}
impl SearchEnvelopesCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -84,11 +82,11 @@ impl SearchEnvelopesCommand {
let criteria = parse_query(&self.query)?;
let mut arg = None;
let mut coroutine = ImapSearch::new(context, criteria, !self.seq);
let mut coroutine = ImapSearch::new(imap.context, criteria, !self.seq);
let ids = loop {
match coroutine.resume(arg.take()) {
ImapSearchResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSearchResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSearchResult::Ok { ids, .. } => break ids,
ImapSearchResult::Err { err, .. } => bail!(err),
}
+5 -7
View File
@@ -16,7 +16,6 @@ use serde::{Serialize, Serializer};
use crate::imap::{
account::ImapAccount, envelope::search::parse_query, mailbox::arg::MailboxNameOptionalArg,
stream,
};
/// Sort messages by criteria.
@@ -56,18 +55,17 @@ pub struct SortEnvelopesCommand {
}
impl SortEnvelopesCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
// SELECT mailbox
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
let context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -89,7 +87,7 @@ impl SortEnvelopesCommand {
let ids = loop {
match coroutine.resume(arg.take()) {
ImapSortResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSortResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSortResult::Ok { ids, .. } => break ids,
ImapSortResult::Err { err, .. } => bail!(err),
}
+17 -18
View File
@@ -3,7 +3,6 @@ use std::{collections::HashMap, fmt, num::NonZeroU32};
use anyhow::{bail, Result};
use clap::Parser;
use io_imap::{
context::ImapContext,
coroutines::{fetch::*, select::*, thread::*},
types::{
extensions::thread::{Thread, ThreadingAlgorithm},
@@ -12,14 +11,13 @@ use io_imap::{
},
};
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use pimalaya_toolbox::{stream::imap::ImapSession, terminal::printer::Printer};
use serde::{Serialize, Serializer};
use crate::imap::{
account::ImapAccount,
envelope::{list::decode_mime, search::parse_query},
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag},
stream::{self, Stream},
};
/// Thread IMAP messages by algorithm.
@@ -51,18 +49,17 @@ pub struct ThreadEnvelopesCommand {
}
impl ThreadEnvelopesCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -73,14 +70,17 @@ impl ThreadEnvelopesCommand {
let search_criteria = parse_query(&self.query)?;
let mut arg = None;
let mut coroutine = ImapThread::new(context, algorithm, search_criteria, !self.seq);
let mut coroutine = ImapThread::new(imap.context, algorithm, search_criteria, !self.seq);
let (context, threads) = loop {
let threads = loop {
match coroutine.resume(arg.take()) {
ImapThreadResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapThreadResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapThreadResult::Ok {
context, threads, ..
} => break (context, threads),
} => {
imap.context = context;
break threads;
}
ImapThreadResult::Err { err, .. } => bail!(err),
}
};
@@ -90,7 +90,7 @@ impl ThreadEnvelopesCommand {
// Fetch subjects for all messages in threads
let subjects = if !all_ids.is_empty() {
fetch_subjects(&mut stream, context, &all_ids, !self.seq)?
fetch_subjects(imap, &all_ids, !self.seq)?
} else {
HashMap::new()
};
@@ -138,8 +138,7 @@ fn collect_thread_ids_recursive(thread: &Thread, ids: &mut Vec<NonZeroU32>) {
}
fn fetch_subjects(
stream: &mut Stream,
context: ImapContext,
mut imap: ImapSession,
ids: &[NonZeroU32],
uid: bool,
) -> Result<HashMap<u32, String>> {
@@ -161,11 +160,11 @@ fn fetch_subjects(
]);
let mut arg = None;
let mut coroutine = ImapFetch::new(context, sequence_set, item_names, uid);
let mut coroutine = ImapFetch::new(imap.context, sequence_set, item_names, uid);
let data = loop {
match coroutine.resume(arg.take()) {
ImapFetchResult::Io { io } => arg = Some(handle(&mut *stream, io)?),
ImapFetchResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapFetchResult::Ok { data, .. } => break data,
ImapFetchResult::Err { err, .. } => bail!(err),
}
+7 -9
View File
@@ -13,7 +13,6 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag},
stream,
};
/// Add IMAP flag(s) to message(s).
@@ -40,18 +39,17 @@ pub struct AddFlagsCommand {
}
impl AddFlagsCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -67,11 +65,11 @@ impl AddFlagsCommand {
let mut arg = None;
let mut coroutine =
ImapStoreSilent::new(context, sequence_set, StoreType::Add, flags, !self.seq);
ImapStoreSilent::new(imap.context, sequence_set, StoreType::Add, flags, !self.seq);
loop {
match coroutine.resume(arg.take()) {
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapStoreSilentResult::Ok { .. } => break,
ImapStoreSilentResult::Err { err, .. } => bail!(err),
}
+5 -5
View File
@@ -23,12 +23,12 @@ pub enum FlagCommand {
}
impl FlagCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
match self {
Self::List(cmd) => cmd.exec(printer, account),
Self::Add(cmd) => cmd.exec(printer, account),
Self::Set(cmd) => cmd.exec(printer, account),
Self::Remove(cmd) => cmd.exec(printer, account),
Self::List(cmd) => cmd.execute(printer, account),
Self::Add(cmd) => cmd.execute(printer, account),
Self::Set(cmd) => cmd.execute(printer, account),
Self::Remove(cmd) => cmd.execute(printer, account),
}
}
}
+5 -6
View File
@@ -11,7 +11,7 @@ use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::{Serialize, Serializer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
/// List available IMAP flags for the given mailbox.
///
@@ -25,17 +25,16 @@ pub struct ListFlagsCommand {
}
impl ListFlagsCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
let (flags, permanent_flags) = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { data, .. } => {
break (
data.flags.unwrap_or_default(),
+13 -10
View File
@@ -13,7 +13,6 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag},
stream,
};
/// Remove IMAP flag(s) from message(s).
@@ -40,18 +39,17 @@ pub struct RemoveFlagsCommand {
}
impl RemoveFlagsCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -66,12 +64,17 @@ impl RemoveFlagsCommand {
.collect::<Result<_, _>>()?;
let mut arg = None;
let mut coroutine =
ImapStoreSilent::new(context, sequence_set, StoreType::Remove, flags, !self.seq);
let mut coroutine = ImapStoreSilent::new(
imap.context,
sequence_set,
StoreType::Remove,
flags,
!self.seq,
);
loop {
match coroutine.resume(arg.take()) {
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapStoreSilentResult::Ok { .. } => break,
ImapStoreSilentResult::Err { err, .. } => bail!(err),
}
+13 -10
View File
@@ -13,7 +13,6 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag},
stream,
};
/// Set IMAP flag(s) on message(s), replacing any existing flags.
@@ -40,18 +39,17 @@ pub struct SetFlagsCommand {
}
impl SetFlagsCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -66,12 +64,17 @@ impl SetFlagsCommand {
.collect::<Result<_, _>>()?;
let mut arg = None;
let mut coroutine =
ImapStoreSilent::new(context, sequence_set, StoreType::Replace, flags, !self.seq);
let mut coroutine = ImapStoreSilent::new(
imap.context,
sequence_set,
StoreType::Replace,
flags,
!self.seq,
);
loop {
match coroutine.resume(arg.take()) {
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapStoreSilentResult::Ok { .. } => break,
ImapStoreSilentResult::Err { err, .. } => bail!(err),
}
+5 -6
View File
@@ -14,7 +14,7 @@ use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::Serialize;
use crate::imap::{account::ImapAccount, stream};
use crate::imap::account::ImapAccount;
/// Get information about the IMAP server.
///
@@ -31,9 +31,8 @@ pub struct IdCommand {
}
impl IdCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mut params = HashMap::new();
params.extend([
@@ -60,11 +59,11 @@ impl IdCommand {
}
let mut arg = None;
let mut coroutine = ImapId::new(context, Some(params.into_iter().collect()));
let mut coroutine = ImapId::new(imap.context, Some(params.into_iter().collect()));
let params = loop {
match coroutine.resume(arg.take()) {
ImapIdResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapIdResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapIdResult::Ok { server_id, .. } => break server_id,
ImapIdResult::Err { err, .. } => bail!(err),
}
+5 -5
View File
@@ -4,7 +4,7 @@ use io_imap::coroutines::close::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, stream};
use crate::imap::account::ImapAccount;
/// Close the current, selected mailbox.
///
@@ -19,15 +19,15 @@ use crate::imap::{account::ImapAccount, stream};
pub struct CloseMailboxCommand;
impl CloseMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mut arg = None;
let mut close_coroutine = ImapClose::new(context);
let mut close_coroutine = ImapClose::new(imap.context);
loop {
match close_coroutine.resume(arg.take()) {
ImapCloseResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapCloseResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapCloseResult::Ok { .. } => break,
ImapCloseResult::Err { err, .. } => bail!(err),
}
+13 -13
View File
@@ -37,20 +37,20 @@ pub enum MailboxCommand {
}
impl MailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
match self {
Self::Close(cmd) => cmd.exec(printer, account),
Self::Create(cmd) => cmd.exec(printer, account),
Self::Delete(cmd) => cmd.exec(printer, account),
Self::Expunge(cmd) => cmd.exec(printer, account),
Self::List(cmd) => cmd.exec(printer, account),
Self::Purge(cmd) => cmd.exec(printer, account),
Self::Rename(cmd) => cmd.exec(printer, account),
Self::Select(cmd) => cmd.exec(printer, account),
Self::Status(cmd) => cmd.exec(printer, account),
Self::Subscribe(cmd) => cmd.exec(printer, account),
Self::Unselect(cmd) => cmd.exec(printer, account),
Self::Unsubscribe(cmd) => cmd.exec(printer, account),
Self::Close(cmd) => cmd.execute(printer, account),
Self::Create(cmd) => cmd.execute(printer, account),
Self::Delete(cmd) => cmd.execute(printer, account),
Self::Expunge(cmd) => cmd.execute(printer, account),
Self::List(cmd) => cmd.execute(printer, account),
Self::Purge(cmd) => cmd.execute(printer, account),
Self::Rename(cmd) => cmd.execute(printer, account),
Self::Select(cmd) => cmd.execute(printer, account),
Self::Status(cmd) => cmd.execute(printer, account),
Self::Subscribe(cmd) => cmd.execute(printer, account),
Self::Unselect(cmd) => cmd.execute(printer, account),
Self::Unsubscribe(cmd) => cmd.execute(printer, account),
}
}
}
+5 -5
View File
@@ -4,7 +4,7 @@ use io_imap::coroutines::create::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
/// Create the given mailbox.
///
@@ -17,17 +17,17 @@ pub struct CreateMailboxCommand {
}
impl CreateMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapCreate::new(context, mailbox);
let mut coroutine = ImapCreate::new(imap.context, mailbox);
loop {
match coroutine.resume(arg.take()) {
ImapCreateResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapCreateResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapCreateResult::Ok { .. } => break,
ImapCreateResult::Err { err, .. } => bail!(err),
}
+5 -6
View File
@@ -4,7 +4,7 @@ use io_imap::coroutines::delete::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
/// Delete the given mailbox.
///
@@ -17,17 +17,16 @@ pub struct DeleteMailboxCommand {
}
impl DeleteMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapDelete::new(context, mailbox);
let mut coroutine = ImapDelete::new(imap.context, mailbox);
loop {
match coroutine.resume(arg.take()) {
ImapDeleteResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapDeleteResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapDeleteResult::Ok { .. } => break,
ImapDeleteResult::Err { err, .. } => bail!(err),
}
+7 -9
View File
@@ -7,7 +7,6 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameArg, MailboxSelectFlag},
stream,
};
/// Expunge the given mailbox.
@@ -23,18 +22,17 @@ pub struct ExpungeMailboxCommand {
}
impl ExpungeMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -42,11 +40,11 @@ impl ExpungeMailboxCommand {
}
let mut arg = None;
let mut coroutine = ImapExpunge::new(context);
let mut coroutine = ImapExpunge::new(imap.context);
loop {
match coroutine.resume(arg.take()) {
ImapExpungeResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapExpungeResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapExpungeResult::Ok { .. } => break,
ImapExpungeResult::Err { err, .. } => bail!(err),
}
+7 -8
View File
@@ -11,7 +11,7 @@ use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::{Serialize, Serializer};
use crate::imap::{account::ImapAccount, stream};
use crate::imap::account::ImapAccount;
/// List, search and filter mailboxes.
///
@@ -34,30 +34,29 @@ pub struct ListMailboxesCommand {
}
impl ListMailboxesCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let reference = self.reference.try_into()?;
let pattern = self.pattern.try_into()?;
let mailboxes = if self.all {
let mut arg = None;
let mut coroutine = ImapList::new(context, reference, pattern);
let mut coroutine = ImapList::new(imap.context, reference, pattern);
loop {
match coroutine.resume(arg.take()) {
ImapListResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapListResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapListResult::Ok { mailboxes, .. } => break mailboxes,
ImapListResult::Err { err, .. } => bail!(err),
}
}
} else {
let mut arg = None;
let mut coroutine = ImapLsub::new(context, reference, pattern);
let mut coroutine = ImapLsub::new(imap.context, reference, pattern);
loop {
match coroutine.resume(arg.take()) {
ImapLsubResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapLsubResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapLsubResult::Ok { mailboxes, .. } => break mailboxes,
ImapLsubResult::Err { err, .. } => bail!(err),
}
+10 -12
View File
@@ -10,7 +10,6 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameArg, MailboxSelectFlag},
stream,
};
/// Shortcut for marking as deleted all envelopes then expunging the
@@ -27,18 +26,17 @@ pub struct PurgeMailboxCommand {
}
impl PurgeMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -47,27 +45,27 @@ impl PurgeMailboxCommand {
let mut arg = None;
let mut coroutine = ImapStoreSilent::new(
context,
imap.context,
"1:*".try_into()?,
StoreType::Add,
vec![Flag::Deleted],
false,
);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapStoreSilentResult::Ok { context, .. } => break context,
ImapStoreSilentResult::Err { err, .. } => bail!(err),
}
};
let mut arg = None;
let mut coroutine = ImapExpunge::new(context);
let mut coroutine = ImapExpunge::new(imap.context);
loop {
match coroutine.resume(arg.take()) {
ImapExpungeResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapExpungeResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapExpungeResult::Ok { .. } => break,
ImapExpungeResult::Err { err, .. } => bail!(err),
}
+4 -6
View File
@@ -7,7 +7,6 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameArg, TargetMailboxNameArg},
stream,
};
/// Rename the given mailbox.
@@ -23,18 +22,17 @@ pub struct RenameMailboxCommand {
}
impl RenameMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let from = self.from.name.try_into()?;
let to = self.to.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapRename::new(context, from, to);
let mut coroutine = ImapRename::new(imap.context, from, to);
loop {
match coroutine.resume(arg.take()) {
ImapRenameResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapRenameResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapRenameResult::Ok { .. } => break,
ImapRenameResult::Err { err, .. } => bail!(err),
}
+5 -6
View File
@@ -4,7 +4,7 @@ use io_imap::coroutines::select::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
/// Select the given mailbox.
///
@@ -21,17 +21,16 @@ pub struct SelectMailboxCommand {
}
impl SelectMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { .. } => break,
ImapSelectResult::Err { err, .. } => bail!(err),
}
+5 -6
View File
@@ -11,7 +11,7 @@ use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::{Serialize, Serializer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
/// Get the status of the given mailbox.
///
@@ -24,9 +24,8 @@ pub struct StatusMailboxCommand {
}
impl StatusMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
let item_names = vec![
StatusDataItemName::Messages,
@@ -37,11 +36,11 @@ impl StatusMailboxCommand {
];
let mut arg = None;
let mut coroutine = ImapStatus::new(context, mailbox, item_names);
let mut coroutine = ImapStatus::new(imap.context, mailbox, item_names);
let items = loop {
match coroutine.resume(arg.take()) {
ImapStatusResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapStatusResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapStatusResult::Ok { items, .. } => break items,
ImapStatusResult::Err { err, .. } => bail!(err),
}
+5 -6
View File
@@ -4,7 +4,7 @@ use io_imap::coroutines::subscribe::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
/// Subscribe to the given mailbox.
///
@@ -17,17 +17,16 @@ pub struct SubscribeMailboxCommand {
}
impl SubscribeMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapSubscribe::new(context, mailbox);
let mut coroutine = ImapSubscribe::new(imap.context, mailbox);
loop {
match coroutine.resume(arg.take()) {
ImapSubscribeResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSubscribeResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSubscribeResult::Ok { .. } => break,
ImapSubscribeResult::Err { err, .. } => bail!(err),
}
+5 -6
View File
@@ -4,7 +4,7 @@ use io_imap::coroutines::unselect::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, stream};
use crate::imap::account::ImapAccount;
/// Unselect a current, selected mailbox.
///
@@ -18,15 +18,14 @@ use crate::imap::{account::ImapAccount, stream};
pub struct UnselectMailboxCommand;
impl UnselectMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mut arg = None;
let mut unselect_coroutine = ImapUnselect::new(context);
let mut unselect_coroutine = ImapUnselect::new(imap.context);
loop {
match unselect_coroutine.resume(arg.take()) {
ImapUnselectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapUnselectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapUnselectResult::Ok { .. } => break,
ImapUnselectResult::Err { err, .. } => bail!(err),
}
+5 -6
View File
@@ -4,7 +4,7 @@ use io_imap::coroutines::unsubscribe::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
/// Unsubscribe from the given mailbox.
///
@@ -17,17 +17,16 @@ pub struct UnsubscribeMailboxCommand {
}
impl UnsubscribeMailboxCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapUnsubscribe::new(context, mailbox);
let mut coroutine = ImapUnsubscribe::new(imap.context, mailbox);
loop {
match coroutine.resume(arg.take()) {
ImapUnsubscribeResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapUnsubscribeResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapUnsubscribeResult::Ok { .. } => break,
ImapUnsubscribeResult::Err { err, .. } => bail!(err),
}
+7 -7
View File
@@ -26,14 +26,14 @@ pub enum MessageCommand {
}
impl MessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
match self {
Self::Save(cmd) => cmd.exec(printer, account),
Self::Get(cmd) => cmd.exec(printer, account),
Self::Read(cmd) => cmd.exec(printer, account),
Self::Export(cmd) => cmd.exec(printer, account),
Self::Copy(cmd) => cmd.exec(printer, account),
Self::Move(cmd) => cmd.exec(printer, account),
Self::Save(cmd) => cmd.execute(printer, account),
Self::Get(cmd) => cmd.execute(printer, account),
Self::Read(cmd) => cmd.execute(printer, account),
Self::Export(cmd) => cmd.execute(printer, account),
Self::Copy(cmd) => cmd.execute(printer, account),
Self::Move(cmd) => cmd.execute(printer, account),
}
}
}
+7 -9
View File
@@ -10,7 +10,6 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag, TargetMailboxNameArg},
stream,
};
/// Copy IMAP message(s) to the given mailbox.
@@ -36,18 +35,17 @@ pub struct CopyMessageCommand {
}
impl CopyMessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -58,11 +56,11 @@ impl CopyMessageCommand {
let destination: Mailbox = self.destination.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapCopy::new(context, sequence_set, destination, !self.seq);
let mut coroutine = ImapCopy::new(imap.context, sequence_set, destination, !self.seq);
loop {
match coroutine.resume(arg.take()) {
ImapCopyResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapCopyResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapCopyResult::Ok { .. } => break,
ImapCopyResult::Err { err, .. } => bail!(err),
}
+6 -7
View File
@@ -15,7 +15,7 @@ use io_stream::runtimes::std::handle;
use mail_parser::{MessageParser, MimeHeaders};
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameOptionalFlag, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameOptionalFlag};
/// Export type for message export.
#[derive(Debug, Clone, clap::ValueEnum)]
@@ -61,18 +61,17 @@ pub struct ExportMessageCommand {
}
impl ExportMessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
// SELECT mailbox
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
let context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -93,7 +92,7 @@ impl ExportMessageCommand {
let items = loop {
match coroutine.resume(arg.take()) {
ImapFetchFirstResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapFetchFirstResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapFetchFirstResult::Ok { items, .. } => break items,
ImapFetchFirstResult::Err { err, .. } => bail!(err),
}
+7 -9
View File
@@ -15,7 +15,6 @@ use serde::Serialize;
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag},
stream,
};
/// Get a message and display its structure.
@@ -37,9 +36,8 @@ pub struct GetMessageCommand {
}
impl GetMessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
let Some(id) = NonZeroU32::new(self.id) else {
bail!("ID must be non-zero");
@@ -47,11 +45,11 @@ impl GetMessageCommand {
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -66,11 +64,11 @@ impl GetMessageCommand {
}]);
let mut arg = None;
let mut coroutine = ImapFetchFirst::new(context, id, item_names, !self.seq);
let mut coroutine = ImapFetchFirst::new(imap.context, id, item_names, !self.seq);
let items = loop {
match coroutine.resume(arg.take()) {
ImapFetchFirstResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapFetchFirstResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapFetchFirstResult::Ok { items, .. } => break items,
ImapFetchFirstResult::Err { err, .. } => bail!(err),
}
+7 -9
View File
@@ -10,7 +10,6 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag, TargetMailboxNameArg},
stream,
};
/// Move message(s) to the given mailbox.
@@ -37,18 +36,17 @@ pub struct MoveMessageCommand {
}
impl MoveMessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -59,11 +57,11 @@ impl MoveMessageCommand {
let destination: Mailbox<'static> = self.destination.name.try_into()?;
let mut arg = None;
let mut coroutine = ImapMove::new(context, sequence_set, destination, !self.seq);
let mut coroutine = ImapMove::new(imap.context, sequence_set, destination, !self.seq);
loop {
match coroutine.resume(arg.take()) {
ImapMoveResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapMoveResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapMoveResult::Ok { .. } => break,
ImapMoveResult::Err { err, .. } => bail!(err),
}
+7 -9
View File
@@ -14,7 +14,6 @@ use serde::Serialize;
use crate::imap::{
account::ImapAccount,
mailbox::arg::{MailboxNameOptionalFlag, MailboxSelectFlag},
stream,
};
/// Read message content.
@@ -46,18 +45,17 @@ pub struct ReadMessageCommand {
}
impl ReadMessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (mut context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox = self.mailbox.name.try_into()?;
if self.select.r#true {
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
let mut coroutine = ImapSelect::new(imap.context, mailbox);
context = loop {
imap.context = loop {
match coroutine.resume(arg.take()) {
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapSelectResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapSelectResult::Ok { context, .. } => break context,
ImapSelectResult::Err { err, .. } => bail!(err),
}
@@ -76,11 +74,11 @@ impl ReadMessageCommand {
}]);
let mut arg = None;
let mut coroutine = ImapFetchFirst::new(context, id, item_names, !self.seq);
let mut coroutine = ImapFetchFirst::new(imap.context, id, item_names, !self.seq);
let items = loop {
match coroutine.resume(arg.take()) {
ImapFetchFirstResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapFetchFirstResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapFetchFirstResult::Ok { items, .. } => break items,
ImapFetchFirstResult::Err { err, .. } => bail!(err),
}
+5 -7
View File
@@ -12,7 +12,7 @@ use io_imap::{
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg, stream};
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
/// Save a message to a mailbox.
///
@@ -34,11 +34,9 @@ pub struct SaveMessageCommand {
}
impl SaveMessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
let mut imap = account.new_imap_session()?;
let mailbox: Mailbox<'static> = self.mailbox.name.try_into()?;
let message = if stdin().is_terminal() || printer.is_json() {
self.message
.join(" ")
@@ -63,11 +61,11 @@ impl SaveMessageCommand {
.collect::<Result<_, _>>()?;
let mut arg = None;
let mut coroutine = ImapAppend::new(context, mailbox, flags, None, message);
let mut coroutine = ImapAppend::new(imap.context, mailbox, flags, None, message);
loop {
match coroutine.resume(arg.take()) {
ImapAppendResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapAppendResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
ImapAppendResult::Ok { .. } => break,
ImapAppendResult::Err { err, .. } => bail!(err),
}
-1
View File
@@ -5,4 +5,3 @@ pub mod flag;
pub mod id;
pub mod mailbox;
pub mod message;
pub mod stream;
+3 -1
View File
@@ -20,7 +20,9 @@ fn main() {
let config_paths = cli.config_paths.as_ref();
let account_name = cli.account.name.as_deref();
let result = cli.command.exec(&mut printer, config_paths, account_name);
let result = cli
.command
.execute(&mut printer, config_paths, account_name);
ErrorReport::eval(&mut printer, result)
}
+14
View File
@@ -1,3 +1,17 @@
use anyhow::Result;
use pimalaya_toolbox::stream::smtp::SmtpSession;
use crate::{account::Account, config::SmtpConfig};
pub type SmtpAccount = Account<SmtpConfig>;
impl SmtpAccount {
pub fn new_smtp_session(&self) -> Result<SmtpSession> {
SmtpSession::new(
self.backend.url.clone(),
self.backend.tls.clone().try_into()?,
self.backend.starttls,
self.backend.sasl.clone().try_into()?,
)
}
}
+2 -2
View File
@@ -18,9 +18,9 @@ pub enum SmtpCommand {
}
impl SmtpCommand {
pub fn exec(self, printer: &mut impl Printer, account: SmtpAccount) -> Result<()> {
pub fn execute(self, printer: &mut impl Printer, account: SmtpAccount) -> Result<()> {
match self {
Self::Messages(cmd) => cmd.exec(printer, account),
Self::Messages(cmd) => cmd.execute(printer, account),
}
}
}
+2 -2
View File
@@ -15,9 +15,9 @@ pub enum MessageCommand {
}
impl MessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: SmtpAccount) -> Result<()> {
pub fn execute(self, printer: &mut impl Printer, account: SmtpAccount) -> Result<()> {
match self {
Self::Send(cmd) => cmd.exec(printer, account),
Self::Send(cmd) => cmd.execute(printer, account),
}
}
}
+10 -6
View File
@@ -13,7 +13,7 @@ use io_stream::runtimes::std::handle;
use mail_parser::{Addr, Address, HeaderName, HeaderValue, MessageParser};
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::smtp::{account::SmtpAccount, stream};
use crate::smtp::account::SmtpAccount;
/// Send a message to a mailbox.
///
@@ -28,8 +28,8 @@ pub struct SendMessageCommand {
}
impl SendMessageCommand {
pub fn exec(self, printer: &mut impl Printer, account: SmtpAccount) -> Result<()> {
let (context, mut stream) = stream::connect(account.backend)?;
pub fn execute(self, printer: &mut impl Printer, account: SmtpAccount) -> Result<()> {
let mut imap = account.new_smtp_session()?;
let message = if stdin().is_terminal() || printer.is_json() {
self.message
@@ -48,12 +48,16 @@ impl SendMessageCommand {
let (reverse_path, forward_paths) = into_smtp_msg(message.as_bytes())?;
let mut arg = None;
let mut coroutine =
SendSmtpMessage::new(context, reverse_path, forward_paths, message.into_bytes());
let mut coroutine = SendSmtpMessage::new(
imap.context,
reverse_path,
forward_paths,
message.into_bytes(),
);
loop {
match coroutine.resume(arg.take()) {
SendSmtpMessageResult::Io { io } => arg = Some(handle(&mut stream, io)?),
SendSmtpMessageResult::Io { io } => arg = Some(handle(&mut imap.stream, io)?),
SendSmtpMessageResult::Ok { .. } => break,
SendSmtpMessageResult::Err { err, .. } => bail!(err),
}
-1
View File
@@ -1,4 +1,3 @@
pub mod account;
pub mod command;
pub mod message;
pub mod stream;
-381
View File
@@ -1,381 +0,0 @@
#[cfg(unix)]
use std::os::unix::net::UnixStream;
use std::{
fs,
io::{self, Read, Write},
net::TcpStream,
sync::Arc,
};
use anyhow::{bail, Result};
use gethostname::gethostname;
use io_smtp::{
context::SmtpContext,
coroutines::{authenticate::*, ehlo::*, greeting_with_capability::*, starttls::*},
types::{auth::AuthMechanism, core::EhloDomain, response::Capability, IntoStatic},
};
use io_stream::runtimes::std::handle;
use log::{debug, info};
#[cfg(feature = "native-tls")]
use native_tls::TlsConnector;
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
use rustls::{
crypto::{self, CryptoProvider},
pki_types::{pem::PemObject, CertificateDer},
ClientConfig, ClientConnection, StreamOwned,
};
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
use rustls_platform_verifier::{ConfigVerifierExt, Verifier};
#[cfg(windows)]
use uds_windows::UnixStream;
use crate::config::{RustlsCryptoConfig, SaslMechanismConfig, SmtpConfig, TlsProviderConfig};
pub enum Stream {
Tcp(TcpStream),
Unix(UnixStream),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Rustls(StreamOwned<ClientConnection, TcpStream>),
#[cfg(feature = "native-tls")]
NativeTls(native_tls::TlsStream<TcpStream>),
}
impl Read for Stream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Tcp(s) => s.read(buf),
Self::Unix(s) => s.read(buf),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.read(buf),
#[cfg(feature = "native-tls")]
Self::NativeTls(s) => s.read(buf),
}
}
}
impl Write for Stream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Self::Tcp(s) => s.write(buf),
Self::Unix(s) => s.write(buf),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.write(buf),
#[cfg(feature = "native-tls")]
Self::NativeTls(s) => s.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
Self::Tcp(s) => s.flush(),
Self::Unix(s) => s.flush(),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.flush(),
#[cfg(feature = "native-tls")]
Self::NativeTls(s) => s.flush(),
}
}
}
pub fn connect(mut config: SmtpConfig) -> Result<(SmtpContext, Stream)> {
info!("connecting to SMTP server using {}", config.url);
let mut context = SmtpContext::new();
let host = config.url.host_str().unwrap_or("127.0.0.1");
let domain = EhloDomain::Domain(gethostname().as_encoded_bytes().try_into()?).into_static();
let (mut context, mut stream) = match config.url.scheme() {
scheme if scheme.eq_ignore_ascii_case("smtp") => {
let port = config.url.port().unwrap_or(25);
let mut stream = TcpStream::connect((host, port))?;
let mut coroutine = GetSmtpGreetingWithCapability::new(context, domain.clone());
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
GetSmtpGreetingWithCapabilityResult::Io { io } => {
arg = Some(handle(&mut stream, io)?)
}
GetSmtpGreetingWithCapabilityResult::Ok { context: c } => break context = c,
GetSmtpGreetingWithCapabilityResult::Err { err, .. } => Err(err)?,
}
}
(context, Stream::Tcp(stream))
}
scheme if scheme.eq_ignore_ascii_case("smtps") => {
let default_port = if config.starttls { 587 } else { 465 };
let port = config.url.port().unwrap_or(default_port);
let mut stream = TcpStream::connect((host, port))?;
if config.starttls {
let mut coroutine = SmtpStartTls::new(context);
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
SmtpStartTlsResult::Io { io } => arg = Some(handle(&mut stream, io)?),
SmtpStartTlsResult::Ok { context: c } => break context = c,
SmtpStartTlsResult::Err { err, .. } => Err(err)?,
}
}
}
let tls_provider = match config.tls.provider {
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Some(TlsProviderConfig::Rustls) => TlsProviderConfig::Rustls,
#[cfg(not(feature = "rustls-aws"))]
#[cfg(not(feature = "rustls-ring"))]
Some(TlsProviderConfig::Rustls) => {
bail!("Required cargo feature: `rustls-aws` or `rustls-ring`")
}
#[cfg(feature = "native-tls")]
Some(TlsProviderConfig::NativeTls) => TlsProviderConfig::NativeTls,
#[cfg(not(feature = "native-tls"))]
Some(TlsProviderConfig::NativeTls) => {
bail!("Required cargo feature: `native-tls`")
}
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
None => TlsProviderConfig::Rustls,
#[cfg(not(feature = "rustls-aws"))]
#[cfg(not(feature = "rustls-ring"))]
#[cfg(feature = "native-tls")]
None => TlsProviderConfig::NativeTls,
#[cfg(not(feature = "rustls-aws"))]
#[cfg(not(feature = "rustls-ring"))]
#[cfg(not(feature = "native-tls"))]
None => {
bail!("Required cargo feature: `rustls-aws`, `rustls-ring` or `native-tls`")
}
};
debug!("using TLS provider: {tls_provider:?}");
let mut stream = match tls_provider {
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
TlsProviderConfig::Rustls => {
let crypto_provider = match config.tls.rustls.crypto {
#[cfg(feature = "rustls-aws")]
Some(RustlsCryptoConfig::Aws) => RustlsCryptoConfig::Aws,
#[cfg(not(feature = "rustls-aws"))]
Some(RustlsCryptoConfig::Aws) => {
bail!("Required cargo feature: `rustls-aws`");
}
#[cfg(feature = "rustls-ring")]
Some(RustlsCryptoConfig::Ring) => RustlsCryptoConfig::Ring,
#[cfg(not(feature = "rustls-ring"))]
Some(RustlsCryptoConfig::Ring) => {
bail!("Required cargo feature: `rustls-ring`");
}
#[cfg(feature = "rustls-ring")]
None => RustlsCryptoConfig::Ring,
#[cfg(not(feature = "rustls-ring"))]
#[cfg(feature = "rustls-aws")]
None => RustlsCryptoConfig::Aws,
#[cfg(not(feature = "rustls-aws"))]
#[cfg(not(feature = "rustls-ring"))]
None => {
bail!("Required cargo feature: `rustls-aws` or `rustls-ring`");
}
};
debug!("using rustls crypto provider: {crypto_provider:?}");
let crypto_provider = match crypto_provider {
#[cfg(feature = "rustls-aws")]
RustlsCryptoConfig::Aws => crypto::aws_lc_rs::default_provider(),
#[cfg(feature = "rustls-ring")]
RustlsCryptoConfig::Ring => crypto::ring::default_provider(),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
let crypto_provider = match crypto_provider.install_default() {
Ok(()) => CryptoProvider::get_default().unwrap().clone(),
Err(crypto_provider) => crypto_provider,
};
let mut config = if let Some(pem_path) = &config.tls.cert {
debug!("using TLS cert at {}", pem_path.display());
let pem = fs::read(pem_path)?;
let Some(cert) = CertificateDer::pem_slice_iter(&pem).next() else {
bail!("empty TLS cert at {}", pem_path.display())
};
let verifier =
Verifier::new_with_extra_roots(vec![cert?], crypto_provider)?;
ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(verifier))
.with_no_client_auth()
} else {
debug!("using OS TLS certs");
ClientConfig::with_platform_verifier()?
};
config.alpn_protocols = vec![b"smtp".to_vec()];
let server_name = host.to_string().try_into()?;
let conn = ClientConnection::new(Arc::new(config), server_name)?;
Stream::Rustls(StreamOwned::new(conn, stream))
}
#[cfg(feature = "native-tls")]
TlsProviderConfig::NativeTls => {
let mut builder = TlsConnector::builder();
if let Some(pem_path) = &config.tls.cert {
debug!("using TLS cert at {}", pem_path.display());
let pem = fs::read(pem_path)?;
let cert = native_tls::Certificate::from_pem(&pem)?;
builder.add_root_certificate(cert);
}
let connector = builder.build()?;
Stream::NativeTls(connector.connect(host, stream)?)
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
if config.starttls {
let mut coroutine = SmtpEhlo::new(context, domain.clone());
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
SmtpEhloResult::Io { io } => arg = Some(handle(&mut stream, io)?),
SmtpEhloResult::Ok { context: c } => break context = c,
SmtpEhloResult::Err { err, .. } => Err(err)?,
}
}
} else {
let mut coroutine = GetSmtpGreetingWithCapability::new(context, domain.clone());
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
GetSmtpGreetingWithCapabilityResult::Io { io } => {
arg = Some(handle(&mut stream, io)?)
}
GetSmtpGreetingWithCapabilityResult::Ok { context: c } => {
break context = c
}
GetSmtpGreetingWithCapabilityResult::Err { err, .. } => Err(err)?,
}
}
}
(context, stream)
}
scheme if scheme.eq_ignore_ascii_case("unix") => {
let sock_path = config.url.path();
let mut stream = UnixStream::connect(&sock_path)?;
let mut coroutine = GetSmtpGreetingWithCapability::new(context, domain.clone());
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
GetSmtpGreetingWithCapabilityResult::Io { io } => {
arg = Some(handle(&mut stream, io)?)
}
GetSmtpGreetingWithCapabilityResult::Ok { context: c } => break context = c,
GetSmtpGreetingWithCapabilityResult::Err { err, .. } => Err(err)?,
}
}
(context, Stream::Unix(stream))
}
scheme => {
bail!("Unknown scheme {scheme}, expected smtp, smtps or unix");
}
};
if !context.authenticated {
let mut candidates = vec![];
for mechanism in config.sasl.mechanisms {
match mechanism {
SaslMechanismConfig::Login => {
let Some(auth) = config.sasl.login.take() else {
debug!("missing SASL LOGIN configuration, skipping it");
continue;
};
for capability in &context.capability {
match capability {
Capability::Auth(mechanisms) => {
for m in mechanisms {
match m {
AuthMechanism::Login => {
candidates.push(SmtpAuthenticateCandidate::Login {
login: auth.username.clone(),
password: auth.password.get()?,
domain: domain.clone(),
});
break;
}
_ => continue,
}
}
}
_ => continue,
}
}
debug!("SASL LOGIN disabled by the server, skipping it");
continue;
}
SaslMechanismConfig::Plain => {
let Some(auth) = config.sasl.plain.take() else {
debug!("missing SASL PLAIN configuration, skipping it");
continue;
};
for capability in &context.capability {
match capability {
Capability::Auth(mechanisms) => {
for m in mechanisms {
match m {
AuthMechanism::Plain => {
candidates.push(SmtpAuthenticateCandidate::Plain {
login: auth.authcid.clone(),
password: auth.passwd.get()?,
domain: domain.clone(),
});
break;
}
_ => continue,
}
}
}
_ => continue,
}
}
debug!("SASL PLAIN disabled by the server, skipping it");
continue;
}
SaslMechanismConfig::Anonymous => {
unimplemented!("ANONYMOUS SASL mechanism not yet implemented")
}
};
}
let mut arg = None;
let mut coroutine = SmtpAuthenticate::new(context, candidates);
loop {
match coroutine.resume(arg.take()) {
SmtpAuthenticateResult::Io { io } => arg = Some(handle(&mut stream, io)?),
SmtpAuthenticateResult::Ok { context: c, .. } => break context = c,
SmtpAuthenticateResult::Err { err, .. } => bail!(err),
}
}
}
Ok((context, stream))
}