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", @@ -444,15 +443,6 @@ dependencies = [ "tracing-futures", ] -[[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" @@ -973,19 +963,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[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" 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::>(); + let name = name.chars().collect::>(); + + 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