diff --git a/src/imap/capability.rs b/src/imap/capability.rs index f6b5a51..3929558 100644 --- a/src/imap/capability.rs +++ b/src/imap/capability.rs @@ -1,5 +1,7 @@ use imap_codec::imap_types::core::NonEmptyVec; use imap_codec::imap_types::response::Capability; +use imap_codec::imap_types::extensions::enable::{CapabilityEnable, Utf8Kind}; +use std::collections::HashSet; fn capability_unselect() -> Capability<'static> { Capability::try_from("UNSELECT").unwrap() @@ -14,84 +16,67 @@ fn capability_qresync() -> Capability<'static> { } #[derive(Debug, Clone)] -pub struct ServerCapability { - r#move: bool, - unselect: bool, - condstore: bool, - qresync: bool, -} +pub struct ServerCapability(HashSet>); impl Default for ServerCapability { fn default() -> Self { - Self { - r#move: true, - unselect: true, - condstore: false, - qresync: false, - } + Self(HashSet::from([ + Capability::Imap4Rev1, + Capability::Move, + capability_unselect(), + //capability_condstore(), + //capability_qresync(), + ])) } } impl ServerCapability { pub fn to_vec(&self) -> NonEmptyVec> { - let mut acc = vec![Capability::Imap4Rev1]; - if self.r#move { - acc.push(Capability::Move); - } - if self.unselect { - acc.push(capability_unselect()); - } - if self.condstore { - acc.push(capability_condstore()); - } - if self.qresync { - acc.push(capability_qresync()); - } - acc.try_into().unwrap() + self.0.iter().map(|v| v.clone()).collect::>().try_into().unwrap() } + #[allow(dead_code)] pub fn support(&self, cap: &Capability<'static>) -> bool { - match cap { - Capability::Imap4Rev1 => true, - Capability::Move => self.r#move, - x if *x == capability_condstore() => self.condstore, - x if *x == capability_qresync() => self.qresync, - x if *x == capability_unselect() => self.unselect, - _ => false, - } + self.0.contains(cap) } } +enum ClientStatus { + NotSupportedByServer, + Disabled, + Enabled, +} + pub struct ClientCapability { - condstore: bool, - qresync: bool, -} - -impl Default for ClientCapability { - fn default() -> Self { - Self { - condstore: false, - qresync: false, - } - } + condstore: ClientStatus, + utf8kind: Option, } impl ClientCapability { + pub fn new(sc: &ServerCapability) -> Self { + Self { + condstore: match sc.0.contains(&capability_condstore()) { + true => ClientStatus::Disabled, + _ => ClientStatus::NotSupportedByServer, + }, + utf8kind: None, + } + } + pub fn try_enable( &mut self, - srv: &ServerCapability, - caps: &[Capability<'static>], - ) -> Vec> { + caps: &[CapabilityEnable<'static>], + ) -> Vec> { let mut enabled = vec![]; for cap in caps { match cap { - x if *x == capability_condstore() && srv.condstore && !self.condstore => { - self.condstore = true; - enabled.push(x.clone()); + CapabilityEnable::CondStore if matches!(self.condstore, ClientStatus::Disabled) => { + self.condstore = ClientStatus::Enabled; + enabled.push(cap.clone()); } - x if *x == capability_qresync() && srv.qresync && !self.qresync => { - self.qresync = true; - enabled.push(x.clone()); + CapabilityEnable::Utf8(kind) if Some(kind) != self.utf8kind.as_ref() => { + self.utf8kind = Some(kind.clone()); + enabled.push(cap.clone()); } _ => (), } diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index fbf29f9..ee7c8f3 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -3,14 +3,15 @@ use std::sync::Arc; use anyhow::{anyhow, bail, Result}; use imap_codec::imap_types::command::{Command, CommandBody}; -use imap_codec::imap_types::core::{Atom, Literal, QuotedChar}; +use imap_codec::imap_types::core::{Atom, Literal, QuotedChar, NonEmptyVec}; use imap_codec::imap_types::datetime::DateTime; use imap_codec::imap_types::flag::{Flag, FlagNameAttribute}; use imap_codec::imap_types::mailbox::{ListMailbox, Mailbox as MailboxCodec}; use imap_codec::imap_types::response::{Code, CodeOther, Data}; use imap_codec::imap_types::status::{StatusDataItem, StatusDataItemName}; +use imap_codec::imap_types::extensions::enable::CapabilityEnable; -use crate::imap::capability::ServerCapability; +use crate::imap::capability::{ClientCapability, ServerCapability}; use crate::imap::command::{anystate, MailboxName}; use crate::imap::flow; use crate::imap::mailbox_view::MailboxView; @@ -24,6 +25,7 @@ use crate::mail::IMF; pub struct AuthenticatedContext<'a> { pub req: &'a Command<'static>, pub server_capabilities: &'a ServerCapability, + pub client_capabilities: &'a mut ClientCapability, pub user: &'a Arc, } @@ -65,6 +67,11 @@ pub async fn dispatch<'a>( message, } => ctx.append(mailbox, flags, date, message).await, + // rfc5161 ENABLE + CommandBody::Enable { capabilities } => { + ctx.enable(capabilities) + }, + // Collect other commands _ => anystate::wrong_state(ctx.req.tag.clone()), } @@ -511,6 +518,20 @@ impl<'a> AuthenticatedContext<'a> { } } + fn enable(self, cap_enable: &NonEmptyVec>) -> Result<(Response<'static>, flow::Transition)> { + let mut response_builder = Response::build().to_req(self.req); + let capabilities = self.client_capabilities.try_enable(cap_enable.as_ref()); + if capabilities.len() > 0 { + response_builder = response_builder.data(Data::Enabled { capabilities }); + } + Ok(( + response_builder + .message("ENABLE completed") + .ok()?, + flow::Transition::None, + )) + } + pub(crate) async fn append_internal( self, mailbox: &MailboxCodec<'a>, diff --git a/src/imap/command/examined.rs b/src/imap/command/examined.rs index bddd0f9..0d688c0 100644 --- a/src/imap/command/examined.rs +++ b/src/imap/command/examined.rs @@ -7,7 +7,7 @@ use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames; use imap_codec::imap_types::search::SearchKey; use imap_codec::imap_types::sequence::SequenceSet; -use crate::imap::capability::ServerCapability; +use crate::imap::capability::{ClientCapability, ServerCapability}; use crate::imap::command::{anystate, authenticated}; use crate::imap::flow; use crate::imap::mailbox_view::MailboxView; @@ -19,6 +19,7 @@ pub struct ExaminedContext<'a> { pub user: &'a Arc, pub mailbox: &'a mut MailboxView, pub server_capabilities: &'a ServerCapability, + pub client_capabilities: &'a mut ClientCapability, } pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, flow::Transition)> { @@ -60,6 +61,7 @@ pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, fl authenticated::dispatch(authenticated::AuthenticatedContext { req: ctx.req, server_capabilities: ctx.server_capabilities, + client_capabilities: ctx.client_capabilities, user: ctx.user, }) .await diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs index 28ebbe8..c8cc680 100644 --- a/src/imap/command/selected.rs +++ b/src/imap/command/selected.rs @@ -10,7 +10,7 @@ use imap_codec::imap_types::response::{Code, CodeOther}; use imap_codec::imap_types::search::SearchKey; use imap_codec::imap_types::sequence::SequenceSet; -use crate::imap::capability::ServerCapability; +use crate::imap::capability::{ClientCapability, ServerCapability}; use crate::imap::command::{anystate, authenticated, MailboxName}; use crate::imap::flow; use crate::imap::mailbox_view::MailboxView; @@ -23,6 +23,7 @@ pub struct SelectedContext<'a> { pub user: &'a Arc, pub mailbox: &'a mut MailboxView, pub server_capabilities: &'a ServerCapability, + pub client_capabilities: &'a mut ClientCapability, } pub async fn dispatch<'a>( @@ -76,6 +77,7 @@ pub async fn dispatch<'a>( authenticated::dispatch(authenticated::AuthenticatedContext { req: ctx.req, server_capabilities: ctx.server_capabilities, + client_capabilities: ctx.client_capabilities, user: ctx.user, }) .await diff --git a/src/imap/session.rs b/src/imap/session.rs index 55026b9..6b26478 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -1,4 +1,4 @@ -use crate::imap::capability::ServerCapability; +use crate::imap::capability::{ClientCapability, ServerCapability}; use crate::imap::command::{anonymous, authenticated, examined, selected}; use crate::imap::flow; use crate::imap::response::Response; @@ -9,14 +9,17 @@ use imap_codec::imap_types::command::Command; pub struct Instance { pub login_provider: ArcLoginProvider, pub server_capabilities: ServerCapability, + pub client_capabilities: ClientCapability, pub state: flow::State, } impl Instance { pub fn new(login_provider: ArcLoginProvider, cap: ServerCapability) -> Self { + let client_cap = ClientCapability::new(&cap); Self { login_provider, state: flow::State::NotAuthenticated, server_capabilities: cap, + client_capabilities: client_cap, } } @@ -36,6 +39,7 @@ impl Instance { let ctx = authenticated::AuthenticatedContext { req: &cmd, server_capabilities: &self.server_capabilities, + client_capabilities: &mut self.client_capabilities, user, }; authenticated::dispatch(ctx).await @@ -44,6 +48,7 @@ impl Instance { let ctx = selected::SelectedContext { req: &cmd, server_capabilities: &self.server_capabilities, + client_capabilities: &mut self.client_capabilities, user, mailbox, }; @@ -53,6 +58,7 @@ impl Instance { let ctx = examined::ExaminedContext { req: &cmd, server_capabilities: &self.server_capabilities, + client_capabilities: &mut self.client_capabilities, user, mailbox, };