aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-01-20 18:34:37 +0100
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-01-20 18:34:37 +0100
commit9ae5701c7c6d17c72f27f1413ee2fd3d939428a3 (patch)
treee4a1aed578998c1760b5c3caa10459cc6da6a09d
parent4849d776b4321b463dfda0112ceb52e3c66fc4dc (diff)
downloadaerogramme-feat/list-status.tar.gz
aerogramme-feat/list-status.zip
Implement `LIST X Y RETURN (STATUS (UIDNEXT ...))`feat/list-status
-rw-r--r--Cargo.lock49
-rw-r--r--Cargo.toml1
-rw-r--r--mailrage.toml13
-rw-r--r--src/imap/command/authenticated.rs118
4 files changed, 112 insertions, 69 deletions
diff --git a/Cargo.lock b/Cargo.lock
index afa6980..ea9946b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -60,6 +60,7 @@ dependencies = [
"smtp-message",
"smtp-server",
"sodiumoxide",
+ "thiserror",
"tokio",
"tokio-util",
"toml",
@@ -365,7 +366,7 @@ checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -990,7 +991,7 @@ checksum = "f10dd247355bf631d98d2753d87ae62c84c8dcb996ad9b24a4168e0aec29bd6b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -1258,7 +1259,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -1522,7 +1523,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -1723,7 +1724,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite 0.2.13",
- "socket2 0.4.10",
+ "socket2 0.5.5",
"tokio",
"tower-service",
"tracing",
@@ -1807,7 +1808,7 @@ dependencies = [
[[package]]
name = "imap-codec"
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 = [
"abnf-core",
"base64 0.21.5",
@@ -1834,7 +1835,7 @@ dependencies = [
[[package]]
name = "imap-types"
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 = [
"base64 0.21.5",
"bounded-static",
@@ -2283,7 +2284,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -2399,18 +2400,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.71"
+version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@@ -2793,7 +2794,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -3022,9 +3023,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.43"
+version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@@ -3066,22 +3067,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
-version = "1.0.52"
+version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.52"
+version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -3164,7 +3165,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -3248,7 +3249,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
]
[[package]]
@@ -3436,7 +3437,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
"wasm-bindgen-shared",
]
@@ -3470,7 +3471,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.43",
+ "syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
diff --git a/Cargo.toml b/Cargo.toml
index 55f0284..b5158b2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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" }
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" }
+thiserror = "1.0.56"
[dev-dependencies]
diff --git a/mailrage.toml b/mailrage.toml
deleted file mode 100644
index 4cba391..0000000
--- a/mailrage.toml
+++ /dev/null
@@ -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"
diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs
index 60872ae..26b1946 100644
--- a/src/imap/command/authenticated.rs
+++ b/src/imap/command/authenticated.rs
@@ -1,8 +1,11 @@
use std::collections::BTreeMap;
use std::sync::Arc;
+use thiserror::Error;
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::datetime::DateTime;
use imap_codec::imap_types::extensions::enable::CapabilityEnable;
@@ -30,7 +33,7 @@ pub struct AuthenticatedContext<'a> {
}
pub async fn dispatch<'a>(
- ctx: AuthenticatedContext<'a>,
+ mut ctx: AuthenticatedContext<'a>,
) -> Result<(Response<'static>, flow::Transition)> {
match &ctx.req.body {
// Any state
@@ -47,11 +50,12 @@ pub async fn dispatch<'a>(
CommandBody::Lsub {
reference,
mailbox_wildcard,
- } => ctx.list(reference, mailbox_wildcard, true).await,
+ } => ctx.list(reference, mailbox_wildcard, &[], true).await,
CommandBody::List {
reference,
mailbox_wildcard,
- } => ctx.list(reference, mailbox_wildcard, false).await,
+ r#return,
+ } => ctx.list(reference, mailbox_wildcard, r#return, false).await,
CommandBody::Status {
mailbox,
item_names,
@@ -163,9 +167,10 @@ impl<'a> AuthenticatedContext<'a> {
}
async fn list(
- self,
+ &mut self,
reference: &MailboxCodec<'a>,
mailbox_wildcard: &ListMailbox<'a>,
+ must_return: &[ListReturnItem],
is_lsub: bool,
) -> Result<(Response<'static>, flow::Transition)> {
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
let wildcard = match mailbox_wildcard {
ListMailbox::Token(v) => std::str::from_utf8(v.as_ref())?,
@@ -231,11 +241,13 @@ impl<'a> AuthenticatedContext<'a> {
let mut ret = vec![];
for (mb, is_real) in vmailboxes.iter() {
if matches_wildcard(&wildcard, mb) {
- let mailbox = mb
+ let mailbox: MailboxCodec = mb
.to_string()
.try_into()
.map_err(|_| anyhow!("invalid mailbox name"))?;
let mut items = vec![FlagNameAttribute::from(Atom::unvalidated("Subscribed"))];
+
+ // Decoration
if !*is_real {
items.push(FlagNameAttribute::Noselect);
} else {
@@ -247,19 +259,39 @@ impl<'a> AuthenticatedContext<'a> {
_ => (),
};
}
+
+ // Result type
if is_lsub {
ret.push(Data::Lsub {
items,
delimiter: Some(mbx_hier_delim),
- mailbox,
+ mailbox: mailbox.clone(),
});
} else {
ret.push(Data::List {
items,
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(
- self,
+ &mut self,
mailbox: &MailboxCodec<'static>,
attributes: &[StatusDataItemName],
) -> Result<(Response<'static>, flow::Transition)> {
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 = match mb_opt {
Some(mb) => mb,
- None => {
- return Ok((
- Response::build()
- .to_req(self.req)
- .message("Mailbox does not exist")
- .no()?,
- flow::Transition::None,
- ))
- }
+ None => return Err(CommandError::MailboxNotFound.into()),
};
let view = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await;
@@ -322,20 +383,7 @@ impl<'a> AuthenticatedContext<'a> {
},
});
}
-
- 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,
- ))
+ Ok(ret_attrs)
}
async fn subscribe(
@@ -604,6 +652,12 @@ fn matches_wildcard(wildcard: &str, name: &str) -> bool {
matches[name.len()][wildcard.len()]
}
+#[derive(Error, Debug)]
+pub enum CommandError {
+ #[error("Mailbox not found")]
+ MailboxNotFound,
+}
+
#[cfg(test)]
mod tests {
use super::*;