mirror of
https://github.com/chatmail/core.git
synced 2026-04-21 23:46:31 +03:00
feat: add support for IMAP METADATA
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -224,9 +224,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-imap"
|
name = "async-imap"
|
||||||
version = "0.9.5"
|
version = "0.9.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9d69fc1499878158750f644c4eb46aff55bb9d32d77e3dc4aecf8308d5c3ba6"
|
checksum = "98892ebee4c05fc66757e600a7466f0d9bfcde338f645d64add323789f26cb36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel 2.1.1",
|
"async-channel 2.1.1",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ ratelimit = { path = "./deltachat-ratelimit" }
|
|||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
async-channel = "2.0.0"
|
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-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
||||||
async-smtp = { version = "0.9", 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"] }
|
async_zip = { version = "0.0.12", default-features = false, features = ["deflate", "fs"] }
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use crate::constants::DC_VERSION_STR;
|
|||||||
use crate::contact::Contact;
|
use crate::contact::Contact;
|
||||||
use crate::debug_logging::DebugLogging;
|
use crate::debug_logging::DebugLogging;
|
||||||
use crate::events::{Event, EventEmitter, EventType, Events};
|
use crate::events::{Event, EventEmitter, EventType, Events};
|
||||||
|
use crate::imap::ServerMetadata;
|
||||||
use crate::key::{load_self_public_key, DcKey as _};
|
use crate::key::{load_self_public_key, DcKey as _};
|
||||||
use crate::login_param::LoginParam;
|
use crate::login_param::LoginParam;
|
||||||
use crate::message::{self, MessageState, MsgId};
|
use crate::message::{self, MessageState, MsgId};
|
||||||
@@ -224,6 +225,9 @@ pub struct InnerContext {
|
|||||||
/// <https://datatracker.ietf.org/doc/html/rfc2971>
|
/// <https://datatracker.ietf.org/doc/html/rfc2971>
|
||||||
pub(crate) server_id: RwLock<Option<HashMap<String, String>>>,
|
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>>,
|
pub(crate) last_full_folder_scan: Mutex<Option<Instant>>,
|
||||||
|
|
||||||
/// ID for this `Context` in the current process.
|
/// ID for this `Context` in the current process.
|
||||||
@@ -384,6 +388,7 @@ impl Context {
|
|||||||
resync_request: AtomicBool::new(false),
|
resync_request: AtomicBool::new(false),
|
||||||
new_msgs_notify,
|
new_msgs_notify,
|
||||||
server_id: RwLock::new(None),
|
server_id: RwLock::new(None),
|
||||||
|
metadata: RwLock::new(None),
|
||||||
creation_time: std::time::SystemTime::now(),
|
creation_time: std::time::SystemTime::now(),
|
||||||
last_full_folder_scan: Mutex::new(None),
|
last_full_folder_scan: Mutex::new(None),
|
||||||
last_error: std::sync::RwLock::new("".to_string()),
|
last_error: std::sync::RwLock::new("".to_string()),
|
||||||
@@ -669,6 +674,16 @@ impl Context {
|
|||||||
res.insert("imap_server_id", format!("{server_id:?}"));
|
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("secondary_addrs", secondary_addrs);
|
||||||
res.insert(
|
res.insert(
|
||||||
"fetch_existing_msgs",
|
"fetch_existing_msgs",
|
||||||
|
|||||||
55
src/imap.rs
55
src/imap.rs
@@ -118,6 +118,17 @@ struct OAuth2 {
|
|||||||
access_token: String,
|
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 {
|
impl async_imap::Authenticator for OAuth2 {
|
||||||
type Response = String;
|
type Response = String;
|
||||||
|
|
||||||
@@ -1636,6 +1647,50 @@ impl Imap {
|
|||||||
|
|
||||||
Ok((last_uid, received_msgs))
|
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 {
|
impl Session {
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ pub(crate) struct Capabilities {
|
|||||||
/// <https://tools.ietf.org/html/rfc7162>
|
/// <https://tools.ietf.org/html/rfc7162>
|
||||||
pub can_condstore: bool,
|
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.
|
/// Server ID if the server supports ID capability.
|
||||||
pub server_id: Option<HashMap<String, String>>,
|
pub server_id: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ async fn determine_capabilities(
|
|||||||
can_move: caps.has_str("MOVE"),
|
can_move: caps.has_str("MOVE"),
|
||||||
can_check_quota: caps.has_str("QUOTA"),
|
can_check_quota: caps.has_str("QUOTA"),
|
||||||
can_condstore: caps.has_str("CONDSTORE"),
|
can_condstore: caps.has_str("CONDSTORE"),
|
||||||
|
can_metadata: caps.has_str("METADATA"),
|
||||||
server_id,
|
server_id,
|
||||||
};
|
};
|
||||||
Ok(capabilities)
|
Ok(capabilities)
|
||||||
|
|||||||
@@ -64,4 +64,8 @@ impl Session {
|
|||||||
pub fn can_condstore(&self) -> bool {
|
pub fn can_condstore(&self) -> bool {
|
||||||
self.capabilities.can_condstore
|
self.capabilities.can_condstore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn can_metadata(&self) -> bool {
|
||||||
|
self.capabilities.can_metadata
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,6 +460,10 @@ async fn inbox_loop(
|
|||||||
warn!(ctx, "Failed to download messages: {:#}", err);
|
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;
|
fetch_idle(&ctx, &mut connection, FolderMeaning::Inbox).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user