use std::ops::{Deref, DerefMut};
use anyhow::{Result, anyhow};
use io_managesieve::{
client::{ManagesieveClientStd as Inner, ManagesieveClientStdConnectOptions},
session::ManagesieveSessionOpenOptions,
};
use io_sasl::mechanism::Sasl;
use pimalaya_config::secret::SecretResolver;
use url::Url;
use crate::{
account::context::Account,
config::{AccountConfig, Config, ProxyConfig, SieveConfig, parse_server},
};
pub struct SieveClient {
inner: Inner,
}
impl SieveClient {
pub fn new(config: SieveConfig, resolver: &mut SecretResolver) -> Result<Self> {
let tls = config.tls.into_tls(config.alpn);
let server = parse_sieve_server(&config.server)?;
let sasl: Option<Sasl> = match config.sasl {
Some(_) if server.scheme() == "unix" => None,
Some(cfg) => {
let host = server.host_str().ok_or_else(|| {
anyhow!("Cannot derive host from ManageSieve server `{server}`")
})?;
let port = server
.port()
.unwrap_or(Inner::default_port(server.scheme()));
Some(cfg.try_into_sasl(host, port, resolver)?)
}
None => None,
};
let opts = ManagesieveClientStdConnectOptions {
tls,
proxy: ProxyConfig::resolve(config.proxy, resolver)?,
sasl,
session: ManagesieveSessionOpenOptions {
starttls: config.starttls.unwrap_or(server.scheme() == "sieve"),
allow_cleartext_auth: config.allow_cleartext_auth,
},
};
let (inner, _capabilities) = Inner::connect(&server, opts)?;
Ok(Self { inner })
}
}
pub fn parse_sieve_server(server: &str) -> Result<Url> {
parse_server(server, "sieve", &["sieve", "sieves", "unix"])
}
impl Deref for SieveClient {
type Target = Inner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for SieveClient {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
pub fn build_sieve_client(
config: Config,
name: String,
mut account_config: AccountConfig,
) -> Result<(Account, SieveClient)> {
let sieve_config = account_config
.sieve
.take()
.ok_or_else(|| anyhow!("Sieve config is missing for account `{name}`"))?;
let account = Account::from(config).merge(Account::from(account_config));
let client = SieveClient::new(sieve_config, &mut SecretResolver::new())?;
Ok((account, client))
}
#[cfg(test)]
mod tests {
use crate::sieve::client::parse_sieve_server;
#[test]
fn a_bare_authority_resolves_to_starttls_rather_than_implicit_tls() {
let url = parse_sieve_server("sieve.example.com").unwrap();
assert_eq!(url.scheme(), "sieve");
assert_eq!(url.port(), None);
let url = parse_sieve_server("sieves://sieve.example.com:4190").unwrap();
assert_eq!(url.scheme(), "sieves");
assert_eq!(url.port(), Some(4190));
let url = parse_sieve_server("unix:///run/sieve.sock").unwrap();
assert_eq!(url.scheme(), "unix");
assert!(parse_sieve_server("imaps://sieve.example.com").is_err());
}
}