use std::borrow::Cow;
use std::convert::TryFrom;
use std::fmt;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use log::error;
use crate::{
account::{
model::*,
v2::{Account, Mailbox},
},
imap::response_writer::{OutputControl, OutputEvent},
support::{
dns, error::Error, log_prefix::LogPrefix, 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",
"SAVEDATE",
"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: LogPrefix,
pub(super) system_config: Arc<SystemConfig>,
pub(super) dns_resolver: Option<Rc<dns::Resolver>>,
pub(super) data_root: PathBuf,
pub(super) account: Option<Account>,
pub(super) selected: Option<Mailbox>,
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) flag_responses_enabled: bool,
pub(super) multiappend: Option<Multiappend>,
pub(super) logged_out: bool,
pub(super) id_exchanged: bool,
}
pub(super) struct Multiappend {
pub(super) dst: String,
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 = tokio::sync::mpsc::Sender<OutputEvent>;
pub(super) async fn send_event(sender: &mut SendResponse, event: OutputEvent) {
let _ = sender.send(event).await;
}
pub(super) async fn send_response(
sender: &mut SendResponse,
response: s::Response<'static>,
) {
let ctl = match response {
s::Response::Cond(s::CondResponse {
cond: s::RespCondType::Bye,
..
}) => OutputControl::Disconnect,
_ => OutputControl::Buffer,
};
let _ = sender
.send(OutputEvent::ResponseLine {
ctl,
line: s::ResponseLine {
tag: None,
response,
},
})
.await;
}
impl CommandProcessor {
pub fn new(
log_prefix: LogPrefix,
system_config: Arc<SystemConfig>,
data_root: PathBuf,
dns_resolver: Option<Rc<dns::Resolver>>,
) -> Self {
CommandProcessor {
log_prefix,
system_config,
data_root,
dns_resolver,
account: None,
selected: None,
searchres: SeqRange::new(),
unicode_aware: false,
utf8_enabled: false,
condstore_enabled: false,
qresync_enabled: false,
imap4rev2_enabled: false,
flag_responses_enabled: true,
multiappend: None,
logged_out: false,
id_exchanged: false,
}
}
pub fn is_authenticated(&self) -> bool {
self.account.is_some()
}
pub fn logged_out(&self) -> bool {
self.logged_out
}
pub fn log_prefix(&self) -> &LogPrefix {
&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, true)
.unwrap());
}
let max_seqnum = selected!(self)?.max_seqnum();
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)?.next_uid();
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: &LogPrefix,
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",
)),
})
} else {
s::Response::Cond(s::CondResponse {
cond: s::RespCondType::No,
code: Some(s::RespTextCode::ServerBug(())),
quip: Some(Cow::Borrowed(
"Unexpected error; was the mailbox deleted?",
)),
})
}
}
#[cfg(test)]
pub(super) fn catch_all_error_handling(
selected_ok: bool,
log_prefix: &LogPrefix,
e: Error,
) -> s::Response<'static> {
if !selected_ok {
error!("{} Unhandled error, but !selected_ok: {}", log_prefix, e);
s::Response::Cond(s::CondResponse {
cond: s::RespCondType::No,
code: Some(s::RespTextCode::ServerBug(())),
quip: Some(Cow::Borrowed(
"Unexpected error; was the mailbox deleted?",
)),
})
} else {
error!("{} Unhandled internal error: {}", log_prefix, e);
panic!("{} Unhandled internal error: {}", log_prefix, e);
}
}