Implement APPEND
This commit is contained in:
parent
d4e0e66581
commit
46d9525984
4 changed files with 127 additions and 12 deletions
|
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>(
|
||||||
|
|
Loading…
Reference in a new issue