From 9b7d999fd52dc3410b97ca831238b084650be1b3 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Tue, 12 Jul 2022 15:59:13 +0200 Subject: Implement STATUS --- src/imap/command/authenticated.rs | 75 ++++++++++++++++++++++++++++++++------ src/imap/mailbox_view.rs | 76 +++++++++++++++++++++++---------------- 2 files changed, 110 insertions(+), 41 deletions(-) (limited to 'src/imap') diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index e7198ee..bc9fddf 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -2,11 +2,12 @@ use std::collections::BTreeMap; use std::sync::Arc; use anyhow::{anyhow, Result}; +use boitalettres::proto::res::body::Data as Body; use boitalettres::proto::{Request, Response}; use imap_codec::types::command::{CommandBody, StatusAttribute}; use imap_codec::types::flag::FlagNameAttribute; use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec}; -use imap_codec::types::response::{Code, Data}; +use imap_codec::types::response::{Code, Data, StatusAttributeValue}; use crate::imap::command::anonymous; use crate::imap::flow; @@ -165,26 +166,78 @@ impl<'a> AuthenticatedContext<'a> { async fn status( self, mailbox: &MailboxCodec, - _attributes: &[StatusAttribute], + attributes: &[StatusAttribute], ) -> Result<(Response, flow::Transition)> { - let _name = String::try_from(mailbox.clone())?; + let name = String::try_from(mailbox.clone())?; + 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, + )) + } + }; + + let (view, _data) = MailboxView::new(mb).await?; + + let mut ret_attrs = vec![]; + for attr in attributes.iter() { + ret_attrs.push(match attr { + StatusAttribute::Messages => StatusAttributeValue::Messages(view.exists()?), + StatusAttribute::Unseen => { + StatusAttributeValue::Unseen(view.unseen().map(|x| x.get()).unwrap_or(0)) + } + StatusAttribute::Recent => StatusAttributeValue::Recent(view.recent()?), + StatusAttribute::UidNext => StatusAttributeValue::UidNext(view.uidnext()), + StatusAttribute::UidValidity => { + StatusAttributeValue::UidValidity(view.uidvalidity()) + } + }); + } + + let data = vec![Body::Data(Data::Status { + mailbox: mailbox.clone(), + attributes: ret_attrs, + })]; - Ok((Response::bad("Not implemented")?, flow::Transition::None)) + Ok(( + Response::ok("STATUS completed")?.with_body(data), + flow::Transition::None, + )) } async fn subscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> { - let _name = String::try_from(mailbox.clone())?; + let name = String::try_from(mailbox.clone())?; - Ok((Response::bad("Not implemented")?, flow::Transition::None)) + if self.user.has_mailbox(&name).await? { + Ok((Response::ok("SUBSCRIBE complete")?, flow::Transition::None)) + } else { + Ok(( + Response::bad(&format!("Mailbox {} does not exist", name))?, + flow::Transition::None, + )) + } } async fn unsubscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> { - let _name = String::try_from(mailbox.clone())?; + let name = String::try_from(mailbox.clone())?; - Ok(( - Response::bad("Aerogramme does not support unsubscribing from a mailbox")?, - flow::Transition::None, - )) + if self.user.has_mailbox(&name).await? { + Ok(( + Response::bad(&format!( + "Cannot unsubscribe from mailbox {}: not supported by Aerogramme", + name + ))?, + flow::Transition::None, + )) + } else { + Ok(( + Response::bad(&format!("Mailbox {} does not exist", name))?, + flow::Transition::None, + )) + } } /* diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index 0adba18..db6f490 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -19,7 +19,7 @@ use imap_codec::types::sequence::{self, SequenceSet}; use mail_parser::*; use crate::mail::mailbox::Mailbox; -use crate::mail::uidindex::UidIndex; +use crate::mail::uidindex::{ImapUid, ImapUidvalidity, UidIndex}; const DEFAULT_FLAGS: [Flag; 5] = [ Flag::Seen, @@ -55,12 +55,12 @@ impl MailboxView { }; let mut data = Vec::::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(new_view.exists_status()?); + data.push(new_view.recent_status()?); + data.extend(new_view.flags_status()?.into_iter()); + data.push(new_view.uidvalidity_status()?); + data.push(new_view.uidnext_status()?); + if let Some(unseen) = new_view.unseen_status()? { data.push(unseen); } @@ -87,9 +87,9 @@ impl MailboxView { if new_view.known_state.uidvalidity != self.known_state.uidvalidity { // TODO: do we want to push less/more info than this? - data.push(new_view.uidvalidity()?); - data.push(new_view.exists()?); - data.push(new_view.uidnext()?); + data.push(new_view.uidvalidity_status()?); + data.push(new_view.exists_status()?); + data.push(new_view.uidnext_status()?); } else { // Calculate diff between two mailbox states // See example in IMAP RFC in section on NOOP command: @@ -117,7 +117,7 @@ impl MailboxView { // - if new mails arrived, notify client of number of existing mails if new_view.known_state.table.len() != self.known_state.table.len() - n_expunge { - data.push(new_view.exists()?); + data.push(new_view.exists_status()?); } // - if flags changed for existing mails, tell client @@ -315,37 +315,39 @@ impl MailboxView { // ---- /// Produce an OK [UIDVALIDITY _] message corresponding to `known_state` - fn uidvalidity(&self) -> Result { + fn uidvalidity_status(&self) -> Result { let uid_validity = Status::ok( None, - Some(Code::UidValidity(self.known_state.uidvalidity)), + Some(Code::UidValidity(self.uidvalidity())), "UIDs valid", ) .map_err(Error::msg)?; Ok(Body::Status(uid_validity)) } + pub(crate) fn uidvalidity(&self) -> ImapUidvalidity { + self.known_state.uidvalidity + } + /// Produce an OK [UIDNEXT _] message corresponding to `known_state` - fn uidnext(&self) -> Result { + fn uidnext_status(&self) -> Result { let next_uid = Status::ok( None, - Some(Code::UidNext(self.known_state.uidnext)), + Some(Code::UidNext(self.uidnext())), "Predict next UID", ) .map_err(Error::msg)?; Ok(Body::Status(next_uid)) } + pub(crate) fn uidnext(&self) -> ImapUid { + self.known_state.uidnext + } + /// Produces an UNSEEN message (if relevant) corresponding to the /// first unseen message id in `known_state` - fn unseen(&self) -> Result> { - let unseen = self - .known_state - .idx_by_flag - .get(&"$unseen".to_string()) - .and_then(|os| os.get_min()) - .cloned(); - if let Some(unseen) = unseen { + fn unseen_status(&self) -> Result> { + if let Some(unseen) = self.unseen() { let status_unseen = Status::ok(None, Some(Code::Unseen(unseen.clone())), "First unseen UID") .map_err(Error::msg)?; @@ -355,29 +357,43 @@ impl MailboxView { } } + pub(crate) fn unseen(&self) -> Option { + self.known_state + .idx_by_flag + .get(&"$unseen".to_string()) + .and_then(|os| os.get_min()) + .cloned() + } + /// Produce an EXISTS message corresponding to the number of mails /// in `known_state` - fn exists(&self) -> Result { - let exists = u32::try_from(self.known_state.idx_by_uid.len())?; - Ok(Body::Data(Data::Exists(exists))) + fn exists_status(&self) -> Result { + Ok(Body::Data(Data::Exists(self.exists()?))) + } + + pub(crate) fn exists(&self) -> Result { + Ok(u32::try_from(self.known_state.idx_by_uid.len())?) } /// Produce a RECENT message corresponding to the number of /// recent mails in `known_state` - fn recent(&self) -> Result { + fn recent_status(&self) -> Result { + Ok(Body::Data(Data::Recent(self.recent()?))) + } + + pub(crate) fn recent(&self) -> Result { 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))) + Ok(u32::try_from(recent)?) } /// Produce a FLAGS and a PERMANENTFLAGS message that indicates /// the flags that are in `known_state` + default flags - fn flags(&self) -> Result> { + fn flags_status(&self) -> Result> { let mut flags: Vec = self .known_state .idx_by_flag -- cgit v1.2.3