diff --git a/src/imap/capability.rs b/src/imap/capability.rs index 21b95cb..d88673c 100644 --- a/src/imap/capability.rs +++ b/src/imap/capability.rs @@ -48,15 +48,21 @@ impl ServerCapability { } } -enum ClientStatus { +pub enum ClientStatus { NotSupportedByServer, Disabled, Enabled, } +impl ClientStatus { + pub fn is_enabled(&self) -> bool { + matches!(self, Self::Enabled) + } +} + pub struct ClientCapability { - condstore: ClientStatus, - utf8kind: Option, + pub condstore: ClientStatus, + pub utf8kind: Option, } impl ClientCapability { diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index 8e5b2e6..954e758 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -292,7 +292,7 @@ impl<'a> AuthenticatedContext<'a> { } }; - let view = MailboxView::new(mb).await; + let view = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await; let mut ret_attrs = vec![]; for attr in attributes.iter() { @@ -439,7 +439,7 @@ impl<'a> AuthenticatedContext<'a> { }; tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.selected"); - let mb = MailboxView::new(mb).await; + let mb = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await; let data = mb.summary()?; Ok(( @@ -474,7 +474,7 @@ impl<'a> AuthenticatedContext<'a> { }; tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.examined"); - let mb = MailboxView::new(mb).await; + let mb = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await; let data = mb.summary()?; Ok(( diff --git a/src/imap/command/examined.rs b/src/imap/command/examined.rs index 3dd11e2..4767340 100644 --- a/src/imap/command/examined.rs +++ b/src/imap/command/examined.rs @@ -127,7 +127,7 @@ impl<'a> ExaminedContext<'a> { } pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> { - self.mailbox.0.mailbox.force_sync().await?; + self.mailbox.internal.mailbox.force_sync().await?; let updates = self.mailbox.update().await?; Ok(( diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs index 35c3eb4..c38c5d3 100644 --- a/src/imap/command/selected.rs +++ b/src/imap/command/selected.rs @@ -152,7 +152,7 @@ impl<'a> SelectedContext<'a> { } pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> { - self.mailbox.0.mailbox.force_sync().await?; + self.mailbox.internal.mailbox.force_sync().await?; let updates = self.mailbox.update().await?; Ok(( diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index e9f85a6..046acfa 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -8,7 +8,7 @@ use futures::stream::{FuturesOrdered, StreamExt}; use imap_codec::imap_types::core::Charset; use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItem}; use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType}; -use imap_codec::imap_types::response::{Code, Data, Status}; +use imap_codec::imap_types::response::{Code, CodeOther, Data, Status}; use imap_codec::imap_types::search::SearchKey; use imap_codec::imap_types::sequence::SequenceSet; @@ -39,12 +39,18 @@ const DEFAULT_FLAGS: [Flag; 5] = [ /// To do this, it keeps a variable `known_state` that corresponds to /// what the client knows, and produces IMAP messages to be sent to the /// client that go along updates to `known_state`. -pub struct MailboxView(pub FrozenMailbox); +pub struct MailboxView { + pub internal: FrozenMailbox, + pub is_condstore: bool, +} impl MailboxView { /// Creates a new IMAP view into a mailbox. - pub async fn new(mailbox: Arc) -> Self { - Self(mailbox.frozen().await) + pub async fn new(mailbox: Arc, is_cond: bool) -> Self { + Self { + internal: mailbox.frozen().await, + is_condstore: is_cond, + } } /// Create an updated view, useful to make a diff @@ -54,8 +60,8 @@ impl MailboxView { /// This does NOT trigger a sync, it bases itself on what is currently /// loaded in RAM by Bayou. pub async fn update(&mut self) -> Result>> { - let old_snapshot = self.0.update().await; - let new_snapshot = &self.0.snapshot; + let old_snapshot = self.internal.update().await; + let new_snapshot = &self.internal.snapshot; let mut data = Vec::::new(); @@ -130,6 +136,9 @@ impl MailboxView { data.extend(self.flags_status()?.into_iter()); data.push(self.uidvalidity_status()?); data.push(self.uidnext_status()?); + if self.is_condstore { + data.push(self.highestmodseq_status()?); + } /*self.unseen_first_status()? .map(|unseen_status| data.push(unseen_status));*/ @@ -144,7 +153,7 @@ impl MailboxView { flags: &[Flag<'a>], is_uid_store: &bool, ) -> Result>> { - self.0.sync().await?; + self.internal.sync().await?; let flags = flags.iter().map(|x| x.to_string()).collect::>(); @@ -153,13 +162,13 @@ impl MailboxView { for mi in mails.iter() { match kind { StoreType::Add => { - self.0.mailbox.add_flags(mi.uuid, &flags[..]).await?; + self.internal.mailbox.add_flags(mi.uuid, &flags[..]).await?; } StoreType::Remove => { - self.0.mailbox.del_flags(mi.uuid, &flags[..]).await?; + self.internal.mailbox.del_flags(mi.uuid, &flags[..]).await?; } StoreType::Replace => { - self.0.mailbox.set_flags(mi.uuid, &flags[..]).await?; + self.internal.mailbox.set_flags(mi.uuid, &flags[..]).await?; } } } @@ -169,8 +178,8 @@ impl MailboxView { } pub async fn expunge(&mut self) -> Result>> { - self.0.sync().await?; - let state = self.0.peek().await; + self.internal.sync().await?; + let state = self.internal.peek().await; let deleted_flag = Flag::Deleted.to_string(); let msgs = state @@ -180,7 +189,7 @@ impl MailboxView { .map(|(uuid, _)| *uuid); for msg in msgs { - self.0.mailbox.delete(msg).await?; + self.internal.mailbox.delete(msg).await?; } self.update().await @@ -197,7 +206,7 @@ impl MailboxView { let mut new_uuids = vec![]; for mi in mails.iter() { - new_uuids.push(to.copy_from(&self.0.mailbox, mi.uuid).await?); + new_uuids.push(to.copy_from(&self.internal.mailbox, mi.uuid).await?); } let mut ret = vec![]; @@ -224,7 +233,7 @@ impl MailboxView { let mails = idx.fetch(sequence_set, *is_uid_copy)?; for mi in mails.iter() { - to.move_from(&self.0.mailbox, mi.uuid).await?; + to.move_from(&self.internal.mailbox, mi.uuid).await?; } let mut ret = vec![]; @@ -268,7 +277,7 @@ impl MailboxView { .iter() .map(|midx| midx.uuid) .collect::>(); - let query_result = self.0.query(&uuids, query_scope).fetch().await?; + let query_result = self.internal.query(&uuids, query_scope).fetch().await?; // [3/6] Derive an IMAP-specific view from the results, apply the filters let views = query_result @@ -294,7 +303,7 @@ impl MailboxView { .filter(|(_mv, seen)| matches!(seen, SeenFlag::MustAdd)) .map(|(mv, _seen)| async move { let seen_flag = Flag::Seen.to_string(); - self.0 + self.internal .mailbox .add_flags(*mv.query_result.uuid(), &[seen_flag]) .await?; @@ -332,7 +341,7 @@ impl MailboxView { // 4. Fetch additional info about the emails let query_scope = crit.query_scope(); let uuids = to_fetch.iter().map(|midx| midx.uuid).collect::>(); - let query_result = self.0.query(&uuids, query_scope).fetch().await?; + let query_result = self.internal.query(&uuids, query_scope).fetch().await?; // 5. If needed, filter the selection based on the body let kept_query = crit.filter_on_query(&to_fetch, &query_result)?; @@ -354,7 +363,7 @@ impl MailboxView { /// It's not trivial to refactor the code to do that, so we are doing /// some useless computation for now... fn index<'a>(&'a self) -> Result> { - Index::new(&self.0.snapshot) + Index::new(&self.internal.snapshot) } /// Produce an OK [UIDVALIDITY _] message corresponding to `known_state` @@ -369,7 +378,7 @@ impl MailboxView { } pub(crate) fn uidvalidity(&self) -> ImapUidvalidity { - self.0.snapshot.uidvalidity + self.internal.snapshot.uidvalidity } /// Produce an OK [UIDNEXT _] message corresponding to `known_state` @@ -384,7 +393,15 @@ impl MailboxView { } pub(crate) fn uidnext(&self) -> ImapUid { - self.0.snapshot.uidnext + self.internal.snapshot.uidnext + } + + pub(crate) fn highestmodseq_status(&self) -> Result> { + Ok(Body::Status(Status::ok( + None, + Some(Code::Other(CodeOther::unvalidated(format!("HIGHESTMODSEQ {}", 0).into_bytes()))), + "Highest", + )?)) } /// Produce an EXISTS message corresponding to the number of mails @@ -394,7 +411,7 @@ impl MailboxView { } pub(crate) fn exists(&self) -> Result { - Ok(u32::try_from(self.0.snapshot.idx_by_uid.len())?) + Ok(u32::try_from(self.internal.snapshot.idx_by_uid.len())?) } /// Produce a RECENT message corresponding to the number of @@ -416,7 +433,7 @@ impl MailboxView { #[allow(dead_code)] fn unseen_first(&self) -> Result> { Ok(self - .0 + .internal .snapshot .table .values() @@ -428,7 +445,7 @@ impl MailboxView { pub(crate) fn recent(&self) -> Result { let recent = self - .0 + .internal .snapshot .idx_by_flag .get(&"\\Recent".to_string()) @@ -445,7 +462,7 @@ impl MailboxView { // 1. Collecting all the possible flags in the mailbox // 1.a Fetch them from our index let mut known_flags: Vec = self - .0 + .internal .snapshot .idx_by_flag .flags() @@ -485,9 +502,9 @@ impl MailboxView { } pub(crate) fn unseen_count(&self) -> usize { - let total = self.0.snapshot.table.len(); + let total = self.internal.snapshot.table.len(); let seen = self - .0 + .internal .snapshot .idx_by_flag .get(&Flag::Seen.to_string())