diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 7da83432..a3cba8f6 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -42,21 +42,19 @@ async fn main() -> std::io::Result<()> { parse_credentials(&credentials) } else if let Ok(credentials) = std::env::var("CREDENTIALS") { parse_credentials(&credentials) + } else if args.anonymous { + let credentials = "anonymous:".to_string(); + parse_credentials(&credentials) } else { - if args.anonymous { - let credentials = "anonymous:".to_string(); + let credentials = rpassword::prompt_password( + "\nEnter administrator credentials or press [ENTER] to use OAuth: ", + ) + .unwrap(); + + if !credentials.is_empty() { parse_credentials(&credentials) } else { - let credentials = rpassword::prompt_password( - "\nEnter administrator credentials or press [ENTER] to use OAuth: ", - ) - .unwrap(); - - if !credentials.is_empty() { - parse_credentials(&credentials) - } else { - oauth(&url).await - } + oauth(&url).await } }, timeout: args.timeout, @@ -178,7 +176,7 @@ async fn oauth(url: &str) -> Credentials { #[serde(untagged)] pub enum Response { Error(ManagementApiError), - Data { data: T } + Data { data: T }, } #[derive(Deserialize)] @@ -287,7 +285,7 @@ impl Client { Response::Error(error) => { eprintln!("Request failed: {error})"); std::process::exit(1); - }, + } } } } diff --git a/crates/imap-proto/src/parser/acl.rs b/crates/imap-proto/src/parser/acl.rs index 46ed1605..3629c978 100644 --- a/crates/imap-proto/src/parser/acl.rs +++ b/crates/imap-proto/src/parser/acl.rs @@ -8,10 +8,7 @@ use compact_str::ToCompactString; use crate::{ Command, - protocol::{ - ProtocolVersion, - acl::{self, ModRights, ModRightsOp, Rights}, - }, + protocol::acl::{self, ModRights, ModRightsOp, Rights}, receiver::{Request, bad}, utf7::utf7_maybe_decode, }; @@ -34,7 +31,7 @@ use super::PushUnique; */ impl Request { - pub fn parse_acl(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_acl(self, is_utf8: bool) -> trc::Result { let (has_identifier, has_mod_rights) = match self.command { Command::SetAcl => (true, true), Command::DeleteAcl | Command::ListRights => (true, false), @@ -48,7 +45,7 @@ impl Request { .ok_or_else(|| bad(self.tag.to_compact_string(), "Missing mailbox name."))? .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ); let identifier = if has_identifier { tokens @@ -130,10 +127,7 @@ impl ModRights { mod tests { use crate::{ - protocol::{ - ProtocolVersion, - acl::{self, ModRights, ModRightsOp, Rights}, - }, + protocol::acl::{self, ModRights, ModRightsOp, Rights}, receiver::Receiver, }; @@ -217,7 +211,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .unwrap() - .parse_acl(ProtocolVersion::Rev1) + .parse_acl(false) .unwrap(), arguments, "{:?}", diff --git a/crates/imap-proto/src/parser/append.rs b/crates/imap-proto/src/parser/append.rs index 9488340b..6f2d3309 100644 --- a/crates/imap-proto/src/parser/append.rs +++ b/crates/imap-proto/src/parser/append.rs @@ -9,7 +9,7 @@ use compact_str::ToCompactString; use crate::{ Command, protocol::{ - Flag, ProtocolVersion, + Flag, append::{self, Message}, }, receiver::{Request, Token, bad}, @@ -26,7 +26,7 @@ enum State { } impl Request { - pub fn parse_append(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_append(self, is_utf8: bool) -> trc::Result { match self.tokens.len() { 0 | 1 => Err(self.into_error("Missing arguments.")), _ => { @@ -38,7 +38,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ); let mut messages = Vec::new(); @@ -154,7 +154,7 @@ mod tests { use crate::{ protocol::{ - Flag, ProtocolVersion, + Flag, append::{self, Message}, }, receiver::{Error, Receiver}, @@ -254,7 +254,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .expect(command) - .parse_append(ProtocolVersion::Rev1) + .parse_append(false) .expect(command), arguments, "{:?}", @@ -287,7 +287,7 @@ mod tests { match receiver.parse(&mut line.as_bytes().iter()) { Ok(request) => { assert_eq!( - request.parse_append(ProtocolVersion::Rev1).unwrap(), + request.parse_append(false).unwrap(), append::Arguments { tag: "A003".into(), mailbox_name: "saved-messages".into(), diff --git a/crates/imap-proto/src/parser/copy_move.rs b/crates/imap-proto/src/parser/copy_move.rs index 48599611..e845bf36 100644 --- a/crates/imap-proto/src/parser/copy_move.rs +++ b/crates/imap-proto/src/parser/copy_move.rs @@ -8,7 +8,7 @@ use compact_str::ToCompactString; use crate::{ Command, - protocol::{ProtocolVersion, copy_move}, + protocol::copy_move, receiver::{Request, bad}, utf7::utf7_maybe_decode, }; @@ -16,7 +16,7 @@ use crate::{ use super::parse_sequence_set; impl Request { - pub fn parse_copy_move(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_copy_move(self, is_utf8: bool) -> trc::Result { if self.tokens.len() > 1 { let mut tokens = self.tokens.into_iter(); @@ -34,7 +34,7 @@ impl Request { .ok_or_else(|| bad(self.tag.to_compact_string(), "Missing mailbox name."))? .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ), tag: self.tag, }) @@ -47,7 +47,7 @@ impl Request { #[cfg(test)] mod tests { use crate::{ - protocol::{ProtocolVersion, Sequence, copy_move}, + protocol::{Sequence, copy_move}, receiver::Receiver, }; @@ -59,7 +59,7 @@ mod tests { receiver .parse(&mut "A003 COPY 2:4 MEETING\r\n".as_bytes().iter()) .unwrap() - .parse_copy_move(ProtocolVersion::Rev1) + .parse_copy_move(false) .unwrap(), copy_move::Arguments { sequence_set: Sequence::Range { @@ -74,7 +74,7 @@ mod tests { receiver .parse(&mut "A003 COPY 2:4 \"You &- Me\"\r\n".as_bytes().iter()) .unwrap() - .parse_copy_move(ProtocolVersion::Rev1) + .parse_copy_move(false) .unwrap(), copy_move::Arguments { sequence_set: Sequence::Range { diff --git a/crates/imap-proto/src/parser/create.rs b/crates/imap-proto/src/parser/create.rs index b1cdd36e..f9aaa081 100644 --- a/crates/imap-proto/src/parser/create.rs +++ b/crates/imap-proto/src/parser/create.rs @@ -8,13 +8,13 @@ use compact_str::{CompactString, ToCompactString, format_compact}; use crate::{ Command, - protocol::{ProtocolVersion, create, list::Attribute}, + protocol::{create, list::Attribute}, receiver::{Request, Token, bad}, utf7::utf7_maybe_decode, }; impl Request { - pub fn parse_create(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_create(self, is_utf8: bool) -> trc::Result { if !self.tokens.is_empty() { let mut tokens = self.tokens.into_iter(); let mailbox_name = utf7_maybe_decode( @@ -23,7 +23,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ); let mailbox_role = if let Some(Token::ParenthesisOpen) = tokens.next() { match tokens.next() { @@ -101,7 +101,7 @@ impl Request { mod tests { use crate::{ - protocol::{ProtocolVersion, create, list::Attribute}, + protocol::{create, list::Attribute}, receiver::Receiver, }; @@ -147,7 +147,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .unwrap() - .parse_create(ProtocolVersion::Rev2) + .parse_create(true) .unwrap(), arguments ); diff --git a/crates/imap-proto/src/parser/delete.rs b/crates/imap-proto/src/parser/delete.rs index 79750203..58979c04 100644 --- a/crates/imap-proto/src/parser/delete.rs +++ b/crates/imap-proto/src/parser/delete.rs @@ -8,13 +8,13 @@ use compact_str::ToCompactString; use crate::{ Command, - protocol::{ProtocolVersion, delete}, + protocol::delete, receiver::{Request, bad}, utf7::utf7_maybe_decode, }; impl Request { - pub fn parse_delete(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_delete(self, is_utf8: bool) -> trc::Result { match self.tokens.len() { 1 => Ok(delete::Arguments { mailbox_name: utf7_maybe_decode( @@ -24,7 +24,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ), tag: self.tag, }), @@ -36,10 +36,7 @@ impl Request { #[cfg(test)] mod tests { - use crate::{ - protocol::{ProtocolVersion, delete}, - receiver::Receiver, - }; + use crate::{protocol::delete, receiver::Receiver}; #[test] fn parse_delete() { @@ -65,7 +62,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .unwrap() - .parse_delete(ProtocolVersion::Rev2) + .parse_delete(true) .unwrap(), arguments ); diff --git a/crates/imap-proto/src/parser/list.rs b/crates/imap-proto/src/parser/list.rs index 7c67811c..dcd1021f 100644 --- a/crates/imap-proto/src/parser/list.rs +++ b/crates/imap-proto/src/parser/list.rs @@ -9,7 +9,6 @@ use compact_str::{CompactString, ToCompactString}; use crate::{ Command, protocol::{ - ProtocolVersion, list::{self, ReturnOption, SelectionOption}, status::Status, }, @@ -19,7 +18,7 @@ use crate::{ impl Request { #[allow(clippy::while_let_on_iterator)] - pub fn parse_list(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_list(self, is_utf8: bool) -> trc::Result { match self.tokens.len() { 0 | 1 => Err(self.into_error("Missing arguments.")), 2 => { @@ -36,7 +35,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ), tag: self.tag, }) @@ -102,7 +101,7 @@ impl Request { token .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, )); } } @@ -212,7 +211,6 @@ impl ReturnOption { mod tests { use crate::{ protocol::{ - ProtocolVersion, list::{self, ReturnOption, SelectionOption}, status::Status, }, @@ -375,7 +373,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .unwrap() - .parse_list(ProtocolVersion::Rev2) + .parse_list(true) .unwrap(), arguments ); diff --git a/crates/imap-proto/src/parser/lsub.rs b/crates/imap-proto/src/parser/lsub.rs index 1133f4fd..ebd601a8 100644 --- a/crates/imap-proto/src/parser/lsub.rs +++ b/crates/imap-proto/src/parser/lsub.rs @@ -8,16 +8,13 @@ use compact_str::ToCompactString; use crate::{ Command, - protocol::{ - ProtocolVersion, - list::{self, SelectionOption}, - }, + protocol::list::{self, SelectionOption}, receiver::{Request, bad}, utf7::utf7_maybe_decode, }; impl Request { - pub fn parse_lsub(self) -> trc::Result { + pub fn parse_lsub(self, is_utf8: bool) -> trc::Result { if self.tokens.len() > 1 { let mut tokens = self.tokens.into_iter(); @@ -33,7 +30,7 @@ impl Request { .ok_or_else(|| bad(self.tag.to_compact_string(), "Missing mailbox name."))? .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - ProtocolVersion::Rev1, + is_utf8, )], selection_options: vec![SelectionOption::Subscribed], return_options: vec![], @@ -82,7 +79,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .unwrap() - .parse_lsub() + .parse_lsub(false) .unwrap(), arguments ); diff --git a/crates/imap-proto/src/parser/quota.rs b/crates/imap-proto/src/parser/quota.rs index a99d5f30..2159a73e 100644 --- a/crates/imap-proto/src/parser/quota.rs +++ b/crates/imap-proto/src/parser/quota.rs @@ -8,13 +8,13 @@ use compact_str::ToCompactString; use crate::{ Command, - protocol::{ProtocolVersion, quota}, + protocol::quota, receiver::{Request, bad}, utf7::utf7_maybe_decode, }; impl Request { - pub fn parse_get_quota_root(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_get_quota_root(self, is_utf8: bool) -> trc::Result { match self.tokens.len() { 1 => Ok(quota::Arguments { name: utf7_maybe_decode( @@ -24,7 +24,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ), tag: self.tag, }), @@ -53,10 +53,7 @@ impl Request { #[cfg(test)] mod tests { - use crate::{ - protocol::{ProtocolVersion, quota}, - receiver::Receiver, - }; + use crate::{protocol::quota, receiver::Receiver}; #[test] fn parse_quota() { @@ -73,7 +70,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .unwrap() - .parse_get_quota_root(ProtocolVersion::Rev2) + .parse_get_quota_root(true) .unwrap(), arguments ); diff --git a/crates/imap-proto/src/parser/rename.rs b/crates/imap-proto/src/parser/rename.rs index 26bcce6f..4486c586 100644 --- a/crates/imap-proto/src/parser/rename.rs +++ b/crates/imap-proto/src/parser/rename.rs @@ -8,13 +8,13 @@ use compact_str::ToCompactString; use crate::{ Command, - protocol::{ProtocolVersion, rename}, + protocol::rename, receiver::{Request, bad}, utf7::utf7_maybe_decode, }; impl Request { - pub fn parse_rename(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_rename(self, is_utf8: bool) -> trc::Result { match self.tokens.len() { 2 => { let mut tokens = self.tokens.into_iter(); @@ -25,7 +25,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ), new_mailbox_name: utf7_maybe_decode( tokens @@ -33,7 +33,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ), tag: self.tag, }) @@ -47,10 +47,7 @@ impl Request { #[cfg(test)] mod tests { - use crate::{ - protocol::{ProtocolVersion, rename}, - receiver::Receiver, - }; + use crate::{protocol::rename, receiver::Receiver}; #[test] fn parse_rename() { @@ -78,7 +75,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .unwrap() - .parse_rename(ProtocolVersion::Rev2) + .parse_rename(true) .unwrap(), arguments ); diff --git a/crates/imap-proto/src/parser/select.rs b/crates/imap-proto/src/parser/select.rs index 5048b4e1..783d1453 100644 --- a/crates/imap-proto/src/parser/select.rs +++ b/crates/imap-proto/src/parser/select.rs @@ -8,10 +8,7 @@ use compact_str::{CompactString, ToCompactString, format_compact}; use crate::{ Command, - protocol::{ - ProtocolVersion, - select::{self, QResync}, - }, + protocol::select::{self, QResync}, receiver::{Request, Token, bad}, utf7::utf7_maybe_decode, }; @@ -19,7 +16,7 @@ use crate::{ use super::{parse_number, parse_sequence_set}; impl Request { - pub fn parse_select(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_select(self, is_utf8: bool) -> trc::Result { if !self.tokens.is_empty() { let mut tokens = self.tokens.into_iter().peekable(); @@ -30,7 +27,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ); // CONDSTORE parameters @@ -194,7 +191,7 @@ impl Request { mod tests { use crate::{ protocol::{ - ProtocolVersion, Sequence, + Sequence, select::{self, QResync}, }, receiver::Receiver, @@ -354,7 +351,7 @@ mod tests { "Failed to parse command '{}': {:?}", command, err )) - .parse_select(ProtocolVersion::Rev2) + .parse_select(true) .unwrap_or_else(|err| panic!( "Failed to parse command '{}': {:?}", command, err diff --git a/crates/imap-proto/src/parser/status.rs b/crates/imap-proto/src/parser/status.rs index 712e0e81..9e9b9924 100644 --- a/crates/imap-proto/src/parser/status.rs +++ b/crates/imap-proto/src/parser/status.rs @@ -7,13 +7,13 @@ use compact_str::{CompactString, ToCompactString}; use crate::Command; +use crate::protocol::status; use crate::protocol::status::Status; -use crate::protocol::{ProtocolVersion, status}; use crate::receiver::{Request, Token, bad}; use crate::utf7::utf7_maybe_decode; impl Request { - pub fn parse_status(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_status(self, is_utf8: bool) -> trc::Result { match self.tokens.len() { 0..=3 => Err(self.into_error("Missing arguments.")), len => { @@ -24,7 +24,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ); let mut items = Vec::with_capacity(len - 2); @@ -100,10 +100,7 @@ impl Status { #[cfg(test)] mod tests { - use crate::{ - protocol::{ProtocolVersion, status}, - receiver::Receiver, - }; + use crate::{protocol::status, receiver::Receiver}; #[test] fn parse_status() { @@ -117,7 +114,7 @@ mod tests { .iter() ) .unwrap() - .parse_status(ProtocolVersion::Rev2) + .parse_status(true) .unwrap(), status::Arguments { tag: "A042".into(), diff --git a/crates/imap-proto/src/parser/subscribe.rs b/crates/imap-proto/src/parser/subscribe.rs index 4511629a..225632e3 100644 --- a/crates/imap-proto/src/parser/subscribe.rs +++ b/crates/imap-proto/src/parser/subscribe.rs @@ -8,13 +8,13 @@ use compact_str::ToCompactString; use crate::{ Command, - protocol::{ProtocolVersion, subscribe}, + protocol::subscribe, receiver::{Request, bad}, utf7::utf7_maybe_decode, }; impl Request { - pub fn parse_subscribe(self, version: ProtocolVersion) -> trc::Result { + pub fn parse_subscribe(self, is_utf8: bool) -> trc::Result { match self.tokens.len() { 1 => Ok(subscribe::Arguments { mailbox_name: utf7_maybe_decode( @@ -24,7 +24,7 @@ impl Request { .unwrap() .unwrap_string() .map_err(|v| bad(self.tag.to_compact_string(), v))?, - version, + is_utf8, ), tag: self.tag, }), @@ -36,10 +36,7 @@ impl Request { #[cfg(test)] mod tests { - use crate::{ - protocol::{ProtocolVersion, subscribe}, - receiver::Receiver, - }; + use crate::{protocol::subscribe, receiver::Receiver}; #[test] fn parse_subscribe() { @@ -65,7 +62,7 @@ mod tests { receiver .parse(&mut command.as_bytes().iter()) .unwrap() - .parse_subscribe(ProtocolVersion::Rev2) + .parse_subscribe(true) .unwrap(), arguments ); diff --git a/crates/imap-proto/src/protocol/acl.rs b/crates/imap-proto/src/protocol/acl.rs index 7d9878f8..afa56758 100644 --- a/crates/imap-proto/src/protocol/acl.rs +++ b/crates/imap-proto/src/protocol/acl.rs @@ -97,10 +97,10 @@ pub struct MyRightsResponse { } impl GetAclResponse { - pub fn into_bytes(self, is_rev2: bool) -> Vec { + pub fn into_bytes(self, is_utf8: bool) -> Vec { let mut buf = Vec::with_capacity(self.mailbox_name.len() + 10 * self.permissions.len() * 5); buf.extend_from_slice(b"* ACL "); - if is_rev2 { + if is_utf8 { quoted_string(&mut buf, &self.mailbox_name); } else { quoted_string(&mut buf, &utf7_encode(&self.mailbox_name)); @@ -120,12 +120,12 @@ impl GetAclResponse { } impl ListRightsResponse { - pub fn into_bytes(self, is_rev2: bool) -> Vec { + pub fn into_bytes(self, is_utf8: bool) -> Vec { let mut buf = Vec::with_capacity( self.mailbox_name.len() + self.identifier.len() + 10 * self.permissions.len() * 5, ); buf.extend_from_slice(b"* LISTRIGHTS "); - if is_rev2 { + if is_utf8 { quoted_string(&mut buf, &self.mailbox_name); } else { quoted_string(&mut buf, &utf7_encode(&self.mailbox_name)); @@ -144,10 +144,10 @@ impl ListRightsResponse { } impl MyRightsResponse { - pub fn into_bytes(self, is_rev2: bool) -> Vec { + pub fn into_bytes(self, is_utf8: bool) -> Vec { let mut buf = Vec::with_capacity(self.mailbox_name.len() + 10 + self.rights.len()); buf.extend_from_slice(b"* MYRIGHTS "); - if is_rev2 { + if is_utf8 { quoted_string(&mut buf, &self.mailbox_name); } else { quoted_string(&mut buf, &utf7_encode(&self.mailbox_name)); diff --git a/crates/imap-proto/src/protocol/list.rs b/crates/imap-proto/src/protocol/list.rs index 6d0d8d64..deb244e4 100644 --- a/crates/imap-proto/src/protocol/list.rs +++ b/crates/imap-proto/src/protocol/list.rs @@ -30,6 +30,7 @@ pub enum Arguments { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Response { pub is_rev2: bool, + pub is_utf8: bool, pub is_lsub: bool, pub list_items: Vec, pub status_items: Vec, @@ -202,7 +203,7 @@ impl ListItem { } } - pub fn serialize(&self, buf: &mut Vec, is_rev2: bool, is_lsub: bool) { + pub fn serialize(&self, buf: &mut Vec, 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 ("); @@ -219,9 +220,11 @@ impl ListItem { let mut extra_tags = Vec::new(); if normalized_mailbox_name != self.mailbox_name { - if is_rev2 { + if is_rev2 || is_utf8 { quoted_string(buf, &self.mailbox_name); - extra_tags.push(Tag::OldName(normalized_mailbox_name)); + if is_rev2 { + extra_tags.push(Tag::OldName(normalized_mailbox_name)); + } } else { quoted_string(buf, &normalized_mailbox_name); } @@ -252,13 +255,13 @@ impl ImapResponse for Response { (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_lsub); + 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_lsub); + list_item.serialize(&mut buf, self.is_rev2, self.is_utf8, self.is_lsub); } } (true, false) => { @@ -335,8 +338,8 @@ mod tests { let mut buf_1 = Vec::with_capacity(100); let mut buf_2 = Vec::with_capacity(100); - response.serialize(&mut buf_1, false, false); - response.serialize(&mut buf_2, true, false); + 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(); @@ -376,6 +379,7 @@ mod tests { ], is_lsub: false, is_rev2: true, + is_utf8: true, }; let expected_v2 = concat!( "* LIST (\\Subscribed) \"/\" \"INBOX\"\r\n", @@ -390,6 +394,7 @@ mod tests { 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(); diff --git a/crates/imap-proto/src/protocol/select.rs b/crates/imap-proto/src/protocol/select.rs index b9e23171..e4fc3617 100644 --- a/crates/imap-proto/src/protocol/select.rs +++ b/crates/imap-proto/src/protocol/select.rs @@ -36,6 +36,7 @@ pub struct Response { pub uid_validity: u32, pub uid_next: u32, pub is_rev2: bool, + pub is_utf8: bool, pub closed_previous: bool, pub highest_modseq: Option, pub mailbox_id: String, @@ -66,7 +67,8 @@ impl ImapResponse for Response { ); } if self.is_rev2 { - self.mailbox.serialize(&mut buf, self.is_rev2, false); + self.mailbox + .serialize(&mut buf, self.is_rev2, self.is_utf8, false); } else { buf.extend_from_slice(b"* "); buf.extend_from_slice(self.recent_messages.to_string().as_bytes()); @@ -146,6 +148,7 @@ mod tests { uid_next: 4392, closed_previous: false, is_rev2: true, + is_utf8: true, highest_modseq: HighestModSeq::new(100).into(), mailbox_id: "abc".into(), }, @@ -182,6 +185,7 @@ mod tests { uid_next: 4392, closed_previous: true, is_rev2: true, + is_utf8: true, highest_modseq: None, mailbox_id: "abc".into(), }, diff --git a/crates/imap-proto/src/protocol/status.rs b/crates/imap-proto/src/protocol/status.rs index 526915dc..07441ccc 100644 --- a/crates/imap-proto/src/protocol/status.rs +++ b/crates/imap-proto/src/protocol/status.rs @@ -42,9 +42,9 @@ pub enum StatusItemType { } impl StatusItem { - pub fn serialize(&self, buf: &mut Vec, is_rev2: bool) { + pub fn serialize(&self, buf: &mut Vec, is_utf8: bool) { buf.extend_from_slice(b"* STATUS "); - if is_rev2 { + if is_utf8 { quoted_string(buf, &self.mailbox_name); } else { quoted_string(buf, &utf7_encode(&self.mailbox_name)); diff --git a/crates/imap-proto/src/utf7.rs b/crates/imap-proto/src/utf7.rs index fdad75be..578ca748 100644 --- a/crates/imap-proto/src/utf7.rs +++ b/crates/imap-proto/src/utf7.rs @@ -7,8 +7,6 @@ // Ported from https://github.com/jstedfast/MailKit/blob/master/MailKit/Net/Imap/ImapEncoding.cs // Author: Jeffrey Stedfast -use crate::protocol::ProtocolVersion; - static UTF_7_RANK: &[u8] = &[ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, @@ -122,8 +120,8 @@ pub fn utf7_encode(text: &str) -> String { } #[inline(always)] -pub fn utf7_maybe_decode(text: String, version: ProtocolVersion) -> String { - if version.is_rev2() { +pub fn utf7_maybe_decode(text: String, is_utf8: bool) -> String { + if is_utf8 { text } else { utf7_decode(&text).unwrap_or(text) diff --git a/crates/imap/src/core/mod.rs b/crates/imap/src/core/mod.rs index ab30db40..a6519c0b 100644 --- a/crates/imap/src/core/mod.rs +++ b/crates/imap/src/core/mod.rs @@ -53,6 +53,7 @@ pub struct Session { pub is_tls: bool, pub is_condstore: bool, pub is_qresync: bool, + pub is_utf8: bool, pub stream_rx: ReadHalf, pub stream_tx: Arc>>, pub in_flight: InFlight, diff --git a/crates/imap/src/core/session.rs b/crates/imap/src/core/session.rs index 8b836794..a361e61b 100644 --- a/crates/imap/src/core/session.rs +++ b/crates/imap/src/core/session.rs @@ -149,6 +149,7 @@ impl Session { is_tls, is_condstore: false, is_qresync: false, + is_utf8: false, server, instance: session.instance, session_id: session.session_id, @@ -205,6 +206,7 @@ impl Session { is_tls: true, is_condstore: self.is_condstore, is_qresync: self.is_qresync, + is_utf8: self.is_utf8, session_id: self.session_id, in_flight: self.in_flight, remote_addr: self.remote_addr, diff --git a/crates/imap/src/op/acl.rs b/crates/imap/src/op/acl.rs index 32c77a4e..8fd1caaf 100644 --- a/crates/imap/src/op/acl.rs +++ b/crates/imap/src/op/acl.rs @@ -40,8 +40,8 @@ impl Session { self.assert_has_permission(Permission::ImapAuthenticate)?; let op_start = Instant::now(); - let arguments = request.parse_acl(self.version)?; - let is_rev2 = self.version.is_rev2(); + let arguments = request.parse_acl(self.is_utf8)?; + let is_utf8 = self.version.is_rev2() || self.is_utf8; let data = self.state.session_data(); spawn_op!(data, { @@ -143,7 +143,7 @@ impl Session { mailbox_name: arguments.mailbox_name.to_string(), permissions, } - .into_bytes(is_rev2), + .into_bytes(is_utf8), ), ) .await @@ -155,9 +155,9 @@ impl Session { self.assert_has_permission(Permission::ImapMyRights)?; let op_start = Instant::now(); - let arguments = request.parse_acl(self.version)?; + let arguments = request.parse_acl(self.is_utf8)?; let data = self.state.session_data(); - let is_rev2 = self.version.is_rev2(); + let is_utf8 = self.version.is_rev2() || self.is_utf8; spawn_op!(data, { let (mailbox_id, mailbox_, access_token) = data @@ -231,7 +231,7 @@ impl Session { mailbox_name: arguments.mailbox_name.to_string(), rights, } - .into_bytes(is_rev2), + .into_bytes(is_utf8), ), ) .await @@ -244,7 +244,7 @@ impl Session { let op_start = Instant::now(); let command = request.command; - let arguments = request.parse_acl(self.version)?; + let arguments = request.parse_acl(self.is_utf8)?; let data = self.state.session_data(); spawn_op!(data, { @@ -390,7 +390,7 @@ impl Session { self.assert_has_permission(Permission::ImapListRights)?; let op_start = Instant::now(); - let arguments = request.parse_acl(self.version)?; + let arguments = request.parse_acl(self.is_utf8)?; trc::event!( Imap(trc::ImapEvent::ListRights), @@ -418,7 +418,7 @@ impl Session { vec![Rights::Administer], ], } - .into_bytes(self.version.is_rev2()), + .into_bytes(self.version.is_rev2() || self.is_utf8), ), ) .await diff --git a/crates/imap/src/op/append.rs b/crates/imap/src/op/append.rs index 18a0df2e..a696e958 100644 --- a/crates/imap/src/op/append.rs +++ b/crates/imap/src/op/append.rs @@ -30,7 +30,7 @@ impl Session { self.assert_has_permission(Permission::ImapAppend)?; let op_start = Instant::now(); - let arguments = request.parse_append(self.version)?; + let arguments = request.parse_append(self.is_utf8)?; let (data, selected_mailbox) = self.state.session_mailbox_state(); // Refresh mailboxes diff --git a/crates/imap/src/op/copy_move.rs b/crates/imap/src/op/copy_move.rs index 3e7d09e1..ee04fd97 100644 --- a/crates/imap/src/op/copy_move.rs +++ b/crates/imap/src/op/copy_move.rs @@ -53,7 +53,7 @@ impl Session { })?; let op_start = Instant::now(); - let arguments = request.parse_copy_move(self.version)?; + let arguments = request.parse_copy_move(self.is_utf8)?; let (data, src_mailbox) = self.state.mailbox_state(); let is_qresync = self.is_qresync; diff --git a/crates/imap/src/op/create.rs b/crates/imap/src/op/create.rs index f060b1dc..119d38a2 100644 --- a/crates/imap/src/op/create.rs +++ b/crates/imap/src/op/create.rs @@ -30,11 +30,11 @@ impl Session { self.assert_has_permission(Permission::ImapCreate)?; let data = self.state.session_data(); - let version = self.version; + let is_utf8 = self.is_utf8; spawn_op!(data, { for request in requests { - match request.parse_create(version) { + match request.parse_create(is_utf8) { Ok(argument) => match data.create_folder(argument).await { Ok(response) => { data.write_bytes(response.into_bytes()).await?; diff --git a/crates/imap/src/op/delete.rs b/crates/imap/src/op/delete.rs index cd361c71..4deac9b0 100644 --- a/crates/imap/src/op/delete.rs +++ b/crates/imap/src/op/delete.rs @@ -25,11 +25,11 @@ impl Session { self.assert_has_permission(Permission::ImapDelete)?; let data = self.state.session_data(); - let version = self.version; + let is_utf8 = self.is_utf8; spawn_op!(data, { for request in requests { - match request.parse_delete(version) { + match request.parse_delete(is_utf8) { Ok(argument) => match data.delete_folder(argument).await { Ok(response) => { data.write_bytes(response.into_bytes()).await?; diff --git a/crates/imap/src/op/enable.rs b/crates/imap/src/op/enable.rs index b3794443..8ff7ebb8 100644 --- a/crates/imap/src/op/enable.rs +++ b/crates/imap/src/op/enable.rs @@ -31,6 +31,7 @@ impl Session { match capability { Capability::IMAP4rev2 => { self.version = ProtocolVersion::Rev2; + self.is_utf8 = true; } Capability::IMAP4rev1 => { self.version = ProtocolVersion::Rev1; @@ -42,7 +43,9 @@ impl Session { self.is_qresync = true; self.is_condstore = true; } - Capability::Utf8Accept => {} + Capability::Utf8Accept => { + self.is_utf8 = true; + } _ => { continue; } diff --git a/crates/imap/src/op/fetch.rs b/crates/imap/src/op/fetch.rs index ab7a29fd..0c723ed2 100644 --- a/crates/imap/src/op/fetch.rs +++ b/crates/imap/src/op/fetch.rs @@ -56,7 +56,6 @@ impl Session { let (data, mailbox) = self.state.select_data(); let is_qresync = self.is_qresync; - let is_rev2 = self.version.is_rev2(); let mut ops = Vec::with_capacity(requests.len()); @@ -92,7 +91,6 @@ impl Session { mailbox.clone(), is_uid, is_qresync, - is_rev2, enabled_condstore, Instant::now(), ) @@ -117,7 +115,6 @@ impl SessionData { mailbox: Arc, is_uid: bool, is_qresync: bool, - _is_rev2: bool, enabled_condstore: bool, op_start: Instant, ) -> trc::Result { diff --git a/crates/imap/src/op/idle.rs b/crates/imap/src/op/idle.rs index fbbc6b05..9085113a 100644 --- a/crates/imap/src/op/idle.rs +++ b/crates/imap/src/op/idle.rs @@ -45,6 +45,7 @@ impl Session { _ => unreachable!(), }; let is_rev2 = self.version.is_rev2(); + let is_utf8 = self.is_utf8; let is_qresync = self.is_qresync; // Register with state manager @@ -109,7 +110,7 @@ impl Session { } if has_mailbox_changes || has_email_changes { - data.write_changes(&mailbox, has_mailbox_changes, has_email_changes, is_qresync, is_rev2).await?; + data.write_changes(&mailbox, has_mailbox_changes, has_email_changes, is_qresync, is_rev2, is_utf8).await?; } } else { self.write_bytes(&b"* BYE Server shutting down.\r\n"[..]).await.ok(); @@ -129,6 +130,7 @@ impl SessionData { check_emails: bool, is_qresync: bool, is_rev2: bool, + is_utf8: bool, ) -> trc::Result<()> { // Fetch all changed mailboxes if check_mailboxes { @@ -147,7 +149,7 @@ impl SessionData { attributes: vec![Attribute::NonExistent], tags: vec![], } - .serialize(&mut buf, is_rev2, false); + .serialize(&mut buf, is_rev2, is_utf8, false); } // List added mailboxes @@ -157,7 +159,7 @@ impl SessionData { attributes: vec![], tags: vec![], } - .serialize(&mut buf, is_rev2, false); + .serialize(&mut buf, is_rev2, is_utf8, false); } // Obtain status of changed mailboxes for mailbox_name in changes.changed { @@ -173,7 +175,7 @@ impl SessionData { ) .await { - status.serialize(&mut buf, is_rev2); + status.serialize(&mut buf, is_utf8); } } @@ -242,7 +244,6 @@ impl SessionData { mailbox.clone(), true, is_qresync, - is_rev2, false, op_start, ) diff --git a/crates/imap/src/op/list.rs b/crates/imap/src/op/list.rs index 2029046c..f5782710 100644 --- a/crates/imap/src/op/list.rs +++ b/crates/imap/src/op/list.rs @@ -36,19 +36,24 @@ impl Session { // Validate access self.assert_has_permission(Permission::ImapList)?; - request.parse_list(self.version) + request.parse_list(self.is_utf8) } else { // Validate access self.assert_has_permission(Permission::ImapLsub)?; - request.parse_lsub() + request.parse_lsub(self.is_utf8) }?; if !arguments.is_separator_query() { let data = self.state.session_data(); let version = self.version; + let is_utf8 = self.is_utf8; - spawn_op!(data, data.list(arguments, is_lsub, version, op_start).await) + spawn_op!( + data, + data.list(arguments, is_lsub, version, is_utf8, op_start) + .await + ) } else { self.write_bytes( StatusResponse::completed(command) @@ -56,6 +61,7 @@ impl Session { .serialize( list::Response { is_rev2: self.version.is_rev2(), + is_utf8: self.is_utf8, is_lsub, list_items: vec![ListItem { mailbox_name: "".into(), @@ -78,6 +84,7 @@ impl SessionData { arguments: Arguments, is_lsub: bool, version: ProtocolVersion, + is_utf8: bool, op_start: Instant, ) -> trc::Result<()> { let (tag, reference_name, mut patterns, selection_options, return_options) = match arguments @@ -309,6 +316,7 @@ impl SessionData { .serialize( list::Response { is_rev2: version.is_rev2(), + is_utf8, is_lsub, list_items, status_items, diff --git a/crates/imap/src/op/noop.rs b/crates/imap/src/op/noop.rs index 1b27dd27..a9fb9bdc 100644 --- a/crates/imap/src/op/noop.rs +++ b/crates/imap/src/op/noop.rs @@ -21,6 +21,7 @@ impl Session { true, self.is_qresync, self.version.is_rev2(), + self.is_utf8, ) .await?; } diff --git a/crates/imap/src/op/quota.rs b/crates/imap/src/op/quota.rs index 4c1c9d06..9d4116c7 100644 --- a/crates/imap/src/op/quota.rs +++ b/crates/imap/src/op/quota.rs @@ -58,10 +58,10 @@ impl Session { self.assert_has_permission(Permission::ImapStatus)?; let data = self.state.session_data(); - let version = self.version; + let is_utf8 = self.is_utf8; spawn_op!(data, { - match request.parse_get_quota_root(version) { + match request.parse_get_quota_root(is_utf8) { Ok(argument) => match data.get_quota_root(argument).await { Ok(response) => { data.write_bytes(response).await?; diff --git a/crates/imap/src/op/rename.rs b/crates/imap/src/op/rename.rs index eadcb6b8..f06f2b46 100644 --- a/crates/imap/src/op/rename.rs +++ b/crates/imap/src/op/rename.rs @@ -27,7 +27,7 @@ impl Session { self.assert_has_permission(Permission::ImapRename)?; let op_start = Instant::now(); - let arguments = request.parse_rename(self.version)?; + let arguments = request.parse_rename(self.is_utf8)?; let data = self.state.session_data(); spawn_op!(data, { diff --git a/crates/imap/src/op/select.rs b/crates/imap/src/op/select.rs index 0bb94e04..42210b12 100644 --- a/crates/imap/src/op/select.rs +++ b/crates/imap/src/op/select.rs @@ -35,7 +35,7 @@ impl Session { let op_start = Instant::now(); let is_select = request.command == Command::Select; let command = request.command; - let arguments = request.parse_select(self.version)?; + let arguments = request.parse_select(self.is_utf8)?; let data = self.state.session_data(); // Refresh mailboxes @@ -57,6 +57,7 @@ impl Session { // Build new state let is_rev2 = self.version.is_rev2(); + let is_utf8 = self.is_utf8; let mailbox_state = data.mailbox_state(&mailbox).unwrap(); let total_messages = state.total_messages; let highest_modseq = if is_condstore { @@ -99,7 +100,6 @@ impl Session { mailbox.clone(), true, true, - is_rev2, false, Instant::now(), ) @@ -130,6 +130,7 @@ impl Session { uid_next: mailbox_state.uid_next as u32, closed_previous, is_rev2, + is_utf8, highest_modseq, mailbox_id: Id::from_parts(mailbox.id.account_id, mailbox.id.mailbox_id) .to_string(), diff --git a/crates/imap/src/op/status.rs b/crates/imap/src/op/status.rs index bc1f0c47..067ee13e 100644 --- a/crates/imap/src/op/status.rs +++ b/crates/imap/src/op/status.rs @@ -32,14 +32,14 @@ impl Session { // Validate access self.assert_has_permission(Permission::ImapStatus)?; - let version = self.version; + let is_utf8 = self.is_utf8; let data = self.state.session_data(); spawn_op!(data, { let mut did_sync = false; for request in requests.into_iter() { - match request.parse_status(version) { + match request.parse_status(is_utf8) { Ok(arguments) => { let op_start = Instant::now(); if !did_sync { @@ -69,7 +69,7 @@ impl Session { ); let mut buf = Vec::with_capacity(32); - status.serialize(&mut buf, version.is_rev2()); + status.serialize(&mut buf, is_utf8); data.write_bytes( StatusResponse::completed(Command::Status) .with_tag(arguments.tag) diff --git a/crates/imap/src/op/subscribe.rs b/crates/imap/src/op/subscribe.rs index 2bae9e4d..5dcc27f0 100644 --- a/crates/imap/src/op/subscribe.rs +++ b/crates/imap/src/op/subscribe.rs @@ -29,7 +29,7 @@ impl Session { self.assert_has_permission(Permission::ImapSubscribe)?; let op_start = Instant::now(); - let arguments = request.parse_subscribe(self.version)?; + let arguments = request.parse_subscribe(self.is_utf8)?; let data = self.state.session_data(); spawn_op!(data, {