feat(imap): added back auto id feature

Refs: #688
This commit is contained in:
Clément DOUIN
2026-05-31 19:46:48 +02:00
parent f603e68268
commit aff4fddfb4
25 changed files with 311 additions and 252 deletions
+7 -6
View File
@@ -114,15 +114,17 @@ fn check_imap(
imap_config: crate::config::ImapConfig,
) -> BackendCheck {
use io_imap::client::ImapClientStd;
use pimalaya_stream::{sasl::Sasl, std::stream::StreamStd, tls::Tls};
use pimalaya_stream::{sasl::Sasl, tls::Tls};
use crate::imap::id::resolve_auto_id_params;
let result = (|| -> Result<()> {
let mut tls: Tls = imap_config.tls.clone().into();
tls.rustls.alpn = vec!["imap".into()];
let sasl: Option<Sasl> = imap_config.sasl.clone().map(Sasl::try_from).transpose()?;
let auto_id = resolve_auto_id_params(&imap_config.id)?;
let server = crate::imap::client::parse_imap_server(&imap_config.server)?;
let _client =
ImapClientStd::<StreamStd>::connect(&server, &tls, imap_config.starttls, sasl)?;
let _ = ImapClientStd::connect(&server, &tls, imap_config.starttls, sasl, auto_id)?;
Ok(())
})();
@@ -181,7 +183,7 @@ fn check_smtp(
use std::net::Ipv4Addr;
use io_smtp::{client::SmtpClientStd, rfc5321::types::ehlo_domain::EhloDomain};
use pimalaya_stream::{sasl::Sasl, std::stream::StreamStd, tls::Tls};
use pimalaya_stream::{sasl::Sasl, tls::Tls};
let result = (|| -> Result<()> {
let mut tls: Tls = smtp_config.tls.clone().into();
@@ -189,8 +191,7 @@ fn check_smtp(
let sasl: Option<Sasl> = smtp_config.sasl.clone().map(Sasl::try_from).transpose()?;
let domain: EhloDomain<'static> = Ipv4Addr::new(127, 0, 0, 1).into();
let server = crate::smtp::client::parse_smtp_server(&smtp_config.server)?;
let _client =
SmtpClientStd::<StreamStd>::connect(&server, &tls, smtp_config.starttls, domain, sasl)?;
let _client = SmtpClientStd::connect(&server, &tls, smtp_config.starttls, domain, sasl)?;
Ok(())
})();
+27 -4
View File
@@ -116,13 +116,10 @@ pub struct AccountConfig {
pub downloads_dir: Option<PathBuf>,
#[serde(default)]
pub table: TableConfig,
#[serde(default)]
pub envelope: EnvelopeConfig,
#[serde(default)]
pub mailbox: MailboxConfig,
#[serde(default)]
pub attachment: AttachmentConfig,
@@ -362,7 +359,7 @@ pub struct ReaderConfig {
pub default: bool,
}
/// Global / per-account table rendering knobs shared across every list
/// Global / per-account table rendering quirks shared across every list
/// command (envelopes, mailboxes, attachments). The per-column color
/// blocks live under `*.list.table.*-color` (see [`EnvelopeListTableConfig`]
/// & co.).
@@ -418,6 +415,32 @@ pub struct ImapConfig {
/// to advertise the ANONYMOUS mechanism explicitly, set
/// `sasl.anonymous = {}`.
pub sasl: Option<SaslConfig>,
/// RFC 2971 `ID` extension quirks. Some providers (notably
/// mail.qq.com, fastmail) require an `ID` exchange straight after
/// authentication; set `id.auto = true` to opt in.
#[serde(default)]
pub id: ImapIdConfig,
}
/// Per-account `imap.id.*` quirks.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct ImapIdConfig {
/// When `true`, the auth coroutine chains an `ID` round-trip
/// after the tagged auth response. Default `false` skips ID
/// entirely.
#[serde(default)]
pub auto: bool,
/// Parameters sent with the auto-ID command. Empty (default)
/// sends `ID NIL`. For each entry: `true` substitutes himalaya's
/// canned value for the well-known keys (`name`, `version`,
/// `vendor`, `support-url`) or `NIL` for unknown keys; `false`
/// always sends `NIL`. Keys absent from this map are not
/// transmitted.
#[serde(default)]
pub fields: HashMap<String, bool>,
}
/// Maildir configuration.
+13 -6
View File
@@ -30,25 +30,32 @@ use std::{
use anyhow::{Result, anyhow};
use io_imap::client::ImapClientStd as Inner;
use pimalaya_config::toml::TomlConfig;
use pimalaya_stream::{sasl::Sasl, std::stream::StreamStd, tls::Tls};
use pimalaya_stream::{sasl::Sasl, tls::Tls};
use url::Url;
use crate::{account::context::Account, cli::load_or_wizard, config::ImapConfig};
use crate::{
account::context::Account, cli::load_or_wizard, config::ImapConfig,
imap::id::resolve_auto_id_params,
};
pub struct ImapClient {
inner: Inner<StreamStd>,
inner: Inner,
pub account: Account,
}
impl ImapClient {
/// Opens the IMAP connection (TCP/TLS/STARTTLS, greeting, SASL)
/// then wraps the resulting client alongside `account`.
/// then wraps the resulting client alongside `account`. The
/// capability list reported by the connect handshake is discarded;
/// IMAP-specific subcommands that need it should call
/// [`Inner::capability`] explicitly.
pub fn new(config: ImapConfig, account: Account) -> Result<Self> {
let mut tls: Tls = config.tls.into();
tls.rustls.alpn = vec!["imap".into()];
let sasl: Option<Sasl> = config.sasl.map(Sasl::try_from).transpose()?;
let auto_id = resolve_auto_id_params(&config.id)?;
let server = parse_imap_server(&config.server)?;
let inner = Inner::<StreamStd>::connect(&server, &tls, config.starttls, sasl)?;
let (inner, _capability) = Inner::connect(&server, &tls, config.starttls, sasl, auto_id)?;
Ok(Self { inner, account })
}
}
@@ -70,7 +77,7 @@ pub fn parse_imap_server(server: &str) -> Result<Url> {
}
impl Deref for ImapClient {
type Target = Inner<StreamStd>;
type Target = Inner;
fn deref(&self) -> &Self::Target {
&self.inner
+92 -44
View File
@@ -17,7 +17,7 @@
use std::{collections::HashMap, fmt};
use anyhow::Result;
use anyhow::{Result, anyhow};
use clap::Parser;
use comfy_table::{Cell, Row, Table};
use io_imap::types::{
@@ -27,7 +27,7 @@ use io_imap::types::{
use pimalaya_cli::printer::Printer;
use serde::Serialize;
use crate::imap::client::ImapClient;
use crate::{config::ImapIdConfig, imap::client::ImapClient};
/// Get information about the IMAP server.
///
@@ -45,26 +45,11 @@ pub struct ImapIdCommand {
impl ImapIdCommand {
pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> {
let mut params = HashMap::new();
params.extend([
(
IString::try_from("name").unwrap(),
NString::try_from(env!("CARGO_PKG_NAME")).unwrap(),
),
(
IString::try_from("version").unwrap(),
NString::try_from(env!("CARGO_PKG_VERSION")).unwrap(),
),
(
IString::try_from("vendor").unwrap(),
NString::try_from("Pimalaya").unwrap(),
),
(
IString::try_from("support-url").unwrap(),
NString::try_from("https://github.com/pimalaya/himalaya").unwrap(),
),
]);
let mut params: HashMap<IString<'static>, NString<'static>> = HashMap::new();
for key in ["name", "version", "vendor", "support-url"] {
let (k, v) = build_canned_pair(key)?;
params.insert(k, v);
}
if let Some(more) = self.parameter {
params.extend(more);
@@ -93,28 +78,6 @@ impl ImapIdCommand {
}
}
fn parameter_parser(param: &str) -> Result<(IString<'static>, NString<'static>), String> {
let Some((key, val)) = param.split_once(':') else {
return Err(format!("Invalid parameter `{param}`: missing `:`"));
};
let Ok(ikey) = IString::try_from(key.trim()) else {
return Err(format!("Invalid parameter key `{key}`"));
};
let nval = if val.trim().is_empty() {
NString::NIL
} else {
let Ok(nval) = NString::try_from(val.trim()) else {
return Err(format!("Invalid parameter value `{val}` for `{key}`"));
};
nval
};
Ok((ikey.into_static(), nval.into_static()))
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ServerIdTable {
@@ -145,3 +108,88 @@ impl fmt::Display for ServerIdTable {
writeln!(f, "{table}")
}
}
/// Resolves an [`ImapIdConfig`] into the wire-level parameter list
/// passed to the io-imap auth coroutines.
///
/// [`None`] when `auto = false`; otherwise a vec where each entry
/// maps the user-supplied key to either himalaya's canned value
/// (when the user set `true` and the key is well-known) or `NIL`.
/// Unknown keys with `true` log a warning and fall back to `NIL`.
pub fn resolve_auto_id_params(
config: &ImapIdConfig,
) -> Result<Option<Vec<(IString<'static>, NString<'static>)>>> {
if !config.auto {
return Ok(None);
}
let mut params = Vec::with_capacity(config.fields.len());
for (key, &use_canned) in &config.fields {
let ikey = IString::try_from(key.clone())
.map_err(|err| anyhow!("Invalid IMAP ID parameter key `{key}`: {err}"))?
.into_static();
let nval = if use_canned {
match canned_value(key) {
Some(value) => NString::try_from(value)
.map_err(|err| {
anyhow!("Invalid canned IMAP ID value `{value}` for `{key}`: {err}")
})?
.into_static(),
None => {
log::warn!("imap.id.fields.{key} = true: no canned value defined, sending NIL");
NString::NIL
}
}
} else {
NString::NIL
};
params.push((ikey, nval));
}
Ok(Some(params))
}
fn parameter_parser(param: &str) -> Result<(IString<'static>, NString<'static>), String> {
let Some((key, val)) = param.split_once(':') else {
return Err(format!("Invalid parameter `{param}`: missing `:`"));
};
let Ok(ikey) = IString::try_from(key.trim()) else {
return Err(format!("Invalid parameter key `{key}`"));
};
let nval = if val.trim().is_empty() {
NString::NIL
} else {
let Ok(nval) = NString::try_from(val.trim()) else {
return Err(format!("Invalid parameter value `{val}` for `{key}`"));
};
nval
};
Ok((ikey.into_static(), nval.into_static()))
}
fn canned_value(key: &str) -> Option<&'static str> {
match key {
"name" => Some(env!("CARGO_PKG_NAME")),
"version" => Some(env!("CARGO_PKG_VERSION")),
"vendor" => Some("Pimalaya"),
"support-url" => Some("https://github.com/pimalaya/himalaya"),
_ => None,
}
}
fn build_canned_pair(key: &str) -> Result<(IString<'static>, NString<'static>)> {
let ikey = IString::try_from(key)
.map_err(|err| anyhow!("Invalid IMAP ID parameter key `{key}`: {err}"))?
.into_static();
let value =
canned_value(key).ok_or_else(|| anyhow!("No canned IMAP ID value defined for `{key}`"))?;
let nval = NString::try_from(value)
.map_err(|err| anyhow!("Invalid canned IMAP ID value `{value}` for `{key}`: {err}"))?
.into_static();
Ok((ikey, nval))
}
+8 -3
View File
@@ -20,8 +20,9 @@ use std::fmt;
use anyhow::Result;
use clap::{Parser, ValueEnum};
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
use io_jmap::rfc8621::email::{
Email, EmailAddress, EmailComparator, EmailFilter, EmailSortProperty,
use io_jmap::{
rfc8620::filter::Filter,
rfc8621::email::{Email, EmailAddress, EmailComparator, EmailFilter, EmailSortProperty},
};
use pimalaya_cli::printer::Printer;
use serde::Serialize;
@@ -140,7 +141,11 @@ impl JmapEmailQueryCommand {
|| f.subject.is_some()
|| f.body.is_some();
if has_one_filter { Some(f) } else { None }
if has_one_filter {
Some(Filter::from(f))
} else {
None
}
};
let sort = Some(vec![EmailComparator {
+27 -31
View File
@@ -25,16 +25,17 @@
//! its methods directly.
//!
//! Construction picks the first storage backend (`jmap → imap →
//! maildir`) allowed by the `BackendFlag` that is configured on the
//! account. When the account also has SMTP configured, an SMTP slot
//! is registered on the same client so `send_message` works for
//! IMAP/Maildir accounts; JMAP accounts already send via JMAP
//! submission. SMTP connection failures are logged and skipped — the
//! client still opens for reading.
//! maildir → m2dir`) allowed by the [`Backend`] flag that is
//! configured on the account. When the account also has SMTP
//! configured, an SMTP slot is registered on the same client so
//! `send_message` works for IMAP/Maildir accounts; JMAP accounts
//! already send via JMAP submission. SMTP connection failures are
//! logged and skipped: the client still opens for reading.
use std::ops::{Deref, DerefMut};
use anyhow::{Result, bail};
use io_email::client::EmailClientStd;
use crate::{
account::context::Account,
@@ -43,7 +44,7 @@ use crate::{
};
pub struct EmailClient {
inner: io_email::client::EmailClientStd,
inner: EmailClientStd,
pub account: Account,
}
@@ -53,15 +54,12 @@ impl EmailClient {
mut account_config: AccountConfig,
backend: Backend,
) -> Result<Self> {
use io_email::client::EmailClientStd;
let mut inner = EmailClientStd::new();
let mut configured = false;
#[cfg(feature = "jmap")]
if !configured && backend.allows_jmap() {
if let Some(jmap_config) = account_config.jmap.take() {
use io_jmap::client::JmapClientStd;
use pimalaya_stream::tls::Tls;
use crate::jmap::client::{jmap_http_auth, parse_server_url};
@@ -70,10 +68,7 @@ impl EmailClient {
tls.rustls.alpn = vec!["http/1.1".into()];
let http_auth = jmap_http_auth(jmap_config.auth.clone())?;
let url = parse_server_url(&jmap_config.server)?;
let mut client = JmapClientStd::connect(&url, &tls, http_auth)?;
client.session_get(&url)?;
inner = inner.with_jmap(client);
inner = inner.connect_jmap(&url, &tls, http_auth)?;
configured = true;
}
}
@@ -81,17 +76,19 @@ impl EmailClient {
#[cfg(feature = "imap")]
if !configured && backend.allows_imap() {
if let Some(imap_config) = account_config.imap.take() {
use io_imap::client::ImapClientStd;
use pimalaya_stream::{sasl::Sasl, std::stream::StreamStd, tls::Tls};
use io_email::imap::client::ImapClientStd;
use pimalaya_stream::{sasl::Sasl, tls::Tls};
use crate::imap::id::resolve_auto_id_params;
let mut tls: Tls = imap_config.tls.into();
tls.rustls.alpn = vec!["imap".into()];
let sasl: Option<Sasl> = imap_config.sasl.map(Sasl::try_from).transpose()?;
let auto_id = resolve_auto_id_params(&imap_config.id)?;
let server = crate::imap::client::parse_imap_server(&imap_config.server)?;
let client =
ImapClientStd::<StreamStd>::connect(&server, &tls, imap_config.starttls, sasl)?;
inner = inner.with_imap(client);
let imap =
ImapClientStd::connect(&server, &tls, imap_config.starttls, sasl, auto_id)?;
inner = inner.with_imap(imap);
configured = true;
}
}
@@ -99,10 +96,9 @@ impl EmailClient {
#[cfg(feature = "maildir")]
if !configured && backend.allows_maildir() {
if let Some(maildir_config) = account_config.maildir.take() {
use io_maildir::client::MaildirClient;
use io_email::maildir::client::MaildirClient;
let client = MaildirClient::new(maildir_config.root.to_string_lossy().into_owned());
inner = inner.with_maildir(client);
configured = true;
}
@@ -111,10 +107,9 @@ impl EmailClient {
#[cfg(feature = "m2dir")]
if !configured && backend.allows_m2dir() {
if let Some(m2dir_config) = account_config.m2dir.take() {
use io_m2dir::client::M2dirClient;
use io_email::m2dir::client::M2dirClient;
let client = M2dirClient::new(m2dir_config.root.to_string_lossy().into_owned());
inner = inner.with_m2dir(client);
configured = true;
}
@@ -126,22 +121,23 @@ impl EmailClient {
// Register SMTP alongside the storage backend so shared
// `send_message` works for IMAP/Maildir accounts. JMAP already
// sends via submission, but if both are present, SMTP wins
// because storage is registered first.
// sends via submission; the dispatch priority (JMAP → SMTP in
// the send path) keeps that working when both are present.
#[cfg(feature = "smtp")]
if let Some(smtp_config) = account_config.smtp.take() {
use std::net::Ipv4Addr;
use io_smtp::{client::SmtpClientStd, rfc5321::types::ehlo_domain::EhloDomain};
use pimalaya_stream::{sasl::Sasl, std::stream::StreamStd, tls::Tls};
use io_email::smtp::client::SmtpClientStd;
use io_smtp::rfc5321::types::ehlo_domain::EhloDomain;
use pimalaya_stream::{sasl::Sasl, tls::Tls};
let smtp = (|| -> Result<SmtpClientStd<StreamStd>> {
let smtp = (|| -> Result<SmtpClientStd> {
let mut tls: Tls = smtp_config.tls.into();
tls.rustls.alpn = vec!["smtp".into()];
let sasl: Option<Sasl> = smtp_config.sasl.map(Sasl::try_from).transpose()?;
let domain: EhloDomain<'static> = Ipv4Addr::new(127, 0, 0, 1).into();
let server = crate::smtp::client::parse_smtp_server(&smtp_config.server)?;
Ok(SmtpClientStd::<StreamStd>::connect(
Ok(SmtpClientStd::connect(
&server,
&tls,
smtp_config.starttls,
@@ -165,7 +161,7 @@ impl EmailClient {
}
impl Deref for EmailClient {
type Target = io_email::client::EmailClientStd;
type Target = EmailClientStd;
fn deref(&self) -> &Self::Target {
&self.inner
+2 -2
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::Result;
use clap::Parser;
use io_email::flag::Flag;
use io_email::flag::{Flag, FlagOp};
use pimalaya_cli::printer::Printer;
use serde::Serialize;
@@ -46,7 +46,7 @@ impl FlagAddCommand {
let ids: Vec<&str> = self.message_ids.inner.iter().map(String::as_str).collect();
let flags: Vec<Flag> = self.flags.inner.iter().map(Into::into).collect();
client.add_flags(&mailbox, &ids, &flags)?;
client.store_flags(&mailbox, &ids, &flags, FlagOp::Add)?;
let flags: Vec<String> = self.flags.inner.iter().map(ToString::to_string).collect();
printer.out(AddedFlags { flags })
+2 -2
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::Result;
use clap::Parser;
use io_email::flag::Flag;
use io_email::flag::{Flag, FlagOp};
use pimalaya_cli::printer::Printer;
use serde::Serialize;
@@ -46,7 +46,7 @@ impl FlagRemoveCommand {
let ids: Vec<&str> = self.message_ids.inner.iter().map(String::as_str).collect();
let flags: Vec<Flag> = self.flags.inner.iter().map(Into::into).collect();
client.delete_flags(&mailbox, &ids, &flags)?;
client.store_flags(&mailbox, &ids, &flags, FlagOp::Remove)?;
let flags: Vec<String> = self.flags.inner.iter().map(ToString::to_string).collect();
printer.out(RemovedFlags { flags })
+2 -2
View File
@@ -19,7 +19,7 @@ use std::fmt;
use anyhow::Result;
use clap::Parser;
use io_email::flag::Flag;
use io_email::flag::{Flag, FlagOp};
use pimalaya_cli::printer::Printer;
use serde::Serialize;
@@ -46,7 +46,7 @@ impl FlagSetCommand {
let ids: Vec<&str> = self.message_ids.inner.iter().map(String::as_str).collect();
let flags: Vec<Flag> = self.flags.inner.iter().map(Into::into).collect();
client.set_flags(&mailbox, &ids, &flags)?;
client.store_flags(&mailbox, &ids, &flags, FlagOp::Set)?;
let flags: Vec<String> = self.flags.inner.iter().map(ToString::to_string).collect();
printer.out(SetFlags { flags })
+2 -57
View File
@@ -25,8 +25,7 @@
use std::io::{Write, stdout};
use anyhow::{Result, anyhow, bail};
use mail_parser::{Address as ParserAddress, HeaderValue, MessageParser};
use anyhow::Result;
use pimalaya_cli::printer::{Message, Printer};
use crate::shared::client::EmailClient;
@@ -53,63 +52,9 @@ pub fn route(
}
if send {
let (from, to) = extract_envelope(&raw)?;
let to_refs: Vec<&str> = to.iter().map(String::as_str).collect();
client.send_message(raw, &from, &to_refs)?;
client.send_message(raw)?;
return printer.out(Message::new("Message successfully sent"));
}
printer.out(Message::new("Message saved"))
}
/// Extracts the envelope sender from `From:` and envelope recipients
/// from `To:` / `Cc:` / `Bcc:`. Returns an error when `From:` is
/// missing or no recipient header carries at least one address.
pub fn extract_envelope(raw: &[u8]) -> Result<(String, Vec<String>)> {
let parsed = MessageParser::default()
.parse(raw)
.ok_or_else(|| anyhow!("failed to parse outgoing message"))?;
let mut from_emails = Vec::new();
if let Some(HeaderValue::Address(addr)) = parsed.header("From").cloned() {
collect_emails(addr, &mut from_emails);
}
let from = from_emails
.into_iter()
.next()
.ok_or_else(|| anyhow!("outgoing message is missing a `From:` header"))?;
let mut to = Vec::new();
for name in ["To", "Cc", "Bcc"] {
if let Some(HeaderValue::Address(addr)) = parsed.header(name).cloned() {
collect_emails(addr, &mut to);
}
}
if to.is_empty() {
bail!("outgoing message has no recipients (`To:` / `Cc:` / `Bcc:`)");
}
Ok((from, to))
}
fn collect_emails(addr: ParserAddress<'_>, out: &mut Vec<String>) {
match addr {
ParserAddress::List(list) => {
for a in list {
if let Some(email) = a.address {
out.push(email.into_owned());
}
}
}
ParserAddress::Group(groups) => {
for g in groups {
for a in g.addresses {
if let Some(email) = a.address {
out.push(email.into_owned());
}
}
}
}
}
}
+2 -4
View File
@@ -24,7 +24,7 @@ use anyhow::Result;
use clap::Parser;
use pimalaya_cli::printer::{Message, Printer};
use crate::shared::{client::EmailClient, messages::output::extract_envelope};
use crate::shared::client::EmailClient;
/// Send a message via the active account.
///
@@ -68,9 +68,7 @@ impl MessageSendCommand {
.into_bytes()
};
let (from, to) = extract_envelope(&raw)?;
let to_refs: Vec<&str> = to.iter().map(String::as_str).collect();
client.send_message(raw, &from, &to_refs)?;
client.send_message(raw)?;
printer.out(Message::new("Message successfully sent"))
}
}
+4 -4
View File
@@ -32,13 +32,13 @@ use std::{
use anyhow::{Result, anyhow};
use io_smtp::{client::SmtpClientStd as Inner, rfc5321::types::ehlo_domain::EhloDomain};
use pimalaya_config::toml::TomlConfig;
use pimalaya_stream::{sasl::Sasl, std::stream::StreamStd, tls::Tls};
use pimalaya_stream::{sasl::Sasl, tls::Tls};
use url::Url;
use crate::{account::context::Account, cli::load_or_wizard, config::SmtpConfig};
pub struct SmtpClient {
inner: Inner<StreamStd>,
inner: Inner,
#[allow(dead_code)]
pub account: Account,
}
@@ -52,7 +52,7 @@ impl SmtpClient {
let sasl: Option<Sasl> = config.sasl.map(Sasl::try_from).transpose()?;
let domain: EhloDomain<'static> = Ipv4Addr::new(127, 0, 0, 1).into();
let server = parse_smtp_server(&config.server)?;
let inner = Inner::<StreamStd>::connect(&server, &tls, config.starttls, domain, sasl)?;
let inner = Inner::connect(&server, &tls, config.starttls, domain, sasl)?;
Ok(Self { inner, account })
}
}
@@ -74,7 +74,7 @@ pub fn parse_smtp_server(server: &str) -> Result<Url> {
}
impl Deref for SmtpClient {
type Target = Inner<StreamStd>;
type Target = Inner;
fn deref(&self) -> &Self::Target {
&self.inner
+1
View File
@@ -49,6 +49,7 @@ pub fn imap_to_config(w: WizardImapConfig) -> Result<ImapConfig> {
tls: Default::default(),
starttls,
sasl,
id: Default::default(),
})
}
+4 -4
View File
@@ -20,10 +20,6 @@
//! ISPDB in series (secure variants only); each probe owns its own
//! spinner.
use io_discovery::autoconfig::{
client::{DiscoveryAutoconfigClientStd, DiscoveryAutoconfigClientStdError},
types::{Autoconfig, SecurityType, Server, ServerType},
};
use log::debug;
use pimalaya_cli::{
spinner::Spinner,
@@ -32,6 +28,10 @@ use pimalaya_cli::{
smtp::{Encryption as SmtpEncryption, SmtpAuth, SmtpSecret, WizardSmtpConfig},
},
};
use pimconf::autoconfig::{
client::{DiscoveryAutoconfigClientStd, DiscoveryAutoconfigClientStdError},
types::{Autoconfig, SecurityType, Server, ServerType},
};
use crate::wizard::discover::{DiscoveryResult, discovery_resolver, discovery_tls};
+1 -1
View File
@@ -17,7 +17,6 @@
//! PACC step of the wizard's discovery chain.
use io_discovery::pacc::{client::DiscoveryPaccClientStd, types::PaccConfig};
use log::debug;
use pimalaya_cli::{
spinner::Spinner,
@@ -27,6 +26,7 @@ use pimalaya_cli::{
smtp::{Encryption as SmtpEncryption, SmtpAuth, SmtpSecret, WizardSmtpConfig},
},
};
use pimconf::pacc::{client::DiscoveryPaccClientStd, types::PaccConfig};
use crate::wizard::discover::{DiscoveryResult, discovery_resolver, discovery_tls};
+4 -4
View File
@@ -23,10 +23,6 @@
//! SMTP: from `_submission`; the encryption is inferred from the
//! record's port (465 → implicit TLS, otherwise StartTls).
use io_discovery::rfc6186::{
client::DiscoverySrvClientStd,
types::{SrvReport, SrvService},
};
use log::debug;
use pimalaya_cli::{
spinner::Spinner,
@@ -35,6 +31,10 @@ use pimalaya_cli::{
smtp::{Encryption as SmtpEncryption, SmtpAuth, SmtpSecret, WizardSmtpConfig},
},
};
use pimconf::rfc6186::{
client::DiscoverySrvClientStd,
types::{SrvReport, SrvService},
};
use crate::wizard::discover::{DiscoveryResult, discovery_resolver};