use super::bodystructure::body_structure;
use super::encoded_words::decode_rfc2047;
use super::flags_caps::flag_list;
#[allow(clippy::wildcard_imports)]
use super::primitives::*;
#[allow(clippy::wildcard_imports)]
use super::*;
pub(super) fn address(input: &[u8], utf8_mode: bool) -> IResult<&[u8], EnvelopeAddress> {
let (input, _) = char('(')(input)?;
let (input, name_raw) = nstring(input)?;
let (input, _) = sp(input)?;
let (input, adl) = nstring_utf8(input)?;
let (input, _) = sp(input)?;
let (input, mailbox) = nstring_utf8(input)?;
let (input, _) = sp(input)?;
let (input, host) = nstring_utf8(input)?;
let (input, _) = char(')')(input)?;
let name = name_raw.map(|v| {
if utf8_mode {
String::from_utf8_lossy(&v).into_owned()
} else {
decode_rfc2047(&v)
}
});
Ok((
input,
EnvelopeAddress {
name,
adl,
mailbox,
host,
},
))
}
pub(super) fn address_list(input: &[u8], utf8_mode: bool) -> IResult<&[u8], Vec<EnvelopeAddress>> {
alt((
value(vec![], nil_token),
delimited(char('('), many0(|i| address(i, utf8_mode)), char(')')),
))(input)
}
pub(super) fn envelope(input: &[u8], utf8_mode: bool) -> IResult<&[u8], Envelope> {
let (input, _) = char('(')(input)?;
let (input, date) = nstring_utf8(input)?;
let (input, _) = sp(input)?;
let (input, subject_raw) = nstring(input)?;
let subject = subject_raw.map(|v| {
if utf8_mode {
String::from_utf8_lossy(&v).into_owned()
} else {
decode_rfc2047(&v)
}
});
let (input, _) = sp(input)?;
let (input, from) = address_list(input, utf8_mode)?;
let (input, _) = sp(input)?;
let (input, sender) = address_list(input, utf8_mode)?;
let (input, _) = sp(input)?;
let (input, reply_to) = address_list(input, utf8_mode)?;
let (input, _) = sp(input)?;
let (input, to) = address_list(input, utf8_mode)?;
let (input, _) = sp(input)?;
let (input, cc) = address_list(input, utf8_mode)?;
let (input, _) = sp(input)?;
let (input, bcc) = address_list(input, utf8_mode)?;
let (input, _) = sp(input)?;
let (input, in_reply_to) = nstring_utf8(input)?;
let (input, _) = sp(input)?;
let (input, message_id) = nstring_utf8(input)?;
let (input, _) = char(')')(input)?;
let sender = if sender.is_empty() && !from.is_empty() {
from.clone()
} else {
sender
};
let reply_to = if reply_to.is_empty() && !from.is_empty() {
from.clone()
} else {
reply_to
};
Ok((
input,
Envelope {
date,
subject,
from,
sender,
reply_to,
to,
cc,
bcc,
in_reply_to,
message_id,
},
))
}
#[allow(clippy::too_many_lines)]
pub(super) fn fetch_response_inner(input: &[u8], utf8_mode: bool) -> IResult<&[u8], FetchResponse> {
let (input, _) = char('(')(input)?;
let mut fr = FetchResponse::default();
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, attr_name) = fetch_attr_atom(input)?;
let upper = String::from_utf8_lossy(attr_name).to_ascii_uppercase();
input = rest;
match upper.as_str() {
"UID" => {
let (rest, _) = sp(input)?;
let (rest, n) = nz_number(rest)?;
fr.uid = Some(n);
input = rest;
}
"FLAGS" => {
let (rest, _) = sp(input)?;
let (rest, flags) = flag_list(rest)?;
fr.flags = Some(flags);
input = rest;
}
"ENVELOPE" => {
let (rest, _) = sp(input)?;
let (rest, env) = envelope(rest, utf8_mode)?;
fr.envelope = Some(env);
input = rest;
}
"BODYSTRUCTURE" => {
let (rest, _) = sp(input)?;
let (rest, bs) = body_structure(rest, utf8_mode, 0)?;
fr.body_structure = Some(bs);
input = rest;
}
"RFC822.SIZE" => {
let (rest, _) = sp(input)?;
let (rest, n) = number64(rest)?;
fr.rfc822_size = Some(n);
input = rest;
}
"RFC822" => {
let (rest, _) = sp(input)?;
let (rest, data) = nstring(rest)?;
fr.body_sections.push(BodySection {
section: String::new(),
origin: None,
data,
});
input = rest;
}
"RFC822.HEADER" => {
let (rest, _) = sp(input)?;
let (rest, data) = nstring(rest)?;
fr.body_sections.push(BodySection {
section: "HEADER".to_owned(),
origin: None,
data,
});
input = rest;
}
"RFC822.TEXT" => {
let (rest, _) = sp(input)?;
let (rest, data) = nstring(rest)?;
fr.body_sections.push(BodySection {
section: "TEXT".to_owned(),
origin: None,
data,
});
input = rest;
}
"INTERNALDATE" => {
let (rest, _) = sp(input)?;
let (rest, date_val) = nstring_utf8(rest)?;
fr.internal_date = date_val;
input = rest;
}
"MODSEQ" => {
let (rest, _) = sp(input)?;
let (rest, _) = char('(')(rest)?;
if let Ok((rest, n)) = number64(rest) {
let (rest, _) = char(')')(rest)?;
fr.mod_seq = Some(n);
input = rest;
} else {
let (rest, _) = take_while(|b: u8| b != b')')(rest)?;
let (rest, _) = char(')')(rest)?;
input = rest;
}
}
"SAVEDATE" => {
let (rest, _) = sp(input)?;
let (rest, val) = nstring_utf8(rest)?;
fr.save_date = val;
input = rest;
}
"PREVIEW" => {
let (rest, _) = sp(input)?;
let (rest, val) = nstring_utf8(rest)?;
fr.preview = val;
input = rest;
}
"EMAILID" => {
let (rest, _) = sp(input)?;
let (rest, _) = char('(')(rest)?;
let (rest, val) = map(objectid, |a| String::from_utf8_lossy(a).into_owned())(rest)?;
let (rest, _) = char(')')(rest)?;
fr.email_id = Some(val);
input = rest;
}
"THREADID" => {
let (rest, _) = sp(input)?;
let (rest, nil_match) = opt(terminated(
tag_no_case(b"NIL"),
peek(alt((
value((), verify(take(1u8), |b: &[u8]| !is_atom_char(b[0]))),
value((), eof),
))),
))(rest)?;
if nil_match.is_some() {
fr.thread_id = None;
input = rest;
} else {
let (rest, _) = char('(')(rest)?;
let (rest, val) =
map(objectid, |a| String::from_utf8_lossy(a).into_owned())(rest)?;
let (rest, _) = char(')')(rest)?;
fr.thread_id = Some(val);
input = rest;
}
}
_ if upper == "BINARY.SIZE" && input.first() == Some(&b'[') => {
let (rest, section_parts) = binary_section_spec(input)?;
let (rest, _) = sp(rest)?;
let (rest, size) = number64(rest)?;
fr.binary_sizes.push((section_parts, size));
input = rest;
}
_ if upper == "BINARY" && input.first() == Some(&b'[') => {
let (rest, (section_parts, origin)) = binary_section_with_origin(input)?;
let (rest, _) = sp(rest)?;
let (rest, data) = nstring(rest)?;
fr.binary_sections.push(BinarySection {
section: section_parts,
origin,
data,
});
input = rest;
}
_ if upper == "BODY" && input.first() == Some(&b'[') => {
let (rest, section) = body_section_spec(input)?;
let (rest, _) = sp(rest)?;
let (rest, data) = nstring(rest)?;
fr.body_sections.push(BodySection {
section: section.0,
origin: section.1,
data,
});
input = rest;
}
_ if upper == "BODY" => {
let (rest, _) = sp(input)?;
let (rest, bs) = body_structure(rest, utf8_mode, 0)?;
fr.body_structure = Some(bs);
input = rest;
}
_ => {
let (rest, _) = sp(input)?;
let (rest, ()) = skip_fetch_value(rest)?;
input = rest;
}
}
}
Ok((input, fr))
}
fn body_section_spec(input: &[u8]) -> IResult<&[u8], (String, Option<u64>)> {
let (input, _) = char('[')(input)?;
let (input, section_bytes) = scan_section_spec(input)?;
let section = String::from_utf8_lossy(section_bytes).into_owned();
let (input, _) = char(']')(input)?;
let (input, origin) = opt(delimited(char('<'), number64, char('>')))(input)?;
Ok((input, (section, origin)))
}
pub(super) fn scan_section_spec(input: &[u8]) -> IResult<&[u8], &[u8]> {
let start = input;
let mut pos = 0;
let mut paren_depth: u32 = 0;
while pos < input.len() {
match input[pos] {
b']' if paren_depth == 0 => {
return Ok((&input[pos..], &start[..pos]));
}
b'(' => {
paren_depth += 1;
pos += 1;
}
b')' if paren_depth > 0 => {
paren_depth -= 1;
pos += 1;
}
b'"' => {
pos += 1; while pos < input.len() && input[pos] != b'"' {
if input[pos] == b'\\' && pos + 1 < input.len() {
if input[pos + 1] == b'\r' || input[pos + 1] == b'\n' {
break;
}
pos += 2; } else {
pos += 1;
}
}
if pos < input.len() {
pos += 1; }
}
b'~' if pos + 1 < input.len() && input[pos + 1] == b'{' => {
pos += 1; }
b'{' => {
pos += 1; let digit_start = pos;
while pos < input.len() && input[pos].is_ascii_digit() {
pos += 1;
}
if pos > digit_start && pos < input.len() {
let count_end = pos;
if input[pos] == b'+' {
pos += 1;
}
if pos < input.len() && input[pos] == b'}' {
pos += 1; if pos + 1 < input.len() && input[pos] == b'\r' && input[pos + 1] == b'\n' {
pos += 2;
if let Ok(s) = std::str::from_utf8(&input[digit_start..count_end]) {
if let Ok(count) = s.parse::<usize>() {
if let Some(new_pos) = pos.checked_add(count) {
if new_pos <= input.len() {
pos = new_pos;
}
}
}
}
}
}
}
}
_ => {
pos += 1;
}
}
}
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Char,
)))
}
pub(super) fn binary_section_spec(input: &[u8]) -> IResult<&[u8], Vec<u32>> {
let (input, _) = char('[')(input)?;
let (input, parts) = separated_list0(char('.'), nz_number)(input)?;
let (input, _) = char(']')(input)?;
Ok((input, parts))
}
pub(super) fn binary_section_with_origin(input: &[u8]) -> IResult<&[u8], (Vec<u32>, Option<u64>)> {
let (input, parts) = binary_section_spec(input)?;
let (input, origin) = opt(delimited(char('<'), number64, char('>')))(input)?;
Ok((input, (parts, origin)))
}
pub(super) fn skip_tagged_ext_simple<F>(terminator: F) -> impl FnMut(&[u8]) -> IResult<&[u8], ()>
where
F: Fn(u8) -> bool + Copy,
{
move |input: &[u8]| {
alt((
map(
terminated(
tag_no_case(b"NIL"),
peek(alt((
value((), verify(take(1u8), move |b: &[u8]| terminator(b[0]))),
value((), eof),
))),
),
|_| (),
),
map(literal, |_| ()),
map(quoted_string, |_| ()),
map(take_while1(move |b: u8| !terminator(b)), |_| ()),
))(input)
}
}
fn skip_fetch_value(input: &[u8]) -> IResult<&[u8], ()> {
alt((
map(skip_paren_group, |_| ()),
map(nil_token, |_| ()),
map(literal, |_| ()),
map(quoted_string, |_| ()),
map(
take_while1(|b: u8| b != b' ' && b != b')' && b != b'\r'),
|_| (),
),
))(input)
}
pub(super) fn skip_paren_group(input: &[u8]) -> IResult<&[u8], &[u8]> {
let (input, _) = char('(')(input)?;
let mut depth: u32 = 1;
let mut i = 0;
while i < input.len() && depth > 0 {
match input[i] {
b'(' => depth += 1,
b')' => depth -= 1,
b'"' => {
i += 1;
while i < input.len() && input[i] != b'"' {
if input[i] == b'\\' {
if i + 1 >= input.len() {
break;
}
if input[i + 1] == b'\r' || input[i + 1] == b'\n' {
break;
}
i += 1; }
i += 1;
}
if i >= input.len() {
break;
}
}
b'~' if i + 1 < input.len() && input[i + 1] == b'{' => {
i += 1; continue;
}
b'{' => {
let start = i + 1;
let mut end = start;
while end < input.len() && input[end].is_ascii_digit() {
end += 1;
}
if end > start && end < input.len() {
let count_end = end;
if input[end] == b'+' {
end += 1;
}
if end < input.len() && input[end] == b'}' {
end += 1; if end + 1 < input.len() && input[end] == b'\r' && input[end + 1] == b'\n' {
end += 2;
if let Ok(s) = std::str::from_utf8(&input[start..count_end]) {
if let Ok(count) = s.parse::<usize>() {
match end.checked_add(count) {
Some(new_end) if new_end <= input.len() => {
i = new_end;
continue;
}
_ => {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Eof,
)));
}
}
}
}
}
}
}
}
_ => {}
}
i += 1;
}
if depth != 0 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Char,
)));
}
Ok((&input[i..], &input[..i - 1]))
}