From 3b65c259550ddfc943efa968836fab093388e6ff Mon Sep 17 00:00:00 2001 From: link2xt Date: Fri, 17 Jul 2026 19:53:29 +0000 Subject: [PATCH 1/2] chore: update async-imap to 0.11.3 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 125ac02baf..91a177f00b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,9 +271,9 @@ dependencies = [ [[package]] name = "async-imap" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78dceaba06f029d8f4d7df20addd4b7370a30206e3926267ecda2915b0f3f66" +checksum = "9a6728e0f7931b36d725ac234fcb02539e9f7888dbeaaa8a18d9ea5792181570" dependencies = [ "async-channel 2.5.0", "async-compression", diff --git a/Cargo.toml b/Cargo.toml index e4eba3b68d..edc6944a45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ ratelimit = { path = "./deltachat-ratelimit" } anyhow = { workspace = true } async-broadcast = "0.7.2" async-channel = { workspace = true } -async-imap = { version = "0.11.1", default-features = false, features = ["runtime-tokio", "compress"] } +async-imap = { version = "0.11.3", default-features = false, features = ["runtime-tokio", "compress"] } async-native-tls = { version = "0.6", default-features = false, features = ["runtime-tokio"] } async-smtp = { version = "0.10.2", default-features = false, features = ["runtime-tokio"] } async_zip = { version = "0.0.18", default-features = false, features = ["deflate", "tokio-fs"] } From 1448655cac96a10b7a4915d080ade2f68dae6f09 Mon Sep 17 00:00:00 2001 From: link2xt Date: Thu, 16 Jul 2026 19:53:24 +0000 Subject: [PATCH 2/2] feat(imap): use CAPABILITY response code if LOGIN command returns it This saves one round trip during connection establishment if the server returns capabilities in the LOGIN command response already. --- src/imap.rs | 23 ++++++++++++++++------- src/imap/client.rs | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/imap.rs b/src/imap.rs index 29acf6e8c5..fd455e1933 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -387,19 +387,28 @@ impl Imap { user: imap_user.into(), access_token: token, }; - client.authenticate("XOAUTH2", auth).await + client + .authenticate("XOAUTH2", auth) + .await + .map(|session| (session, None)) } else { info!(context, "Logging into IMAP server with LOGIN."); client.login(imap_user, imap_pw).await }; match login_res { - Ok(mut session) => { - let capabilities = match determine_capabilities(&mut session).await { - Ok(capabilities) => capabilities, - Err(err) => { - warn!(context, "Failed to determine capabilities: {err:#}."); - continue 'candidate; + Ok((mut session, login_capabilities_opt)) => { + let capabilities = if let Some(login_capabilities) = login_capabilities_opt { + login_capabilities + } else { + // OK response did not contain the CAPABILITY response code. + // Request capabilities explicitly. + match determine_capabilities(&mut session).await { + Ok(capabilities) => capabilities, + Err(err) => { + warn!(context, "Failed to determine capabilities: {err:#}."); + continue 'candidate; + } } }; let resync_request_sender = self.resync_request_sender.clone(); diff --git a/src/imap/client.rs b/src/imap/client.rs index fff8398437..cb758f46d2 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -53,10 +53,24 @@ fn alpn(port: u16) -> &'static str { pub(crate) async fn determine_capabilities( session: &mut ImapSession>, ) -> Result { - let caps = session + let imap_capabilities = session .capabilities() .await .context("CAPABILITY command error")?; + identify_server(session, imap_capabilities).await +} + +/// Identifies the server by sending ID command if it is supported. +/// +/// Some IMAP servers require sending this command, +/// see , +/// +/// and +/// for details. +pub(crate) async fn identify_server( + session: &mut ImapSession>, + caps: async_imap::types::Capabilities, +) -> Result { let server_id = if caps.has_str("ID") { session.id([("name", Some("Delta Chat"))]).await? } else { @@ -82,18 +96,30 @@ impl Client { } } + /// Logs in with the LOGIN command. + /// + /// If the server supports [ID extension], sends ID command + /// and records the server response in the [`Capabilities`] structure. + /// + /// [ID extension]: https://datatracker.ietf.org/doc/rfc2971/ pub(crate) async fn login( self, username: &str, password: &str, - ) -> Result>> { + ) -> Result<(ImapSession>, Option)> { let Client { inner, .. } = self; - let session = inner - .login(username, password) + let (mut session, login_capabilities_opt) = inner + .login_with_capabilities(username, password) .await .map_err(|(err, _client)| err)?; - Ok(session) + + let capabilities = if let Some(login_capabilities) = login_capabilities_opt { + Some(identify_server(&mut session, login_capabilities).await?) + } else { + None + }; + Ok((session, capabilities)) } pub(crate) async fn authenticate(