From 90b143e1c57c6561998176878b2cc586b2d89c80 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 29 Jun 2022 12:50:44 +0200 Subject: Refactor to allow mutability --- src/imap/command/anonymous.rs | 127 ++++++++++++++++++++++---------------- src/imap/command/authenticated.rs | 39 ++++++------ src/imap/command/selected.rs | 39 ++++++------ 3 files changed, 112 insertions(+), 93 deletions(-) (limited to 'src/imap/command') diff --git a/src/imap/command/anonymous.rs b/src/imap/command/anonymous.rs index 2ab3f97..f5707ef 100644 --- a/src/imap/command/anonymous.rs +++ b/src/imap/command/anonymous.rs @@ -1,76 +1,97 @@ use anyhow::{Error, Result}; -use boitalettres::proto::{res::body::Data as Body, Response}; +use boitalettres::proto::{res::body::Data as Body, Request, Response}; use imap_codec::types::command::CommandBody; use imap_codec::types::core::{AString, Atom}; use imap_codec::types::response::{Capability, Code, Data, Response as ImapRes, Status}; use crate::imap::flow; -use crate::imap::session::InnerContext; +use crate::login::ArcLoginProvider; //--- dispatching -pub async fn dispatch<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> { +pub struct AnonymousContext<'a> { + pub req: &'a Request, + pub login_provider: Option<&'a ArcLoginProvider>, +} + +pub async fn dispatch<'a>(ctx: AnonymousContext<'a>) -> Result<(Response, flow::Transition)> { match &ctx.req.command.body { - CommandBody::Noop => Ok((Response::ok("Noop completed.")?, flow::Transition::No)), - CommandBody::Capability => capability(ctx).await, - CommandBody::Logout => logout(ctx).await, - CommandBody::Login { username, password } => login(ctx, username, password).await, + CommandBody::Noop => Ok((Response::ok("Noop completed.")?, flow::Transition::None)), + CommandBody::Capability => ctx.capability().await, + CommandBody::Logout => ctx.logout().await, + CommandBody::Login { username, password } => ctx.login(username, password).await, _ => Ok(( Response::no("This command is not available in the ANONYMOUS state.")?, - flow::Transition::No, + flow::Transition::None, )), } } //--- Command controllers, private -async fn capability<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> { - let capabilities = vec![Capability::Imap4Rev1, Capability::Idle]; - let res = Response::ok("Server capabilities")?.with_body(Data::Capability(capabilities)); - Ok((res, flow::Transition::No)) -} +impl<'a> AnonymousContext<'a> { + async fn capability(self) -> Result<(Response, flow::Transition)> { + let capabilities = vec![Capability::Imap4Rev1, Capability::Idle]; + let res = Response::ok("Server capabilities")?.with_body(Data::Capability(capabilities)); + Ok((res, flow::Transition::None)) + } -async fn login<'a>( - ctx: InnerContext<'a>, - username: &AString, - password: &AString, -) -> Result<(Response, flow::Transition)> { - let (u, p) = ( - String::try_from(username.clone())?, - String::try_from(password.clone())?, - ); - tracing::info!(user = %u, "command.login"); + async fn login( + self, + username: &AString, + password: &AString, + ) -> Result<(Response, flow::Transition)> { + let (u, p) = ( + String::try_from(username.clone())?, + String::try_from(password.clone())?, + ); + tracing::info!(user = %u, "command.login"); - let creds = match ctx.login.login(&u, &p).await { - Err(e) => { - tracing::debug!(error=%e, "authentication failed"); - return Ok((Response::no("Authentication failed")?, flow::Transition::No)); - } - Ok(c) => c, - }; + let login_provider = match &self.login_provider { + Some(lp) => lp, + None => { + return Ok(( + Response::no("Login command not available (already logged in)")?, + flow::Transition::None, + )) + } + }; - let user = flow::User { - creds, - name: u.clone(), - }; + let creds = match login_provider.login(&u, &p).await { + Err(e) => { + tracing::debug!(error=%e, "authentication failed"); + return Ok(( + Response::no("Authentication failed")?, + flow::Transition::None, + )); + } + Ok(c) => c, + }; - tracing::info!(username=%u, "connected"); - Ok(( - Response::ok("Completed")?, - flow::Transition::Authenticate(user), - )) -} -// C: 10 logout -// S: * BYE Logging out -// S: 10 OK Logout completed. -async fn logout<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> { - // @FIXME we should implement From> and From> in - // boitalettres/src/proto/res/body.rs - Ok(( - Response::ok("Logout completed")?.with_body(vec![Body::Status( - Status::bye(None, "Logging out") - .map_err(|e| Error::msg(e).context("Unable to generate IMAP status"))?, - )]), - flow::Transition::Logout, - )) + let user = flow::User { + creds, + name: u.clone(), + }; + + tracing::info!(username=%u, "connected"); + Ok(( + Response::ok("Completed")?, + flow::Transition::Authenticate(user), + )) + } + + // C: 10 logout + // S: * BYE Logging out + // S: 10 OK Logout completed. + async fn logout(self) -> Result<(Response, flow::Transition)> { + // @FIXME we should implement From> and From> in + // boitalettres/src/proto/res/body.rs + Ok(( + Response::ok("Logout completed")?.with_body(vec![Body::Status( + Status::bye(None, "Logging out") + .map_err(|e| Error::msg(e).context("Unable to generate IMAP status"))?, + )]), + flow::Transition::Logout, + )) + } } diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index f22fcc4..c7e5642 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Error, Result}; -use boitalettres::proto::{res::body::Data as Body, Response}; +use boitalettres::proto::{res::body::Data as Body, Request, Response}; use imap_codec::types::command::CommandBody; use imap_codec::types::core::Atom; use imap_codec::types::flag::Flag; @@ -19,13 +19,13 @@ const DEFAULT_FLAGS: [Flag; 5] = [ Flag::Draft, ]; -pub async fn dispatch<'a>( - inner: InnerContext<'a>, - user: &'a flow::User, -) -> Result<(Response, flow::Transition)> { - let ctx = StateContext { user, inner }; +pub struct AuthenticatedContext<'a> { + pub req: &'a Request, + pub user: &'a flow::User, +} - match &ctx.inner.req.command.body { +pub async fn dispatch<'a>(ctx: AuthenticatedContext<'a>) -> Result<(Response, flow::Transition)> { + match &ctx.req.command.body { CommandBody::Lsub { reference, mailbox_wildcard, @@ -35,32 +35,33 @@ pub async fn dispatch<'a>( mailbox_wildcard, } => ctx.list(reference, mailbox_wildcard).await, CommandBody::Select { mailbox } => ctx.select(mailbox).await, - _ => anonymous::dispatch(ctx.inner).await, + _ => { + let ctx = anonymous::AnonymousContext { + req: ctx.req, + login_provider: None, + }; + anonymous::dispatch(ctx).await + } } } // --- PRIVATE --- -struct StateContext<'a> { - inner: InnerContext<'a>, - user: &'a flow::User, -} - -impl<'a> StateContext<'a> { +impl<'a> AuthenticatedContext<'a> { async fn lsub( - &self, + self, reference: &MailboxCodec, mailbox_wildcard: &ListMailbox, ) -> Result<(Response, flow::Transition)> { - Ok((Response::bad("Not implemented")?, flow::Transition::No)) + Ok((Response::bad("Not implemented")?, flow::Transition::None)) } async fn list( - &self, + self, reference: &MailboxCodec, mailbox_wildcard: &ListMailbox, ) -> Result<(Response, flow::Transition)> { - Ok((Response::bad("Not implemented")?, flow::Transition::No)) + Ok((Response::bad("Not implemented")?, flow::Transition::None)) } /* @@ -91,7 +92,7 @@ impl<'a> StateContext<'a> { * TRACE END --- */ - async fn select(&self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> { + async fn select(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> { let name = String::try_from(mailbox.clone())?; let mut mb = Mailbox::new(&self.user.creds, name.clone())?; diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs index cf0b71b..4e41561 100644 --- a/src/imap/command/selected.rs +++ b/src/imap/command/selected.rs @@ -1,4 +1,5 @@ use anyhow::{Error, Result}; +use boitalettres::proto::Request; use boitalettres::proto::Response; use imap_codec::types::command::CommandBody; use imap_codec::types::core::Tag; @@ -11,42 +12,38 @@ use crate::imap::flow; use crate::imap::session::InnerContext; use crate::mail::Mailbox; -pub async fn dispatch<'a>( - inner: InnerContext<'a>, - user: &'a flow::User, - mailbox: &'a Mailbox, -) -> Result<(Response, flow::Transition)> { - let ctx = StateContext { - inner, - user, - mailbox, - }; +pub struct SelectedContext<'a> { + pub req: &'a Request, + pub user: &'a flow::User, + pub mailbox: &'a mut Mailbox, +} - match &ctx.inner.req.command.body { +pub async fn dispatch<'a>(ctx: SelectedContext<'a>) -> Result<(Response, flow::Transition)> { + match &ctx.req.command.body { CommandBody::Fetch { sequence_set, attributes, uid, } => ctx.fetch(sequence_set, attributes, uid).await, - _ => authenticated::dispatch(ctx.inner, user).await, + _ => { + let ctx = authenticated::AuthenticatedContext { + req: ctx.req, + user: ctx.user, + }; + authenticated::dispatch(ctx).await + } } } // --- PRIVATE --- -struct StateContext<'a> { - inner: InnerContext<'a>, - user: &'a flow::User, - mailbox: &'a Mailbox, -} - -impl<'a> StateContext<'a> { +impl<'a> SelectedContext<'a> { pub async fn fetch( - &self, + self, sequence_set: &SequenceSet, attributes: &MacroOrFetchAttributes, uid: &bool, ) -> Result<(Response, flow::Transition)> { - Ok((Response::bad("Not implemented")?, flow::Transition::No)) + Ok((Response::bad("Not implemented")?, flow::Transition::None)) } } -- cgit v1.2.3