Implement APPEND

This commit is contained in:
Alex 2022-07-12 16:35:11 +02:00
parent d4e0e66581
commit 46d9525984
Signed by: lx
GPG key ID: 0E496D15096376BE
4 changed files with 127 additions and 12 deletions

View file

@ -1,11 +1,13 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::Arc; use std::sync::Arc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, bail, Result};
use boitalettres::proto::res::body::Data as Body; 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::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::mailbox::{ListMailbox, Mailbox as MailboxCodec};
use imap_codec::types::response::{Code, Data, StatusAttributeValue}; use imap_codec::types::response::{Code, Data, StatusAttributeValue};
@ -13,7 +15,10 @@ use crate::imap::command::anonymous;
use crate::imap::flow; use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView; 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::user::{User, INBOX, MAILBOX_HIERARCHY_DELIMITER};
use crate::mail::IMF;
pub struct AuthenticatedContext<'a> { pub struct AuthenticatedContext<'a> {
pub req: &'a Request, 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::Unsubscribe { mailbox } => ctx.unsubscribe(mailbox).await,
CommandBody::Select { mailbox } => ctx.select(mailbox).await, CommandBody::Select { mailbox } => ctx.select(mailbox).await,
CommandBody::Examine { mailbox } => ctx.examine(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 { let ctx = anonymous::AnonymousContext {
req: ctx.req, req: ctx.req,
@ -316,4 +327,52 @@ impl<'a> AuthenticatedContext<'a> {
flow::Transition::Examine(mb), 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))
}
} }

View file

@ -5,14 +5,19 @@ use boitalettres::proto::Request;
use boitalettres::proto::Response; use boitalettres::proto::Response;
use imap_codec::types::command::{CommandBody, SearchKey}; use imap_codec::types::command::{CommandBody, SearchKey};
use imap_codec::types::core::Charset; 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::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 imap_codec::types::sequence::SequenceSet;
use crate::imap::command::authenticated; use crate::imap::command::authenticated;
use crate::imap::flow; use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView; use crate::imap::mailbox_view::MailboxView;
use crate::mail::uidindex::*;
use crate::mail::user::User; use crate::mail::user::User;
pub struct ExaminedContext<'a> { pub struct ExaminedContext<'a> {
@ -37,6 +42,12 @@ pub async fn dispatch<'a>(ctx: ExaminedContext<'a>) -> Result<(Response, flow::T
uid, uid,
} => ctx.search(charset, criteria, uid).await, } => ctx.search(charset, criteria, uid).await,
CommandBody::Noop => ctx.noop().await, CommandBody::Noop => ctx.noop().await,
CommandBody::Append {
mailbox,
flags,
date,
message,
} => ctx.append(mailbox, flags, date, message).await,
_ => { _ => {
let ctx = authenticated::AuthenticatedContext { let ctx = authenticated::AuthenticatedContext {
req: ctx.req, req: ctx.req,
@ -85,4 +96,34 @@ impl<'a> ExaminedContext<'a> {
flow::Transition::None, 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)),
}
}
} }

View file

@ -37,7 +37,7 @@ const DEFAULT_FLAGS: [Flag; 5] = [
/// what the client knows, and produces IMAP messages to be sent to the /// what the client knows, and produces IMAP messages to be sent to the
/// client that go along updates to `known_state`. /// client that go along updates to `known_state`.
pub struct MailboxView { pub struct MailboxView {
mailbox: Arc<Mailbox>, pub(crate) mailbox: Arc<Mailbox>,
known_state: UidIndex, known_state: UidIndex,
} }

View file

@ -96,8 +96,13 @@ impl Mailbox {
} }
/// Insert an email into the mailbox /// Insert an email into the mailbox
pub async fn append<'a>(&self, msg: IMF<'a>, ident: Option<UniqueIdent>) -> Result<()> { pub async fn append<'a>(
self.mbox.write().await.append(msg, ident).await &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 /// 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 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 ident = ident.unwrap_or_else(|| gen_ident());
let message_key = gen_key(); let message_key = gen_key();
@ -292,13 +302,18 @@ impl MailboxInternal {
)?; )?;
// Add mail to Bayou mail index // Add mail to Bayou mail index
let add_mail_op = self let uid_state = self.uid_index.state();
.uid_index let add_mail_op = uid_state.op_mail_add(ident, flags.to_vec());
.state()
.op_mail_add(ident, vec!["\\Unseen".into()]); let uidvalidity = uid_state.uidvalidity;
let uid = match add_mail_op {
UidIndexOp::MailAdd(_, uid, _) => uid,
_ => unreachable!(),
};
self.uid_index.push(add_mail_op).await?; self.uid_index.push(add_mail_op).await?;
Ok(()) Ok((uidvalidity, uid))
} }
async fn append_from_s3<'a>( async fn append_from_s3<'a>(