Implement some IMAP extensions #50
8 changed files with 123 additions and 27 deletions
|
@ -4,9 +4,9 @@ use imap_codec::imap_types::core::AString;
|
||||||
use imap_codec::imap_types::response::Code;
|
use imap_codec::imap_types::response::Code;
|
||||||
use imap_codec::imap_types::secret::Secret;
|
use imap_codec::imap_types::secret::Secret;
|
||||||
|
|
||||||
|
use crate::imap::capability::ServerCapability;
|
||||||
use crate::imap::command::anystate;
|
use crate::imap::command::anystate;
|
||||||
use crate::imap::flow;
|
use crate::imap::flow;
|
||||||
use crate::imap::capability::ServerCapability;
|
|
||||||
use crate::imap::response::Response;
|
use crate::imap::response::Response;
|
||||||
use crate::login::ArcLoginProvider;
|
use crate::login::ArcLoginProvider;
|
||||||
use crate::mail::user::User;
|
use crate::mail::user::User;
|
||||||
|
@ -23,9 +23,9 @@ pub async fn dispatch(ctx: AnonymousContext<'_>) -> Result<(Response<'static>, f
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any State
|
// Any State
|
||||||
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
|
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
|
||||||
CommandBody::Capability => anystate::capability(
|
CommandBody::Capability => {
|
||||||
ctx.req.tag.clone(),
|
anystate::capability(ctx.req.tag.clone(), ctx.server_capabilities)
|
||||||
ctx.server_capabilities),
|
}
|
||||||
CommandBody::Logout => anystate::logout(),
|
CommandBody::Logout => anystate::logout(),
|
||||||
|
|
||||||
// Specific to anonymous context (3 commands)
|
// Specific to anonymous context (3 commands)
|
||||||
|
|
|
@ -2,11 +2,14 @@ use anyhow::Result;
|
||||||
use imap_codec::imap_types::core::Tag;
|
use imap_codec::imap_types::core::Tag;
|
||||||
use imap_codec::imap_types::response::Data;
|
use imap_codec::imap_types::response::Data;
|
||||||
|
|
||||||
use crate::imap::flow;
|
|
||||||
use crate::imap::capability::ServerCapability;
|
use crate::imap::capability::ServerCapability;
|
||||||
|
use crate::imap::flow;
|
||||||
use crate::imap::response::Response;
|
use crate::imap::response::Response;
|
||||||
|
|
||||||
pub(crate) fn capability(tag: Tag<'static>, cap: &ServerCapability) -> Result<(Response<'static>, flow::Transition)> {
|
pub(crate) fn capability(
|
||||||
|
tag: Tag<'static>,
|
||||||
|
cap: &ServerCapability,
|
||||||
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let res = Response::build()
|
let res = Response::build()
|
||||||
.tag(tag)
|
.tag(tag)
|
||||||
.message("Server capabilities")
|
.message("Server capabilities")
|
||||||
|
|
|
@ -10,11 +10,11 @@ use imap_codec::imap_types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
|
||||||
use imap_codec::imap_types::response::{Code, CodeOther, Data};
|
use imap_codec::imap_types::response::{Code, CodeOther, Data};
|
||||||
use imap_codec::imap_types::status::{StatusDataItem, StatusDataItemName};
|
use imap_codec::imap_types::status::{StatusDataItem, StatusDataItemName};
|
||||||
|
|
||||||
|
use crate::imap::capability::ServerCapability;
|
||||||
use crate::imap::command::{anystate, MailboxName};
|
use crate::imap::command::{anystate, MailboxName};
|
||||||
use crate::imap::flow;
|
use crate::imap::flow;
|
||||||
use crate::imap::mailbox_view::MailboxView;
|
use crate::imap::mailbox_view::MailboxView;
|
||||||
use crate::imap::response::Response;
|
use crate::imap::response::Response;
|
||||||
use crate::imap::capability::ServerCapability;
|
|
||||||
|
|
||||||
use crate::mail::mailbox::Mailbox;
|
use crate::mail::mailbox::Mailbox;
|
||||||
use crate::mail::uidindex::*;
|
use crate::mail::uidindex::*;
|
||||||
|
@ -33,9 +33,9 @@ pub async fn dispatch<'a>(
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any state
|
// Any state
|
||||||
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
|
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
|
||||||
CommandBody::Capability => anystate::capability(
|
CommandBody::Capability => {
|
||||||
ctx.req.tag.clone(),
|
anystate::capability(ctx.req.tag.clone(), ctx.server_capabilities)
|
||||||
ctx.server_capabilities),
|
}
|
||||||
CommandBody::Logout => anystate::logout(),
|
CommandBody::Logout => anystate::logout(),
|
||||||
|
|
||||||
// Specific to this state (11 commands)
|
// Specific to this state (11 commands)
|
||||||
|
|
|
@ -7,11 +7,11 @@ use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames;
|
||||||
use imap_codec::imap_types::search::SearchKey;
|
use imap_codec::imap_types::search::SearchKey;
|
||||||
use imap_codec::imap_types::sequence::SequenceSet;
|
use imap_codec::imap_types::sequence::SequenceSet;
|
||||||
|
|
||||||
|
use crate::imap::capability::ServerCapability;
|
||||||
use crate::imap::command::{anystate, authenticated};
|
use crate::imap::command::{anystate, authenticated};
|
||||||
use crate::imap::flow;
|
use crate::imap::flow;
|
||||||
use crate::imap::mailbox_view::MailboxView;
|
use crate::imap::mailbox_view::MailboxView;
|
||||||
use crate::imap::response::Response;
|
use crate::imap::response::Response;
|
||||||
use crate::imap::capability::ServerCapability;
|
|
||||||
use crate::mail::user::User;
|
use crate::mail::user::User;
|
||||||
|
|
||||||
pub struct ExaminedContext<'a> {
|
pub struct ExaminedContext<'a> {
|
||||||
|
@ -25,10 +25,9 @@ pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, fl
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any State
|
// Any State
|
||||||
// noop is specific to this state
|
// noop is specific to this state
|
||||||
CommandBody::Capability => anystate::capability(
|
CommandBody::Capability => {
|
||||||
ctx.req.tag.clone(),
|
anystate::capability(ctx.req.tag.clone(), ctx.server_capabilities)
|
||||||
ctx.server_capabilities,
|
}
|
||||||
),
|
|
||||||
CommandBody::Logout => anystate::logout(),
|
CommandBody::Logout => anystate::logout(),
|
||||||
|
|
||||||
// Specific to the EXAMINE state (specialization of the SELECTED state)
|
// Specific to the EXAMINE state (specialization of the SELECTED state)
|
||||||
|
|
|
@ -10,11 +10,11 @@ use imap_codec::imap_types::response::{Code, CodeOther};
|
||||||
use imap_codec::imap_types::search::SearchKey;
|
use imap_codec::imap_types::search::SearchKey;
|
||||||
use imap_codec::imap_types::sequence::SequenceSet;
|
use imap_codec::imap_types::sequence::SequenceSet;
|
||||||
|
|
||||||
|
use crate::imap::capability::ServerCapability;
|
||||||
use crate::imap::command::{anystate, authenticated, MailboxName};
|
use crate::imap::command::{anystate, authenticated, MailboxName};
|
||||||
use crate::imap::flow;
|
use crate::imap::flow;
|
||||||
use crate::imap::mailbox_view::MailboxView;
|
use crate::imap::mailbox_view::MailboxView;
|
||||||
use crate::imap::response::Response;
|
use crate::imap::response::Response;
|
||||||
use crate::imap::capability::ServerCapability;
|
|
||||||
|
|
||||||
use crate::mail::user::User;
|
use crate::mail::user::User;
|
||||||
|
|
||||||
|
@ -31,10 +31,9 @@ pub async fn dispatch<'a>(
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any State
|
// Any State
|
||||||
// noop is specific to this state
|
// noop is specific to this state
|
||||||
CommandBody::Capability => anystate::capability(
|
CommandBody::Capability => {
|
||||||
ctx.req.tag.clone(),
|
anystate::capability(ctx.req.tag.clone(), ctx.server_capabilities)
|
||||||
ctx.server_capabilities,
|
}
|
||||||
),
|
|
||||||
CommandBody::Logout => anystate::logout(),
|
CommandBody::Logout => anystate::logout(),
|
||||||
|
|
||||||
// Specific to this state (7 commands + NOOP)
|
// Specific to this state (7 commands + NOOP)
|
||||||
|
@ -63,6 +62,11 @@ pub async fn dispatch<'a>(
|
||||||
mailbox,
|
mailbox,
|
||||||
uid,
|
uid,
|
||||||
} => ctx.copy(sequence_set, mailbox, uid).await,
|
} => ctx.copy(sequence_set, mailbox, uid).await,
|
||||||
|
CommandBody::Move {
|
||||||
|
sequence_set,
|
||||||
|
mailbox,
|
||||||
|
uid,
|
||||||
|
} => ctx.r#move(sequence_set, mailbox, uid).await,
|
||||||
|
|
||||||
// UNSELECT extension (rfc3691)
|
// UNSELECT extension (rfc3691)
|
||||||
CommandBody::Unselect => ctx.unselect().await,
|
CommandBody::Unselect => ctx.unselect().await,
|
||||||
|
@ -245,4 +249,58 @@ impl<'a> SelectedContext<'a> {
|
||||||
flow::Transition::None,
|
flow::Transition::None,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn r#move(
|
||||||
|
self,
|
||||||
|
sequence_set: &SequenceSet,
|
||||||
|
mailbox: &MailboxCodec<'a>,
|
||||||
|
uid: &bool,
|
||||||
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
|
|
||||||
|
let mb_opt = self.user.open_mailbox(&name).await?;
|
||||||
|
let mb = match mb_opt {
|
||||||
|
Some(mb) => mb,
|
||||||
|
None => {
|
||||||
|
return Ok((
|
||||||
|
Response::build()
|
||||||
|
.to_req(self.req)
|
||||||
|
.message("Destination mailbox does not exist")
|
||||||
|
.code(Code::TryCreate)
|
||||||
|
.no()?,
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (uidval, uid_map, data) = self.mailbox.r#move(sequence_set, mb, uid).await?;
|
||||||
|
|
||||||
|
// compute code
|
||||||
|
let copyuid_str = format!(
|
||||||
|
"{} {} {}",
|
||||||
|
uidval,
|
||||||
|
uid_map
|
||||||
|
.iter()
|
||||||
|
.map(|(sid, _)| format!("{}", sid))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(","),
|
||||||
|
uid_map
|
||||||
|
.iter()
|
||||||
|
.map(|(_, tuid)| format!("{}", tuid))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",")
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Response::build()
|
||||||
|
.to_req(self.req)
|
||||||
|
.message("COPY completed")
|
||||||
|
.code(Code::Other(CodeOther::unvalidated(
|
||||||
|
format!("COPYUID {}", copyuid_str).into_bytes(),
|
||||||
|
)))
|
||||||
|
.set_body(data)
|
||||||
|
.ok()?,
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -600,6 +600,37 @@ impl MailboxView {
|
||||||
Ok((to_state.uidvalidity, ret))
|
Ok((to_state.uidvalidity, ret))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn r#move(
|
||||||
|
&mut self,
|
||||||
|
sequence_set: &SequenceSet,
|
||||||
|
to: Arc<Mailbox>,
|
||||||
|
is_uid_copy: &bool,
|
||||||
|
) -> Result<(ImapUidvalidity, Vec<(ImapUid, ImapUid)>, Vec<Body<'static>>)> {
|
||||||
|
let mails = self.get_mail_ids(sequence_set, *is_uid_copy)?;
|
||||||
|
|
||||||
|
let mut new_uuids = vec![];
|
||||||
|
for mi in mails.iter() {
|
||||||
|
let copy_action = to.copy_from(&self.mailbox, mi.uuid).await?;
|
||||||
|
new_uuids.push(copy_action);
|
||||||
|
self.mailbox.delete(mi.uuid).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ret = vec![];
|
||||||
|
let to_state = to.current_uid_index().await;
|
||||||
|
for (mi, new_uuid) in mails.iter().zip(new_uuids.iter()) {
|
||||||
|
let dest_uid = to_state
|
||||||
|
.table
|
||||||
|
.get(new_uuid)
|
||||||
|
.ok_or(anyhow!("moved mail not in destination mailbox"))?
|
||||||
|
.0;
|
||||||
|
ret.push((mi.uid, dest_uid));
|
||||||
|
}
|
||||||
|
|
||||||
|
let update = self.update().await?;
|
||||||
|
|
||||||
|
Ok((to_state.uidvalidity, ret, update))
|
||||||
|
}
|
||||||
|
|
||||||
/// Looks up state changes in the mailbox and produces a set of IMAP
|
/// Looks up state changes in the mailbox and produces a set of IMAP
|
||||||
/// responses describing the new state.
|
/// responses describing the new state.
|
||||||
pub async fn fetch<'b>(
|
pub async fn fetch<'b>(
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
mod capability;
|
||||||
mod command;
|
mod command;
|
||||||
mod flow;
|
mod flow;
|
||||||
mod mailbox_view;
|
mod mailbox_view;
|
||||||
mod response;
|
mod response;
|
||||||
mod session;
|
mod session;
|
||||||
mod capability;
|
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
@ -102,14 +102,18 @@ async fn client(mut ctx: ClientContext) -> Result<()> {
|
||||||
let (mut server, _) = ServerFlow::send_greeting(
|
let (mut server, _) = ServerFlow::send_greeting(
|
||||||
ctx.stream,
|
ctx.stream,
|
||||||
ServerFlowOptions::default(),
|
ServerFlowOptions::default(),
|
||||||
Greeting::ok(Some(Code::Capability(ctx.server_capabilities.to_vec())), "Aerogramme").unwrap(),
|
Greeting::ok(
|
||||||
|
Some(Code::Capability(ctx.server_capabilities.to_vec())),
|
||||||
|
"Aerogramme",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
use crate::imap::response::{Body, Response as MyResponse};
|
use crate::imap::response::{Body, Response as MyResponse};
|
||||||
use crate::imap::session::Instance;
|
use crate::imap::session::Instance;
|
||||||
use imap_codec::imap_types::command::Command;
|
use imap_codec::imap_types::command::Command;
|
||||||
use imap_codec::imap_types::response::{Response, Code, Status};
|
use imap_codec::imap_types::response::{Code, Response, Status};
|
||||||
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
let (cmd_tx, mut cmd_rx) = mpsc::channel::<Command<'static>>(10);
|
let (cmd_tx, mut cmd_rx) = mpsc::channel::<Command<'static>>(10);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
use crate::imap::capability::ServerCapability;
|
||||||
use crate::imap::command::{anonymous, authenticated, examined, selected};
|
use crate::imap::command::{anonymous, authenticated, examined, selected};
|
||||||
use crate::imap::flow;
|
use crate::imap::flow;
|
||||||
use crate::imap::response::Response;
|
use crate::imap::response::Response;
|
||||||
use crate::imap::capability::ServerCapability;
|
|
||||||
use crate::login::ArcLoginProvider;
|
use crate::login::ArcLoginProvider;
|
||||||
use imap_codec::imap_types::command::Command;
|
use imap_codec::imap_types::command::Command;
|
||||||
|
|
||||||
|
@ -33,10 +33,11 @@ impl Instance {
|
||||||
anonymous::dispatch(ctx).await
|
anonymous::dispatch(ctx).await
|
||||||
}
|
}
|
||||||
flow::State::Authenticated(ref user) => {
|
flow::State::Authenticated(ref user) => {
|
||||||
let ctx = authenticated::AuthenticatedContext {
|
let ctx = authenticated::AuthenticatedContext {
|
||||||
req: &cmd,
|
req: &cmd,
|
||||||
server_capabilities: &self.server_capabilities,
|
server_capabilities: &self.server_capabilities,
|
||||||
user };
|
user,
|
||||||
|
};
|
||||||
authenticated::dispatch(ctx).await
|
authenticated::dispatch(ctx).await
|
||||||
}
|
}
|
||||||
flow::State::Selected(ref user, ref mut mailbox) => {
|
flow::State::Selected(ref user, ref mut mailbox) => {
|
||||||
|
|
Loading…
Reference in a new issue