#[allow(clippy::wildcard_imports)]
use super::primitives::*;
#[allow(clippy::wildcard_imports)]
use super::*;
pub(super) fn parse_untagged_quota(input: &[u8]) -> IResult<&[u8], UntaggedResponse> {
let (input, _) = tag_no_case(b"QUOTA ")(input)?;
let (input, root) = astring_utf8(input)?;
let (input, _) = sp(input)?;
let (input, resources) = parse_quota_resource_list(input)?;
let (input, _) = take_while(|b: u8| b == b' ')(input)?;
let (input, _) = crlf(input)?;
Ok((input, UntaggedResponse::Quota { root, resources }))
}
fn parse_quota_resource_list(input: &[u8]) -> IResult<&[u8], Vec<QuotaResource>> {
let (input, _) = char('(')(input)?;
let mut resources = Vec::new();
let mut input = input;
loop {
let (rest, _) = take_while(|b: u8| b == b' ')(input)?;
input = rest;
if input.first() == Some(&b')') {
input = &input[1..];
break;
}
let (rest, name_bytes) = atom(input)?;
let name = String::from_utf8_lossy(name_bytes).into_owned();
let (rest, _) = sp(rest)?;
let (rest, usage) = number64(rest)?;
let (rest, _) = sp(rest)?;
let (rest, limit) = number64(rest)?;
resources.push(QuotaResource { name, usage, limit });
input = rest;
}
Ok((input, resources))
}
pub(super) fn parse_untagged_quotaroot(
input: &[u8],
utf8_mode: bool,
) -> IResult<&[u8], UntaggedResponse> {
let (input, _) = tag_no_case(b"QUOTAROOT ")(input)?;
let (input, mailbox_bytes) = astring(input)?;
let mailbox = decode_mailbox_from_wire(&mailbox_bytes, utf8_mode);
let mut roots = Vec::new();
let mut input = input;
loop {
let Ok((rest, _)) = sp(input) else {
break;
};
if rest.starts_with(b"\r\n") {
break;
}
let Ok((rest, root)) = astring_utf8(rest) else {
break;
};
roots.push(root);
input = rest;
}
let (input, _) = take_while(|b: u8| b == b' ')(input)?;
let (input, _) = crlf(input)?;
Ok((input, UntaggedResponse::QuotaRoot { mailbox, roots }))
}
pub(super) fn parse_untagged_acl(
input: &[u8],
utf8_mode: bool,
) -> IResult<&[u8], UntaggedResponse> {
let (input, _) = tag_no_case(b"ACL ")(input)?;
let (input, mailbox_bytes) = astring(input)?;
let mailbox = decode_mailbox_from_wire(&mailbox_bytes, utf8_mode);
let mut entries = Vec::new();
let mut input = input;
loop {
let Ok((rest, _)) = sp(input) else {
break;
};
if rest.starts_with(b"\r\n") {
break;
}
let Ok((rest, identifier)) = astring_utf8(rest) else {
break;
};
let Ok((rest, _)) = sp(rest) else {
break;
};
let Ok((rest, rights)) = astring_utf8(rest) else {
break;
};
entries.push(AclEntry { identifier, rights });
input = rest;
}
let (input, _) = take_while(|b: u8| b == b' ')(input)?;
let (input, _) = crlf(input)?;
Ok((input, UntaggedResponse::Acl { mailbox, entries }))
}
pub(super) fn parse_untagged_myrights(
input: &[u8],
utf8_mode: bool,
) -> IResult<&[u8], UntaggedResponse> {
let (input, _) = tag_no_case(b"MYRIGHTS ")(input)?;
let (input, mailbox_bytes) = astring(input)?;
let mailbox = decode_mailbox_from_wire(&mailbox_bytes, utf8_mode);
let (input, _) = sp(input)?;
let (input, rights) = astring_utf8(input)?;
let (input, _) = take_while(|b: u8| b == b' ')(input)?;
let (input, _) = crlf(input)?;
Ok((input, UntaggedResponse::MyRights { mailbox, rights }))
}
pub(super) fn parse_untagged_listrights(
input: &[u8],
utf8_mode: bool,
) -> IResult<&[u8], UntaggedResponse> {
let (input, _) = tag_no_case(b"LISTRIGHTS ")(input)?;
let (input, mailbox_bytes) = astring(input)?;
let mailbox = decode_mailbox_from_wire(&mailbox_bytes, utf8_mode);
let (input, _) = sp(input)?;
let (input, identifier) = astring_utf8(input)?;
let (input, _) = sp(input)?;
let (input, required) = astring_utf8(input)?;
let mut optional = Vec::new();
let mut input = input;
loop {
let Ok((rest, _)) = sp(input) else {
break;
};
if rest.starts_with(b"\r\n") {
break;
}
let Ok((rest, rights_group)) = astring_utf8(rest) else {
break;
};
optional.push(rights_group);
input = rest;
}
let (input, _) = take_while(|b: u8| b == b' ')(input)?;
let (input, _) = crlf(input)?;
Ok((
input,
UntaggedResponse::ListRights {
mailbox,
identifier,
required,
optional,
},
))
}
pub(super) fn parse_untagged_metadata(
input: &[u8],
utf8_mode: bool,
) -> IResult<&[u8], UntaggedResponse> {
let (input, _) = tag_no_case(b"METADATA ")(input)?;
let (input, mailbox_bytes) = astring(input)?;
let mailbox = decode_mailbox_from_wire(&mailbox_bytes, utf8_mode);
let (input, _) = sp(input)?;
let (input, entries) = if input.first() == Some(&b'(') {
parse_metadata_entry_list(input)?
} else {
let mut entries = Vec::new();
let (rest, first_name) = astring_utf8(input)?;
entries.push(MetadataEntry {
name: first_name,
value: None,
});
let mut input = rest;
loop {
if input.starts_with(b"\r\n") || input.is_empty() {
break;
}
let Ok((rest, _)) = sp(input) else {
break;
};
if rest.starts_with(b"\r\n") {
break;
}
let Ok((rest, name)) = astring_utf8(rest) else {
break;
};
entries.push(MetadataEntry { name, value: None });
input = rest;
}
(input, entries)
};
let (input, _) = take_while(|b: u8| b == b' ')(input)?;
let (input, _) = crlf(input)?;
Ok((input, UntaggedResponse::Metadata { mailbox, entries }))
}
fn parse_metadata_entry_list(input: &[u8]) -> IResult<&[u8], Vec<MetadataEntry>> {
let (input, _) = char('(')(input)?;
let mut entries = Vec::new();
let mut input = input;
loop {
let (rest, _) = take_while(|b: u8| b == b' ')(input)?;
input = rest;
if input.first() == Some(&b')') {
input = &input[1..];
break;
}
let (rest, name) = astring_utf8(input)?;
let (rest, _) = sp(rest)?;
let (rest, value) = nstring(rest)?;
entries.push(MetadataEntry { name, value });
input = rest;
}
Ok((input, entries))
}
const MAX_THREAD_NESTING_DEPTH: u32 = 64;
pub(super) fn parse_untagged_thread(input: &[u8]) -> IResult<&[u8], UntaggedResponse> {
let (input, _) = tag_no_case(b"THREAD")(input)?;
let mut threads = Vec::new();
let mut input = input;
loop {
let (rest, _) = take_while(|b: u8| b == b' ')(input)?;
input = rest;
if input.starts_with(b"\r\n") || input.is_empty() {
break;
}
if input.first() != Some(&b'(') {
break;
}
let (rest, node) = parse_thread_node(input, 0)?;
threads.push(node);
input = rest;
}
let (input, _) = crlf(input)?;
Ok((input, UntaggedResponse::Thread(threads)))
}
pub(super) fn parse_untagged_unknown(input: &[u8]) -> IResult<&[u8], UntaggedResponse> {
let (input, raw) = scan_unknown_response(input)?;
let (input, _) = crlf(input)?;
Ok((
input,
UntaggedResponse::Unknown(String::from_utf8_lossy(raw).into_owned()),
))
}
pub(super) fn scan_unknown_response(input: &[u8]) -> IResult<&[u8], &[u8]> {
let start = input;
let mut pos = 0;
while pos < input.len() {
let b = input[pos];
if b == b'\r' || b == b'\n' {
return Ok((&input[pos..], &start[..pos]));
}
if b == b'"' {
pos += 1; while pos < input.len() {
match input[pos] {
b'\\' => {
if pos + 1 < input.len()
&& input[pos + 1] != b'\r'
&& input[pos + 1] != b'\n'
{
pos += 2;
} else {
pos += 1;
break;
}
}
b'"' => {
pos += 1; break;
}
b'\r' | b'\n' => break,
_ => {
pos += 1;
}
}
}
continue;
}
if b == b'~' && pos + 1 < input.len() && input[pos + 1] == b'{' {
if let Some(advance) = try_skip_literal(&input[pos + 1..]) {
pos += 1 + advance; continue;
}
}
if b == b'{' {
if let Some(advance) = try_skip_literal(&input[pos..]) {
pos += advance;
continue;
}
}
pos += 1;
}
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::CrLf,
)))
}
pub(super) fn try_skip_literal(input: &[u8]) -> Option<usize> {
if input.is_empty() || input[0] != b'{' {
return None;
}
let mut pos = 1;
let digit_start = pos;
while pos < input.len() && input[pos].is_ascii_digit() {
pos += 1;
}
let digit_end = pos;
if digit_end == digit_start {
return None;
}
if pos < input.len() && input[pos] == b'+' {
pos += 1;
}
if pos + 2 >= input.len()
|| input[pos] != b'}'
|| input[pos + 1] != b'\r'
|| input[pos + 2] != b'\n'
{
return None;
}
pos += 3;
let count_str = std::str::from_utf8(&input[digit_start..digit_end]).ok()?;
let count: usize = count_str.parse().ok()?;
let total = pos.checked_add(count)?;
if total > input.len() {
return None;
}
Some(total)
}
fn parse_thread_node(input: &[u8], depth: u32) -> IResult<&[u8], ThreadNode> {
if depth > MAX_THREAD_NESTING_DEPTH {
return Err(nom::Err::Failure(nom::error::Error::new(
input,
nom::error::ErrorKind::TooLarge,
)));
}
let (input, _) = char('(')(input)?;
let mut input = input;
let root_id: Option<u32> = if input.first().map_or(true, |b| *b == b'(' || *b == b')') {
None
} else {
let (rest, uid) = nz_number(input)?;
input = rest;
Some(uid)
};
let mut chain_uids: Vec<Option<u32>> = Vec::new();
let mut branch_groups: Vec<Vec<ThreadNode>> = Vec::new();
loop {
let (rest, _) = take_while(|b: u8| b == b' ')(input)?;
input = rest;
if input.first() == Some(&b')') {
input = &input[1..];
break;
}
if input.first() == Some(&b'(') {
let (rest, child) = parse_thread_node(input, depth + 1)?;
if let Some(last) = branch_groups.last_mut() {
last.push(child);
} else {
branch_groups.push(vec![child]);
}
input = rest;
} else {
let (rest, uid) = nz_number(input)?;
chain_uids.push(Some(uid));
branch_groups.push(Vec::new());
input = rest;
}
}
let children = build_thread_tree(&chain_uids, &branch_groups);
let mut node = ThreadNode {
id: root_id,
children,
};
if node.id.is_none() && node.children.len() == 1 {
if let Some(child) = node.children.pop() {
node = child;
}
}
Ok((input, node))
}
pub(super) fn build_thread_tree(
chain_uids: &[Option<u32>],
branch_groups: &[Vec<ThreadNode>],
) -> Vec<ThreadNode> {
if chain_uids.is_empty() {
return branch_groups.iter().flatten().cloned().collect();
}
let last = chain_uids.len() - 1;
let last_children: Vec<ThreadNode> = if last < branch_groups.len() {
branch_groups[last].clone()
} else {
vec![]
};
let mut result = ThreadNode {
id: chain_uids[last],
children: last_children,
};
for i in (0..last).rev() {
let mut children: Vec<ThreadNode> = if i < branch_groups.len() {
branch_groups[i].clone()
} else {
vec![]
};
children.push(result);
result = ThreadNode {
id: chain_uids[i],
children,
};
}
vec![result]
}