#[allow(clippy::wildcard_imports)]
use super::primitives::*;
#[allow(clippy::wildcard_imports)]
use super::*;
pub(super) fn flag_or_perm(input: &[u8]) -> IResult<&[u8], Flag> {
alt((
map(tag(b"\\*"), |_| Flag::Wildcard),
map(tuple((char('\\'), atom)), |(_, name)| {
let full = format!("\\{}", String::from_utf8_lossy(name));
Flag::from_imap_str(&full)
}),
map(atom, |a| Flag::from_imap_str(&String::from_utf8_lossy(a))),
))(input)
}
fn parse_flag_list(input: &[u8], allow_wildcard: bool) -> IResult<&[u8], Vec<Flag>> {
let (input, flags) = delimited(
char('('),
separated_list0(take_while1(|b: u8| b == b' '), flag_or_perm),
char(')'),
)(input)?;
if allow_wildcard {
Ok((input, flags))
} else {
Ok((
input,
flags.into_iter().filter(|f| *f != Flag::Wildcard).collect(),
))
}
}
pub(super) fn flag_list(input: &[u8]) -> IResult<&[u8], Vec<Flag>> {
parse_flag_list(input, false)
}
pub(super) fn flag_perm_list(input: &[u8]) -> IResult<&[u8], Vec<Flag>> {
parse_flag_list(input, true)
}
pub(super) fn capability(input: &[u8]) -> IResult<&[u8], Capability> {
let (input, raw) = atom(input)?;
let s = String::from_utf8_lossy(raw);
Ok((input, Capability::from_imap_str(&s)))
}
pub(super) fn capability_list(input: &[u8]) -> IResult<&[u8], Vec<Capability>> {
separated_list0(take_while1(|b: u8| b == b' ' || b == b'\t'), capability)(input)
}
pub(super) fn uid_range(input: &[u8]) -> IResult<&[u8], UidRange> {
let (input, first) = nz_number(input)?;
let (input, second) = if input.first() == Some(&b':') {
let (input, _) = char(':')(input)?;
let (input, end) = nz_number(input)?;
(input, Some(end))
} else {
(input, None)
};
let (start, end) = match second {
Some(end) if first > end => (end, Some(first)),
other => (first, other),
};
Ok((input, UidRange { start, end }))
}
pub(super) fn uid_set(input: &[u8]) -> IResult<&[u8], Vec<UidRange>> {
separated_list1(char(','), uid_range)(input)
}
fn seq_number(input: &[u8]) -> IResult<&[u8], u32> {
if input.first() == Some(&b'*') {
Ok((&input[1..], u32::MAX))
} else {
nz_number(input)
}
}
pub(super) fn seq_range(input: &[u8]) -> IResult<&[u8], UidRange> {
let (input, first) = seq_number(input)?;
let (input, second) = if input.first() == Some(&b':') {
let (input, _) = char(':')(input)?;
let (input, end) = seq_number(input)?;
(input, Some(end))
} else {
(input, None)
};
let (start, end) = match second {
Some(end) if first > end => (end, Some(first)),
other => (first, other),
};
Ok((input, UidRange { start, end }))
}
pub(super) fn sequence_set(input: &[u8]) -> IResult<&[u8], Vec<UidRange>> {
separated_list1(char(','), seq_range)(input)
}
pub(super) fn response_code(input: &[u8]) -> IResult<&[u8], ResponseCode> {
let (input, _) = char('[')(input)?;
let (input, code_atom) = atom(input)?;
let code_str = String::from_utf8_lossy(code_atom);
let upper = code_str.to_ascii_uppercase();
let (input, code) = response_code_inner(input, &code_str, &upper)?;
let (input, _) = char(']')(input)?;
Ok((input, code))
}
fn response_code_optional_tail(input: &[u8]) -> IResult<&[u8], Option<String>> {
let (input, val) = opt(preceded(sp, take_while(|b: u8| b != b']')))(input)?;
Ok((input, val.map(|v| String::from_utf8_lossy(v).into_owned())))
}
#[allow(clippy::too_many_lines)]
fn response_code_inner<'a>(
input: &'a [u8],
code_str: &str,
upper: &str,
) -> IResult<&'a [u8], ResponseCode> {
match upper {
"ALERT" => Ok((input, ResponseCode::Alert)),
"PARSE" => Ok((input, ResponseCode::Parse)),
"READ-ONLY" => Ok((input, ResponseCode::ReadOnly)),
"READ-WRITE" => Ok((input, ResponseCode::ReadWrite)),
"TRYCREATE" => Ok((input, ResponseCode::TryCreate)),
"NOMODSEQ" => Ok((input, ResponseCode::NoModSeq)),
"CLOSED" => Ok((input, ResponseCode::Closed)),
"UNAVAILABLE" => Ok((input, ResponseCode::Unavailable)),
"AUTHENTICATIONFAILED" => Ok((input, ResponseCode::AuthenticationFailed)),
"AUTHORIZATIONFAILED" => Ok((input, ResponseCode::AuthorizationFailed)),
"EXPIRED" => Ok((input, ResponseCode::Expired)),
"PRIVACYREQUIRED" => Ok((input, ResponseCode::PrivacyRequired)),
"CONTACTADMIN" => Ok((input, ResponseCode::ContactAdmin)),
"NOPERM" => Ok((input, ResponseCode::NoPerm)),
"INUSE" => Ok((input, ResponseCode::InUse)),
"EXPUNGEISSUED" => Ok((input, ResponseCode::ExpungeIssued)),
"CORRUPTION" => Ok((input, ResponseCode::Corruption)),
"SERVERBUG" => Ok((input, ResponseCode::ServerBug)),
"CLIENTBUG" => Ok((input, ResponseCode::ClientBug)),
"CANNOT" => Ok((input, ResponseCode::Cannot)),
"LIMIT" => Ok((input, ResponseCode::Limit)),
"OVERQUOTA" => Ok((input, ResponseCode::OverQuota)),
"ALREADYEXISTS" => Ok((input, ResponseCode::AlreadyExists)),
"NONEXISTENT" => Ok((input, ResponseCode::NonExistent)),
"NEWNAME" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::NewName(value)))
}
"REFERRAL" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::Referral(value)))
}
"URLMECH" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::UrlMech(value)))
}
"BADURL" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::BadUrl(value)))
}
"BADCOMPARATOR" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::BadComparator(value)))
}
"ANNOTATE" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::Annotate(value)))
}
"ANNOTATIONS" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::Annotations(value)))
}
"TEMPFAIL" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::TempFail(value)))
}
"MAXCONVERTMESSAGES" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::MaxConvertMessages(value)))
}
"MAXCONVERTPARTS" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::MaxConvertParts(value)))
}
"NOUPDATE" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::NoUpdate(value)))
}
"NOTIFICATIONOVERFLOW" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::NotificationOverflow(value)))
}
"BADEVENT" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::BadEvent(value)))
}
"UNDEFINED-FILTER" => {
let (input, value) = response_code_optional_tail(input)?;
Ok((input, ResponseCode::UndefinedFilter(value)))
}
"UIDNOTSTICKY" => Ok((input, ResponseCode::UidNotSticky)),
"NOTSAVED" => Ok((input, ResponseCode::NotSaved)),
"HASCHILDREN" => Ok((input, ResponseCode::HasChildren)),
"UNKNOWN-CTE" => Ok((input, ResponseCode::UnknownCte)),
"TOOBIG" => Ok((input, ResponseCode::TooBig)),
"COMPRESSIONACTIVE" => Ok((input, ResponseCode::CompressionActive)),
"USEATTR" => Ok((input, ResponseCode::UseAttr)),
"UIDNEXT" => {
let (input, _) = sp(input)?;
let (input, n) = number(input)?;
Ok((input, ResponseCode::UidNext(n)))
}
"UIDVALIDITY" => {
let (input, _) = sp(input)?;
let (input, n) = number(input)?;
Ok((input, ResponseCode::UidValidity(n)))
}
"UNSEEN" => {
let (input, _) = sp(input)?;
let (input, n) = number(input)?;
Ok((input, ResponseCode::Unseen(n)))
}
"HIGHESTMODSEQ" => {
let (input, _) = sp(input)?;
let (input, n) = number64(input)?;
Ok((input, ResponseCode::HighestModSeq(n)))
}
"CAPABILITY" => {
let (input, _) = sp(input)?;
let (input, caps) = capability_list(input)?;
Ok((input, ResponseCode::Capability(caps)))
}
"PERMANENTFLAGS" => {
let (input, _) = sp(input)?;
let (input, flags) = flag_perm_list(input)?;
Ok((input, ResponseCode::PermanentFlags(flags)))
}
"BADCHARSET" => {
let (input, charsets) = opt(preceded(
sp,
delimited(
char('('),
separated_list0(take_while1(|b: u8| b == b' '), astring_utf8),
char(')'),
),
))(input)?;
Ok((
input,
ResponseCode::BadCharset(charsets.unwrap_or_default()),
))
}
"APPENDUID" => {
let (input, _) = sp(input)?;
let (input, uid_validity) = number(input)?;
let (input, _) = sp(input)?;
let (input, uids) = uid_set(input)?;
Ok((input, ResponseCode::AppendUid { uid_validity, uids }))
}
"COPYUID" => {
let (input, _) = sp(input)?;
let (input, uid_validity) = number(input)?;
let (input, _) = sp(input)?;
let (input, source_uids) = uid_set(input)?;
let (input, _) = sp(input)?;
let (input, dest_uids) = uid_set(input)?;
Ok((
input,
ResponseCode::CopyUid {
uid_validity,
source_uids,
dest_uids,
},
))
}
"MODIFIED" => {
let (input, _) = sp(input)?;
let (input, ranges) = sequence_set(input)?;
Ok((input, ResponseCode::Modified(ranges)))
}
"MAILBOXID" => {
let (input, _) = sp(input)?;
let (input, _) = char('(')(input)?;
let (input, val) = map(objectid, |a| String::from_utf8_lossy(a).into_owned())(input)?;
let (input, _) = char(')')(input)?;
Ok((input, ResponseCode::MailboxId(val)))
}
"METADATA" => {
let (input, _) = sp(input)?;
let (input, sub_atom) = atom(input)?;
let sub = String::from_utf8_lossy(sub_atom).to_ascii_uppercase();
match sub.as_str() {
"LONGENTRIES" => {
let (input, _) = sp(input)?;
let (input, n) = number64(input)?;
Ok((input, ResponseCode::MetadataLongEntries(n)))
}
"MAXSIZE" => {
let (input, _) = sp(input)?;
let (input, n) = number64(input)?;
Ok((input, ResponseCode::MetadataMaxSize(n)))
}
"TOOMANY" => Ok((input, ResponseCode::MetadataTooMany)),
"NOPRIVATE" => Ok((input, ResponseCode::MetadataNoPrivate)),
_ => {
let (input, val) = opt(preceded(sp, take_while(|b: u8| b != b']')))(input)?;
Ok((
input,
ResponseCode::Other {
name: format!("METADATA {sub}"),
value: val.map(|v| String::from_utf8_lossy(v).into_owned()),
},
))
}
}
}
_ => {
let (input, value) = response_code_optional_tail(input)?;
Ok((
input,
ResponseCode::Other {
name: code_str.to_owned(),
value,
},
))
}
}
}
pub(super) fn resp_text(input: &[u8]) -> IResult<&[u8], (Option<ResponseCode>, String)> {
let (input, code) = opt(response_code)(input)?;
let (input, _) = opt(char(' '))(input)?;
let (input, text_bytes) = take_while(|b: u8| b != b'\r' && b != b'\n')(input)?;
let text = String::from_utf8_lossy(text_bytes).into_owned();
Ok((input, (code, text)))
}