use crate::store::FlagSet;
pub(super) fn parse_literal_size(line: &str) -> Option<usize> {
let start = line.rfind('{')?;
let end = line.rfind('}')?;
if end <= start + 1 {
return None;
}
line[start + 1..end].parse::<usize>().ok()
}
pub(super) fn parse_mailbox_name(token: &str) -> String {
token.trim_matches('"').to_string()
}
pub(super) fn parse_append_mailbox(line: &str) -> String {
let mut parts = line.split_whitespace();
let _tag = parts.next();
let cmd = parts.next().unwrap_or("");
if !cmd.eq_ignore_ascii_case("APPEND") {
return "INBOX".to_string();
}
let mailbox = parts.next().unwrap_or("INBOX");
parse_mailbox_name(mailbox)
}
pub(super) fn parse_internal_date_from_line(line: &str) -> Option<String> {
let mut in_quotes = false;
let mut current = String::new();
let mut candidate = None;
for ch in line.chars() {
if ch == '"' {
if in_quotes {
if looks_like_internal_date(¤t) {
candidate = Some(current.clone());
}
current.clear();
in_quotes = false;
} else {
in_quotes = true;
}
} else if in_quotes {
current.push(ch);
}
}
candidate
}
fn looks_like_internal_date(value: &str) -> bool {
value.contains('-') && value.contains(':') && value.contains(' ')
}
pub(super) fn list_match(name: &str, pattern: &str) -> bool {
if pattern == "*" || pattern == "%" {
return true;
}
if pattern.is_empty() {
return false;
}
if let Some(prefix) = pattern.strip_suffix('*') {
return name.starts_with(prefix);
}
if let Some(prefix) = pattern.strip_suffix('%') {
return name.starts_with(prefix);
}
name.eq_ignore_ascii_case(pattern)
}
pub(super) fn parse_imap_args(line: &str) -> Vec<String> {
let mut args = Vec::new();
let mut in_quotes = false;
let mut current = String::new();
let mut chars = line.chars().peekable();
let mut skipped = 0;
while let Some(ch) = chars.peek().copied() {
if ch == ' ' {
skipped += 1;
chars.next();
if skipped >= 2 {
break;
}
} else {
chars.next();
}
}
for ch in chars {
match ch {
'"' => {
in_quotes = !in_quotes;
}
' ' if !in_quotes => {
if !current.is_empty() {
args.push(current.clone());
current.clear();
}
}
_ => current.push(ch),
}
}
if !current.is_empty() {
args.push(current);
}
args
}
pub(super) fn parse_sequence_set(token: &str, max: u32) -> Vec<u32> {
let mut result = Vec::new();
for part in token.split(',') {
if let Some((start, end)) = part.split_once(':') {
let start = start.parse::<u32>().unwrap_or(1);
let end = if end == "*" {
max.max(start)
} else {
end.parse::<u32>().unwrap_or(start)
};
for v in start..=end {
result.push(v);
}
} else if part == "*" {
result.push(max.max(1));
} else if let Ok(v) = part.parse::<u32>() {
result.push(v);
}
}
result
}
pub(super) fn parse_flag_list_from_line(line: &str) -> FlagSet {
let mut flags = FlagSet::default();
let Some(start) = line.find('(') else {
return flags;
};
let Some(end) = line[start..].find(')') else {
return flags;
};
let content = &line[start + 1..start + end];
for raw in content.split_whitespace() {
match raw.to_ascii_uppercase().as_str() {
"\\SEEN" => flags.seen = true,
"\\FLAGGED" => flags.flagged = true,
"\\DELETED" => flags.deleted = true,
"\\ANSWERED" => flags.answered = true,
"\\DRAFT" => flags.draft = true,
_ => {}
}
}
flags
}
pub(super) fn parse_copy_target(line: &str) -> Option<(String, String, bool)> {
let mut parts = line.split_whitespace();
let _tag = parts.next()?;
let cmd = parts.next()?;
let mut is_uid = false;
let cmd = if cmd.eq_ignore_ascii_case("UID") {
is_uid = true;
parts.next()?
} else {
cmd
};
if !cmd.eq_ignore_ascii_case("COPY") {
return None;
}
let seqset = parts.next()?.to_string();
let dest = parts.next().unwrap_or("INBOX");
Some((seqset, parse_mailbox_name(dest), is_uid))
}
pub(super) fn parse_move_target(line: &str) -> Option<(String, String, bool)> {
let mut parts = line.split_whitespace();
let _tag = parts.next()?;
let cmd = parts.next()?;
let mut is_uid = false;
let cmd = if cmd.eq_ignore_ascii_case("UID") {
is_uid = true;
parts.next()?
} else {
cmd
};
if !cmd.eq_ignore_ascii_case("MOVE") {
return None;
}
let seqset = parts.next()?.to_string();
let dest = parts.next().unwrap_or("INBOX");
Some((seqset, parse_mailbox_name(dest), is_uid))
}
pub(super) fn parse_uid_range(range: &str, max_uid: u32) -> (u32, u32) {
if let Some((start, end)) = range.split_once(':') {
let start = start.parse::<u32>().unwrap_or(1);
let end = if end == "*" {
max_uid.max(start)
} else {
end.parse::<u32>().unwrap_or(max_uid)
};
return (start, end);
}
let single = range.parse::<u32>().unwrap_or(1);
(single, single)
}
pub(super) fn parse_seq_range(range: &str, max_seq: u32) -> (u32, u32) {
if let Some((start, end)) = range.split_once(':') {
let start = start.parse::<u32>().unwrap_or(1);
let end = if end == "*" {
max_seq.max(start)
} else {
end.parse::<u32>().unwrap_or(max_seq)
};
return (start, end);
}
let single = range.parse::<u32>().unwrap_or(1);
(single, single)
}