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,414 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::utf7::utf7_encode;
|
||||
|
||||
use super::{
|
||||
ImapResponse, quoted_string,
|
||||
status::{Status, StatusItem},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Arguments {
|
||||
Basic {
|
||||
tag: String,
|
||||
reference_name: String,
|
||||
mailbox_name: String,
|
||||
},
|
||||
Extended {
|
||||
tag: String,
|
||||
reference_name: String,
|
||||
mailbox_name: Vec<String>,
|
||||
selection_options: Vec<SelectionOption>,
|
||||
return_options: Vec<ReturnOption>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Response {
|
||||
pub is_rev2: bool,
|
||||
pub is_utf8: bool,
|
||||
pub is_lsub: bool,
|
||||
pub list_items: Vec<ListItem>,
|
||||
pub status_items: Vec<StatusItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SelectionOption {
|
||||
Subscribed,
|
||||
Remote,
|
||||
RecursiveMatch,
|
||||
SpecialUse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ReturnOption {
|
||||
Subscribed,
|
||||
Children,
|
||||
Status(Vec<Status>),
|
||||
SpecialUse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Attribute {
|
||||
NoInferiors,
|
||||
NoSelect,
|
||||
Marked,
|
||||
Unmarked,
|
||||
NonExistent,
|
||||
HasChildren,
|
||||
HasNoChildren,
|
||||
Subscribed,
|
||||
Remote,
|
||||
All,
|
||||
Archive,
|
||||
Drafts,
|
||||
Flagged,
|
||||
Junk,
|
||||
Sent,
|
||||
Trash,
|
||||
Important,
|
||||
Memos,
|
||||
Scheduled,
|
||||
Snoozed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ChildInfo {
|
||||
Subscribed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Tag {
|
||||
ChildInfo(Vec<ChildInfo>),
|
||||
OldName(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ListItem {
|
||||
pub mailbox_name: String,
|
||||
pub attributes: Vec<Attribute>,
|
||||
pub tags: Vec<Tag>,
|
||||
}
|
||||
|
||||
impl Arguments {
|
||||
pub fn is_separator_query(&self) -> bool {
|
||||
match self {
|
||||
Arguments::Basic {
|
||||
mailbox_name,
|
||||
reference_name,
|
||||
..
|
||||
} => mailbox_name.is_empty() && reference_name.is_empty(),
|
||||
Arguments::Extended {
|
||||
mailbox_name,
|
||||
reference_name,
|
||||
..
|
||||
} => mailbox_name.is_empty() && reference_name.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_tag(self) -> String {
|
||||
match self {
|
||||
Arguments::Basic { tag, .. } => tag,
|
||||
Arguments::Extended { tag, .. } => tag,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
pub fn is_rev1(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Attribute::NoInferiors | Attribute::NoSelect | Attribute::Marked | Attribute::Unmarked
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.extend_from_slice(match self {
|
||||
Attribute::NoInferiors => b"\\NoInferiors",
|
||||
Attribute::NoSelect => b"\\NoSelect",
|
||||
Attribute::Marked => b"\\Marked",
|
||||
Attribute::Unmarked => b"\\Unmarked",
|
||||
Attribute::NonExistent => b"\\NonExistent",
|
||||
Attribute::HasChildren => b"\\HasChildren",
|
||||
Attribute::HasNoChildren => b"\\HasNoChildren",
|
||||
Attribute::Subscribed => b"\\Subscribed",
|
||||
Attribute::Remote => b"\\Remote",
|
||||
Attribute::All => b"\\All",
|
||||
Attribute::Archive => b"\\Archive",
|
||||
Attribute::Drafts => b"\\Drafts",
|
||||
Attribute::Flagged => b"\\Flagged",
|
||||
Attribute::Junk => b"\\Junk",
|
||||
Attribute::Sent => b"\\Sent",
|
||||
Attribute::Trash => b"\\Trash",
|
||||
Attribute::Important => b"\\Important",
|
||||
Attribute::Memos => b"\\Memos",
|
||||
Attribute::Scheduled => b"\\Scheduled",
|
||||
Attribute::Snoozed => b"\\Snoozed",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Attribute {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
hashify::tiny_map!(value.as_bytes(),
|
||||
"archive" => Attribute::Archive,
|
||||
"drafts" => Attribute::Drafts,
|
||||
"junk" => Attribute::Junk,
|
||||
"sent" => Attribute::Sent,
|
||||
"trash" => Attribute::Trash,
|
||||
"important" => Attribute::Important,
|
||||
"memos" => Attribute::Memos,
|
||||
"scheduled" => Attribute::Scheduled,
|
||||
"snoozed" => Attribute::Snoozed,
|
||||
)
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildInfo {
|
||||
pub fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
buf.push(b'\"');
|
||||
buf.extend_from_slice(match self {
|
||||
ChildInfo::Subscribed => b"SUBSCRIBED",
|
||||
});
|
||||
buf.push(b'\"');
|
||||
}
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn serialize(&self, buf: &mut Vec<u8>) {
|
||||
match self {
|
||||
Tag::ChildInfo(child_info) => {
|
||||
buf.extend_from_slice(b"\"CHILDINFO\" (");
|
||||
for (pos, child_info) in child_info.iter().enumerate() {
|
||||
if pos > 0 {
|
||||
buf.push(b' ');
|
||||
}
|
||||
child_info.serialize(buf);
|
||||
}
|
||||
buf.push(b')');
|
||||
}
|
||||
Tag::OldName(old_name) => {
|
||||
buf.extend_from_slice(b"\"OLDNAME\" (");
|
||||
quoted_string(buf, old_name);
|
||||
buf.push(b')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListItem {
|
||||
pub fn new(name: impl Into<String>) -> Self {
|
||||
ListItem {
|
||||
mailbox_name: name.into(),
|
||||
attributes: Vec::new(),
|
||||
tags: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize(&self, buf: &mut Vec<u8>, is_rev2: bool, is_utf8: bool, is_lsub: bool) {
|
||||
let normalized_mailbox_name = utf7_encode(&self.mailbox_name);
|
||||
if !is_lsub {
|
||||
buf.extend_from_slice(b"* LIST (");
|
||||
} else {
|
||||
buf.extend_from_slice(b"* LSUB (");
|
||||
}
|
||||
for (pos, attr) in self.attributes.iter().enumerate() {
|
||||
if pos > 0 {
|
||||
buf.push(b' ');
|
||||
}
|
||||
attr.serialize(buf);
|
||||
}
|
||||
buf.extend_from_slice(b") \"/\" ");
|
||||
let mut extra_tags = Vec::new();
|
||||
|
||||
if normalized_mailbox_name != self.mailbox_name {
|
||||
if is_rev2 || is_utf8 {
|
||||
quoted_string(buf, &self.mailbox_name);
|
||||
if is_rev2 {
|
||||
extra_tags.push(Tag::OldName(normalized_mailbox_name));
|
||||
}
|
||||
} else {
|
||||
quoted_string(buf, &normalized_mailbox_name);
|
||||
}
|
||||
} else {
|
||||
quoted_string(buf, &self.mailbox_name);
|
||||
}
|
||||
|
||||
if !extra_tags.is_empty() || !self.tags.is_empty() {
|
||||
buf.extend_from_slice(b" (");
|
||||
for (pos, tag) in extra_tags.iter().chain(self.tags.iter()).enumerate() {
|
||||
if pos > 0 {
|
||||
buf.push(b' ');
|
||||
}
|
||||
tag.serialize(buf);
|
||||
}
|
||||
buf.extend_from_slice(b")\r\n");
|
||||
} else {
|
||||
buf.extend_from_slice(b"\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImapResponse for Response {
|
||||
fn serialize(self) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(100);
|
||||
|
||||
match (self.list_items.is_empty(), self.status_items.is_empty()) {
|
||||
(false, false) => {
|
||||
for (list_item, status_item) in self.list_items.iter().zip(self.status_items.iter())
|
||||
{
|
||||
list_item.serialize(&mut buf, self.is_rev2, self.is_utf8, self.is_lsub);
|
||||
status_item.serialize(&mut buf, self.is_rev2);
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
for list_item in &self.list_items {
|
||||
list_item.serialize(&mut buf, self.is_rev2, self.is_utf8, self.is_lsub);
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
for status_item in &self.status_items {
|
||||
status_item.serialize(&mut buf, self.is_rev2);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::protocol::{
|
||||
ImapResponse,
|
||||
status::{Status, StatusItem, StatusItemType},
|
||||
};
|
||||
|
||||
use super::{Attribute, ChildInfo, ListItem, Tag};
|
||||
|
||||
#[test]
|
||||
fn serialize_list_item() {
|
||||
for (response, expected_v2, expected_v1) in [
|
||||
(
|
||||
super::ListItem {
|
||||
mailbox_name: "".into(),
|
||||
attributes: vec![],
|
||||
tags: vec![],
|
||||
},
|
||||
"* LIST () \"/\" \"\"\r\n",
|
||||
"* LIST () \"/\" \"\"\r\n",
|
||||
),
|
||||
(
|
||||
super::ListItem {
|
||||
mailbox_name: "中國書店".into(),
|
||||
attributes: vec![Attribute::NoInferiors, Attribute::Drafts],
|
||||
tags: vec![],
|
||||
},
|
||||
concat!(
|
||||
"* LIST (\\NoInferiors \\Drafts) \"/\" \"中國書店\" ",
|
||||
"(\"OLDNAME\" (\"&Ti1XC2b4Xpc-\"))\r\n"
|
||||
),
|
||||
"* LIST (\\NoInferiors \\Drafts) \"/\" \"&Ti1XC2b4Xpc-\"\r\n",
|
||||
),
|
||||
(
|
||||
super::ListItem {
|
||||
mailbox_name: "☺".into(),
|
||||
attributes: vec![Attribute::Subscribed, Attribute::Remote],
|
||||
tags: vec![Tag::ChildInfo(vec![ChildInfo::Subscribed])],
|
||||
},
|
||||
concat!(
|
||||
"* LIST (\\Subscribed \\Remote) \"/\" \"☺\" ",
|
||||
"(\"OLDNAME\" (\"&Jjo-\") \"CHILDINFO\" (\"SUBSCRIBED\"))\r\n"
|
||||
),
|
||||
concat!(
|
||||
"* LIST (\\Subscribed \\Remote) \"/\" \"&Jjo-\" ",
|
||||
"(\"CHILDINFO\" (\"SUBSCRIBED\"))\r\n"
|
||||
),
|
||||
),
|
||||
(
|
||||
super::ListItem {
|
||||
mailbox_name: "foo".into(),
|
||||
attributes: vec![Attribute::HasNoChildren],
|
||||
tags: vec![Tag::ChildInfo(vec![ChildInfo::Subscribed])],
|
||||
},
|
||||
"* LIST (\\HasNoChildren) \"/\" \"foo\" (\"CHILDINFO\" (\"SUBSCRIBED\"))\r\n",
|
||||
"* LIST (\\HasNoChildren) \"/\" \"foo\" (\"CHILDINFO\" (\"SUBSCRIBED\"))\r\n",
|
||||
),
|
||||
] {
|
||||
let mut buf_1 = Vec::with_capacity(100);
|
||||
let mut buf_2 = Vec::with_capacity(100);
|
||||
|
||||
response.serialize(&mut buf_1, false, false, false);
|
||||
response.serialize(&mut buf_2, true, true, false);
|
||||
|
||||
let response_v1 = String::from_utf8(buf_1).unwrap();
|
||||
let response_v2 = String::from_utf8(buf_2).unwrap();
|
||||
|
||||
assert_eq!(response_v2, expected_v2);
|
||||
assert_eq!(response_v1, expected_v1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_list() {
|
||||
let mut response = super::Response {
|
||||
list_items: vec![
|
||||
ListItem {
|
||||
mailbox_name: "INBOX".into(),
|
||||
attributes: vec![Attribute::Subscribed],
|
||||
tags: vec![],
|
||||
},
|
||||
ListItem {
|
||||
mailbox_name: "foo".into(),
|
||||
attributes: vec![],
|
||||
tags: vec![Tag::ChildInfo(vec![ChildInfo::Subscribed])],
|
||||
},
|
||||
],
|
||||
status_items: vec![
|
||||
StatusItem {
|
||||
mailbox_name: "INBOX".into(),
|
||||
items: vec![(Status::Messages, StatusItemType::Number(17))],
|
||||
},
|
||||
StatusItem {
|
||||
mailbox_name: "foo".into(),
|
||||
items: vec![
|
||||
(Status::Messages, StatusItemType::Number(30)),
|
||||
(Status::Unseen, StatusItemType::Number(29)),
|
||||
],
|
||||
},
|
||||
],
|
||||
is_lsub: false,
|
||||
is_rev2: true,
|
||||
is_utf8: true,
|
||||
};
|
||||
let expected_v2 = concat!(
|
||||
"* LIST (\\Subscribed) \"/\" \"INBOX\"\r\n",
|
||||
"* STATUS \"INBOX\" (MESSAGES 17)\r\n",
|
||||
"* LIST () \"/\" \"foo\" (\"CHILDINFO\" (\"SUBSCRIBED\"))\r\n",
|
||||
"* STATUS \"foo\" (MESSAGES 30 UNSEEN 29)\r\n",
|
||||
);
|
||||
let expected_v1 = concat!(
|
||||
"* LSUB (\\Subscribed) \"/\" \"INBOX\"\r\n",
|
||||
"* LSUB () \"/\" \"foo\" (\"CHILDINFO\" (\"SUBSCRIBED\"))\r\n",
|
||||
);
|
||||
|
||||
let response_v2 = String::from_utf8(response.clone().serialize()).unwrap();
|
||||
response.is_rev2 = false;
|
||||
response.is_utf8 = false;
|
||||
response.is_lsub = true;
|
||||
response.status_items.clear();
|
||||
let response_v1 = String::from_utf8(response.serialize()).unwrap();
|
||||
|
||||
assert_eq!(response_v2, expected_v2);
|
||||
assert_eq!(response_v1, expected_v1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user