From b77a54e5dc868b89ebb8e34930b02a4e4e065f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sat, 14 Mar 2026 14:05:11 +0100 Subject: [PATCH] refactor: use imap and smtp streams from toolbox --- Cargo.lock | 112 ++++++---- Cargo.toml | 17 +- MIGRATION.md | 2 + config.sample.toml | 32 +-- src/account.rs | 6 +- src/cli.rs | 6 +- src/config.rs | 136 ++++++------ src/imap/account.rs | 14 ++ src/imap/command.rs | 12 +- src/imap/envelope/command.rs | 12 +- src/imap/envelope/get.rs | 16 +- src/imap/envelope/list.rs | 16 +- src/imap/envelope/search.rs | 16 +- src/imap/envelope/sort.rs | 12 +- src/imap/envelope/thread.rs | 35 ++- src/imap/flag/add.rs | 16 +- src/imap/flag/command.rs | 10 +- src/imap/flag/list.rs | 11 +- src/imap/flag/remove.rs | 23 +- src/imap/flag/set.rs | 23 +- src/imap/id.rs | 11 +- src/imap/mailbox/close.rs | 10 +- src/imap/mailbox/command.rs | 26 +-- src/imap/mailbox/create.rs | 10 +- src/imap/mailbox/delete.rs | 11 +- src/imap/mailbox/expunge.rs | 16 +- src/imap/mailbox/list.rs | 15 +- src/imap/mailbox/purge.rs | 22 +- src/imap/mailbox/rename.rs | 10 +- src/imap/mailbox/select.rs | 11 +- src/imap/mailbox/status.rs | 11 +- src/imap/mailbox/subscribe.rs | 11 +- src/imap/mailbox/unselect.rs | 11 +- src/imap/mailbox/unsubscribe.rs | 11 +- src/imap/message/command.rs | 14 +- src/imap/message/copy.rs | 16 +- src/imap/message/export.rs | 13 +- src/imap/message/get.rs | 16 +- src/imap/message/move.rs | 16 +- src/imap/message/read.rs | 16 +- src/imap/message/save.rs | 12 +- src/imap/mod.rs | 1 - src/main.rs | 4 +- src/smtp/account.rs | 14 ++ src/smtp/command.rs | 4 +- src/smtp/message/command.rs | 4 +- src/smtp/message/send.rs | 16 +- src/smtp/mod.rs | 1 - src/smtp/stream.rs | 381 -------------------------------- 49 files changed, 449 insertions(+), 792 deletions(-) delete mode 100644 src/smtp/stream.rs diff --git a/Cargo.lock b/Cargo.lock index ee4772e5..476a6042 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -43,9 +58,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -56,6 +71,15 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-query" version = "1.1.5" @@ -156,9 +180,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -213,9 +237,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -223,11 +247,11 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "clap_lex", "strsim", @@ -236,18 +260,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.66" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031" +checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -257,15 +281,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clap_mangen" -version = "0.2.31" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ea63a92086df93893164221ad4f24142086d535b3a0957b9b9bea2dc86301" +checksum = "7e30ffc187e2e3aeafcd1c6e2aa416e29739454c0ccaa419226d5ecd181f2d78" dependencies = [ "clap", "roff", @@ -282,9 +306,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -418,7 +442,7 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "env_filter", "jiff", @@ -547,9 +571,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.19.0" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ "bitflags", "libc", @@ -598,12 +622,9 @@ dependencies = [ "io-stream", "log", "mail-parser", - "native-tls", "open", "pimalaya-toolbox", "rfc2047-decoder", - "rustls", - "rustls-platform-verifier", "secrecy", "serde", "shellexpand", @@ -789,7 +810,7 @@ dependencies = [ [[package]] name = "io-imap" version = "0.0.1" -source = "git+https://github.com/pimalaya/io-imap?branch=io#7e9db5349bb4b6495e10a5bba10c0c79a6bba610" +source = "git+https://github.com/pimalaya/io-imap?branch=io#35186c5ea0f6c535a528029e22ebb6a02bff8473" dependencies = [ "imap-codec", "io-stream", @@ -936,9 +957,9 @@ checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libgit2-sys" -version = "0.17.0+1.8.1" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -1103,9 +1124,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1126,9 +1147,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ "bitflags", "cfg-if", @@ -1167,9 +1188,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -1260,7 +1281,7 @@ dependencies = [ [[package]] name = "pimalaya-toolbox" version = "0.0.4" -source = "git+https://github.com/pimalaya/toolbox#d4e301ccbdbbfafcb7e9b18df8d6c098ac6ada0b" +source = "git+https://github.com/pimalaya/toolbox#64babb1b4358dc57910d815fa91f0dbae9423a9c" dependencies = [ "anyhow", "clap", @@ -1268,15 +1289,25 @@ dependencies = [ "clap_mangen", "dirs", "env_logger", + "gethostname", "git2", + "io-imap", "io-process", + "io-smtp", + "io-stream", "log", + "native-tls", + "rustls", + "rustls-platform-verifier", "secrecy", "serde", "serde-toml-merge", "serde_json", "shellexpand", + "thiserror 2.0.18", "toml", + "uds_windows", + "url", ] [[package]] @@ -1506,9 +1537,9 @@ dependencies = [ [[package]] name = "roff" -version = "0.2.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" +checksum = "dbf2048e0e979efb2ca7b91c4f1a8d77c91853e9b987c94c555668a8994915ad" [[package]] name = "rustix" @@ -1530,6 +1561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -1852,9 +1884,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", diff --git a/Cargo.toml b/Cargo.toml index ef24135f..39fae759 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,14 +18,14 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["imap", "smtp", "rustls-ring"] -imap = ["dep:io-imap"] -smtp = ["dep:io-smtp"] +imap = ["dep:io-imap", "pimalaya-toolbox/imap"] +smtp = ["dep:io-smtp", "pimalaya-toolbox/smtp"] -rustls-aws = ["dep:rustls-platform-verifier", "rustls/aws-lc-rs"] -rustls-ring = ["dep:rustls-platform-verifier", "rustls/ring"] -native-tls = ["dep:native-tls"] +native-tls = ["pimalaya-toolbox/native-tls"] +rustls-aws = ["pimalaya-toolbox/rustls-aws"] +rustls-ring = ["pimalaya-toolbox/rustls-ring"] -vendored = ["native-tls?/vendored"] +vendored = ["pimalaya-toolbox/vendored"] [build-dependencies] pimalaya-toolbox = { version = "0.0.4", default-features = false, features = ["build"] } @@ -44,12 +44,9 @@ io-smtp = { version = "0.0.1", default-features = false, features = ["ext_auth", io-stream = { version = "0.0.2", default-features = false, features = ["std"] } log = "0.4" mail-parser = "0.9" -native-tls = { version = "0.2", optional = true } open = "5" -pimalaya-toolbox = { version = "0.0.4", default-features = false, features = ["config", "terminal", "secret", "command"] } +pimalaya-toolbox = { version = "0.0.4", default-features = false, features = ["config", "terminal", "secret"] } rfc2047-decoder = "1" -rustls = { version = "0.23", default-features = false, optional = true } -rustls-platform-verifier = { version = "0.6", optional = true } secrecy = "0.10" serde = { version = "1", features = ["derive"] } shellexpand = "3.1" diff --git a/MIGRATION.md b/MIGRATION.md index 9ec4ad5f..019fb694 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -28,6 +28,8 @@ The v2 also removes away some complexity: - Secrets do not support keyring natively anymore due to many issues with it. Instead use [pimalaya/mimosa](https://github.com/pimalaya/mimosa) or equivalent. - OAuth is not supported natively anymore. Instead use [pimalaya/ortie](https://github.com/pimalaya/ortie) or equivalent. +A direct consequence is a v2 binary size 3 times smaller than the v1! + ### I/O-free Additionally, Pimalaya has been working for the past year on an adaptation of the [Sans I/O](https://sans-io.readthedocs.io/) pattern for its libraries. It makes libraries not tied up to any sort of I/O: sync vs async, tokio vs async-std vs smol, rustls vs native-tls etc. The concept has been implemented into recent libraries, and has been tested in other CLIs like [pimalaya/ortie](https://github.com/pimalaya/ortie), [pimalaya/cardamum](https://github.com/pimalaya/cardamum) or [pimalaya/calendula](https://github.com/pimalaya/calendula). Himalaya CLI v2 implements these changes. As a direct consequence, it supports out of the box TLS via `native-tls` or via `rustls` (supporting both `aws-lc` and `ring` crypto providers) diff --git a/config.sample.toml b/config.sample.toml index 209d734c..23381c41 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -51,11 +51,9 @@ imap.starttls = false # SASL mechanisms to try in this particular order imap.sasl.mechanisms = ["plain", "login"] -# SASL LOGIN -# https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 -imap.sasl.login.username = "username" -imap.sasl.login.password.command = ["mimosa", "password", "read", "example"] -#imap.sasl.plain.password.raw = "***" +# SASL ANONYMOUS +# https://datatracker.ietf.org/doc/html/rfc4505 +imap.sasl.anonymous.message = "himalaya" # SASL PLAIN # https://datatracker.ietf.org/doc/html/rfc4616 @@ -63,9 +61,11 @@ imap.sasl.plain.authcid = "username" imap.sasl.plain.passwd.command = ["mimosa", "password", "read", "example"] #imap.sasl.plain.passwd.raw = "***" -# SASL ANONYMOUS -# https://datatracker.ietf.org/doc/html/rfc4505 -imap.sasl.anonymous.message = "himalaya" +# SASL LOGIN +# https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 +imap.sasl.login.username = "username" +imap.sasl.login.password.command = ["mimosa", "password", "read", "example"] +#imap.sasl.login.password.raw = "***" # -------------------------------- # SMTP config @@ -91,11 +91,9 @@ smtp.starttls = false # SASL mechanisms to try in this particular order smtp.sasl.mechanisms = ["plain", "login"] -# SASL LOGIN -# https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 -smtp.sasl.login.username = "username" -smtp.sasl.login.password.command = ["mimosa", "password", "read", "example"] -#smtp.sasl.login.password.raw = "***" +# SASL ANONYMOUS +# https://datatracker.ietf.org/doc/html/rfc4505 +smtp.sasl.anonymous.message = "himalaya" # SASL PLAIN # https://datatracker.ietf.org/doc/html/rfc4616 @@ -103,6 +101,8 @@ smtp.sasl.plain.authcid = "username" smtp.sasl.plain.passwd.command = ["mimosa", "password", "read", "example"] #smtp.sasl.plain.passwd.raw = "***" -# SASL ANONYMOUS -# https://datatracker.ietf.org/doc/html/rfc4505 -smtp.sasl.anonymous.message = "himalaya" +# SASL LOGIN +# https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00 +smtp.sasl.login.username = "username" +smtp.sasl.login.password.command = ["mimosa", "password", "read", "example"] +#smtp.sasl.login.password.raw = "***" diff --git a/src/account.rs b/src/account.rs index 1e30f0ff..cdb3fb69 100644 --- a/src/account.rs +++ b/src/account.rs @@ -5,8 +5,8 @@ use anyhow::Result; use comfy_table::{presets, ContentArrangement}; use dirs::download_dir; -#[derive(Debug)] -pub struct Account { +#[derive(Clone, Debug)] +pub struct Account { pub backend: B, pub downloads_dir: PathBuf, @@ -14,7 +14,7 @@ pub struct Account { pub table_arrangement: ContentArrangement, } -impl Account { +impl Account { pub fn new(config: Config, account_config: AccountConfig, backend: B) -> Result { Ok(Self { backend, diff --git a/src/cli.rs b/src/cli.rs index 9f7caa85..a9c1b809 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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) } } } diff --git a/src/config.rs b/src/config.rs index 55b94be0..2725e92f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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 for Tls { + type Error = SecretError; + + fn try_from(config: TlsConfig) -> Result { + 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, #[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, } -/// Secret configuration. -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum SecretConfig { - Raw(SecretString), - Command(Vec), -} +impl TryFrom for Sasl { + type Error = SecretError; -impl SecretConfig { - pub fn get(&self) -> Result { - 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 { + 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(self, v: String) -> Result { - 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 { - deserializer.deserialize_string(ShellExpandedStringVisitor) -} diff --git a/src/imap/account.rs b/src/imap/account.rs index 81d73a8d..8ea00605 100644 --- a/src/imap/account.rs +++ b/src/imap/account.rs @@ -1,3 +1,17 @@ +use anyhow::Result; +use pimalaya_toolbox::stream::imap::ImapSession; + use crate::{account::Account, config::ImapConfig}; pub type ImapAccount = Account; + +impl ImapAccount { + pub fn new_imap_session(&self) -> Result { + ImapSession::new( + self.backend.url.clone(), + self.backend.tls.clone().try_into()?, + self.backend.starttls, + self.backend.sasl.clone().try_into()?, + ) + } +} diff --git a/src/imap/command.rs b/src/imap/command.rs index 64470939..2a0352fe 100644 --- a/src/imap/command.rs +++ b/src/imap/command.rs @@ -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), } } } diff --git a/src/imap/envelope/command.rs b/src/imap/envelope/command.rs index a24d8299..092848a0 100644 --- a/src/imap/envelope/command.rs +++ b/src/imap/envelope/command.rs @@ -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), } } } diff --git a/src/imap/envelope/get.rs b/src/imap/envelope/get.rs index 9e793486..a0d14d01 100644 --- a/src/imap/envelope/get.rs +++ b/src/imap/envelope/get.rs @@ -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), } diff --git a/src/imap/envelope/list.rs b/src/imap/envelope/list.rs index 08977d71..0252e6b9 100644 --- a/src/imap/envelope/list.rs +++ b/src/imap/envelope/list.rs @@ -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), } diff --git a/src/imap/envelope/search.rs b/src/imap/envelope/search.rs index 03589926..8a53f5ff 100644 --- a/src/imap/envelope/search.rs +++ b/src/imap/envelope/search.rs @@ -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), } diff --git a/src/imap/envelope/sort.rs b/src/imap/envelope/sort.rs index ad3fbfb9..ccb93b49 100644 --- a/src/imap/envelope/sort.rs +++ b/src/imap/envelope/sort.rs @@ -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), } diff --git a/src/imap/envelope/thread.rs b/src/imap/envelope/thread.rs index 9892e8ce..948582ea 100644 --- a/src/imap/envelope/thread.rs +++ b/src/imap/envelope/thread.rs @@ -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) { } fn fetch_subjects( - stream: &mut Stream, - context: ImapContext, + mut imap: ImapSession, ids: &[NonZeroU32], uid: bool, ) -> Result> { @@ -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), } diff --git a/src/imap/flag/add.rs b/src/imap/flag/add.rs index aef16db4..aadfc516 100644 --- a/src/imap/flag/add.rs +++ b/src/imap/flag/add.rs @@ -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), } diff --git a/src/imap/flag/command.rs b/src/imap/flag/command.rs index 2b5bd61b..abe4757d 100644 --- a/src/imap/flag/command.rs +++ b/src/imap/flag/command.rs @@ -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), } } } diff --git a/src/imap/flag/list.rs b/src/imap/flag/list.rs index c758d60a..828eaf41 100644 --- a/src/imap/flag/list.rs +++ b/src/imap/flag/list.rs @@ -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(), diff --git a/src/imap/flag/remove.rs b/src/imap/flag/remove.rs index 096dd09f..ddf8cfb8 100644 --- a/src/imap/flag/remove.rs +++ b/src/imap/flag/remove.rs @@ -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::>()?; 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), } diff --git a/src/imap/flag/set.rs b/src/imap/flag/set.rs index 9c3c0688..0a6f8679 100644 --- a/src/imap/flag/set.rs +++ b/src/imap/flag/set.rs @@ -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::>()?; 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), } diff --git a/src/imap/id.rs b/src/imap/id.rs index c30769da..a7b20189 100644 --- a/src/imap/id.rs +++ b/src/imap/id.rs @@ -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), } diff --git a/src/imap/mailbox/close.rs b/src/imap/mailbox/close.rs index 6541b6f8..8103c1c9 100644 --- a/src/imap/mailbox/close.rs +++ b/src/imap/mailbox/close.rs @@ -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), } diff --git a/src/imap/mailbox/command.rs b/src/imap/mailbox/command.rs index 152748cf..2c54a5a4 100644 --- a/src/imap/mailbox/command.rs +++ b/src/imap/mailbox/command.rs @@ -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), } } } diff --git a/src/imap/mailbox/create.rs b/src/imap/mailbox/create.rs index b6aa8e9a..799bd8d7 100644 --- a/src/imap/mailbox/create.rs +++ b/src/imap/mailbox/create.rs @@ -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), } diff --git a/src/imap/mailbox/delete.rs b/src/imap/mailbox/delete.rs index 905a4126..5fca63e9 100644 --- a/src/imap/mailbox/delete.rs +++ b/src/imap/mailbox/delete.rs @@ -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), } diff --git a/src/imap/mailbox/expunge.rs b/src/imap/mailbox/expunge.rs index a37dae9a..5558757b 100644 --- a/src/imap/mailbox/expunge.rs +++ b/src/imap/mailbox/expunge.rs @@ -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), } diff --git a/src/imap/mailbox/list.rs b/src/imap/mailbox/list.rs index 0d441556..42a78d63 100644 --- a/src/imap/mailbox/list.rs +++ b/src/imap/mailbox/list.rs @@ -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), } diff --git a/src/imap/mailbox/purge.rs b/src/imap/mailbox/purge.rs index c1194faa..5cbc00b7 100644 --- a/src/imap/mailbox/purge.rs +++ b/src/imap/mailbox/purge.rs @@ -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), } diff --git a/src/imap/mailbox/rename.rs b/src/imap/mailbox/rename.rs index 7635647b..aeecc753 100644 --- a/src/imap/mailbox/rename.rs +++ b/src/imap/mailbox/rename.rs @@ -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), } diff --git a/src/imap/mailbox/select.rs b/src/imap/mailbox/select.rs index 9aa8e6ba..b7e57611 100644 --- a/src/imap/mailbox/select.rs +++ b/src/imap/mailbox/select.rs @@ -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), } diff --git a/src/imap/mailbox/status.rs b/src/imap/mailbox/status.rs index 73dc8057..43c06f96 100644 --- a/src/imap/mailbox/status.rs +++ b/src/imap/mailbox/status.rs @@ -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), } diff --git a/src/imap/mailbox/subscribe.rs b/src/imap/mailbox/subscribe.rs index 147e7b26..3bc0193a 100644 --- a/src/imap/mailbox/subscribe.rs +++ b/src/imap/mailbox/subscribe.rs @@ -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), } diff --git a/src/imap/mailbox/unselect.rs b/src/imap/mailbox/unselect.rs index 9a08021b..0c532281 100644 --- a/src/imap/mailbox/unselect.rs +++ b/src/imap/mailbox/unselect.rs @@ -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), } diff --git a/src/imap/mailbox/unsubscribe.rs b/src/imap/mailbox/unsubscribe.rs index 4741838b..85766482 100644 --- a/src/imap/mailbox/unsubscribe.rs +++ b/src/imap/mailbox/unsubscribe.rs @@ -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), } diff --git a/src/imap/message/command.rs b/src/imap/message/command.rs index 20a6950b..682cb906 100644 --- a/src/imap/message/command.rs +++ b/src/imap/message/command.rs @@ -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), } } } diff --git a/src/imap/message/copy.rs b/src/imap/message/copy.rs index 0c008e84..5ba70432 100644 --- a/src/imap/message/copy.rs +++ b/src/imap/message/copy.rs @@ -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), } diff --git a/src/imap/message/export.rs b/src/imap/message/export.rs index 4433d438..49ed760b 100644 --- a/src/imap/message/export.rs +++ b/src/imap/message/export.rs @@ -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), } diff --git a/src/imap/message/get.rs b/src/imap/message/get.rs index 24fa955c..b09546dd 100644 --- a/src/imap/message/get.rs +++ b/src/imap/message/get.rs @@ -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), } diff --git a/src/imap/message/move.rs b/src/imap/message/move.rs index ec49ec8f..acb3da29 100644 --- a/src/imap/message/move.rs +++ b/src/imap/message/move.rs @@ -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), } diff --git a/src/imap/message/read.rs b/src/imap/message/read.rs index 1e0c9a4a..a6fb8c56 100644 --- a/src/imap/message/read.rs +++ b/src/imap/message/read.rs @@ -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), } diff --git a/src/imap/message/save.rs b/src/imap/message/save.rs index faadf454..99a33c5a 100644 --- a/src/imap/message/save.rs +++ b/src/imap/message/save.rs @@ -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::>()?; 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), } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index b29c5cdb..546f64e6 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -5,4 +5,3 @@ pub mod flag; pub mod id; pub mod mailbox; pub mod message; -pub mod stream; diff --git a/src/main.rs b/src/main.rs index 45999692..6706c5b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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) } diff --git a/src/smtp/account.rs b/src/smtp/account.rs index bf8c3f6d..34eb5bc5 100644 --- a/src/smtp/account.rs +++ b/src/smtp/account.rs @@ -1,3 +1,17 @@ +use anyhow::Result; +use pimalaya_toolbox::stream::smtp::SmtpSession; + use crate::{account::Account, config::SmtpConfig}; pub type SmtpAccount = Account; + +impl SmtpAccount { + pub fn new_smtp_session(&self) -> Result { + SmtpSession::new( + self.backend.url.clone(), + self.backend.tls.clone().try_into()?, + self.backend.starttls, + self.backend.sasl.clone().try_into()?, + ) + } +} diff --git a/src/smtp/command.rs b/src/smtp/command.rs index bfb78f3d..d9306736 100644 --- a/src/smtp/command.rs +++ b/src/smtp/command.rs @@ -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), } } } diff --git a/src/smtp/message/command.rs b/src/smtp/message/command.rs index 9f655424..55bbd241 100644 --- a/src/smtp/message/command.rs +++ b/src/smtp/message/command.rs @@ -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), } } } diff --git a/src/smtp/message/send.rs b/src/smtp/message/send.rs index 0e842920..2ebe27f1 100644 --- a/src/smtp/message/send.rs +++ b/src/smtp/message/send.rs @@ -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), } diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 1d207ba4..306628f0 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -1,4 +1,3 @@ pub mod account; pub mod command; pub mod message; -pub mod stream; diff --git a/src/smtp/stream.rs b/src/smtp/stream.rs deleted file mode 100644 index acb263c7..00000000 --- a/src/smtp/stream.rs +++ /dev/null @@ -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), - #[cfg(feature = "native-tls")] - NativeTls(native_tls::TlsStream), -} - -impl Read for Stream { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - 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 { - 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)) -}