aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2022-07-13 15:26:00 +0200
committerAlex Auvolat <alex@adnab.me>2022-07-13 15:26:00 +0200
commitc703e3e2b813cf74fc2d3d87b045dcc9fb93d190 (patch)
treee7aa3a1ca2922207bad4275c4a2afb111bf2f13a
parent15a354f9499c82dff69db71d92097bec51ee54bf (diff)
downloadaerogramme-c703e3e2b813cf74fc2d3d87b045dcc9fb93d190.tar.gz
aerogramme-c703e3e2b813cf74fc2d3d87b045dcc9fb93d190.zip
Fix list wildcards
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml1
-rw-r--r--src/imap/command/authenticated.rs74
-rw-r--r--src/imap/flow.rs10
-rw-r--r--src/imap/session.rs8
5 files changed, 85 insertions, 31 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 11b1d86..b2d5365 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -30,7 +30,6 @@ dependencies = [
"clap",
"duplexify",
"futures",
- "globset",
"hex",
"im",
"imap-codec",
@@ -445,15 +444,6 @@ dependencies = [
]
[[package]]
-name = "bstr"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
-dependencies = [
- "memchr",
-]
-
-[[package]]
name = "bumpalo"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -974,19 +964,6 @@ dependencies = [
]
[[package]]
-name = "globset"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
-dependencies = [
- "aho-corasick",
- "bstr",
- "fnv",
- "log",
- "regex",
-]
-
-[[package]]
name = "gloo-timers"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index dd6cf68..5398f34 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,7 +15,6 @@ clap = { version = "3.1.18", features = ["derive", "env"] }
duplexify = "1.1.0"
hex = "0.4"
futures = "0.3"
-globset = "0.4"
im = "15"
itertools = "0.10"
lazy_static = "1.4"
diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs
index 6c2e43b..5cf8cf9 100644
--- a/src/imap/command/authenticated.rs
+++ b/src/imap/command/authenticated.rs
@@ -121,6 +121,29 @@ impl<'a> AuthenticatedContext<'a> {
));
}
+ let wildcard = String::try_from(mailbox_wildcard.clone())?;
+ if wildcard.is_empty() {
+ if is_lsub {
+ return Ok((
+ Response::ok("LSUB complete")?.with_body(vec![Data::Lsub {
+ items: vec![],
+ delimiter: Some(MAILBOX_HIERARCHY_DELIMITER),
+ mailbox: "".try_into().unwrap(),
+ }]),
+ flow::Transition::None,
+ ));
+ } else {
+ return Ok((
+ Response::ok("LIST complete")?.with_body(vec![Data::List {
+ items: vec![],
+ delimiter: Some(MAILBOX_HIERARCHY_DELIMITER),
+ mailbox: "".try_into().unwrap(),
+ }]),
+ flow::Transition::None,
+ ));
+ }
+ }
+
let mailboxes = self.user.list_mailboxes().await?;
let mut vmailboxes = BTreeMap::new();
for mb in mailboxes.iter() {
@@ -135,12 +158,9 @@ impl<'a> AuthenticatedContext<'a> {
vmailboxes.insert(mb, true);
}
- let wildcard = String::try_from(mailbox_wildcard.clone())?;
- let wildcard_pat = globset::Glob::new(&wildcard)?.compile_matcher();
-
let mut ret = vec![];
for (mb, is_real) in vmailboxes.iter() {
- if wildcard_pat.is_match(mb) {
+ if matches_wildcard(&wildcard, &mb) {
let mailbox = mb
.to_string()
.try_into()
@@ -378,3 +398,49 @@ impl<'a> AuthenticatedContext<'a> {
Ok((mb, uidvalidity, uid))
}
}
+
+fn matches_wildcard(wildcard: &str, name: &str) -> bool {
+ let wildcard = wildcard.chars().collect::<Vec<char>>();
+ let name = name.chars().collect::<Vec<char>>();
+
+ let mut matches = vec![vec![false; wildcard.len() + 1]; name.len() + 1];
+
+ for i in 0..=name.len() {
+ for j in 0..=wildcard.len() {
+ matches[i][j] = (i == 0 && j == 0)
+ || (j > 0
+ && matches[i][j - 1]
+ && (wildcard[j - 1] == '%' || wildcard[j - 1] == '*'))
+ || (i > 0
+ && j > 0
+ && matches[i - 1][j - 1]
+ && wildcard[j - 1] == name[i - 1]
+ && wildcard[j - 1] != '%'
+ && wildcard[j - 1] != '*')
+ || (i > 0
+ && j > 0
+ && matches[i - 1][j]
+ && (wildcard[j - 1] == '*'
+ || (wildcard[j - 1] == '%' && name[i - 1] != MAILBOX_HIERARCHY_DELIMITER)));
+ }
+ }
+
+ matches[name.len()][wildcard.len()]
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_wildcard_matches() {
+ assert!(matches_wildcard("INBOX", "INBOX"));
+ assert!(matches_wildcard("*", "INBOX"));
+ assert!(matches_wildcard("%", "INBOX"));
+ assert!(!matches_wildcard("%", "Test.Azerty"));
+ assert!(!matches_wildcard("INBOX.*", "INBOX"));
+ assert!(matches_wildcard("Sent.*", "Sent.A"));
+ assert!(matches_wildcard("Sent.*", "Sent.A.B"));
+ assert!(!matches_wildcard("Sent.%", "Sent.A.B"));
+ }
+}
diff --git a/src/imap/flow.rs b/src/imap/flow.rs
index 303b498..0adf1f0 100644
--- a/src/imap/flow.rs
+++ b/src/imap/flow.rs
@@ -41,8 +41,14 @@ impl State {
match (self, tr) {
(s, Transition::None) => Ok(s),
(State::NotAuthenticated, Transition::Authenticate(u)) => Ok(State::Authenticated(u)),
- (State::Authenticated(u), Transition::Select(m)) => Ok(State::Selected(u, m)),
- (State::Authenticated(u), Transition::Examine(m)) => Ok(State::Examined(u, m)),
+ (
+ State::Authenticated(u) | State::Selected(u, _) | State::Examined(u, _),
+ Transition::Select(m),
+ ) => Ok(State::Selected(u, m)),
+ (
+ State::Authenticated(u) | State::Selected(u, _) | State::Examined(u, _),
+ Transition::Examine(m),
+ ) => Ok(State::Examined(u, m)),
(State::Selected(u, _), Transition::Unselect) => Ok(State::Authenticated(u)),
(State::Examined(u, _), Transition::Unselect) => Ok(State::Authenticated(u)),
(_, Transition::Logout) => Ok(State::Logout),
diff --git a/src/imap/session.rs b/src/imap/session.rs
index 622a3f6..15141d3 100644
--- a/src/imap/session.rs
+++ b/src/imap/session.rs
@@ -140,7 +140,13 @@ impl Instance {
let res = match ctrl {
Ok((res, tr)) => {
//@FIXME remove unwrap
- self.state = self.state.apply(tr).unwrap();
+ self.state = match self.state.apply(tr) {
+ Ok(new_state) => new_state,
+ Err(e) => {
+ tracing::error!("Invalid transition: {}, exiting", e);
+ break;
+ }
+ };
//@FIXME enrich here the command with some global status