Implement some IMAP extensions #50
5 changed files with 75 additions and 59 deletions
|
@ -1,5 +1,7 @@
|
||||||
use imap_codec::imap_types::core::NonEmptyVec;
|
use imap_codec::imap_types::core::NonEmptyVec;
|
||||||
use imap_codec::imap_types::response::Capability;
|
use imap_codec::imap_types::response::Capability;
|
||||||
|
use imap_codec::imap_types::extensions::enable::{CapabilityEnable, Utf8Kind};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
fn capability_unselect() -> Capability<'static> {
|
fn capability_unselect() -> Capability<'static> {
|
||||||
Capability::try_from("UNSELECT").unwrap()
|
Capability::try_from("UNSELECT").unwrap()
|
||||||
|
@ -14,84 +16,67 @@ fn capability_qresync() -> Capability<'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ServerCapability {
|
pub struct ServerCapability(HashSet<Capability<'static>>);
|
||||||
r#move: bool,
|
|
||||||
unselect: bool,
|
|
||||||
condstore: bool,
|
|
||||||
qresync: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ServerCapability {
|
impl Default for ServerCapability {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self(HashSet::from([
|
||||||
r#move: true,
|
Capability::Imap4Rev1,
|
||||||
unselect: true,
|
Capability::Move,
|
||||||
condstore: false,
|
capability_unselect(),
|
||||||
qresync: false,
|
//capability_condstore(),
|
||||||
}
|
//capability_qresync(),
|
||||||
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerCapability {
|
impl ServerCapability {
|
||||||
pub fn to_vec(&self) -> NonEmptyVec<Capability<'static>> {
|
pub fn to_vec(&self) -> NonEmptyVec<Capability<'static>> {
|
||||||
let mut acc = vec![Capability::Imap4Rev1];
|
self.0.iter().map(|v| v.clone()).collect::<Vec<_>>().try_into().unwrap()
|
||||||
if self.r#move {
|
|
||||||
acc.push(Capability::Move);
|
|
||||||
}
|
|
||||||
if self.unselect {
|
|
||||||
acc.push(capability_unselect());
|
|
||||||
}
|
|
||||||
if self.condstore {
|
|
||||||
acc.push(capability_condstore());
|
|
||||||
}
|
|
||||||
if self.qresync {
|
|
||||||
acc.push(capability_qresync());
|
|
||||||
}
|
|
||||||
acc.try_into().unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn support(&self, cap: &Capability<'static>) -> bool {
|
pub fn support(&self, cap: &Capability<'static>) -> bool {
|
||||||
match cap {
|
self.0.contains(cap)
|
||||||
Capability::Imap4Rev1 => true,
|
|
||||||
Capability::Move => self.r#move,
|
|
||||||
x if *x == capability_condstore() => self.condstore,
|
|
||||||
x if *x == capability_qresync() => self.qresync,
|
|
||||||
x if *x == capability_unselect() => self.unselect,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ClientStatus {
|
||||||
|
NotSupportedByServer,
|
||||||
|
Disabled,
|
||||||
|
Enabled,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ClientCapability {
|
pub struct ClientCapability {
|
||||||
condstore: bool,
|
condstore: ClientStatus,
|
||||||
qresync: bool,
|
utf8kind: Option<Utf8Kind>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ClientCapability {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
condstore: false,
|
|
||||||
qresync: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientCapability {
|
impl ClientCapability {
|
||||||
|
pub fn new(sc: &ServerCapability) -> Self {
|
||||||
|
Self {
|
||||||
|
condstore: match sc.0.contains(&capability_condstore()) {
|
||||||
|
true => ClientStatus::Disabled,
|
||||||
|
_ => ClientStatus::NotSupportedByServer,
|
||||||
|
},
|
||||||
|
utf8kind: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_enable(
|
pub fn try_enable(
|
||||||
&mut self,
|
&mut self,
|
||||||
srv: &ServerCapability,
|
caps: &[CapabilityEnable<'static>],
|
||||||
caps: &[Capability<'static>],
|
) -> Vec<CapabilityEnable<'static>> {
|
||||||
) -> Vec<Capability<'static>> {
|
|
||||||
let mut enabled = vec![];
|
let mut enabled = vec![];
|
||||||
for cap in caps {
|
for cap in caps {
|
||||||
match cap {
|
match cap {
|
||||||
x if *x == capability_condstore() && srv.condstore && !self.condstore => {
|
CapabilityEnable::CondStore if matches!(self.condstore, ClientStatus::Disabled) => {
|
||||||
self.condstore = true;
|
self.condstore = ClientStatus::Enabled;
|
||||||
enabled.push(x.clone());
|
enabled.push(cap.clone());
|
||||||
}
|
}
|
||||||
x if *x == capability_qresync() && srv.qresync && !self.qresync => {
|
CapabilityEnable::Utf8(kind) if Some(kind) != self.utf8kind.as_ref() => {
|
||||||
self.qresync = true;
|
self.utf8kind = Some(kind.clone());
|
||||||
enabled.push(x.clone());
|
enabled.push(cap.clone());
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,15 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use imap_codec::imap_types::command::{Command, CommandBody};
|
use imap_codec::imap_types::command::{Command, CommandBody};
|
||||||
use imap_codec::imap_types::core::{Atom, Literal, QuotedChar};
|
use imap_codec::imap_types::core::{Atom, Literal, QuotedChar, NonEmptyVec};
|
||||||
use imap_codec::imap_types::datetime::DateTime;
|
use imap_codec::imap_types::datetime::DateTime;
|
||||||
use imap_codec::imap_types::flag::{Flag, FlagNameAttribute};
|
use imap_codec::imap_types::flag::{Flag, FlagNameAttribute};
|
||||||
use imap_codec::imap_types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
|
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 imap_codec::imap_types::extensions::enable::CapabilityEnable;
|
||||||
|
|
||||||
use crate::imap::capability::ServerCapability;
|
use crate::imap::capability::{ClientCapability, 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;
|
||||||
|
@ -24,6 +25,7 @@ use crate::mail::IMF;
|
||||||
pub struct AuthenticatedContext<'a> {
|
pub struct AuthenticatedContext<'a> {
|
||||||
pub req: &'a Command<'static>,
|
pub req: &'a Command<'static>,
|
||||||
pub server_capabilities: &'a ServerCapability,
|
pub server_capabilities: &'a ServerCapability,
|
||||||
|
pub client_capabilities: &'a mut ClientCapability,
|
||||||
pub user: &'a Arc<User>,
|
pub user: &'a Arc<User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +67,11 @@ pub async fn dispatch<'a>(
|
||||||
message,
|
message,
|
||||||
} => ctx.append(mailbox, flags, date, message).await,
|
} => ctx.append(mailbox, flags, date, message).await,
|
||||||
|
|
||||||
|
// rfc5161 ENABLE
|
||||||
|
CommandBody::Enable { capabilities } => {
|
||||||
|
ctx.enable(capabilities)
|
||||||
|
},
|
||||||
|
|
||||||
// Collect other commands
|
// Collect other commands
|
||||||
_ => anystate::wrong_state(ctx.req.tag.clone()),
|
_ => anystate::wrong_state(ctx.req.tag.clone()),
|
||||||
}
|
}
|
||||||
|
@ -511,6 +518,20 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enable(self, cap_enable: &NonEmptyVec<CapabilityEnable<'static>>) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
|
let mut response_builder = Response::build().to_req(self.req);
|
||||||
|
let capabilities = self.client_capabilities.try_enable(cap_enable.as_ref());
|
||||||
|
if capabilities.len() > 0 {
|
||||||
|
response_builder = response_builder.data(Data::Enabled { capabilities });
|
||||||
|
}
|
||||||
|
Ok((
|
||||||
|
response_builder
|
||||||
|
.message("ENABLE completed")
|
||||||
|
.ok()?,
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn append_internal(
|
pub(crate) async fn append_internal(
|
||||||
self,
|
self,
|
||||||
mailbox: &MailboxCodec<'a>,
|
mailbox: &MailboxCodec<'a>,
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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::capability::{ClientCapability, 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;
|
||||||
|
@ -19,6 +19,7 @@ pub struct ExaminedContext<'a> {
|
||||||
pub user: &'a Arc<User>,
|
pub user: &'a Arc<User>,
|
||||||
pub mailbox: &'a mut MailboxView,
|
pub mailbox: &'a mut MailboxView,
|
||||||
pub server_capabilities: &'a ServerCapability,
|
pub server_capabilities: &'a ServerCapability,
|
||||||
|
pub client_capabilities: &'a mut ClientCapability,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, flow::Transition)> {
|
pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
|
@ -60,6 +61,7 @@ pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, fl
|
||||||
authenticated::dispatch(authenticated::AuthenticatedContext {
|
authenticated::dispatch(authenticated::AuthenticatedContext {
|
||||||
req: ctx.req,
|
req: ctx.req,
|
||||||
server_capabilities: ctx.server_capabilities,
|
server_capabilities: ctx.server_capabilities,
|
||||||
|
client_capabilities: ctx.client_capabilities,
|
||||||
user: ctx.user,
|
user: ctx.user,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -10,7 +10,7 @@ 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::capability::{ClientCapability, 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;
|
||||||
|
@ -23,6 +23,7 @@ pub struct SelectedContext<'a> {
|
||||||
pub user: &'a Arc<User>,
|
pub user: &'a Arc<User>,
|
||||||
pub mailbox: &'a mut MailboxView,
|
pub mailbox: &'a mut MailboxView,
|
||||||
pub server_capabilities: &'a ServerCapability,
|
pub server_capabilities: &'a ServerCapability,
|
||||||
|
pub client_capabilities: &'a mut ClientCapability,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch<'a>(
|
pub async fn dispatch<'a>(
|
||||||
|
@ -76,6 +77,7 @@ pub async fn dispatch<'a>(
|
||||||
authenticated::dispatch(authenticated::AuthenticatedContext {
|
authenticated::dispatch(authenticated::AuthenticatedContext {
|
||||||
req: ctx.req,
|
req: ctx.req,
|
||||||
server_capabilities: ctx.server_capabilities,
|
server_capabilities: ctx.server_capabilities,
|
||||||
|
client_capabilities: ctx.client_capabilities,
|
||||||
user: ctx.user,
|
user: ctx.user,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::imap::capability::ServerCapability;
|
use crate::imap::capability::{ClientCapability, 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;
|
||||||
|
@ -9,14 +9,17 @@ use imap_codec::imap_types::command::Command;
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
pub login_provider: ArcLoginProvider,
|
pub login_provider: ArcLoginProvider,
|
||||||
pub server_capabilities: ServerCapability,
|
pub server_capabilities: ServerCapability,
|
||||||
|
pub client_capabilities: ClientCapability,
|
||||||
pub state: flow::State,
|
pub state: flow::State,
|
||||||
}
|
}
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub fn new(login_provider: ArcLoginProvider, cap: ServerCapability) -> Self {
|
pub fn new(login_provider: ArcLoginProvider, cap: ServerCapability) -> Self {
|
||||||
|
let client_cap = ClientCapability::new(&cap);
|
||||||
Self {
|
Self {
|
||||||
login_provider,
|
login_provider,
|
||||||
state: flow::State::NotAuthenticated,
|
state: flow::State::NotAuthenticated,
|
||||||
server_capabilities: cap,
|
server_capabilities: cap,
|
||||||
|
client_capabilities: client_cap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +39,7 @@ impl Instance {
|
||||||
let ctx = authenticated::AuthenticatedContext {
|
let ctx = authenticated::AuthenticatedContext {
|
||||||
req: &cmd,
|
req: &cmd,
|
||||||
server_capabilities: &self.server_capabilities,
|
server_capabilities: &self.server_capabilities,
|
||||||
|
client_capabilities: &mut self.client_capabilities,
|
||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
authenticated::dispatch(ctx).await
|
authenticated::dispatch(ctx).await
|
||||||
|
@ -44,6 +48,7 @@ impl Instance {
|
||||||
let ctx = selected::SelectedContext {
|
let ctx = selected::SelectedContext {
|
||||||
req: &cmd,
|
req: &cmd,
|
||||||
server_capabilities: &self.server_capabilities,
|
server_capabilities: &self.server_capabilities,
|
||||||
|
client_capabilities: &mut self.client_capabilities,
|
||||||
user,
|
user,
|
||||||
mailbox,
|
mailbox,
|
||||||
};
|
};
|
||||||
|
@ -53,6 +58,7 @@ impl Instance {
|
||||||
let ctx = examined::ExaminedContext {
|
let ctx = examined::ExaminedContext {
|
||||||
req: &cmd,
|
req: &cmd,
|
||||||
server_capabilities: &self.server_capabilities,
|
server_capabilities: &self.server_capabilities,
|
||||||
|
client_capabilities: &mut self.client_capabilities,
|
||||||
user,
|
user,
|
||||||
mailbox,
|
mailbox,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue