aboutsummaryrefslogtreecommitdiff
path: root/src/imap
diff options
context:
space:
mode:
authorQuentin <quentin@dufour.io>2024-01-04 11:11:01 +0000
committerQuentin <quentin@dufour.io>2024-01-04 11:11:01 +0000
commitbcf6de83419b405fea95b740869f98d43586ea7c (patch)
tree9f52832b90685913beda8f1bf19a22b2ec7bc6c6 /src/imap
parentb9a0c1e6eced036eb71e8221a4f236f72832fec2 (diff)
parent7ae9966675c85b34f1a99d81062b44b74385a15b (diff)
downloadaerogramme-bcf6de83419b405fea95b740869f98d43586ea7c.tar.gz
aerogramme-bcf6de83419b405fea95b740869f98d43586ea7c.zip
Merge pull request 'Implement some IMAP extensions' (#50) from feat/more-ext into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/aerogramme/pulls/50
Diffstat (limited to 'src/imap')
-rw-r--r--src/imap/capability.rs93
-rw-r--r--src/imap/command/anonymous.rs8
-rw-r--r--src/imap/command/anystate.rs14
-rw-r--r--src/imap/command/authenticated.rs33
-rw-r--r--src/imap/command/examined.rs20
-rw-r--r--src/imap/command/selected.rs81
-rw-r--r--src/imap/mailbox_view.rs39
-rw-r--r--src/imap/mod.rs20
-rw-r--r--src/imap/session.rs20
9 files changed, 304 insertions, 24 deletions
diff --git a/src/imap/capability.rs b/src/imap/capability.rs
new file mode 100644
index 0000000..631c3e2
--- /dev/null
+++ b/src/imap/capability.rs
@@ -0,0 +1,93 @@
+use imap_codec::imap_types::core::NonEmptyVec;
+use imap_codec::imap_types::extensions::enable::{CapabilityEnable, Utf8Kind};
+use imap_codec::imap_types::response::Capability;
+use std::collections::HashSet;
+
+fn capability_unselect() -> Capability<'static> {
+ Capability::try_from("UNSELECT").unwrap()
+}
+
+fn capability_condstore() -> Capability<'static> {
+ Capability::try_from("CONDSTORE").unwrap()
+}
+
+fn capability_qresync() -> Capability<'static> {
+ Capability::try_from("QRESYNC").unwrap()
+}
+
+#[derive(Debug, Clone)]
+pub struct ServerCapability(HashSet<Capability<'static>>);
+
+impl Default for ServerCapability {
+ fn default() -> Self {
+ Self(HashSet::from([
+ Capability::Imap4Rev1,
+ Capability::Move,
+ Capability::LiteralPlus,
+ capability_unselect(),
+ //capability_condstore(),
+ //capability_qresync(),
+ ]))
+ }
+}
+
+impl ServerCapability {
+ pub fn to_vec(&self) -> NonEmptyVec<Capability<'static>> {
+ self.0
+ .iter()
+ .map(|v| v.clone())
+ .collect::<Vec<_>>()
+ .try_into()
+ .unwrap()
+ }
+
+ #[allow(dead_code)]
+ pub fn support(&self, cap: &Capability<'static>) -> bool {
+ self.0.contains(cap)
+ }
+}
+
+enum ClientStatus {
+ NotSupportedByServer,
+ Disabled,
+ Enabled,
+}
+
+pub struct ClientCapability {
+ condstore: ClientStatus,
+ utf8kind: Option<Utf8Kind>,
+}
+
+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(
+ &mut self,
+ caps: &[CapabilityEnable<'static>],
+ ) -> Vec<CapabilityEnable<'static>> {
+ let mut enabled = vec![];
+ for cap in caps {
+ match cap {
+ CapabilityEnable::CondStore if matches!(self.condstore, ClientStatus::Disabled) => {
+ self.condstore = ClientStatus::Enabled;
+ enabled.push(cap.clone());
+ }
+ CapabilityEnable::Utf8(kind) if Some(kind) != self.utf8kind.as_ref() => {
+ self.utf8kind = Some(kind.clone());
+ enabled.push(cap.clone());
+ }
+ _ => (),
+ }
+ }
+
+ enabled
+ }
+}
diff --git a/src/imap/command/anonymous.rs b/src/imap/command/anonymous.rs
index fbd10e9..0582b06 100644
--- a/src/imap/command/anonymous.rs
+++ b/src/imap/command/anonymous.rs
@@ -1,8 +1,10 @@
use anyhow::Result;
use imap_codec::imap_types::command::{Command, CommandBody};
use imap_codec::imap_types::core::AString;
+use imap_codec::imap_types::response::Code;
use imap_codec::imap_types::secret::Secret;
+use crate::imap::capability::ServerCapability;
use crate::imap::command::anystate;
use crate::imap::flow;
use crate::imap::response::Response;
@@ -13,6 +15,7 @@ use crate::mail::user::User;
pub struct AnonymousContext<'a> {
pub req: &'a Command<'static>,
+ pub server_capabilities: &'a ServerCapability,
pub login_provider: &'a ArcLoginProvider,
}
@@ -20,7 +23,9 @@ pub async fn dispatch(ctx: AnonymousContext<'_>) -> Result<(Response<'static>, f
match &ctx.req.body {
// Any State
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
- CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
+ CommandBody::Capability => {
+ anystate::capability(ctx.req.tag.clone(), ctx.server_capabilities)
+ }
CommandBody::Logout => anystate::logout(),
// Specific to anonymous context (3 commands)
@@ -69,6 +74,7 @@ impl<'a> AnonymousContext<'a> {
Ok((
Response::build()
.to_req(self.req)
+ .code(Code::Capability(self.server_capabilities.to_vec()))
.message("Completed")
.ok()?,
flow::Transition::Authenticate(user),
diff --git a/src/imap/command/anystate.rs b/src/imap/command/anystate.rs
index 42fe645..718ba3f 100644
--- a/src/imap/command/anystate.rs
+++ b/src/imap/command/anystate.rs
@@ -1,17 +1,19 @@
use anyhow::Result;
-use imap_codec::imap_types::core::{NonEmptyVec, Tag};
-use imap_codec::imap_types::response::{Capability, Data};
+use imap_codec::imap_types::core::Tag;
+use imap_codec::imap_types::response::Data;
+use crate::imap::capability::ServerCapability;
use crate::imap::flow;
use crate::imap::response::Response;
-pub(crate) fn capability(tag: Tag<'static>) -> Result<(Response<'static>, flow::Transition)> {
- let capabilities: NonEmptyVec<Capability> =
- (vec![Capability::Imap4Rev1, Capability::Idle]).try_into()?;
+pub(crate) fn capability(
+ tag: Tag<'static>,
+ cap: &ServerCapability,
+) -> Result<(Response<'static>, flow::Transition)> {
let res = Response::build()
.tag(tag)
.message("Server capabilities")
- .data(Data::Capability(capabilities))
+ .data(Data::Capability(cap.to_vec()))
.ok()?;
Ok((res, flow::Transition::None))
diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs
index 1bb4c6d..1481a80 100644
--- a/src/imap/command/authenticated.rs
+++ b/src/imap/command/authenticated.rs
@@ -3,13 +3,15 @@ use std::sync::Arc;
use anyhow::{anyhow, bail, Result};
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, NonEmptyVec, QuotedChar};
use imap_codec::imap_types::datetime::DateTime;
+use imap_codec::imap_types::extensions::enable::CapabilityEnable;
use imap_codec::imap_types::flag::{Flag, FlagNameAttribute};
use imap_codec::imap_types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
use imap_codec::imap_types::response::{Code, CodeOther, Data};
use imap_codec::imap_types::status::{StatusDataItem, StatusDataItemName};
+use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anystate, MailboxName};
use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView;
@@ -22,6 +24,8 @@ use crate::mail::IMF;
pub struct AuthenticatedContext<'a> {
pub req: &'a Command<'static>,
+ pub server_capabilities: &'a ServerCapability,
+ pub client_capabilities: &'a mut ClientCapability,
pub user: &'a Arc<User>,
}
@@ -31,7 +35,9 @@ pub async fn dispatch<'a>(
match &ctx.req.body {
// Any state
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
- CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
+ CommandBody::Capability => {
+ anystate::capability(ctx.req.tag.clone(), ctx.server_capabilities)
+ }
CommandBody::Logout => anystate::logout(),
// Specific to this state (11 commands)
@@ -61,6 +67,9 @@ pub async fn dispatch<'a>(
message,
} => ctx.append(mailbox, flags, date, message).await,
+ // rfc5161 ENABLE
+ CommandBody::Enable { capabilities } => ctx.enable(capabilities),
+
// Collect other commands
_ => anystate::wrong_state(ctx.req.tag.clone()),
}
@@ -301,6 +310,9 @@ impl<'a> AuthenticatedContext<'a> {
StatusDataItemName::DeletedStorage => {
bail!("quota not implemented, can't return freed storage after EXPUNGE will be run");
},
+ StatusDataItemName::HighestModSeq => {
+ bail!("highestmodseq not yet implemented");
+ }
});
}
@@ -504,6 +516,21 @@ 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(
self,
mailbox: &MailboxCodec<'a>,
@@ -520,7 +547,7 @@ impl<'a> AuthenticatedContext<'a> {
};
if date.is_some() {
- bail!("Cannot set date when appending message");
+ tracing::warn!("Cannot set date when appending message");
}
let msg =
diff --git a/src/imap/command/examined.rs b/src/imap/command/examined.rs
index 7de94f4..0d688c0 100644
--- a/src/imap/command/examined.rs
+++ b/src/imap/command/examined.rs
@@ -7,6 +7,7 @@ use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames;
use imap_codec::imap_types::search::SearchKey;
use imap_codec::imap_types::sequence::SequenceSet;
+use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anystate, authenticated};
use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView;
@@ -17,18 +18,22 @@ pub struct ExaminedContext<'a> {
pub req: &'a Command<'static>,
pub user: &'a Arc<User>,
pub mailbox: &'a mut MailboxView,
+ pub server_capabilities: &'a ServerCapability,
+ pub client_capabilities: &'a mut ClientCapability,
}
pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, flow::Transition)> {
match &ctx.req.body {
// Any State
// noop is specific to this state
- CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
+ CommandBody::Capability => {
+ anystate::capability(ctx.req.tag.clone(), ctx.server_capabilities)
+ }
CommandBody::Logout => anystate::logout(),
// Specific to the EXAMINE state (specialization of the SELECTED state)
// ~3 commands -> close, fetch, search + NOOP
- CommandBody::Close => ctx.close().await,
+ CommandBody::Close => ctx.close("CLOSE").await,
CommandBody::Fetch {
sequence_set,
macro_or_item_names,
@@ -44,14 +49,19 @@ pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, fl
Response::build()
.to_req(ctx.req)
.message("Forbidden command: can't write in read-only mode (EXAMINE)")
- .bad()?,
+ .no()?,
flow::Transition::None,
)),
+ // UNSELECT extension (rfc3691)
+ CommandBody::Unselect => ctx.close("UNSELECT").await,
+
// In examined mode, we fallback to authenticated when needed
_ => {
authenticated::dispatch(authenticated::AuthenticatedContext {
req: ctx.req,
+ server_capabilities: ctx.server_capabilities,
+ client_capabilities: ctx.client_capabilities,
user: ctx.user,
})
.await
@@ -64,11 +74,11 @@ pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, fl
impl<'a> ExaminedContext<'a> {
/// CLOSE in examined state is not the same as in selected state
/// (in selected state it also does an EXPUNGE, here it doesn't)
- async fn close(self) -> Result<(Response<'static>, flow::Transition)> {
+ async fn close(self, kind: &str) -> Result<(Response<'static>, flow::Transition)> {
Ok((
Response::build()
.to_req(self.req)
- .message("CLOSE completed")
+ .message(format!("{} completed", kind))
.ok()?,
flow::Transition::Unselect,
))
diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs
index 220a952..c8cc680 100644
--- a/src/imap/command/selected.rs
+++ b/src/imap/command/selected.rs
@@ -10,6 +10,7 @@ use imap_codec::imap_types::response::{Code, CodeOther};
use imap_codec::imap_types::search::SearchKey;
use imap_codec::imap_types::sequence::SequenceSet;
+use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anystate, authenticated, MailboxName};
use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView;
@@ -21,6 +22,8 @@ pub struct SelectedContext<'a> {
pub req: &'a Command<'static>,
pub user: &'a Arc<User>,
pub mailbox: &'a mut MailboxView,
+ pub server_capabilities: &'a ServerCapability,
+ pub client_capabilities: &'a mut ClientCapability,
}
pub async fn dispatch<'a>(
@@ -29,7 +32,9 @@ pub async fn dispatch<'a>(
match &ctx.req.body {
// Any State
// noop is specific to this state
- CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
+ CommandBody::Capability => {
+ anystate::capability(ctx.req.tag.clone(), ctx.server_capabilities)
+ }
CommandBody::Logout => anystate::logout(),
// Specific to this state (7 commands + NOOP)
@@ -58,11 +63,21 @@ pub async fn dispatch<'a>(
mailbox,
uid,
} => ctx.copy(sequence_set, mailbox, uid).await,
+ CommandBody::Move {
+ sequence_set,
+ mailbox,
+ uid,
+ } => ctx.r#move(sequence_set, mailbox, uid).await,
+
+ // UNSELECT extension (rfc3691)
+ CommandBody::Unselect => ctx.unselect().await,
// In selected mode, we fallback to authenticated when needed
_ => {
authenticated::dispatch(authenticated::AuthenticatedContext {
req: ctx.req,
+ server_capabilities: ctx.server_capabilities,
+ client_capabilities: ctx.client_capabilities,
user: ctx.user,
})
.await
@@ -84,6 +99,16 @@ impl<'a> SelectedContext<'a> {
))
}
+ async fn unselect(self) -> Result<(Response<'static>, flow::Transition)> {
+ Ok((
+ Response::build()
+ .to_req(self.req)
+ .message("UNSELECT completed")
+ .ok()?,
+ flow::Transition::Unselect,
+ ))
+ }
+
pub async fn fetch(
self,
sequence_set: &SequenceSet,
@@ -226,4 +251,58 @@ impl<'a> SelectedContext<'a> {
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,
+ ))
+ }
}
diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs
index 7434512..861d27c 100644
--- a/src/imap/mailbox_view.rs
+++ b/src/imap/mailbox_view.rs
@@ -600,6 +600,37 @@ impl MailboxView {
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
/// responses describing the new state.
pub async fn fetch<'b>(
@@ -1242,7 +1273,11 @@ fn get_message_section<'a>(
part.as_ref().map(|p| p.0.as_ref()).unwrap_or(&[]),
|part_msg| {
let mut ret = vec![];
- for f in &part_msg.mime().kv {
+ let mime = match &part_msg {
+ AnyPart::Msg(msg) => msg.child.mime(),
+ other => other.mime(),
+ };
+ for f in mime.kv.iter() {
let (k, v) = match f {
header::Field::Good(header::Kv2(k, v)) => (k, v),
_ => continue,
@@ -1272,7 +1307,7 @@ fn get_message_section<'a>(
let bytes = match &part {
AnyPart::Txt(p) => p.mime.fields.raw,
AnyPart::Bin(p) => p.mime.fields.raw,
- AnyPart::Msg(p) => p.mime.fields.raw,
+ AnyPart::Msg(p) => p.child.mime().raw,
AnyPart::Mult(p) => p.mime.fields.raw,
};
Ok(bytes.to_vec().into())
diff --git a/src/imap/mod.rs b/src/imap/mod.rs
index 31eeaa8..aac1fd3 100644
--- a/src/imap/mod.rs
+++ b/src/imap/mod.rs
@@ -1,3 +1,4 @@
+mod capability;
mod command;
mod flow;
mod mailbox_view;
@@ -17,12 +18,14 @@ use imap_flow::server::{ServerFlow, ServerFlowEvent, ServerFlowOptions};
use imap_flow::stream::AnyStream;
use crate::config::ImapConfig;
+use crate::imap::capability::ServerCapability;
use crate::login::ArcLoginProvider;
/// Server is a thin wrapper to register our Services in BàL
pub struct Server {
bind_addr: SocketAddr,
login_provider: ArcLoginProvider,
+ capabilities: ServerCapability,
}
struct ClientContext {
@@ -30,12 +33,14 @@ struct ClientContext {
addr: SocketAddr,
login_provider: ArcLoginProvider,
must_exit: watch::Receiver<bool>,
+ server_capabilities: ServerCapability,
}
pub fn new(config: ImapConfig, login: ArcLoginProvider) -> Server {
Server {
bind_addr: config.bind_addr,
login_provider: login,
+ capabilities: ServerCapability::default(),
}
}
@@ -66,6 +71,7 @@ impl Server {
addr: remote_addr.clone(),
login_provider: self.login_provider.clone(),
must_exit: must_exit.clone(),
+ server_capabilities: self.capabilities.clone(),
};
let conn = tokio::spawn(client_wrapper(client));
connections.push(conn);
@@ -83,7 +89,7 @@ async fn client_wrapper(ctx: ClientContext) {
let addr = ctx.addr.clone();
match client(ctx).await {
Ok(()) => {
- tracing::info!("closing successful session for {:?}", addr);
+ tracing::debug!("closing successful session for {:?}", addr);
}
Err(e) => {
tracing::error!("closing errored session for {:?}: {}", addr, e);
@@ -96,28 +102,34 @@ async fn client(mut ctx: ClientContext) -> Result<()> {
let (mut server, _) = ServerFlow::send_greeting(
ctx.stream,
ServerFlowOptions::default(),
- Greeting::ok(None, "Aerogramme").unwrap(),
+ Greeting::ok(
+ Some(Code::Capability(ctx.server_capabilities.to_vec())),
+ "Aerogramme",
+ )
+ .unwrap(),
)
.await?;
use crate::imap::response::{Body, Response as MyResponse};
use crate::imap::session::Instance;
use imap_codec::imap_types::command::Command;
- use imap_codec::imap_types::response::{Response, Status};
+ use imap_codec::imap_types::response::{Code, Response, Status};
use tokio::sync::mpsc;
let (cmd_tx, mut cmd_rx) = mpsc::channel::<Command<'static>>(10);
let (resp_tx, mut resp_rx) = mpsc::unbounded_channel::<MyResponse<'static>>();
let bckgrnd = tokio::spawn(async move {
- let mut session = Instance::new(ctx.login_provider);
+ let mut session = Instance::new(ctx.login_provider, ctx.server_capabilities);
loop {
let cmd = match cmd_rx.recv().await {
None => break,
Some(cmd_recv) => cmd_recv,
};
+ tracing::debug!(cmd=?cmd, sock=%ctx.addr, "command");
let maybe_response = session.command(cmd).await;
+ tracing::debug!(cmd=?maybe_response.completion, sock=%ctx.addr, "response");
match resp_tx.send(maybe_response) {
Err(_) => break,
diff --git a/src/imap/session.rs b/src/imap/session.rs
index 5c67f8e..6b26478 100644
--- a/src/imap/session.rs
+++ b/src/imap/session.rs
@@ -1,3 +1,4 @@
+use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anonymous, authenticated, examined, selected};
use crate::imap::flow;
use crate::imap::response::Response;
@@ -7,13 +8,18 @@ use imap_codec::imap_types::command::Command;
//-----
pub struct Instance {
pub login_provider: ArcLoginProvider,
+ pub server_capabilities: ServerCapability,
+ pub client_capabilities: ClientCapability,
pub state: flow::State,
}
impl Instance {
- pub fn new(login_provider: ArcLoginProvider) -> Self {
+ pub fn new(login_provider: ArcLoginProvider, cap: ServerCapability) -> Self {
+ let client_cap = ClientCapability::new(&cap);
Self {
login_provider,
state: flow::State::NotAuthenticated,
+ server_capabilities: cap,
+ client_capabilities: client_cap,
}
}
@@ -25,16 +31,24 @@ impl Instance {
let ctx = anonymous::AnonymousContext {
req: &cmd,
login_provider: &self.login_provider,
+ server_capabilities: &self.server_capabilities,
};
anonymous::dispatch(ctx).await
}
flow::State::Authenticated(ref user) => {
- let ctx = authenticated::AuthenticatedContext { req: &cmd, user };
+ let ctx = authenticated::AuthenticatedContext {
+ req: &cmd,
+ server_capabilities: &self.server_capabilities,
+ client_capabilities: &mut self.client_capabilities,
+ user,
+ };
authenticated::dispatch(ctx).await
}
flow::State::Selected(ref user, ref mut mailbox) => {
let ctx = selected::SelectedContext {
req: &cmd,
+ server_capabilities: &self.server_capabilities,
+ client_capabilities: &mut self.client_capabilities,
user,
mailbox,
};
@@ -43,6 +57,8 @@ impl Instance {
flow::State::Examined(ref user, ref mut mailbox) => {
let ctx = examined::ExaminedContext {
req: &cmd,
+ server_capabilities: &self.server_capabilities,
+ client_capabilities: &mut self.client_capabilities,
user,
mailbox,
};