aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2022-07-12 16:35:11 +0200
committerAlex Auvolat <alex@adnab.me>2022-07-12 16:35:11 +0200
commit46d952598474e851ee528515d7a9ffab88d3ad49 (patch)
treea062bc94e8a1841f8d017c97cccbebc1d44cf1a4
parentd4e0e66581ff785e89edd15e2b8d68640f370a0e (diff)
downloadaerogramme-46d952598474e851ee528515d7a9ffab88d3ad49.tar.gz
aerogramme-46d952598474e851ee528515d7a9ffab88d3ad49.zip
Implement APPEND
-rw-r--r--src/imap/command/authenticated.rs63
-rw-r--r--src/imap/command/examined.rs43
-rw-r--r--src/imap/mailbox_view.rs2
-rw-r--r--src/mail/mailbox.rs31
4 files changed, 127 insertions, 12 deletions
diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs
index 6208290..32a8e1e 100644
--- a/src/imap/command/authenticated.rs
+++ b/src/imap/command/authenticated.rs
@@ -1,11 +1,13 @@
use std::collections::BTreeMap;
use std::sync::Arc;
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, 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::core::NonZeroBytes;
+use imap_codec::types::datetime::MyDateTime;
+use imap_codec::types::flag::{Flag, FlagNameAttribute};
use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
use imap_codec::types::response::{Code, Data, StatusAttributeValue};
@@ -13,7 +15,10 @@ use crate::imap::command::anonymous;
use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView;
+use crate::mail::mailbox::Mailbox;
+use crate::mail::uidindex::*;
use crate::mail::user::{User, INBOX, MAILBOX_HIERARCHY_DELIMITER};
+use crate::mail::IMF;
pub struct AuthenticatedContext<'a> {
pub req: &'a Request,
@@ -44,6 +49,12 @@ pub async fn dispatch<'a>(ctx: AuthenticatedContext<'a>) -> Result<(Response, fl
CommandBody::Unsubscribe { mailbox } => ctx.unsubscribe(mailbox).await,
CommandBody::Select { mailbox } => ctx.select(mailbox).await,
CommandBody::Examine { mailbox } => ctx.examine(mailbox).await,
+ CommandBody::Append {
+ mailbox,
+ flags,
+ date,
+ message,
+ } => ctx.append(mailbox, flags, date, message).await,
_ => {
let ctx = anonymous::AnonymousContext {
req: ctx.req,
@@ -316,4 +327,52 @@ impl<'a> AuthenticatedContext<'a> {
flow::Transition::Examine(mb),
))
}
+
+ async fn append(
+ self,
+ mailbox: &MailboxCodec,
+ flags: &[Flag],
+ date: &Option<MyDateTime>,
+ message: &NonZeroBytes,
+ ) -> Result<(Response, flow::Transition)> {
+ match self.append_internal(mailbox, flags, date, message).await {
+ Ok((_mb, uidvalidity, uid)) => Ok((
+ Response::ok("APPEND completed")?.with_extra_code(Code::Other(
+ "APPENDUID".try_into().unwrap(),
+ Some(format!("{} {}", uidvalidity, uid)),
+ )),
+ flow::Transition::None,
+ )),
+ Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)),
+ }
+ }
+
+ pub(crate) async fn append_internal(
+ self,
+ mailbox: &MailboxCodec,
+ flags: &[Flag],
+ date: &Option<MyDateTime>,
+ message: &NonZeroBytes,
+ ) -> Result<(Arc<Mailbox>, ImapUidvalidity, ImapUidvalidity)> {
+ 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 => bail!("Mailbox does not exist"),
+ };
+
+ if date.is_some() {
+ bail!("Cannot set date when appending message");
+ }
+
+ let msg = IMF::try_from(message.as_slice())
+ .map_err(|_| anyhow!("Could not parse e-mail message"))?;
+ let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
+ // TODO: filter allowed flags? ping @Quentin
+
+ let (uidvalidity, uid) = mb.append(msg, None, &flags[..]).await?;
+
+ Ok((mb, uidvalidity, uid))
+ }
}
diff --git a/src/imap/command/examined.rs b/src/imap/command/examined.rs
index 9dba680..ea773de 100644
--- a/src/imap/command/examined.rs
+++ b/src/imap/command/examined.rs
@@ -5,14 +5,19 @@ use boitalettres::proto::Request;
use boitalettres::proto::Response;
use imap_codec::types::command::{CommandBody, SearchKey};
use imap_codec::types::core::Charset;
+use imap_codec::types::core::NonZeroBytes;
+use imap_codec::types::datetime::MyDateTime;
use imap_codec::types::fetch_attributes::MacroOrFetchAttributes;
-
+use imap_codec::types::flag::{Flag, FlagNameAttribute};
+use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
+use imap_codec::types::response::{Code, Data, StatusAttributeValue};
use imap_codec::types::sequence::SequenceSet;
use crate::imap::command::authenticated;
use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView;
+use crate::mail::uidindex::*;
use crate::mail::user::User;
pub struct ExaminedContext<'a> {
@@ -37,6 +42,12 @@ pub async fn dispatch<'a>(ctx: ExaminedContext<'a>) -> Result<(Response, flow::T
uid,
} => ctx.search(charset, criteria, uid).await,
CommandBody::Noop => ctx.noop().await,
+ CommandBody::Append {
+ mailbox,
+ flags,
+ date,
+ message,
+ } => ctx.append(mailbox, flags, date, message).await,
_ => {
let ctx = authenticated::AuthenticatedContext {
req: ctx.req,
@@ -85,4 +96,34 @@ impl<'a> ExaminedContext<'a> {
flow::Transition::None,
))
}
+
+ async fn append(
+ self,
+ mailbox: &MailboxCodec,
+ flags: &[Flag],
+ date: &Option<MyDateTime>,
+ message: &NonZeroBytes,
+ ) -> Result<(Response, flow::Transition)> {
+ let ctx2 = authenticated::AuthenticatedContext {
+ req: self.req,
+ user: self.user,
+ };
+
+ match ctx2.append_internal(mailbox, flags, date, message).await {
+ Ok((mb, uidvalidity, uid)) => {
+ let resp = Response::ok("APPEND completed")?.with_extra_code(Code::Other(
+ "APPENDUID".try_into().unwrap(),
+ Some(format!("{} {}", uidvalidity, uid)),
+ ));
+
+ if Arc::ptr_eq(&mb, &self.mailbox.mailbox) {
+ let data = self.mailbox.update().await?;
+ Ok((resp.with_body(data), flow::Transition::None))
+ } else {
+ Ok((resp, flow::Transition::None))
+ }
+ }
+ Err(e) => Ok((Response::no(&e.to_string())?, flow::Transition::None)),
+ }
+ }
}
diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs
index db6f490..e4df3d0 100644
--- a/src/imap/mailbox_view.rs
+++ b/src/imap/mailbox_view.rs
@@ -37,7 +37,7 @@ const DEFAULT_FLAGS: [Flag; 5] = [
/// 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>,
+ pub(crate) mailbox: Arc<Mailbox>,
known_state: UidIndex,
}
diff --git a/src/mail/mailbox.rs b/src/mail/mailbox.rs
index e80fa34..0e8af70 100644
--- a/src/mail/mailbox.rs
+++ b/src/mail/mailbox.rs
@@ -96,8 +96,13 @@ impl Mailbox {
}
/// Insert an email into the mailbox
- pub async fn append<'a>(&self, msg: IMF<'a>, ident: Option<UniqueIdent>) -> Result<()> {
- self.mbox.write().await.append(msg, ident).await
+ pub async fn append<'a>(
+ &self,
+ msg: IMF<'a>,
+ ident: Option<UniqueIdent>,
+ flags: &[Flag],
+ ) -> Result<(ImapUidvalidity, ImapUid)> {
+ self.mbox.write().await.append(msg, ident, flags).await
}
/// Insert an email into the mailbox, copying it from an existing S3 object
@@ -260,7 +265,12 @@ impl MailboxInternal {
self.uid_index.push(del_flag_op).await
}
- async fn append(&mut self, mail: IMF<'_>, ident: Option<UniqueIdent>) -> Result<()> {
+ async fn append(
+ &mut self,
+ mail: IMF<'_>,
+ ident: Option<UniqueIdent>,
+ flags: &[Flag],
+ ) -> Result<(ImapUidvalidity, ImapUid)> {
let ident = ident.unwrap_or_else(|| gen_ident());
let message_key = gen_key();
@@ -292,13 +302,18 @@ impl MailboxInternal {
)?;
// Add mail to Bayou mail index
- let add_mail_op = self
- .uid_index
- .state()
- .op_mail_add(ident, vec!["\\Unseen".into()]);
+ let uid_state = self.uid_index.state();
+ let add_mail_op = uid_state.op_mail_add(ident, flags.to_vec());
+
+ let uidvalidity = uid_state.uidvalidity;
+ let uid = match add_mail_op {
+ UidIndexOp::MailAdd(_, uid, _) => uid,
+ _ => unreachable!(),
+ };
+
self.uid_index.push(add_mail_op).await?;
- Ok(())
+ Ok((uidvalidity, uid))
}
async fn append_from_s3<'a>(