Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use compact_str::{CompactString, ToCompactString};
|
||||
|
||||
use crate::{
|
||||
Command,
|
||||
protocol::{
|
||||
list::{self, ReturnOption, SelectionOption},
|
||||
status::Status,
|
||||
},
|
||||
receiver::{Request, Token, bad},
|
||||
utf7::utf7_maybe_decode,
|
||||
};
|
||||
|
||||
impl Request<Command> {
|
||||
#[allow(clippy::while_let_on_iterator)]
|
||||
pub fn parse_list(self, is_utf8: bool) -> trc::Result<list::Arguments> {
|
||||
match self.tokens.len() {
|
||||
0 | 1 => Err(self.into_error("Missing arguments.")),
|
||||
2 => {
|
||||
let mut tokens = self.tokens.into_iter();
|
||||
Ok(list::Arguments::Basic {
|
||||
reference_name: tokens
|
||||
.next()
|
||||
.unwrap()
|
||||
.unwrap_string()
|
||||
.map_err(|v| bad(self.tag.to_compact_string(), v))?,
|
||||
mailbox_name: utf7_maybe_decode(
|
||||
tokens
|
||||
.next()
|
||||
.unwrap()
|
||||
.unwrap_string()
|
||||
.map_err(|v| bad(self.tag.to_compact_string(), v))?,
|
||||
is_utf8,
|
||||
),
|
||||
tag: self.tag,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
let mut tokens = self.tokens.into_iter();
|
||||
let mut selection_options = Vec::new();
|
||||
let mut return_options = Vec::new();
|
||||
let mut mailbox_name = Vec::new();
|
||||
|
||||
let reference_name = match tokens.next().unwrap() {
|
||||
Token::ParenthesisOpen => {
|
||||
while let Some(token) = tokens.next() {
|
||||
match token {
|
||||
Token::ParenthesisClose => break,
|
||||
Token::Argument(value) => {
|
||||
selection_options.push(
|
||||
SelectionOption::parse(&value)
|
||||
.map_err(|v| bad(self.tag.to_compact_string(), v))?,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
return Err(bad(
|
||||
self.tag.to_compact_string(),
|
||||
"Invalid selection option argument.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
bad(self.tag.to_compact_string(), "Missing reference name.")
|
||||
})?
|
||||
.unwrap_string()
|
||||
.map_err(|v| bad(self.tag.to_compact_string(), v))?
|
||||
}
|
||||
token => token
|
||||
.unwrap_string()
|
||||
.map_err(|v| bad(self.tag.to_compact_string(), v))?,
|
||||
};
|
||||
|
||||
match tokens
|
||||
.next()
|
||||
.ok_or_else(|| bad(self.tag.to_compact_string(), "Missing mailbox name."))?
|
||||
{
|
||||
Token::ParenthesisOpen => {
|
||||
while let Some(token) = tokens.next() {
|
||||
match token {
|
||||
Token::ParenthesisClose => break,
|
||||
token => {
|
||||
mailbox_name.push(
|
||||
token
|
||||
.unwrap_string()
|
||||
.map_err(|v| bad(self.tag.to_compact_string(), v))?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
token => {
|
||||
mailbox_name.push(utf7_maybe_decode(
|
||||
token
|
||||
.unwrap_string()
|
||||
.map_err(|v| bad(self.tag.to_compact_string(), v))?,
|
||||
is_utf8,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if tokens
|
||||
.next()
|
||||
.is_some_and(|token| token.eq_ignore_ascii_case(b"return"))
|
||||
{
|
||||
if tokens
|
||||
.next()
|
||||
.is_none_or(|token| !token.is_parenthesis_open())
|
||||
{
|
||||
return Err(bad(
|
||||
self.tag.to_compact_string(),
|
||||
"Invalid return option, expected parenthesis.",
|
||||
));
|
||||
}
|
||||
|
||||
while let Some(token) = tokens.next() {
|
||||
match token {
|
||||
Token::ParenthesisClose => break,
|
||||
Token::Argument(value) => {
|
||||
let mut return_option = ReturnOption::parse(&value)
|
||||
.map_err(|v| bad(self.tag.to_compact_string(), v))?;
|
||||
if let ReturnOption::Status(status) = &mut return_option {
|
||||
if tokens
|
||||
.next()
|
||||
.is_none_or(|token| !token.is_parenthesis_open())
|
||||
{
|
||||
return Err(bad(
|
||||
CompactString::from_string_buffer(self.tag),
|
||||
"Invalid return option, expected parenthesis after STATUS.",
|
||||
));
|
||||
}
|
||||
while let Some(token) = tokens.next() {
|
||||
match token {
|
||||
Token::ParenthesisClose => break,
|
||||
Token::Argument(value) => {
|
||||
status.push(Status::parse(&value).map_err(
|
||||
|v| bad(self.tag.to_compact_string(), v),
|
||||
)?);
|
||||
}
|
||||
_ => {
|
||||
return Err(bad(
|
||||
CompactString::from_string_buffer(self.tag),
|
||||
"Invalid status return option argument.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return_options.push(return_option);
|
||||
}
|
||||
_ => {
|
||||
return Err(bad(
|
||||
self.tag.to_compact_string(),
|
||||
"Invalid return option argument.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(list::Arguments::Extended {
|
||||
tag: self.tag,
|
||||
reference_name,
|
||||
mailbox_name,
|
||||
selection_options,
|
||||
return_options,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectionOption {
|
||||
pub fn parse(value: &[u8]) -> super::Result<Self> {
|
||||
hashify::tiny_map_ignore_case!(value,
|
||||
"SUBSCRIBED" => Self::Subscribed,
|
||||
"REMOTE" => Self::Remote,
|
||||
"RECURSIVEMATCH" => Self::RecursiveMatch,
|
||||
"SPECIAL-USE" => Self::SpecialUse,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Unsupported selection option '{}'.",
|
||||
String::from_utf8_lossy(value)
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ReturnOption {
|
||||
pub fn parse(value: &[u8]) -> super::Result<Self> {
|
||||
hashify::tiny_map_ignore_case!(value,
|
||||
"SUBSCRIBED" => Self::Subscribed,
|
||||
"CHILDREN" => Self::Children,
|
||||
"STATUS" => Self::Status(Vec::with_capacity(2)),
|
||||
"SPECIAL-USE" => Self::SpecialUse,
|
||||
)
|
||||
.ok_or_else(|| format!("Invalid return option {:?}", String::from_utf8_lossy(value)).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
protocol::{
|
||||
list::{self, ReturnOption, SelectionOption},
|
||||
status::Status,
|
||||
},
|
||||
receiver::Receiver,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn parse_list() {
|
||||
let mut receiver = Receiver::new();
|
||||
|
||||
for (command, arguments) in [
|
||||
(
|
||||
"A682 LIST \"\" *\r\n",
|
||||
list::Arguments::Basic {
|
||||
tag: "A682".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: "*".into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"A02 LIST (SUBSCRIBED) \"\" \"*\"\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "A02".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["*".into()],
|
||||
selection_options: vec![SelectionOption::Subscribed],
|
||||
return_options: vec![],
|
||||
},
|
||||
),
|
||||
(
|
||||
"A03 LIST () \"\" \"%\" RETURN (CHILDREN)\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "A03".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["%".into()],
|
||||
selection_options: vec![],
|
||||
return_options: vec![ReturnOption::Children],
|
||||
},
|
||||
),
|
||||
(
|
||||
"A04 LIST (REMOTE) \"\" \"%\" RETURN (CHILDREN)\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "A04".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["%".into()],
|
||||
selection_options: vec![SelectionOption::Remote],
|
||||
return_options: vec![ReturnOption::Children],
|
||||
},
|
||||
),
|
||||
(
|
||||
"A05 LIST (REMOTE SUBSCRIBED) \"\" \"*\"\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "A05".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["*".into()],
|
||||
selection_options: vec![SelectionOption::Remote, SelectionOption::Subscribed],
|
||||
return_options: vec![],
|
||||
},
|
||||
),
|
||||
(
|
||||
"A06 LIST (REMOTE) \"\" \"*\" RETURN (SUBSCRIBED)\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "A06".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["*".into()],
|
||||
selection_options: vec![SelectionOption::Remote],
|
||||
return_options: vec![ReturnOption::Subscribed],
|
||||
},
|
||||
),
|
||||
(
|
||||
"C04 LIST (SUBSCRIBED RECURSIVEMATCH) \"\" \"%\"\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "C04".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["%".into()],
|
||||
selection_options: vec![
|
||||
SelectionOption::Subscribed,
|
||||
SelectionOption::RecursiveMatch,
|
||||
],
|
||||
return_options: vec![],
|
||||
},
|
||||
),
|
||||
(
|
||||
"C04 LIST (SUBSCRIBED RECURSIVEMATCH) \"\" \"%\" RETURN (CHILDREN)\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "C04".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["%".into()],
|
||||
selection_options: vec![
|
||||
SelectionOption::Subscribed,
|
||||
SelectionOption::RecursiveMatch,
|
||||
],
|
||||
return_options: vec![ReturnOption::Children],
|
||||
},
|
||||
),
|
||||
(
|
||||
"a1 LIST \"\" (\"foo\")\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "a1".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["foo".into()],
|
||||
selection_options: vec![],
|
||||
return_options: vec![],
|
||||
},
|
||||
),
|
||||
(
|
||||
"a3.1 LIST \"\" (% music/rock)\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "a3.1".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["%".into(), "music/rock".into()],
|
||||
selection_options: vec![],
|
||||
return_options: vec![],
|
||||
},
|
||||
),
|
||||
(
|
||||
"BBB LIST \"\" (\"INBOX\" \"Drafts\" \"Sent/%\")\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "BBB".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["INBOX".into(), "Drafts".into(), "Sent/%".into()],
|
||||
selection_options: vec![],
|
||||
return_options: vec![],
|
||||
},
|
||||
),
|
||||
(
|
||||
"A01 LIST \"\" % RETURN (STATUS (MESSAGES UNSEEN))\r\n",
|
||||
list::Arguments::Extended {
|
||||
tag: "A01".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["%".into()],
|
||||
selection_options: vec![],
|
||||
return_options: vec![ReturnOption::Status(vec![
|
||||
Status::Messages,
|
||||
Status::Unseen,
|
||||
])],
|
||||
},
|
||||
),
|
||||
(
|
||||
concat!(
|
||||
"A02 LIST (SUBSCRIBED RECURSIVEMATCH) \"\" ",
|
||||
"% RETURN (CHILDREN STATUS (MESSAGES))\r\n"
|
||||
),
|
||||
list::Arguments::Extended {
|
||||
tag: "A02".into(),
|
||||
reference_name: "".into(),
|
||||
mailbox_name: vec!["%".into()],
|
||||
selection_options: vec![
|
||||
SelectionOption::Subscribed,
|
||||
SelectionOption::RecursiveMatch,
|
||||
],
|
||||
return_options: vec![
|
||||
ReturnOption::Children,
|
||||
ReturnOption::Status(vec![Status::Messages]),
|
||||
],
|
||||
},
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
receiver
|
||||
.parse(&mut command.as_bytes().iter())
|
||||
.unwrap()
|
||||
.parse_list(true)
|
||||
.unwrap(),
|
||||
arguments
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user