From 6d37924399dd5d04f5be2506e3e044dd165f6399 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 3 Jan 2024 12:29:19 +0100 Subject: [PATCH] rework capability --- src/imap/capability.rs | 30 ++++++++++++++++++++++++++++++ src/imap/command/anonymous.rs | 8 +++++++- src/imap/command/anystate.rs | 14 +++++--------- src/imap/command/authenticated.rs | 6 +++++- src/imap/command/examined.rs | 8 +++++++- src/imap/command/selected.rs | 8 +++++++- src/imap/mod.rs | 12 +++++++++--- src/imap/session.rs | 13 +++++++++++-- 8 files changed, 81 insertions(+), 18 deletions(-) create mode 100644 src/imap/capability.rs diff --git a/src/imap/capability.rs b/src/imap/capability.rs new file mode 100644 index 0000000..b98e8f8 --- /dev/null +++ b/src/imap/capability.rs @@ -0,0 +1,30 @@ +use imap_codec::imap_types::core::NonEmptyVec; +use imap_codec::imap_types::response::Capability; + +#[derive(Debug, Clone)] +pub struct ServerCapability { + r#move: bool, + unselect: bool, +} + +impl Default for ServerCapability { + fn default() -> Self { + Self { + r#move: true, + unselect: true, + } + } +} + +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::try_from("UNSELECT").unwrap()); + } + acc.try_into().unwrap() + } +} diff --git a/src/imap/command/anonymous.rs b/src/imap/command/anonymous.rs index fbd10e9..fda78c2 100644 --- a/src/imap/command/anonymous.rs +++ b/src/imap/command/anonymous.rs @@ -1,10 +1,12 @@ use anyhow::Result; use imap_codec::imap_types::command::{Command, CommandBody}; use imap_codec::imap_types::core::AString; +use imap_codec::imap_types::response::Code; use imap_codec::imap_types::secret::Secret; use crate::imap::command::anystate; use crate::imap::flow; +use crate::imap::capability::ServerCapability; use crate::imap::response::Response; use crate::login::ArcLoginProvider; use crate::mail::user::User; @@ -13,6 +15,7 @@ use crate::mail::user::User; pub struct AnonymousContext<'a> { pub req: &'a Command<'static>, + pub server_capabilities: &'a ServerCapability, pub login_provider: &'a ArcLoginProvider, } @@ -20,7 +23,9 @@ pub async fn dispatch(ctx: AnonymousContext<'_>) -> Result<(Response<'static>, f match &ctx.req.body { // Any State CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()), - CommandBody::Capability => anystate::capability(ctx.req.tag.clone()), + CommandBody::Capability => anystate::capability( + ctx.req.tag.clone(), + ctx.server_capabilities), CommandBody::Logout => anystate::logout(), // Specific to anonymous context (3 commands) @@ -69,6 +74,7 @@ impl<'a> AnonymousContext<'a> { Ok(( Response::build() .to_req(self.req) + .code(Code::Capability(self.server_capabilities.to_vec())) .message("Completed") .ok()?, flow::Transition::Authenticate(user), diff --git a/src/imap/command/anystate.rs b/src/imap/command/anystate.rs index f326852..7d7c0b2 100644 --- a/src/imap/command/anystate.rs +++ b/src/imap/command/anystate.rs @@ -1,20 +1,16 @@ use anyhow::Result; -use imap_codec::imap_types::core::{NonEmptyVec, Tag}; -use imap_codec::imap_types::response::{Capability, Data}; +use imap_codec::imap_types::core::Tag; +use imap_codec::imap_types::response::Data; use crate::imap::flow; +use crate::imap::capability::ServerCapability; use crate::imap::response::Response; -pub(crate) fn capability(tag: Tag<'static>) -> Result<(Response<'static>, flow::Transition)> { - let capabilities: NonEmptyVec = (vec![ - Capability::Imap4Rev1, - Capability::try_from("UNSELECT").unwrap(), - ]) - .try_into()?; +pub(crate) fn capability(tag: Tag<'static>, cap: &ServerCapability) -> Result<(Response<'static>, flow::Transition)> { let res = Response::build() .tag(tag) .message("Server capabilities") - .data(Data::Capability(capabilities)) + .data(Data::Capability(cap.to_vec())) .ok()?; Ok((res, flow::Transition::None)) diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index 1bb4c6d..a6a5203 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -14,6 +14,7 @@ use crate::imap::command::{anystate, MailboxName}; use crate::imap::flow; use crate::imap::mailbox_view::MailboxView; use crate::imap::response::Response; +use crate::imap::capability::ServerCapability; use crate::mail::mailbox::Mailbox; use crate::mail::uidindex::*; @@ -22,6 +23,7 @@ use crate::mail::IMF; pub struct AuthenticatedContext<'a> { pub req: &'a Command<'static>, + pub server_capabilities: &'a ServerCapability, pub user: &'a Arc, } @@ -31,7 +33,9 @@ pub async fn dispatch<'a>( match &ctx.req.body { // Any state CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()), - CommandBody::Capability => anystate::capability(ctx.req.tag.clone()), + CommandBody::Capability => anystate::capability( + ctx.req.tag.clone(), + ctx.server_capabilities), CommandBody::Logout => anystate::logout(), // Specific to this state (11 commands) diff --git a/src/imap/command/examined.rs b/src/imap/command/examined.rs index 8876297..7cccf7b 100644 --- a/src/imap/command/examined.rs +++ b/src/imap/command/examined.rs @@ -11,19 +11,24 @@ use crate::imap::command::{anystate, authenticated}; use crate::imap::flow; use crate::imap::mailbox_view::MailboxView; use crate::imap::response::Response; +use crate::imap::capability::ServerCapability; use crate::mail::user::User; pub struct ExaminedContext<'a> { pub req: &'a Command<'static>, pub user: &'a Arc, pub mailbox: &'a mut MailboxView, + pub server_capabilities: &'a ServerCapability, } pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, flow::Transition)> { match &ctx.req.body { // Any State // noop is specific to this state - CommandBody::Capability => anystate::capability(ctx.req.tag.clone()), + CommandBody::Capability => anystate::capability( + ctx.req.tag.clone(), + ctx.server_capabilities, + ), CommandBody::Logout => anystate::logout(), // Specific to the EXAMINE state (specialization of the SELECTED state) @@ -55,6 +60,7 @@ pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, fl _ => { authenticated::dispatch(authenticated::AuthenticatedContext { req: ctx.req, + server_capabilities: ctx.server_capabilities, user: ctx.user, }) .await diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs index 0653226..681b509 100644 --- a/src/imap/command/selected.rs +++ b/src/imap/command/selected.rs @@ -14,6 +14,7 @@ use crate::imap::command::{anystate, authenticated, MailboxName}; use crate::imap::flow; use crate::imap::mailbox_view::MailboxView; use crate::imap::response::Response; +use crate::imap::capability::ServerCapability; use crate::mail::user::User; @@ -21,6 +22,7 @@ pub struct SelectedContext<'a> { pub req: &'a Command<'static>, pub user: &'a Arc, pub mailbox: &'a mut MailboxView, + pub server_capabilities: &'a ServerCapability, } pub async fn dispatch<'a>( @@ -29,7 +31,10 @@ pub async fn dispatch<'a>( match &ctx.req.body { // Any State // noop is specific to this state - CommandBody::Capability => anystate::capability(ctx.req.tag.clone()), + CommandBody::Capability => anystate::capability( + ctx.req.tag.clone(), + ctx.server_capabilities, + ), CommandBody::Logout => anystate::logout(), // Specific to this state (7 commands + NOOP) @@ -66,6 +71,7 @@ pub async fn dispatch<'a>( _ => { authenticated::dispatch(authenticated::AuthenticatedContext { req: ctx.req, + server_capabilities: ctx.server_capabilities, user: ctx.user, }) .await diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 31eeaa8..693c99a 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -3,6 +3,7 @@ mod flow; mod mailbox_view; mod response; mod session; +mod capability; use std::net::SocketAddr; @@ -17,12 +18,14 @@ use imap_flow::server::{ServerFlow, ServerFlowEvent, ServerFlowOptions}; use imap_flow::stream::AnyStream; use crate::config::ImapConfig; +use crate::imap::capability::ServerCapability; use crate::login::ArcLoginProvider; /// Server is a thin wrapper to register our Services in BàL pub struct Server { bind_addr: SocketAddr, login_provider: ArcLoginProvider, + capabilities: ServerCapability, } struct ClientContext { @@ -30,12 +33,14 @@ struct ClientContext { addr: SocketAddr, login_provider: ArcLoginProvider, must_exit: watch::Receiver, + server_capabilities: ServerCapability, } pub fn new(config: ImapConfig, login: ArcLoginProvider) -> Server { Server { bind_addr: config.bind_addr, login_provider: login, + capabilities: ServerCapability::default(), } } @@ -66,6 +71,7 @@ impl Server { addr: remote_addr.clone(), login_provider: self.login_provider.clone(), must_exit: must_exit.clone(), + server_capabilities: self.capabilities.clone(), }; let conn = tokio::spawn(client_wrapper(client)); connections.push(conn); @@ -96,21 +102,21 @@ async fn client(mut ctx: ClientContext) -> Result<()> { let (mut server, _) = ServerFlow::send_greeting( ctx.stream, ServerFlowOptions::default(), - Greeting::ok(None, "Aerogramme").unwrap(), + Greeting::ok(Some(Code::Capability(ctx.server_capabilities.to_vec())), "Aerogramme").unwrap(), ) .await?; use crate::imap::response::{Body, Response as MyResponse}; use crate::imap::session::Instance; use imap_codec::imap_types::command::Command; - use imap_codec::imap_types::response::{Response, Status}; + use imap_codec::imap_types::response::{Response, Code, Status}; use tokio::sync::mpsc; let (cmd_tx, mut cmd_rx) = mpsc::channel::>(10); let (resp_tx, mut resp_rx) = mpsc::unbounded_channel::>(); let bckgrnd = tokio::spawn(async move { - let mut session = Instance::new(ctx.login_provider); + let mut session = Instance::new(ctx.login_provider, ctx.server_capabilities); loop { let cmd = match cmd_rx.recv().await { None => break, diff --git a/src/imap/session.rs b/src/imap/session.rs index 5c67f8e..c4f062f 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -1,19 +1,22 @@ use crate::imap::command::{anonymous, authenticated, examined, selected}; use crate::imap::flow; use crate::imap::response::Response; +use crate::imap::capability::ServerCapability; use crate::login::ArcLoginProvider; use imap_codec::imap_types::command::Command; //----- pub struct Instance { pub login_provider: ArcLoginProvider, + pub server_capabilities: ServerCapability, pub state: flow::State, } impl Instance { - pub fn new(login_provider: ArcLoginProvider) -> Self { + pub fn new(login_provider: ArcLoginProvider, cap: ServerCapability) -> Self { Self { login_provider, state: flow::State::NotAuthenticated, + server_capabilities: cap, } } @@ -25,16 +28,21 @@ impl Instance { let ctx = anonymous::AnonymousContext { req: &cmd, login_provider: &self.login_provider, + server_capabilities: &self.server_capabilities, }; anonymous::dispatch(ctx).await } flow::State::Authenticated(ref user) => { - let ctx = authenticated::AuthenticatedContext { req: &cmd, user }; + let ctx = authenticated::AuthenticatedContext { + req: &cmd, + server_capabilities: &self.server_capabilities, + user }; authenticated::dispatch(ctx).await } flow::State::Selected(ref user, ref mut mailbox) => { let ctx = selected::SelectedContext { req: &cmd, + server_capabilities: &self.server_capabilities, user, mailbox, }; @@ -43,6 +51,7 @@ impl Instance { flow::State::Examined(ref user, ref mut mailbox) => { let ctx = examined::ExaminedContext { req: &cmd, + server_capabilities: &self.server_capabilities, user, mailbox, };