diff --git a/Cargo.lock b/Cargo.lock index d5e41aa835..3f2285cdd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,24 +273,26 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998282f8f49ccd6116b0ed8a4de0fbd3151697920e7c7533416d6e25e76434a7" +checksum = "e26a9844c659a2a293d239c7910b752f8487fe122c6c8bd1659bf85a6507c302" dependencies = [ "flate2", "futures-core", "futures-io", "memchr", "pin-project-lite", + "tokio", ] [[package]] name = "async-imap" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2162818f7b394e342a6591864bfc960824ed13e3f6609fee0d19e770ebcd99e1" +checksum = "5488cd022c3c7bc41a9b34a540d9ac0d9c5cd42fdb106a67616521b7592d5b4e" dependencies = [ "async-channel 2.3.1", + "async-compression", "base64 0.21.7", "bytes", "chrono", @@ -299,6 +301,7 @@ dependencies = [ "log", "nom", "once_cell", + "pin-project", "pin-utils", "self_cell", "stop-token", diff --git a/Cargo.toml b/Cargo.toml index 98f5da7b30..85249c0f75 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 = { version = "0.10.2", default-features = false, features = ["runtime-tokio", "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..8c56f2bbcf 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.get_mut().set_read_timeout(timeout) + } +} /// Session stream with a read buffer. pub(crate) trait SessionBufStream: SessionStream + AsyncBufRead {}