use std::borrow::Cow;
use std::convert::TryFrom;
use std::fmt;
use std::path::PathBuf;
use std::sync::Arc;
use log::error;
use crate::account::{
account::Account,
mailbox::{StatefulMailbox, StatelessMailbox},
model::*,
};
use crate::support::{error::Error, system_config::SystemConfig};
pub(super) use crate::imap::syntax as s;
pub(super) static CAPABILITIES: &[&str] = &[
"IMAP4rev1",
"IMAP4rev2",
concat_appendlimit!("APPENDLIMIT="),
"AUTH=PLAIN",
"BINARY",
"CHILDREN",
"COMPRESS=DEFLATE",
"CONDSTORE",
"CREATE-SPECIAL-USE",
"ENABLE",
"ESEARCH",
"ID",
"IDLE",
"LIST-EXTENDED",
"LIST-STATUS",
"LITERAL+",
"MOVE",
"MULTIAPPEND",
"NAMESPACE",
"OBJECTID",
"QRESYNC",
"SASL-IR",
"SEARCHRES",
"SPECIAL-USE",
"STATUS=SIZE",
"UIDPLUS",
"UNSELECT",
"UTF8=ACCEPT",
"XCRY",
"XLIST",
"XVANQUISH",
"XYZZY",
];
pub(super) static TAGLINE: &str = concat!(
"It's my IMAP and I'll CRY if I want to! (",
env!("CARGO_PKG_NAME"),
" ",
env!("CARGO_PKG_VERSION_MAJOR"),
".",
env!("CARGO_PKG_VERSION_MINOR"),
".",
env!("CARGO_PKG_VERSION_PATCH"),
" ready)"
);
pub struct CommandProcessor {
pub(super) log_prefix: String,
pub(super) system_config: Arc<SystemConfig>,
pub(super) data_root: PathBuf,
pub(super) account: Option<Account>,
pub(super) selected: Option<StatefulMailbox>,
pub(super) searchres: SeqRange<Uid>,
pub(super) unicode_aware: bool,
pub(super) utf8_enabled: bool,
pub(super) condstore_enabled: bool,
pub(super) qresync_enabled: bool,
pub(super) imap4rev2_enabled: bool,
pub(super) multiappend: Option<Multiappend>,
pub(super) logged_out: bool,
pub(super) id_exchanged: bool,
}
pub(super) struct Multiappend {
pub(super) dst: StatelessMailbox,
pub(super) request: AppendRequest,
}
pub(super) type CmdResult = Result<s::Response<'static>, s::Response<'static>>;
pub(super) type PartialResult<T> = Result<T, s::Response<'static>>;
pub(super) type SendResponse<'a> = &'a (dyn Send + Sync + Fn(s::Response<'_>));
impl CommandProcessor {
pub fn new(
log_prefix: String,
system_config: Arc<SystemConfig>,
data_root: PathBuf,
) -> Self {
CommandProcessor {
log_prefix,
system_config,
data_root,
account: None,
selected: None,
searchres: SeqRange::new(),
unicode_aware: false,
utf8_enabled: false,
condstore_enabled: false,
qresync_enabled: false,
imap4rev2_enabled: false,
multiappend: None,
logged_out: false,
id_exchanged: false,
}
}
pub fn unicode_aware(&self) -> bool {
self.unicode_aware
}
pub fn logged_out(&self) -> bool {
self.logged_out
}
pub fn log_prefix(&self) -> &str {
&self.log_prefix
}
pub(super) fn parse_seqnum_range(
&mut self,
raw: &str,
) -> PartialResult<SeqRange<Seqnum>> {
if "$" == raw {
return Ok(selected!(self)?.uid_range_to_seqnum(&self.searchres));
}
let max_seqnum = selected!(self)?.max_seqnum().unwrap_or(Seqnum::MIN);
let seqrange = SeqRange::parse(raw, max_seqnum).ok_or_else(|| {
s::Response::Cond(s::CondResponse {
cond: s::RespCondType::Bad,
code: Some(s::RespTextCode::Parse(())),
quip: Some(Cow::Borrowed("Unparsable sequence set")),
})
})?;
if seqrange.max().unwrap_or(0) > max_seqnum.0.get() {
return Err(s::Response::Cond(s::CondResponse {
cond: s::RespCondType::Bad,
code: Some(s::RespTextCode::ClientBug(())),
quip: Some(Cow::Borrowed(
"Message sequence number out of range",
)),
}));
}
Ok(seqrange)
}
pub(super) fn parse_uid_range(
&mut self,
raw: &str,
) -> PartialResult<SeqRange<Uid>> {
if "$" == raw {
let _ = selected!(self)?;
return Ok(self.searchres.clone());
}
let max_uid = selected!(self)?.max_uid().unwrap_or(Uid::MIN);
let seqrange = SeqRange::parse(raw, max_uid).ok_or_else(|| {
s::Response::Cond(s::CondResponse {
cond: s::RespCondType::Bad,
code: Some(s::RespTextCode::Parse(())),
quip: Some(Cow::Borrowed("Unparsable sequence set")),
})
})?;
Ok(seqrange)
}
}
pub(super) fn success() -> CmdResult {
Ok(s::Response::Cond(s::CondResponse {
cond: s::RespCondType::Ok,
code: None,
quip: None,
}))
}
pub(super) fn parse_global_seqrange<
T: TryFrom<u32> + Into<u32> + PartialOrd + Send + Sync + Default,
>(
s: &str,
) -> PartialResult<SeqRange<T>>
where
SeqRange<T>: fmt::Debug,
{
if s.contains('*') {
return Err(s::Response::Cond(s::CondResponse {
cond: s::RespCondType::Bad,
code: Some(s::RespTextCode::Parse(())),
quip: Some(Cow::Borrowed("'*' not allowed in sequence set here")),
}));
}
SeqRange::parse(s, T::default()).ok_or_else(|| {
s::Response::Cond(s::CondResponse {
cond: s::RespCondType::Bad,
code: Some(s::RespTextCode::Parse(())),
quip: Some(Cow::Borrowed("Invalid sequence set")),
})
})
}
#[cfg(not(test))]
pub(super) fn catch_all_error_handling(
selected_ok: bool,
log_prefix: &str,
e: Error,
) -> s::Response<'static> {
if selected_ok {
error!("{} Unhandled internal error: {}", log_prefix, e);
}
s::Response::Cond(s::CondResponse {
cond: s::RespCondType::No,
code: Some(s::RespTextCode::ServerBug(())),
quip: Some(Cow::Borrowed(
"Unexpected error; check server logs for details",
)),
})
}
#[cfg(test)]
pub(super) fn catch_all_error_handling(
selected_ok: bool,
log_prefix: &str,
e: Error,
) -> s::Response<'static> {
if !selected_ok {
s::Response::Cond(s::CondResponse {
cond: s::RespCondType::No,
code: Some(s::RespTextCode::ServerBug(())),
quip: Some(Cow::Borrowed(
"Unexpected error; check server logs for details",
)),
})
} else {
error!("{} Unhandled internal error: {}", log_prefix, e);
panic!("{} Unhandled internal error: {}", log_prefix, e);
}
}