feat: add support for IMAP METADATA

This commit is contained in:
link2xt
2024-01-25 00:17:37 +00:00
parent 06f1fe18d6
commit cb3f03fd39
8 changed files with 86 additions and 3 deletions

View File

@@ -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 {
/// <https://datatracker.ietf.org/doc/html/rfc2971>
pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
/// IMAP METADATA.
pub(crate) metadata: RwLock<Option<ServerMetadata>>,
pub(crate) last_full_folder_scan: Mutex<Option<Instant>>,
/// 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",

View File

@@ -118,6 +118,17 @@ struct OAuth2 {
access_token: String,
}
#[derive(Debug)]
pub(crate) struct ServerMetadata {
/// IMAP METADATA `/shared/comment` as defined in
/// <https://www.rfc-editor.org/rfc/rfc5464#section-6.2.1>.
pub comment: Option<String>,
/// IMAP METADATA `/shared/admin` as defined in
/// <https://www.rfc-editor.org/rfc/rfc5464#section-6.2.2>.
pub admin: Option<String>,
}
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 {

View File

@@ -21,6 +21,10 @@ pub(crate) struct Capabilities {
/// <https://tools.ietf.org/html/rfc7162>
pub can_condstore: bool,
/// True if the server has METADATA capability as defined in
/// <https://tools.ietf.org/html/rfc5464>
pub can_metadata: bool,
/// Server ID if the server supports ID capability.
pub server_id: Option<HashMap<String, String>>,
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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;
}
};