Fix list wildcards
This commit is contained in:
parent
15a354f949
commit
c703e3e2b8
5 changed files with 85 additions and 31 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -30,7 +30,6 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"duplexify",
|
"duplexify",
|
||||||
"futures",
|
"futures",
|
||||||
"globset",
|
|
||||||
"hex",
|
"hex",
|
||||||
"im",
|
"im",
|
||||||
"imap-codec",
|
"imap-codec",
|
||||||
|
@ -444,15 +443,6 @@ dependencies = [
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bstr"
|
|
||||||
version = "0.2.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.10.0"
|
version = "3.10.0"
|
||||||
|
@ -973,19 +963,6 @@ dependencies = [
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"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]]
|
[[package]]
|
||||||
name = "gloo-timers"
|
name = "gloo-timers"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
|
@ -15,7 +15,6 @@ clap = { version = "3.1.18", features = ["derive", "env"] }
|
||||||
duplexify = "1.1.0"
|
duplexify = "1.1.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
globset = "0.4"
|
|
||||||
im = "15"
|
im = "15"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
|
|
@ -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 mailboxes = self.user.list_mailboxes().await?;
|
||||||
let mut vmailboxes = BTreeMap::new();
|
let mut vmailboxes = BTreeMap::new();
|
||||||
for mb in mailboxes.iter() {
|
for mb in mailboxes.iter() {
|
||||||
|
@ -135,12 +158,9 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
vmailboxes.insert(mb, true);
|
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![];
|
let mut ret = vec![];
|
||||||
for (mb, is_real) in vmailboxes.iter() {
|
for (mb, is_real) in vmailboxes.iter() {
|
||||||
if wildcard_pat.is_match(mb) {
|
if matches_wildcard(&wildcard, &mb) {
|
||||||
let mailbox = mb
|
let mailbox = mb
|
||||||
.to_string()
|
.to_string()
|
||||||
.try_into()
|
.try_into()
|
||||||
|
@ -378,3 +398,49 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
Ok((mb, uidvalidity, uid))
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,8 +41,14 @@ impl State {
|
||||||
match (self, tr) {
|
match (self, tr) {
|
||||||
(s, Transition::None) => Ok(s),
|
(s, Transition::None) => Ok(s),
|
||||||
(State::NotAuthenticated, Transition::Authenticate(u)) => Ok(State::Authenticated(u)),
|
(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::Selected(u, _), Transition::Unselect) => Ok(State::Authenticated(u)),
|
||||||
(State::Examined(u, _), Transition::Unselect) => Ok(State::Authenticated(u)),
|
(State::Examined(u, _), Transition::Unselect) => Ok(State::Authenticated(u)),
|
||||||
(_, Transition::Logout) => Ok(State::Logout),
|
(_, Transition::Logout) => Ok(State::Logout),
|
||||||
|
|
|
@ -140,7 +140,13 @@ impl Instance {
|
||||||
let res = match ctrl {
|
let res = match ctrl {
|
||||||
Ok((res, tr)) => {
|
Ok((res, tr)) => {
|
||||||
//@FIXME remove unwrap
|
//@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
|
//@FIXME enrich here the command with some global status
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue