aboutsummaryrefslogtreecommitdiff
path: root/src/imap/command
diff options
context:
space:
mode:
authorQuentin <quentin@dufour.io>2024-01-15 07:07:06 +0000
committerQuentin <quentin@dufour.io>2024-01-15 07:07:06 +0000
commit55e26d24a08519ded6a6898453dcd6db287f45c8 (patch)
tree074d9f8dbc161d2aa8f59be26826b40c09d3d658 /src/imap/command
parentd49a2355f71fe555a67a815c31800f901a0d0a71 (diff)
parent81bfed3b7df354148f284ed4e3708c1a086d6e58 (diff)
downloadaerogramme-55e26d24a08519ded6a6898453dcd6db287f45c8.tar.gz
aerogramme-55e26d24a08519ded6a6898453dcd6db287f45c8.zip
Merge pull request 'CONDSTORE' (#71) from feat/condstore-try-2 into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/aerogramme/pulls/71
Diffstat (limited to 'src/imap/command')
-rw-r--r--src/imap/command/authenticated.rs32
-rw-r--r--src/imap/command/examined.rs52
-rw-r--r--src/imap/command/selected.rs89
3 files changed, 123 insertions, 50 deletions
diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs
index 1481a80..9b6bb24 100644
--- a/src/imap/command/authenticated.rs
+++ b/src/imap/command/authenticated.rs
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use anyhow::{anyhow, bail, Result};
-use imap_codec::imap_types::command::{Command, CommandBody};
+use imap_codec::imap_types::command::{Command, CommandBody, SelectExamineModifier};
use imap_codec::imap_types::core::{Atom, Literal, NonEmptyVec, QuotedChar};
use imap_codec::imap_types::datetime::DateTime;
use imap_codec::imap_types::extensions::enable::CapabilityEnable;
@@ -58,8 +58,8 @@ pub async fn dispatch<'a>(
} => ctx.status(mailbox, item_names).await,
CommandBody::Subscribe { mailbox } => ctx.subscribe(mailbox).await,
CommandBody::Unsubscribe { mailbox } => ctx.unsubscribe(mailbox).await,
- CommandBody::Select { mailbox } => ctx.select(mailbox).await,
- CommandBody::Examine { mailbox } => ctx.examine(mailbox).await,
+ CommandBody::Select { mailbox, modifiers } => ctx.select(mailbox, modifiers).await,
+ CommandBody::Examine { mailbox, modifiers } => ctx.examine(mailbox, modifiers).await,
CommandBody::Append {
mailbox,
flags,
@@ -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() {
@@ -311,8 +311,9 @@ impl<'a> AuthenticatedContext<'a> {
bail!("quota not implemented, can't return freed storage after EXPUNGE will be run");
},
StatusDataItemName::HighestModSeq => {
- bail!("highestmodseq not yet implemented");
- }
+ self.client_capabilities.enable_condstore();
+ StatusDataItem::HighestModSeq(view.highestmodseq().get())
+ },
});
}
@@ -404,6 +405,7 @@ impl<'a> AuthenticatedContext<'a> {
it is therefore correct to not return it even if there are unseen messages
RFC9051 (imap4rev2) says that OK [UNSEEN] responses are deprecated after SELECT and EXAMINE
For Aerogramme, we just don't send the OK [UNSEEN], it's correct to do in both specifications.
+
20 select "INBOX.achats"
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded JUNK $label1)
@@ -420,7 +422,10 @@ impl<'a> AuthenticatedContext<'a> {
async fn select(
self,
mailbox: &MailboxCodec<'a>,
+ modifiers: &[SelectExamineModifier],
) -> Result<(Response<'static>, flow::Transition)> {
+ self.client_capabilities.select_enable(modifiers);
+
let name: &str = MailboxName(mailbox).try_into()?;
let mb_opt = self.user.open_mailbox(&name).await?;
@@ -438,7 +443,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((
@@ -455,7 +460,10 @@ impl<'a> AuthenticatedContext<'a> {
async fn examine(
self,
mailbox: &MailboxCodec<'a>,
+ modifiers: &[SelectExamineModifier],
) -> Result<(Response<'static>, flow::Transition)> {
+ self.client_capabilities.select_enable(modifiers);
+
let name: &str = MailboxName(mailbox).try_into()?;
let mb_opt = self.user.open_mailbox(&name).await?;
@@ -473,7 +481,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((
@@ -496,7 +504,7 @@ impl<'a> AuthenticatedContext<'a> {
) -> Result<(Response<'static>, flow::Transition)> {
let append_tag = self.req.tag.clone();
match self.append_internal(mailbox, flags, date, message).await {
- Ok((_mb, uidvalidity, uid)) => Ok((
+ Ok((_mb, uidvalidity, uid, _modseq)) => Ok((
Response::build()
.tag(append_tag)
.message("APPEND completed")
@@ -537,7 +545,7 @@ impl<'a> AuthenticatedContext<'a> {
flags: &[Flag<'a>],
date: &Option<DateTime>,
message: &Literal<'a>,
- ) -> Result<(Arc<Mailbox>, ImapUidvalidity, ImapUidvalidity)> {
+ ) -> Result<(Arc<Mailbox>, ImapUidvalidity, ImapUid, ModSeq)> {
let name: &str = MailboxName(mailbox).try_into()?;
let mb_opt = self.user.open_mailbox(&name).await?;
@@ -555,9 +563,9 @@ impl<'a> AuthenticatedContext<'a> {
let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
// TODO: filter allowed flags? ping @Quentin
- let (uidvalidity, uid) = mb.append(msg, None, &flags[..]).await?;
+ let (uidvalidity, uid, modseq) = mb.append(msg, None, &flags[..]).await?;
- Ok((mb, uidvalidity, uid))
+ Ok((mb, uidvalidity, uid, modseq))
}
}
diff --git a/src/imap/command/examined.rs b/src/imap/command/examined.rs
index 3dd11e2..9fc0990 100644
--- a/src/imap/command/examined.rs
+++ b/src/imap/command/examined.rs
@@ -1,16 +1,18 @@
use std::sync::Arc;
+use std::num::NonZeroU64;
use anyhow::Result;
-use imap_codec::imap_types::command::{Command, CommandBody};
+use imap_codec::imap_types::command::{Command, CommandBody, FetchModifier};
use imap_codec::imap_types::core::Charset;
use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames;
use imap_codec::imap_types::search::SearchKey;
use imap_codec::imap_types::sequence::SequenceSet;
+use crate::imap::attributes::AttributesProxy;
use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anystate, authenticated};
use crate::imap::flow;
-use crate::imap::mailbox_view::MailboxView;
+use crate::imap::mailbox_view::{MailboxView, UpdateParameters};
use crate::imap::response::Response;
use crate::mail::user::User;
@@ -37,8 +39,9 @@ pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, fl
CommandBody::Fetch {
sequence_set,
macro_or_item_names,
+ modifiers,
uid,
- } => ctx.fetch(sequence_set, macro_or_item_names, uid).await,
+ } => ctx.fetch(sequence_set, macro_or_item_names, modifiers, uid).await,
CommandBody::Search {
charset,
criteria,
@@ -88,17 +91,33 @@ impl<'a> ExaminedContext<'a> {
self,
sequence_set: &SequenceSet,
attributes: &'a MacroOrMessageDataItemNames<'static>,
+ modifiers: &[FetchModifier],
uid: &bool,
) -> Result<(Response<'static>, flow::Transition)> {
- match self.mailbox.fetch(sequence_set, attributes, uid).await {
- Ok(resp) => Ok((
- Response::build()
- .to_req(self.req)
- .message("FETCH completed")
- .set_body(resp)
- .ok()?,
- flow::Transition::None,
- )),
+ let ap = AttributesProxy::new(attributes, modifiers, *uid);
+ let mut changed_since: Option<NonZeroU64> = None;
+ modifiers.iter().for_each(|m| match m {
+ FetchModifier::ChangedSince(val) => {
+ changed_since = Some(*val);
+ },
+ });
+
+ match self.mailbox.fetch(sequence_set, &ap, changed_since, uid).await {
+ Ok(resp) => {
+ // Capabilities enabling logic only on successful command
+ // (according to my understanding of the spec)
+ self.client_capabilities.attributes_enable(&ap);
+ self.client_capabilities.fetch_modifiers_enable(modifiers);
+
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("FETCH completed")
+ .set_body(resp)
+ .ok()?,
+ flow::Transition::None,
+ ))
+ },
Err(e) => Ok((
Response::build()
.to_req(self.req)
@@ -115,7 +134,10 @@ impl<'a> ExaminedContext<'a> {
criteria: &SearchKey<'a>,
uid: &bool,
) -> Result<(Response<'static>, flow::Transition)> {
- let found = self.mailbox.search(charset, criteria, *uid).await?;
+ let (found, enable_condstore) = self.mailbox.search(charset, criteria, *uid).await?;
+ if enable_condstore {
+ self.client_capabilities.enable_condstore();
+ }
Ok((
Response::build()
.to_req(self.req)
@@ -127,9 +149,9 @@ 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?;
+ let updates = self.mailbox.update(UpdateParameters::default()).await?;
Ok((
Response::build()
.to_req(self.req)
diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs
index 35c3eb4..c13b71a 100644
--- a/src/imap/command/selected.rs
+++ b/src/imap/command/selected.rs
@@ -1,7 +1,8 @@
use std::sync::Arc;
+use std::num::NonZeroU64;
use anyhow::Result;
-use imap_codec::imap_types::command::{Command, CommandBody};
+use imap_codec::imap_types::command::{Command, CommandBody, FetchModifier, StoreModifier};
use imap_codec::imap_types::core::Charset;
use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames;
use imap_codec::imap_types::flag::{Flag, StoreResponse, StoreType};
@@ -13,9 +14,9 @@ use imap_codec::imap_types::sequence::SequenceSet;
use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anystate, authenticated, MailboxName};
use crate::imap::flow;
-use crate::imap::mailbox_view::MailboxView;
+use crate::imap::mailbox_view::{MailboxView, UpdateParameters};
use crate::imap::response::Response;
-
+use crate::imap::attributes::AttributesProxy;
use crate::mail::user::User;
pub struct SelectedContext<'a> {
@@ -43,8 +44,9 @@ pub async fn dispatch<'a>(
CommandBody::Fetch {
sequence_set,
macro_or_item_names,
+ modifiers,
uid,
- } => ctx.fetch(sequence_set, macro_or_item_names, uid).await,
+ } => ctx.fetch(sequence_set, macro_or_item_names, modifiers, uid).await,
CommandBody::Search {
charset,
criteria,
@@ -56,8 +58,9 @@ pub async fn dispatch<'a>(
kind,
response,
flags,
+ modifiers,
uid,
- } => ctx.store(sequence_set, kind, response, flags, uid).await,
+ } => ctx.store(sequence_set, kind, response, flags, modifiers, uid).await,
CommandBody::Copy {
sequence_set,
mailbox,
@@ -113,17 +116,34 @@ impl<'a> SelectedContext<'a> {
self,
sequence_set: &SequenceSet,
attributes: &'a MacroOrMessageDataItemNames<'static>,
+ modifiers: &[FetchModifier],
uid: &bool,
) -> Result<(Response<'static>, flow::Transition)> {
- match self.mailbox.fetch(sequence_set, attributes, uid).await {
- Ok(resp) => Ok((
- Response::build()
- .to_req(self.req)
- .message("FETCH completed")
- .set_body(resp)
- .ok()?,
- flow::Transition::None,
- )),
+ let ap = AttributesProxy::new(attributes, modifiers, *uid);
+ let mut changed_since: Option<NonZeroU64> = None;
+ modifiers.iter().for_each(|m| match m {
+ FetchModifier::ChangedSince(val) => {
+ changed_since = Some(*val);
+ },
+ });
+
+ match self.mailbox.fetch(sequence_set, &ap, changed_since, uid).await {
+ Ok(resp) => {
+ // Capabilities enabling logic only on successful command
+ // (according to my understanding of the spec)
+ self.client_capabilities.attributes_enable(&ap);
+ self.client_capabilities.fetch_modifiers_enable(modifiers);
+
+ // Response to the client
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("FETCH completed")
+ .set_body(resp)
+ .ok()?,
+ flow::Transition::None,
+ ))
+ },
Err(e) => Ok((
Response::build()
.to_req(self.req)
@@ -140,7 +160,10 @@ impl<'a> SelectedContext<'a> {
criteria: &SearchKey<'a>,
uid: &bool,
) -> Result<(Response<'static>, flow::Transition)> {
- let found = self.mailbox.search(charset, criteria, *uid).await?;
+ let (found, enable_condstore) = self.mailbox.search(charset, criteria, *uid).await?;
+ if enable_condstore {
+ self.client_capabilities.enable_condstore();
+ }
Ok((
Response::build()
.to_req(self.req)
@@ -152,9 +175,9 @@ 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?;
+ let updates = self.mailbox.update(UpdateParameters::default()).await?;
Ok((
Response::build()
.to_req(self.req)
@@ -185,19 +208,39 @@ impl<'a> SelectedContext<'a> {
kind: &StoreType,
response: &StoreResponse,
flags: &[Flag<'a>],
+ modifiers: &[StoreModifier],
uid: &bool,
) -> Result<(Response<'static>, flow::Transition)> {
- let data = self
+ let mut unchanged_since: Option<NonZeroU64> = None;
+ modifiers.iter().for_each(|m| match m {
+ StoreModifier::UnchangedSince(val) => {
+ unchanged_since = Some(*val);
+ },
+ });
+
+ let (data, modified) = self
.mailbox
- .store(sequence_set, kind, response, flags, uid)
+ .store(sequence_set, kind, response, flags, unchanged_since, uid)
.await?;
- Ok((
- Response::build()
+ let mut ok_resp = Response::build()
.to_req(self.req)
.message("STORE completed")
- .set_body(data)
- .ok()?,
+ .set_body(data);
+
+
+ match modified[..] {
+ [] => (),
+ [_head, ..] => {
+ let modified_str = format!("MODIFIED {}", modified.into_iter().map(|x| x.to_string()).collect::<Vec<_>>().join(","));
+ ok_resp = ok_resp.code(Code::Other(CodeOther::unvalidated(modified_str.into_bytes())));
+ },
+ };
+
+
+ self.client_capabilities.store_modifiers_enable(modifiers);
+
+ Ok((ok_resp.ok()?,
flow::Transition::None,
))
}