diff --git a/Cargo.lock b/Cargo.lock index 2c973e6ec..c39233160 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,9 +224,9 @@ dependencies = [ [[package]] name = "async-imap" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d69fc1499878158750f644c4eb46aff55bb9d32d77e3dc4aecf8308d5c3ba6" +checksum = "98892ebee4c05fc66757e600a7466f0d9bfcde338f645d64add323789f26cb36" dependencies = [ "async-channel 2.1.1", "base64 0.21.5", diff --git a/Cargo.toml b/Cargo.toml index f438f4378..37d47bf14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ ratelimit = { path = "./deltachat-ratelimit" } anyhow = "1" async-channel = "2.0.0" -async-imap = { version = "0.9.5", default-features = false, features = ["runtime-tokio"] } +async-imap = { version = "0.9.7", default-features = false, features = ["runtime-tokio"] } 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.12", default-features = false, features = ["deflate", "fs"] } diff --git a/src/context.rs b/src/context.rs index ebadb60bf..cfe7a9118 100644 --- a/src/context.rs +++ b/src/context.rs @@ -19,6 +19,7 @@ use crate::constants::DC_VERSION_STR; use crate::contact::Contact; use crate::debug_logging::DebugLogging; use crate::events::{Event, EventEmitter, EventType, Events}; +use crate::imap::ServerMetadata; use crate::key::{load_self_public_key, DcKey as _}; use crate::login_param::LoginParam; use crate::message::{self, MessageState, MsgId}; @@ -224,6 +225,9 @@ pub struct InnerContext { /// pub(crate) server_id: RwLock>>, + /// IMAP METADATA. + pub(crate) metadata: RwLock>, + pub(crate) last_full_folder_scan: Mutex>, /// ID for this `Context` in the current process. @@ -384,6 +388,7 @@ impl Context { resync_request: AtomicBool::new(false), new_msgs_notify, server_id: RwLock::new(None), + metadata: RwLock::new(None), creation_time: std::time::SystemTime::now(), last_full_folder_scan: Mutex::new(None), last_error: std::sync::RwLock::new("".to_string()), @@ -669,6 +674,16 @@ impl Context { res.insert("imap_server_id", format!("{server_id:?}")); } + if let Some(metadata) = &*self.metadata.read().await { + if let Some(comment) = &metadata.comment { + res.insert("imap_server_comment", format!("{comment:?}")); + } + + if let Some(admin) = &metadata.admin { + res.insert("imap_server_admin", format!("{admin:?}")); + } + } + res.insert("secondary_addrs", secondary_addrs); res.insert( "fetch_existing_msgs", diff --git a/src/imap.rs b/src/imap.rs index b637c2270..760661d8f 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -118,6 +118,17 @@ struct OAuth2 { access_token: String, } +#[derive(Debug)] +pub(crate) struct ServerMetadata { + /// IMAP METADATA `/shared/comment` as defined in + /// . + pub comment: Option, + + /// IMAP METADATA `/shared/admin` as defined in + /// . + pub admin: Option, +} + impl async_imap::Authenticator for OAuth2 { type Response = String; @@ -1636,6 +1647,50 @@ impl Imap { Ok((last_uid, received_msgs)) } + + /// Retrieves server metadata if it is supported. + /// + /// We get [`/shared/comment`](https://www.rfc-editor.org/rfc/rfc5464#section-6.2.1) + /// and [`/shared/admin`](https://www.rfc-editor.org/rfc/rfc5464#section-6.2.2) + /// metadata. + pub(crate) async fn fetch_metadata(&mut self, context: &Context) -> Result<()> { + let session = self.session.as_mut().context("no session")?; + if !session.can_metadata() { + return Ok(()); + } + + let mut lock = context.metadata.write().await; + if (*lock).is_some() { + return Ok(()); + } + + info!( + context, + "Server supports metadata, retrieving server comment and admin contact." + ); + + let mut comment = None; + let mut admin = None; + + let mailbox = ""; + let options = ""; + let metadata = session + .get_metadata(mailbox, options, "(/shared/comment /shared/admin)") + .await?; + for m in metadata { + match m.entry.as_ref() { + "/shared/comment" => { + comment = m.value; + } + "/shared/admin" => { + admin = m.value; + } + _ => {} + } + } + *lock = Some(ServerMetadata { comment, admin }); + Ok(()) + } } impl Session { diff --git a/src/imap/capabilities.rs b/src/imap/capabilities.rs index 14385c088..ced5def49 100644 --- a/src/imap/capabilities.rs +++ b/src/imap/capabilities.rs @@ -21,6 +21,10 @@ pub(crate) struct Capabilities { /// pub can_condstore: bool, + /// True if the server has METADATA capability as defined in + /// + pub can_metadata: bool, + /// Server ID if the server supports ID capability. pub server_id: Option>, } diff --git a/src/imap/client.rs b/src/imap/client.rs index 6c7ca397a..5d728e5fb 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -59,6 +59,7 @@ async fn determine_capabilities( can_move: caps.has_str("MOVE"), can_check_quota: caps.has_str("QUOTA"), can_condstore: caps.has_str("CONDSTORE"), + can_metadata: caps.has_str("METADATA"), server_id, }; Ok(capabilities) diff --git a/src/imap/session.rs b/src/imap/session.rs index ced292591..1e2b5fc7a 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -64,4 +64,8 @@ impl Session { pub fn can_condstore(&self) -> bool { self.capabilities.can_condstore } + + pub fn can_metadata(&self) -> bool { + self.capabilities.can_metadata + } } diff --git a/src/scheduler.rs b/src/scheduler.rs index ff4a96c22..7ad7b3c96 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -460,6 +460,10 @@ async fn inbox_loop( warn!(ctx, "Failed to download messages: {:#}", err); } + if let Err(err) = connection.fetch_metadata(&ctx).await { + warn!(ctx, "Failed to fetch metadata: {err:#}."); + } + fetch_idle(&ctx, &mut connection, FolderMeaning::Inbox).await; } };