Merge pull request 'Implement LIST X Y RETURN (STATUS (UIDNEXT ...))
' (#75) from feat/list-status into main
Reviewed-on: #75
This commit is contained in:
commit
49ff733a30
7 changed files with 199 additions and 73 deletions
49
Cargo.lock
generated
49
Cargo.lock
generated
|
@ -60,6 +60,7 @@ dependencies = [
|
||||||
"smtp-message",
|
"smtp-message",
|
||||||
"smtp-server",
|
"smtp-server",
|
||||||
"sodiumoxide",
|
"sodiumoxide",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
|
@ -365,7 +366,7 @@ checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -990,7 +991,7 @@ checksum = "f10dd247355bf631d98d2753d87ae62c84c8dcb996ad9b24a4168e0aec29bd6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1258,7 +1259,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1522,7 +1523,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1723,7 +1724,7 @@ dependencies = [
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite 0.2.13",
|
"pin-project-lite 0.2.13",
|
||||||
"socket2 0.4.10",
|
"socket2 0.5.5",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -1807,7 +1808,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imap-codec"
|
name = "imap-codec"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#0f27fe2f10d16c96e0be18914fdbeda9df545beb"
|
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#0adcc244282c64cc7874ffa9cd22e4a451ee19f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"abnf-core",
|
"abnf-core",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
|
@ -1834,7 +1835,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imap-types"
|
name = "imap-types"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#0f27fe2f10d16c96e0be18914fdbeda9df545beb"
|
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#0adcc244282c64cc7874ffa9cd22e4a451ee19f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"bounded-static",
|
"bounded-static",
|
||||||
|
@ -2283,7 +2284,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2399,18 +2400,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.71"
|
version = "1.0.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
|
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.33"
|
version = "1.0.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -2793,7 +2794,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3022,9 +3023,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.43"
|
version = "2.0.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
|
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3066,22 +3067,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.52"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
|
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.52"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
|
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3164,7 +3165,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3248,7 +3249,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3436,7 +3437,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3470,7 +3471,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.43",
|
"syn 2.0.48",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
|
@ -60,6 +60,7 @@ smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature
|
||||||
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
||||||
imap-codec = { version = "2.0.0", features = ["bounded-static", "ext_condstore_qresync"] }
|
imap-codec = { version = "2.0.0", features = ["bounded-static", "ext_condstore_qresync"] }
|
||||||
imap-flow = { git = "https://github.com/superboum/imap-flow.git", branch = "custom/aerogramme" }
|
imap-flow = { git = "https://github.com/superboum/imap-flow.git", branch = "custom/aerogramme" }
|
||||||
|
thiserror = "1.0.56"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
s3_endpoint = "http://[::1]:3900"
|
|
||||||
k2v_endpoint = "http://[::1]:3904"
|
|
||||||
aws_region = "garage"
|
|
||||||
|
|
||||||
[imap]
|
|
||||||
bind_addr = "[::1]:4567"
|
|
||||||
|
|
||||||
[login_static.users.quentin]
|
|
||||||
password = "$argon2id$v=19$m=4096,t=3,p=1$jR52Nq76f8yO0UXdhK+FiQ$KeIzDI4PJ/2bX+expyyaRkMZus0/1FsgTXtnvPUjwyw"
|
|
||||||
aws_access_key_id = "GK68198c3b4148f61dcd625b7e"
|
|
||||||
aws_secret_access_key = "1d4bd3853a4f7810b97cbb2f8eb52c7603eb93c202fe98ca40f4e3f6b7e70fa0"
|
|
||||||
user_secret = "poupou"
|
|
||||||
bucket = "quentin-mailrage"
|
|
|
@ -18,6 +18,10 @@ fn capability_uidplus() -> Capability<'static> {
|
||||||
Capability::try_from("UIDPLUS").unwrap()
|
Capability::try_from("UIDPLUS").unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn capability_liststatus() -> Capability<'static> {
|
||||||
|
Capability::try_from("LIST-STATUS").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
fn capability_qresync() -> Capability<'static> {
|
fn capability_qresync() -> Capability<'static> {
|
||||||
Capability::try_from("QRESYNC").unwrap()
|
Capability::try_from("QRESYNC").unwrap()
|
||||||
|
@ -38,6 +42,7 @@ impl Default for ServerCapability {
|
||||||
capability_unselect(),
|
capability_unselect(),
|
||||||
capability_condstore(),
|
capability_condstore(),
|
||||||
capability_uidplus(),
|
capability_uidplus(),
|
||||||
|
capability_liststatus(),
|
||||||
//capability_qresync(),
|
//capability_qresync(),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use imap_codec::imap_types::command::{Command, CommandBody, SelectExamineModifier};
|
use imap_codec::imap_types::command::{
|
||||||
|
Command, CommandBody, ListReturnItem, SelectExamineModifier,
|
||||||
|
};
|
||||||
use imap_codec::imap_types::core::{Atom, Literal, NonEmptyVec, QuotedChar};
|
use imap_codec::imap_types::core::{Atom, Literal, NonEmptyVec, QuotedChar};
|
||||||
use imap_codec::imap_types::datetime::DateTime;
|
use imap_codec::imap_types::datetime::DateTime;
|
||||||
use imap_codec::imap_types::extensions::enable::CapabilityEnable;
|
use imap_codec::imap_types::extensions::enable::CapabilityEnable;
|
||||||
|
@ -30,7 +33,7 @@ pub struct AuthenticatedContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch<'a>(
|
pub async fn dispatch<'a>(
|
||||||
ctx: AuthenticatedContext<'a>,
|
mut ctx: AuthenticatedContext<'a>,
|
||||||
) -> Result<(Response<'static>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any state
|
// Any state
|
||||||
|
@ -47,11 +50,12 @@ pub async fn dispatch<'a>(
|
||||||
CommandBody::Lsub {
|
CommandBody::Lsub {
|
||||||
reference,
|
reference,
|
||||||
mailbox_wildcard,
|
mailbox_wildcard,
|
||||||
} => ctx.list(reference, mailbox_wildcard, true).await,
|
} => ctx.list(reference, mailbox_wildcard, &[], true).await,
|
||||||
CommandBody::List {
|
CommandBody::List {
|
||||||
reference,
|
reference,
|
||||||
mailbox_wildcard,
|
mailbox_wildcard,
|
||||||
} => ctx.list(reference, mailbox_wildcard, false).await,
|
r#return,
|
||||||
|
} => ctx.list(reference, mailbox_wildcard, r#return, false).await,
|
||||||
CommandBody::Status {
|
CommandBody::Status {
|
||||||
mailbox,
|
mailbox,
|
||||||
item_names,
|
item_names,
|
||||||
|
@ -163,9 +167,10 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list(
|
async fn list(
|
||||||
self,
|
&mut self,
|
||||||
reference: &MailboxCodec<'a>,
|
reference: &MailboxCodec<'a>,
|
||||||
mailbox_wildcard: &ListMailbox<'a>,
|
mailbox_wildcard: &ListMailbox<'a>,
|
||||||
|
must_return: &[ListReturnItem],
|
||||||
is_lsub: bool,
|
is_lsub: bool,
|
||||||
) -> Result<(Response<'static>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let mbx_hier_delim: QuotedChar = QuotedChar::unvalidated(MBX_HIER_DELIM_RAW);
|
let mbx_hier_delim: QuotedChar = QuotedChar::unvalidated(MBX_HIER_DELIM_RAW);
|
||||||
|
@ -181,6 +186,11 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let status_item_names = must_return.iter().find_map(|m| match m {
|
||||||
|
ListReturnItem::Status(v) => Some(v),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
// @FIXME would probably need a rewrite to better use the imap_codec library
|
// @FIXME would probably need a rewrite to better use the imap_codec library
|
||||||
let wildcard = match mailbox_wildcard {
|
let wildcard = match mailbox_wildcard {
|
||||||
ListMailbox::Token(v) => std::str::from_utf8(v.as_ref())?,
|
ListMailbox::Token(v) => std::str::from_utf8(v.as_ref())?,
|
||||||
|
@ -231,11 +241,13 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
for (mb, is_real) in vmailboxes.iter() {
|
for (mb, is_real) in vmailboxes.iter() {
|
||||||
if matches_wildcard(&wildcard, mb) {
|
if matches_wildcard(&wildcard, mb) {
|
||||||
let mailbox = mb
|
let mailbox: MailboxCodec = mb
|
||||||
.to_string()
|
.to_string()
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| anyhow!("invalid mailbox name"))?;
|
.map_err(|_| anyhow!("invalid mailbox name"))?;
|
||||||
let mut items = vec![FlagNameAttribute::from(Atom::unvalidated("Subscribed"))];
|
let mut items = vec![FlagNameAttribute::from(Atom::unvalidated("Subscribed"))];
|
||||||
|
|
||||||
|
// Decoration
|
||||||
if !*is_real {
|
if !*is_real {
|
||||||
items.push(FlagNameAttribute::Noselect);
|
items.push(FlagNameAttribute::Noselect);
|
||||||
} else {
|
} else {
|
||||||
|
@ -247,19 +259,39 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Result type
|
||||||
if is_lsub {
|
if is_lsub {
|
||||||
ret.push(Data::Lsub {
|
ret.push(Data::Lsub {
|
||||||
items,
|
items,
|
||||||
delimiter: Some(mbx_hier_delim),
|
delimiter: Some(mbx_hier_delim),
|
||||||
mailbox,
|
mailbox: mailbox.clone(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ret.push(Data::List {
|
ret.push(Data::List {
|
||||||
items,
|
items,
|
||||||
delimiter: Some(mbx_hier_delim),
|
delimiter: Some(mbx_hier_delim),
|
||||||
mailbox,
|
mailbox: mailbox.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also collect status
|
||||||
|
if let Some(sin) = status_item_names {
|
||||||
|
let ret_attrs = match self.status_items(mb, sin).await {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!(err=?e, mailbox=%mb, "Unable to fetch status for mailbox");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = Data::Status {
|
||||||
|
mailbox,
|
||||||
|
items: ret_attrs.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
ret.push(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,23 +311,52 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn status(
|
async fn status(
|
||||||
self,
|
&mut self,
|
||||||
mailbox: &MailboxCodec<'static>,
|
mailbox: &MailboxCodec<'static>,
|
||||||
attributes: &[StatusDataItemName],
|
attributes: &[StatusDataItemName],
|
||||||
) -> Result<(Response<'static>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(mailbox).try_into()?;
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
|
|
||||||
|
let ret_attrs = match self.status_items(name, attributes).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => match e.downcast_ref::<CommandError>() {
|
||||||
|
Some(CommandError::MailboxNotFound) => {
|
||||||
|
return Ok((
|
||||||
|
Response::build()
|
||||||
|
.to_req(self.req)
|
||||||
|
.message("Mailbox does not exist")
|
||||||
|
.no()?,
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => return Err(e.into()),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = Data::Status {
|
||||||
|
mailbox: mailbox.clone(),
|
||||||
|
items: ret_attrs.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Response::build()
|
||||||
|
.to_req(self.req)
|
||||||
|
.message("STATUS completed")
|
||||||
|
.data(data)
|
||||||
|
.ok()?,
|
||||||
|
flow::Transition::None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn status_items(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
attributes: &[StatusDataItemName],
|
||||||
|
) -> Result<Vec<StatusDataItem>> {
|
||||||
let mb_opt = self.user.open_mailbox(name).await?;
|
let mb_opt = self.user.open_mailbox(name).await?;
|
||||||
let mb = match mb_opt {
|
let mb = match mb_opt {
|
||||||
Some(mb) => mb,
|
Some(mb) => mb,
|
||||||
None => {
|
None => return Err(CommandError::MailboxNotFound.into()),
|
||||||
return Ok((
|
|
||||||
Response::build()
|
|
||||||
.to_req(self.req)
|
|
||||||
.message("Mailbox does not exist")
|
|
||||||
.no()?,
|
|
||||||
flow::Transition::None,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let view = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await;
|
let view = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await;
|
||||||
|
@ -322,20 +383,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Ok(ret_attrs)
|
||||||
let data = Data::Status {
|
|
||||||
mailbox: mailbox.clone(),
|
|
||||||
items: ret_attrs.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
Response::build()
|
|
||||||
.to_req(self.req)
|
|
||||||
.message("STATUS completed")
|
|
||||||
.data(data)
|
|
||||||
.ok()?,
|
|
||||||
flow::Transition::None,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn subscribe(
|
async fn subscribe(
|
||||||
|
@ -604,6 +652,12 @@ fn matches_wildcard(wildcard: &str, name: &str) -> bool {
|
||||||
matches[name.len()][wildcard.len()]
|
matches[name.len()][wildcard.len()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum CommandError {
|
||||||
|
#[error("Mailbox not found")]
|
||||||
|
MailboxNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -6,13 +6,14 @@ use crate::common::fragments::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
rfc3501_imap4rev1_base();
|
rfc3501_imap4rev1_base();
|
||||||
rfc3691_imapext_unselect();
|
|
||||||
rfc5161_imapext_enable();
|
|
||||||
rfc6851_imapext_move();
|
rfc6851_imapext_move();
|
||||||
rfc7888_imapext_literal();
|
|
||||||
rfc4551_imapext_condstore();
|
rfc4551_imapext_condstore();
|
||||||
rfc2177_imapext_idle();
|
rfc2177_imapext_idle();
|
||||||
rfc4315_imapext_uidplus();
|
rfc5161_imapext_enable(); // 1
|
||||||
|
rfc3691_imapext_unselect(); // 2
|
||||||
|
rfc7888_imapext_literal(); // 3
|
||||||
|
rfc4315_imapext_uidplus(); // 4
|
||||||
|
rfc5819_imapext_liststatus(); // 5
|
||||||
println!("✅ SUCCESS 🌟🚀🥳🙏🥹");
|
println!("✅ SUCCESS 🌟🚀🥳🙏🥹");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,3 +308,50 @@ fn rfc4315_imapext_uidplus() {
|
||||||
})
|
})
|
||||||
.expect("test fully run");
|
.expect("test fully run");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Example
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// 30 list "" "*" RETURN (STATUS (MESSAGES UNSEEN))
|
||||||
|
/// * LIST (\Subscribed) "." INBOX
|
||||||
|
/// * STATUS INBOX (MESSAGES 2 UNSEEN 1)
|
||||||
|
/// 30 OK LIST completed
|
||||||
|
/// ```
|
||||||
|
fn rfc5819_imapext_liststatus() {
|
||||||
|
println!("🧪 rfc5819_imapext_liststatus");
|
||||||
|
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
|
||||||
|
// Test setup, check capability, add 2 emails, read 1
|
||||||
|
connect(imap_socket).context("server says hello")?;
|
||||||
|
capability(imap_socket, Extension::ListStatus).context("check server capabilities")?;
|
||||||
|
login(imap_socket, Account::Alice).context("login test")?;
|
||||||
|
select(imap_socket, Mailbox::Inbox, SelectMod::None).context("select inbox")?;
|
||||||
|
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
|
||||||
|
lmtp_deliver_email(lmtp_socket, Email::Basic).context("mail delivered successfully")?;
|
||||||
|
lmtp_deliver_email(lmtp_socket, Email::Multipart).context("mail delivered successfully")?;
|
||||||
|
noop_exists(imap_socket, 2).context("noop loop must detect a new email")?;
|
||||||
|
fetch(
|
||||||
|
imap_socket,
|
||||||
|
Selection::FirstId,
|
||||||
|
FetchKind::Rfc822,
|
||||||
|
FetchMod::None,
|
||||||
|
)
|
||||||
|
.context("read one message")?;
|
||||||
|
close(imap_socket).context("close inbox")?;
|
||||||
|
|
||||||
|
// Test return status MESSAGES UNSEEN
|
||||||
|
let ret = list(
|
||||||
|
imap_socket,
|
||||||
|
MbxSelect::All,
|
||||||
|
ListReturn::StatusMessagesUnseen,
|
||||||
|
)?;
|
||||||
|
assert!(ret.contains("* STATUS INBOX (MESSAGES 2 UNSEEN 1)"));
|
||||||
|
|
||||||
|
// Test that without RETURN, no status is sent
|
||||||
|
let ret = list(imap_socket, MbxSelect::All, ListReturn::None)?;
|
||||||
|
assert!(!ret.contains("* STATUS"));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.expect("test fully run");
|
||||||
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub enum Extension {
|
||||||
LiteralPlus,
|
LiteralPlus,
|
||||||
Idle,
|
Idle,
|
||||||
UidPlus,
|
UidPlus,
|
||||||
|
ListStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Enable {
|
pub enum Enable {
|
||||||
|
@ -107,6 +108,15 @@ pub enum StatusKind {
|
||||||
HighestModSeq,
|
HighestModSeq,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum MbxSelect {
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ListReturn {
|
||||||
|
None,
|
||||||
|
StatusMessagesUnseen,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn capability(imap: &mut TcpStream, ext: Extension) -> Result<()> {
|
pub fn capability(imap: &mut TcpStream, ext: Extension) -> Result<()> {
|
||||||
imap.write(&b"5 capability\r\n"[..])?;
|
imap.write(&b"5 capability\r\n"[..])?;
|
||||||
|
|
||||||
|
@ -118,6 +128,7 @@ pub fn capability(imap: &mut TcpStream, ext: Extension) -> Result<()> {
|
||||||
Extension::LiteralPlus => Some("LITERAL+"),
|
Extension::LiteralPlus => Some("LITERAL+"),
|
||||||
Extension::Idle => Some("IDLE"),
|
Extension::Idle => Some("IDLE"),
|
||||||
Extension::UidPlus => Some("UIDPLUS"),
|
Extension::UidPlus => Some("UIDPLUS"),
|
||||||
|
Extension::ListStatus => Some("LIST-STATUS"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buffer: [u8; 6000] = [0; 6000];
|
let mut buffer: [u8; 6000] = [0; 6000];
|
||||||
|
@ -169,6 +180,25 @@ pub fn create_mailbox(imap: &mut TcpStream, mbx: Mailbox) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list(imap: &mut TcpStream, select: MbxSelect, mod_return: ListReturn) -> Result<String> {
|
||||||
|
let mut buffer: [u8; 6000] = [0; 6000];
|
||||||
|
|
||||||
|
let select_str = match select {
|
||||||
|
MbxSelect::All => "%",
|
||||||
|
};
|
||||||
|
|
||||||
|
let mod_return_str = match mod_return {
|
||||||
|
ListReturn::None => "",
|
||||||
|
ListReturn::StatusMessagesUnseen => " RETURN (STATUS (MESSAGES UNSEEN))",
|
||||||
|
};
|
||||||
|
|
||||||
|
imap.write(format!("19 LIST \"\" \"{}\"{}\r\n", select_str, mod_return_str).as_bytes())?;
|
||||||
|
|
||||||
|
let read = read_lines(imap, &mut buffer, Some(&b"19 OK"[..]))?;
|
||||||
|
let srv_msg = std::str::from_utf8(read)?;
|
||||||
|
Ok(srv_msg.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn select(imap: &mut TcpStream, mbx: Mailbox, modifier: SelectMod) -> Result<String> {
|
pub fn select(imap: &mut TcpStream, mbx: Mailbox, modifier: SelectMod) -> Result<String> {
|
||||||
let mut buffer: [u8; 6000] = [0; 6000];
|
let mut buffer: [u8; 6000] = [0; 6000];
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue