diff options
Diffstat (limited to 'src/imap')
-rw-r--r-- | src/imap/command/authenticated.rs | 68 | ||||
-rw-r--r-- | src/imap/command/selected.rs | 3 | ||||
-rw-r--r-- | src/imap/flow.rs | 5 | ||||
-rw-r--r-- | src/imap/mailbox_view.rs | 154 | ||||
-rw-r--r-- | src/imap/mod.rs | 1 |
5 files changed, 173 insertions, 58 deletions
diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index 47df5be..443edda 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -8,18 +8,11 @@ use imap_codec::types::response::{Code, Data, Status}; use crate::imap::command::anonymous; use crate::imap::flow; +use crate::imap::mailbox_view::MailboxView; use crate::mail::mailbox::Mailbox; use crate::mail::user::User; -const DEFAULT_FLAGS: [Flag; 5] = [ - Flag::Seen, - Flag::Answered, - Flag::Flagged, - Flag::Deleted, - Flag::Draft, -]; - pub struct AuthenticatedContext<'a> { pub req: &'a Request, pub user: &'a User, @@ -96,59 +89,24 @@ impl<'a> AuthenticatedContext<'a> { async fn select(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> { let name = String::try_from(mailbox.clone())?; - let mut mb = self.user.open_mailbox(name)?; + let mb_opt = self.user.open_mailbox(&name).await?; + let mb = match mb_opt { + Some(mb) => mb, + None => { + return Ok(( + Response::no("Mailbox does not exist")?, + flow::Transition::None, + )) + } + }; tracing::info!(username=%self.user.username, mailbox=%name, "mailbox.selected"); - let sum = mb.summary().await?; - tracing::trace!(summary=%sum, "mailbox.summary"); - - let mut res = Vec::<Body>::new(); - - res.push(Body::Data(Data::Exists(sum.exists))); - - res.push(Body::Data(Data::Recent(sum.recent))); - - let mut flags: Vec<Flag> = sum.flags.map(|f| match f.chars().next() { - Some('\\') => None, - Some('$') if f == "$unseen" => None, - Some(_) => match Atom::try_from(f.clone()) { - Err(_) => { - tracing::error!(username=%self.user.username, mailbox=%name, flag=%f, "Unable to encode flag as IMAP atom"); - None - }, - Ok(a) => Some(Flag::Keyword(a)), - }, - None => None, - }).flatten().collect(); - flags.extend_from_slice(&DEFAULT_FLAGS); - - res.push(Body::Data(Data::Flags(flags.clone()))); - - let uid_validity = Status::ok(None, Some(Code::UidValidity(sum.validity)), "UIDs valid") - .map_err(Error::msg)?; - 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(Body::Status(next_uid)); - - if let Some(unseen) = sum.unseen { - let status_unseen = - Status::ok(None, Some(Code::Unseen(unseen.clone())), "First unseen UID") - .map_err(Error::msg)?; - res.push(Body::Status(status_unseen)); - } - - flags.push(Flag::Permanent); - let permanent_flags = - Status::ok(None, Some(Code::PermanentFlags(flags)), "Flags permitted") - .map_err(Error::msg)?; - res.push(Body::Status(permanent_flags)); + let (mb, data) = MailboxView::new(mb).await?; Ok(( Response::ok("Select completed")? .with_extra_code(Code::ReadWrite) - .with_body(res), + .with_body(data), flow::Transition::Select(mb), )) } diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs index b1bba23..4e3ff2f 100644 --- a/src/imap/command/selected.rs +++ b/src/imap/command/selected.rs @@ -9,6 +9,7 @@ use imap_codec::types::sequence::SequenceSet; use crate::imap::command::authenticated; use crate::imap::flow; +use crate::imap::mailbox_view::MailboxView; use crate::mail::mailbox::Mailbox; use crate::mail::user::User; @@ -16,7 +17,7 @@ use crate::mail::user::User; pub struct SelectedContext<'a> { pub req: &'a Request, pub user: &'a User, - pub mailbox: &'a mut Mailbox, + pub mailbox: &'a mut MailboxView, } pub async fn dispatch<'a>(ctx: SelectedContext<'a>) -> Result<(Response, flow::Transition)> { diff --git a/src/imap/flow.rs b/src/imap/flow.rs index 0fe6f92..c9d7e40 100644 --- a/src/imap/flow.rs +++ b/src/imap/flow.rs @@ -1,6 +1,7 @@ use std::error::Error as StdError; use std::fmt; +use crate::imap::mailbox_view::MailboxView; use crate::mail::mailbox::Mailbox; use crate::mail::user::User; @@ -18,14 +19,14 @@ impl StdError for Error {} pub enum State { NotAuthenticated, Authenticated(User), - Selected(User, Mailbox), + Selected(User, MailboxView), Logout, } pub enum Transition { None, Authenticate(User), - Select(Mailbox), + Select(MailboxView), Unselect, Logout, } diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs new file mode 100644 index 0000000..ec5580d --- /dev/null +++ b/src/imap/mailbox_view.rs @@ -0,0 +1,154 @@ +use std::sync::Arc; + +use anyhow::{Error, Result}; +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; +use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec}; +use imap_codec::types::response::{Code, Data, Status}; + +use crate::mail::mailbox::{Mailbox, Summary}; +use crate::mail::uidindex::UidIndex; + +const DEFAULT_FLAGS: [Flag; 5] = [ + Flag::Seen, + Flag::Answered, + Flag::Flagged, + Flag::Deleted, + Flag::Draft, +]; + +/// A MailboxView is responsible for giving the client the information +/// it needs about a mailbox, such as an initial summary of the mailbox's +/// content and continuous updates indicating when the content +/// of the mailbox has been changed. +/// To do this, it keeps a variable `known_state` that corresponds to +/// what the client knows, and produces IMAP messages to be sent to the +/// client that go along updates to `known_state`. +pub struct MailboxView { + mailbox: Arc<Mailbox>, + known_state: UidIndex, +} + +impl MailboxView { + /// Creates a new IMAP view into a mailbox. + /// Generates the necessary IMAP messages so that the client + /// has a satisfactory summary of the current mailbox's state. + pub async fn new(mailbox: Arc<Mailbox>) -> Result<(Self, Vec<Body>)> { + let state = mailbox.current_uid_index().await; + + let new_view = Self { + mailbox, + known_state: state, + }; + + let mut data = Vec::<Body>::new(); + data.push(new_view.exists()?); + data.push(new_view.recent()?); + data.extend(new_view.flags()?.into_iter()); + data.push(new_view.uidvalidity()?); + data.push(new_view.uidnext()?); + if let Some(unseen) = new_view.unseen()? { + data.push(unseen); + } + + Ok((new_view, data)) + } + + // ---- + + /// Produce an OK [UIDVALIDITY _] message corresponding to `known_state` + fn uidvalidity(&self) -> Result<Body> { + let uid_validity = Status::ok( + None, + Some(Code::UidValidity(self.known_state.uidvalidity)), + "UIDs valid", + ) + .map_err(Error::msg)?; + Ok(Body::Status(uid_validity)) + } + + /// Produce an OK [UIDNEXT _] message corresponding to `known_state` + fn uidnext(&self) -> Result<Body> { + let next_uid = Status::ok( + None, + Some(Code::UidNext(self.known_state.uidnext)), + "Predict next UID", + ) + .map_err(Error::msg)?; + Ok(Body::Status(next_uid)) + } + + /// Produces an UNSEEN message (if relevant) corresponding to the + /// first unseen message id in `known_state` + fn unseen(&self) -> Result<Option<Body>> { + let unseen = self + .known_state + .idx_by_flag + .get(&"$unseen".to_string()) + .and_then(|os| os.get_min()) + .cloned(); + if let Some(unseen) = unseen { + let status_unseen = + Status::ok(None, Some(Code::Unseen(unseen.clone())), "First unseen UID") + .map_err(Error::msg)?; + Ok(Some(Body::Status(status_unseen))) + } else { + Ok(None) + } + } + + /// Produce an EXISTS message corresponding to the number of mails + /// in `known_state` + fn exists(&self) -> Result<Body> { + let exists = u32::try_from(self.known_state.idx_by_uid.len())?; + Ok(Body::Data(Data::Exists(exists))) + } + + /// Produce a RECENT message corresponding to the number of + /// recent mails in `known_state` + fn recent(&self) -> Result<Body> { + let recent = self + .known_state + .idx_by_flag + .get(&"\\Recent".to_string()) + .map(|os| os.len()) + .unwrap_or(0); + let recent = u32::try_from(recent)?; + Ok(Body::Data(Data::Recent(recent))) + } + + /// Produce a FLAGS and a PERMANENTFLAGS message that indicates + /// the flags that are in `known_state` + default flags + fn flags(&self) -> Result<Vec<Body>> { + let mut flags: Vec<Flag> = self + .known_state + .idx_by_flag + .flags() + .map(|f| match f.chars().next() { + Some('\\') => None, + Some('$') if f == "$unseen" => None, + Some(_) => match Atom::try_from(f.clone()) { + Err(_) => { + tracing::error!(flag=%f, "Unable to encode flag as IMAP atom"); + None + } + Ok(a) => Some(Flag::Keyword(a)), + }, + None => None, + }) + .flatten() + .collect(); + flags.extend_from_slice(&DEFAULT_FLAGS); + let mut ret = vec![Body::Data(Data::Flags(flags.clone()))]; + + flags.push(Flag::Permanent); + let permanent_flags = + Status::ok(None, Some(Code::PermanentFlags(flags)), "Flags permitted") + .map_err(Error::msg)?; + ret.push(Body::Status(permanent_flags)); + + Ok(ret) + } +} diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 0e9f49a..f85bcc6 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -1,5 +1,6 @@ mod command; mod flow; +mod mailbox_view; mod session; use std::task::{Context, Poll}; |