Implement STATUS

This commit is contained in:
Alex 2022-07-12 15:59:13 +02:00
parent fc2e25ce76
commit 9b7d999fd5
Signed by: lx
GPG key ID: 0E496D15096376BE
4 changed files with 127 additions and 43 deletions

View file

@ -2,11 +2,12 @@ use std::collections::BTreeMap;
use std::sync::Arc; use std::sync::Arc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use boitalettres::proto::res::body::Data as Body;
use boitalettres::proto::{Request, Response}; use boitalettres::proto::{Request, Response};
use imap_codec::types::command::{CommandBody, StatusAttribute}; use imap_codec::types::command::{CommandBody, StatusAttribute};
use imap_codec::types::flag::FlagNameAttribute; use imap_codec::types::flag::FlagNameAttribute;
use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec}; 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::command::anonymous;
use crate::imap::flow; use crate::imap::flow;
@ -165,26 +166,78 @@ impl<'a> AuthenticatedContext<'a> {
async fn status( async fn status(
self, self,
mailbox: &MailboxCodec, mailbox: &MailboxCodec,
_attributes: &[StatusAttribute], attributes: &[StatusAttribute],
) -> Result<(Response, flow::Transition)> { ) -> 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,
))
}
};
Ok((Response::bad("Not implemented")?, 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::ok("STATUS completed")?.with_body(data),
flow::Transition::None,
))
} }
async fn subscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> { 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)> { async fn unsubscribe(self, mailbox: &MailboxCodec) -> Result<(Response, flow::Transition)> {
let _name = String::try_from(mailbox.clone())?; let name = String::try_from(mailbox.clone())?;
if self.user.has_mailbox(&name).await? {
Ok(( Ok((
Response::bad("Aerogramme does not support unsubscribing from a mailbox")?, Response::bad(&format!(
"Cannot unsubscribe from mailbox {}: not supported by Aerogramme",
name
))?,
flow::Transition::None, flow::Transition::None,
)) ))
} else {
Ok((
Response::bad(&format!("Mailbox {} does not exist", name))?,
flow::Transition::None,
))
}
} }
/* /*

View file

@ -19,7 +19,7 @@ use imap_codec::types::sequence::{self, SequenceSet};
use mail_parser::*; use mail_parser::*;
use crate::mail::mailbox::Mailbox; use crate::mail::mailbox::Mailbox;
use crate::mail::uidindex::UidIndex; use crate::mail::uidindex::{ImapUid, ImapUidvalidity, UidIndex};
const DEFAULT_FLAGS: [Flag; 5] = [ const DEFAULT_FLAGS: [Flag; 5] = [
Flag::Seen, Flag::Seen,
@ -55,12 +55,12 @@ impl MailboxView {
}; };
let mut data = Vec::<Body>::new(); let mut data = Vec::<Body>::new();
data.push(new_view.exists()?); data.push(new_view.exists_status()?);
data.push(new_view.recent()?); data.push(new_view.recent_status()?);
data.extend(new_view.flags()?.into_iter()); data.extend(new_view.flags_status()?.into_iter());
data.push(new_view.uidvalidity()?); data.push(new_view.uidvalidity_status()?);
data.push(new_view.uidnext()?); data.push(new_view.uidnext_status()?);
if let Some(unseen) = new_view.unseen()? { if let Some(unseen) = new_view.unseen_status()? {
data.push(unseen); data.push(unseen);
} }
@ -87,9 +87,9 @@ impl MailboxView {
if new_view.known_state.uidvalidity != self.known_state.uidvalidity { if new_view.known_state.uidvalidity != self.known_state.uidvalidity {
// TODO: do we want to push less/more info than this? // TODO: do we want to push less/more info than this?
data.push(new_view.uidvalidity()?); data.push(new_view.uidvalidity_status()?);
data.push(new_view.exists()?); data.push(new_view.exists_status()?);
data.push(new_view.uidnext()?); data.push(new_view.uidnext_status()?);
} else { } else {
// Calculate diff between two mailbox states // Calculate diff between two mailbox states
// See example in IMAP RFC in section on NOOP command: // 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 mails arrived, notify client of number of existing mails
if new_view.known_state.table.len() != self.known_state.table.len() - n_expunge { 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 // - if flags changed for existing mails, tell client
@ -315,37 +315,39 @@ impl MailboxView {
// ---- // ----
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state` /// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
fn uidvalidity(&self) -> Result<Body> { fn uidvalidity_status(&self) -> Result<Body> {
let uid_validity = Status::ok( let uid_validity = Status::ok(
None, None,
Some(Code::UidValidity(self.known_state.uidvalidity)), Some(Code::UidValidity(self.uidvalidity())),
"UIDs valid", "UIDs valid",
) )
.map_err(Error::msg)?; .map_err(Error::msg)?;
Ok(Body::Status(uid_validity)) Ok(Body::Status(uid_validity))
} }
pub(crate) fn uidvalidity(&self) -> ImapUidvalidity {
self.known_state.uidvalidity
}
/// Produce an OK [UIDNEXT _] message corresponding to `known_state` /// Produce an OK [UIDNEXT _] message corresponding to `known_state`
fn uidnext(&self) -> Result<Body> { fn uidnext_status(&self) -> Result<Body> {
let next_uid = Status::ok( let next_uid = Status::ok(
None, None,
Some(Code::UidNext(self.known_state.uidnext)), Some(Code::UidNext(self.uidnext())),
"Predict next UID", "Predict next UID",
) )
.map_err(Error::msg)?; .map_err(Error::msg)?;
Ok(Body::Status(next_uid)) Ok(Body::Status(next_uid))
} }
pub(crate) fn uidnext(&self) -> ImapUid {
self.known_state.uidnext
}
/// Produces an UNSEEN message (if relevant) corresponding to the /// Produces an UNSEEN message (if relevant) corresponding to the
/// first unseen message id in `known_state` /// first unseen message id in `known_state`
fn unseen(&self) -> Result<Option<Body>> { fn unseen_status(&self) -> Result<Option<Body>> {
let unseen = self if let Some(unseen) = self.unseen() {
.known_state
.idx_by_flag
.get(&"$unseen".to_string())
.and_then(|os| os.get_min())
.cloned();
if let Some(unseen) = unseen {
let status_unseen = let status_unseen =
Status::ok(None, Some(Code::Unseen(unseen.clone())), "First unseen UID") Status::ok(None, Some(Code::Unseen(unseen.clone())), "First unseen UID")
.map_err(Error::msg)?; .map_err(Error::msg)?;
@ -355,29 +357,43 @@ impl MailboxView {
} }
} }
pub(crate) fn unseen(&self) -> Option<ImapUid> {
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 /// Produce an EXISTS message corresponding to the number of mails
/// in `known_state` /// in `known_state`
fn exists(&self) -> Result<Body> { fn exists_status(&self) -> Result<Body> {
let exists = u32::try_from(self.known_state.idx_by_uid.len())?; Ok(Body::Data(Data::Exists(self.exists()?)))
Ok(Body::Data(Data::Exists(exists))) }
pub(crate) fn exists(&self) -> Result<u32> {
Ok(u32::try_from(self.known_state.idx_by_uid.len())?)
} }
/// Produce a RECENT message corresponding to the number of /// Produce a RECENT message corresponding to the number of
/// recent mails in `known_state` /// recent mails in `known_state`
fn recent(&self) -> Result<Body> { fn recent_status(&self) -> Result<Body> {
Ok(Body::Data(Data::Recent(self.recent()?)))
}
pub(crate) fn recent(&self) -> Result<u32> {
let recent = self let recent = self
.known_state .known_state
.idx_by_flag .idx_by_flag
.get(&"\\Recent".to_string()) .get(&"\\Recent".to_string())
.map(|os| os.len()) .map(|os| os.len())
.unwrap_or(0); .unwrap_or(0);
let recent = u32::try_from(recent)?; Ok(u32::try_from(recent)?)
Ok(Body::Data(Data::Recent(recent)))
} }
/// Produce a FLAGS and a PERMANENTFLAGS message that indicates /// Produce a FLAGS and a PERMANENTFLAGS message that indicates
/// the flags that are in `known_state` + default flags /// the flags that are in `known_state` + default flags
fn flags(&self) -> Result<Vec<Body>> { fn flags_status(&self) -> Result<Vec<Body>> {
let mut flags: Vec<Flag> = self let mut flags: Vec<Flag> = self
.known_state .known_state
.idx_by_flag .idx_by_flag

View file

@ -16,7 +16,7 @@ use crate::time::now_msec;
/// required by Aerogramme. /// required by Aerogramme.
/// Their main property is to be unique without having to rely /// Their main property is to be unique without having to rely
/// on synchronization between IMAP processes. /// on synchronization between IMAP processes.
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct UniqueIdent(pub [u8; 24]); pub struct UniqueIdent(pub [u8; 24]);
struct IdentGenerator { struct IdentGenerator {
@ -78,6 +78,12 @@ impl std::fmt::Display for UniqueIdent {
} }
} }
impl std::fmt::Debug for UniqueIdent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
impl FromStr for UniqueIdent { impl FromStr for UniqueIdent {
type Err = &'static str; type Err = &'static str;

View file

@ -71,7 +71,10 @@ impl User {
/// Opens an existing mailbox given its IMAP name. /// Opens an existing mailbox given its IMAP name.
pub async fn open_mailbox(&self, name: &str) -> Result<Option<Arc<Mailbox>>> { pub async fn open_mailbox(&self, name: &str) -> Result<Option<Arc<Mailbox>>> {
let (mut list, ct) = self.load_mailbox_list().await?; let (mut list, ct) = self.load_mailbox_list().await?;
eprintln!("List of mailboxes: {:?}", &list.0); eprintln!("List of mailboxes:");
for ent in list.0.iter() {
eprintln!(" - {:?}", ent);
}
if let Some((uidvalidity, Some(mbid))) = list.get_mailbox(name) { if let Some((uidvalidity, Some(mbid))) = list.get_mailbox(name) {
let mb_opt = self.open_mailbox_by_id(mbid, uidvalidity).await?; let mb_opt = self.open_mailbox_by_id(mbid, uidvalidity).await?;
@ -88,6 +91,12 @@ impl User {
} }
} }
/// Check whether mailbox exists
pub async fn has_mailbox(&self, name: &str) -> Result<bool> {
let (list, _ct) = self.load_mailbox_list().await?;
Ok(list.has_mailbox(name))
}
/// Creates a new mailbox in the user's IMAP namespace. /// Creates a new mailbox in the user's IMAP namespace.
pub async fn create_mailbox(&self, name: &str) -> Result<()> { pub async fn create_mailbox(&self, name: &str) -> Result<()> {
if name.ends_with(MAILBOX_HIERARCHY_DELIMITER) { if name.ends_with(MAILBOX_HIERARCHY_DELIMITER) {