diff options
Diffstat (limited to 'src/imap/command')
-rw-r--r-- | src/imap/command/authenticated.rs | 32 | ||||
-rw-r--r-- | src/imap/command/examined.rs | 52 | ||||
-rw-r--r-- | src/imap/command/selected.rs | 89 |
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, )) } |