diff --git a/Cargo.lock b/Cargo.lock index a5364b8..44c9cb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,7 +362,7 @@ dependencies = [ [[package]] name = "boitalettres" version = "0.0.1" -source = "git+https://git.deuxfleurs.fr/quentin/boitalettres.git?branch=expose_req_res#2c43b7686a7cd06f733719350bd61f792d20338e" +source = "git+https://git.deuxfleurs.fr/KokaKiwi/boitalettres.git?branch=main#01ee8c872b15e2aecb4c0ef5c2dd3aa6cc3c4d3c" dependencies = [ "async-compat", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 2cbf5f9..4393d1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ tower = "0.4" imap-codec = "0.5" k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", branch = "main" } -boitalettres = { git = "https://git.deuxfleurs.fr/quentin/boitalettres.git", branch = "expose_req_res" } +boitalettres = { git = "https://git.deuxfleurs.fr/KokaKiwi/boitalettres.git", branch = "main" } smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" } smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" } diff --git a/src/imap/command/anonymous.rs b/src/imap/command/anonymous.rs index c1b800e..a2f2260 100644 --- a/src/imap/command/anonymous.rs +++ b/src/imap/command/anonymous.rs @@ -10,16 +10,13 @@ use crate::imap::session::InnerContext; //--- dispatching pub async fn dispatch<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> { - match &ctx.req.body { + match &ctx.req.command.body { CommandBody::Capability => capability(ctx).await, CommandBody::Login { username, password } => login(ctx, username, password).await, - _ => Status::no( - Some(ctx.req.tag.clone()), - None, - "This command is not available in the ANONYMOUS state.", - ) - .map(|s| (vec![ImapRes::Status(s)], flow::Transition::No)) - .map_err(Error::msg), + _ => Ok(( + Response::no("This command is not available in the ANONYMOUS state.")?, + flow::Transition::No + )), } } @@ -27,13 +24,7 @@ pub async fn dispatch<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Tran async fn capability<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> { let capabilities = vec![Capability::Imap4Rev1, Capability::Idle]; - let res = vec![ - ImapRes::Data(Data::Capability(capabilities)), - ImapRes::Status( - Status::ok(Some(ctx.req.tag.clone()), None, "Server capabilities") - .map_err(Error::msg)?, - ), - ]; + let res = Response::ok("Server capabilities")?.with_body(Data::Capability(capabilities)); Ok((res, flow::Transition::No)) } @@ -51,13 +42,7 @@ async fn login<'a>( let creds = match ctx.login.login(&u, &p).await { Err(e) => { tracing::debug!(error=%e, "authentication failed"); - return Ok(( - vec![ImapRes::Status( - Status::no(Some(ctx.req.tag.clone()), None, "Authentication failed") - .map_err(Error::msg)?, - )], - flow::Transition::No, - )); + return Ok((Response::no("Authentication failed")?, flow::Transition::No)); } Ok(c) => c, }; @@ -66,16 +51,10 @@ async fn login<'a>( creds, name: u.clone(), }; - let tr = flow::Transition::Authenticate(user); tracing::info!(username=%u, "connected"); Ok(( - vec![ - //@FIXME we could send a capability status here too - ImapRes::Status( - Status::ok(Some(ctx.req.tag.clone()), None, "completed").map_err(Error::msg)?, - ), - ], - tr, + Response::ok("Completed")?, + flow::Transition::Authenticate(user) )) } diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index 0b1f01e..85c1c82 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Error, Result}; -use boitalettres::proto::Response; +use boitalettres::proto::{Response, res::body::Data as Body}; use imap_codec::types::command::CommandBody; -use imap_codec::types::core::{Atom, Tag}; +use imap_codec::types::core::Atom; use imap_codec::types::flag::Flag; use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec}; use imap_codec::types::response::{Code, Data, Response as ImapRes, Status}; @@ -23,13 +23,9 @@ pub async fn dispatch<'a>( inner: InnerContext<'a>, user: &'a flow::User, ) -> Result<(Response, flow::Transition)> { - let ctx = StateContext { - user, - tag: &inner.req.tag, - inner, - }; + let ctx = StateContext { user, inner }; - match &ctx.inner.req.body { + match &ctx.inner.req.command.body { CommandBody::Lsub { reference, mailbox_wildcard, @@ -48,7 +44,6 @@ pub async fn dispatch<'a>( struct StateContext<'a> { inner: InnerContext<'a>, user: &'a flow::User, - tag: &'a Tag, } impl<'a> StateContext<'a> { @@ -58,9 +53,7 @@ impl<'a> StateContext<'a> { mailbox_wildcard: &ListMailbox, ) -> Result<(Response, flow::Transition)> { Ok(( - vec![ImapRes::Status( - Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?, - )], + Response::bad("Not implemented")?, flow::Transition::No, )) } @@ -71,9 +64,7 @@ impl<'a> StateContext<'a> { mailbox_wildcard: &ListMailbox, ) -> Result<(Response, flow::Transition)> { Ok(( - vec![ImapRes::Status( - Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?, - )], + Response::bad("Not implemented")?, flow::Transition::No, )) } @@ -115,20 +106,11 @@ impl<'a> StateContext<'a> { let sum = mb.summary().await?; tracing::trace!(summary=%sum, "mailbox.summary"); - let r_unseen = Status::ok( - None, - Some(Code::Unseen( - std::num::NonZeroU32::new(1).ok_or(anyhow!("Invalid message identifier"))?, - )), - "First unseen UID", - ) - .map_err(Error::msg)?; + let mut res = Vec::::new(); - let mut res = Vec::::new(); + res.push(Body::Data(Data::Exists(sum.exists))); - res.push(ImapRes::Data(Data::Exists(sum.exists))); - - res.push(ImapRes::Data(Data::Recent(sum.recent))); + res.push(Body::Data(Data::Recent(sum.recent))); let mut flags: Vec = sum.flags.map(|f| match f.chars().next() { Some('\\') => None, @@ -144,7 +126,7 @@ impl<'a> StateContext<'a> { }).flatten().collect(); flags.extend_from_slice(&DEFAULT_FLAGS); - res.push(ImapRes::Data(Data::Flags(flags.clone()))); + res.push(Body::Data(Data::Flags(flags.clone()))); let uid_validity = Status::ok( None, @@ -152,14 +134,14 @@ impl<'a> StateContext<'a> { "UIDs valid" ) .map_err(Error::msg)?; - res.push(ImapRes::Status(uid_validity)); + res.push(Body::Status(uid_validity)); let next_uid = Status::ok( None, Some(Code::UidNext(sum.next)), "Predict next UID" ).map_err(Error::msg)?; - res.push(ImapRes::Status(next_uid)); + res.push(Body::Status(next_uid)); if let Some(unseen) = sum.unseen { let status_unseen = Status::ok( @@ -168,7 +150,7 @@ impl<'a> StateContext<'a> { "First unseen UID", ) .map_err(Error::msg)?; - res.push(ImapRes::Status(status_unseen)); + res.push(Body::Status(status_unseen)); } flags.push(Flag::Permanent); @@ -177,16 +159,11 @@ impl<'a> StateContext<'a> { Some(Code::PermanentFlags(flags)), "Flags permitted", ).map_err(Error::msg)?; - res.push(ImapRes::Status(permanent_flags)); + res.push(Body::Status(permanent_flags)); - let last = Status::ok( - Some(self.tag.clone()), - Some(Code::ReadWrite), - "Select completed", - ).map_err(Error::msg)?; - res.push(ImapRes::Status(last)); - - let tr = flow::Transition::Select(mb); - Ok((res, tr)) + Ok(( + Response::ok("Select completed")?.with_body(res), + flow::Transition::Select(mb), + )) } } diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs index e44bf36..e013eaa 100644 --- a/src/imap/command/selected.rs +++ b/src/imap/command/selected.rs @@ -17,13 +17,12 @@ pub async fn dispatch<'a>( mailbox: &'a Mailbox, ) -> Result<(Response, flow::Transition)> { let ctx = StateContext { - tag: &inner.req.tag, inner, user, mailbox, }; - match &ctx.inner.req.body { + match &ctx.inner.req.command.body { CommandBody::Fetch { sequence_set, attributes, @@ -39,7 +38,6 @@ struct StateContext<'a> { inner: InnerContext<'a>, user: &'a flow::User, mailbox: &'a Mailbox, - tag: &'a Tag, } impl<'a> StateContext<'a> { @@ -50,9 +48,7 @@ impl<'a> StateContext<'a> { uid: &bool, ) -> Result<(Response, flow::Transition)> { Ok(( - vec![ImapRes::Status( - Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?, - )], + Response::bad("Not implemented")?, flow::Transition::No, )) } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 4b44140..63f0220 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -92,7 +92,7 @@ impl Service for Connection { } fn call(&mut self, req: Request) -> Self::Future { - tracing::debug!("Got request: {:#?}", req); + tracing::debug!("Got request: {:#?}", req.command); self.session.process(req) } } diff --git a/src/imap/session.rs b/src/imap/session.rs index 2ff4117..dfef0f4 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -40,7 +40,6 @@ impl Manager { pub fn process(&self, req: Request) -> BoxFuture<'static, Result> { let (tx, rx) = oneshot::channel(); - let tag = req.tag.clone(); let msg = Message { req, tx }; // We use try_send on a bounded channel to protect the daemons from DoS. @@ -51,17 +50,13 @@ impl Manager { Ok(()) => (), Err(TrySendError::Full(_)) => { return async { - Status::bad(Some(tag), None, "Too fast! Send less pipelined requests!") - .map(|s| vec![ImapRes::Status(s)]) - .map_err(|e| BalError::Text(e.to_string())) + Response::bad("Too fast! Send less pipelined requests.") } .boxed() } Err(TrySendError::Closed(_)) => { return async { - Status::bad(Some(tag), None, "The session task has exited") - .map(|s| vec![ImapRes::Status(s)]) - .map_err(|e| BalError::Text(e.to_string())) + Response::bad("Session task has existed.") } .boxed() } @@ -73,9 +68,7 @@ impl Manager { Ok(r) => r, Err(e) => { tracing::warn!("Got error {:#?}", e); - Status::bad(Some(tag), None, "No response from the session handler") - .map(|s| vec![ImapRes::Status(s)]) - .map_err(|e| BalError::Text(e.to_string())) + Response::bad("No response from the session handler") } } } @@ -122,21 +115,16 @@ impl Instance { }; // Command behavior is modulated by the state. - // To prevent state error, we handle the same command in separate code path depending - // on the State. + // To prevent state error, we handle the same command in separate code paths. let ctrl = match &self.state { flow::State::NotAuthenticated => anonymous::dispatch(ctx).await, flow::State::Authenticated(user) => authenticated::dispatch(ctx, user).await, flow::State::Selected(user, mailbox) => { selected::dispatch(ctx, user, mailbox).await } - _ => Status::bad( - Some(ctx.req.tag.clone()), - None, - "No commands are allowed in the LOGOUT state.", - ) - .map(|s| (vec![ImapRes::Status(s)], flow::Transition::No)) - .map_err(Error::msg), + _ => Response::bad("No commands are allowed in the LOGOUT state.") + .map(|r| (r, flow::Transition::No)) + .map_err(Error::msg), }; // Process result @@ -152,9 +140,7 @@ impl Instance { Ok(be) => Err(be), Err(e) => { tracing::warn!(error=%e, "internal.error"); - Status::bad(Some(msg.req.tag.clone()), None, "Internal error") - .map(|s| vec![ImapRes::Status(s)]) - .map_err(|e| BalError::Text(e.to_string())) + Response::bad("Internal error") } }, };