diff --git a/Cargo.lock b/Cargo.lock index b11f2a8870..499353606c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -282,15 +282,16 @@ dependencies = [ "futures-io", "memchr", "pin-project-lite", + "tokio", ] [[package]] name = "async-imap" version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2162818f7b394e342a6591864bfc960824ed13e3f6609fee0d19e770ebcd99e1" +source = "git+https://github.com/async-email/async-imap.git?branch=link2xt/imap-compress#8fd4d3cc490d960835f21d40668bf099fad4f483" dependencies = [ "async-channel 2.3.1", + "async-compression", "base64 0.21.7", "bytes", "chrono", @@ -299,6 +300,7 @@ dependencies = [ "log", "nom", "once_cell", + "pin-project", "pin-utils", "self_cell", "stop-token", diff --git a/Cargo.toml b/Cargo.toml index 8dc3fe888f..8ad7fe937b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ ratelimit = { path = "./deltachat-ratelimit" } anyhow = { workspace = true } async-broadcast = "0.7.1" async-channel = { workspace = true } -async-imap = { version = "0.10.1", default-features = false, features = ["runtime-tokio"] } +async-imap = { git = "https://github.com/async-email/async-imap.git", default-features = false, features = ["runtime-tokio", "compress"], branch = "link2xt/imap-compress" } async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] } async-smtp = { version = "0.9", default-features = false, features = ["runtime-tokio"] } async_zip = { version = "0.0.17", default-features = false, features = ["deflate", "tokio-fs"] } diff --git a/src/imap.rs b/src/imap.rs index 40f96b101d..0a74b8b6b5 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -39,6 +39,7 @@ use crate::login_param::{ use crate::message::{self, Message, MessageState, MessengerMessage, MsgId, Viewtype}; use crate::mimeparser; use crate::net::proxy::ProxyConfig; +use crate::net::session::SessionStream; use crate::oauth2::get_oauth2_access_token; use crate::receive_imf::{ from_field_to_contact_id, get_prefetch_parent_message, receive_imf_inner, ReceivedMsg, @@ -55,7 +56,7 @@ pub mod scan_folders; pub mod select_folder; pub(crate) mod session; -use client::Client; +use client::{determine_capabilities, Client}; use mailparse::SingleInfo; use session::Session; @@ -376,7 +377,23 @@ impl Imap { }; match login_res { - Ok(session) => { + Ok(mut session) => { + let capabilities = determine_capabilities(&mut session).await?; + + let session = if capabilities.can_compress { + info!(context, "Enabling IMAP compression."); + let compressed_session = session + .compress(|s| { + let session_stream: Box = Box::new(s); + session_stream + }) + .await + .context("Failed to enable IMAP compression")?; + Session::new(compressed_session, capabilities) + } else { + Session::new(session, capabilities) + }; + // Store server ID in the context to display in account info. let mut lock = context.server_id.write().await; lock.clone_from(&session.capabilities.server_id); diff --git a/src/imap/capabilities.rs b/src/imap/capabilities.rs index 160b1e138d..3a216be318 100644 --- a/src/imap/capabilities.rs +++ b/src/imap/capabilities.rs @@ -25,6 +25,10 @@ pub(crate) struct Capabilities { /// pub can_metadata: bool, + /// True if the server has COMPRESS=DEFLATE capability as defined in + /// + pub can_compress: bool, + /// True if the server supports XDELTAPUSH capability. /// This capability means setting /private/devicetoken IMAP METADATA /// on the INBOX results in new mail notifications diff --git a/src/imap/client.rs b/src/imap/client.rs index a52d337dcb..ea23c9fb6d 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -7,7 +7,6 @@ use async_imap::Session as ImapSession; use tokio::io::BufWriter; use super::capabilities::Capabilities; -use super::session::Session; use crate::context::Context; use crate::login_param::{ConnectionCandidate, ConnectionSecurity}; use crate::net::dns::{lookup_host_with_cache, update_connect_timestamp}; @@ -51,7 +50,7 @@ fn alpn(port: u16) -> &'static [&'static str] { /// Determine server capabilities. /// /// If server supports ID capability, send our client ID. -async fn determine_capabilities( +pub(crate) async fn determine_capabilities( session: &mut ImapSession>, ) -> Result { let caps = session @@ -69,6 +68,7 @@ async fn determine_capabilities( can_check_quota: caps.has_str("QUOTA"), can_condstore: caps.has_str("CONDSTORE"), can_metadata: caps.has_str("METADATA"), + can_compress: caps.has_str("COMPRESS=DEFLATE"), can_push: caps.has_str("XDELTAPUSH"), is_chatmail: caps.has_str("XCHATMAIL"), server_id, @@ -83,28 +83,31 @@ impl Client { } } - pub(crate) async fn login(self, username: &str, password: &str) -> Result { + pub(crate) async fn login( + self, + username: &str, + password: &str, + ) -> Result>> { let Client { inner, .. } = self; - let mut session = inner + + let session = inner .login(username, password) .await .map_err(|(err, _client)| err)?; - let capabilities = determine_capabilities(&mut session).await?; - Ok(Session::new(session, capabilities)) + Ok(session) } pub(crate) async fn authenticate( self, auth_type: &str, authenticator: impl async_imap::Authenticator, - ) -> Result { + ) -> Result>> { let Client { inner, .. } = self; - let mut session = inner + let session = inner .authenticate(auth_type, authenticator) .await .map_err(|(err, _client)| err)?; - let capabilities = determine_capabilities(&mut session).await?; - Ok(Session::new(session, capabilities)) + Ok(session) } async fn connection_attempt( diff --git a/src/net/session.rs b/src/net/session.rs index b6df40ea59..4549d7db13 100644 --- a/src/net/session.rs +++ b/src/net/session.rs @@ -53,6 +53,11 @@ impl SessionStream for shadowsocks::ProxyClientStream { self.get_mut().set_read_timeout(timeout) } } +impl SessionStream for async_imap::DeflateStream { + fn set_read_timeout(&mut self, timeout: Option) { + self.with_lock(|mut stream| stream.set_read_timeout(timeout)) + } +} /// Session stream with a read buffer. pub(crate) trait SessionBufStream: SessionStream + AsyncBufRead {}