imap: add mailbox_token() parser

memfd
Manos Pitsidianakis 2020-06-23 12:31:40 +03:00
parent c2300e8ea0
commit c7835ccc13
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
1 changed files with 70 additions and 149 deletions

View File

@ -346,7 +346,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
let (input, _) = tag(b") ")(input)?; let (input, _) = tag(b") ")(input)?;
let (input, separator) = delimited(tag(b"\""), take(1_u32), tag(b"\""))(input)?; let (input, separator) = delimited(tag(b"\""), take(1_u32), tag(b"\""))(input)?;
let (input, _) = take(1_u32)(input)?; let (input, _) = take(1_u32)(input)?;
let (input, path) = alt((delimited(tag("\""), is_not("\""), tag("\"")), rest))(input)?; let (input, path) = mailbox_token(input)?;
Ok(( Ok((
input, input,
({ ({
@ -366,7 +366,8 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
let _ = f.set_special_usage(SpecialUsageMailbox::Drafts); let _ = f.set_special_usage(SpecialUsageMailbox::Drafts);
} }
} }
f.imap_path = String::from_utf8_lossy(path).into(); f.is_subscribed = path == "INBOX";
f.imap_path = path.into();
f.hash = get_path_hash!(&f.imap_path); f.hash = get_path_hash!(&f.imap_path);
f.path = if separator == b'/' { f.path = if separator == b'/' {
f.imap_path.clone() f.imap_path.clone()
@ -385,51 +386,6 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
debug!(f) debug!(f)
}), }),
)) ))
/*
do_parse!(
ws!(alt_complete!(tag!("* LIST (") | tag!("* LSUB (")))
>> properties: take_until!(&b")"[0..])
>> tag!(b") ")
>> separator: delimited!(tag!(b"\""), take!(1), tag!(b"\""))
>> take!(1)
>> path: alt_complete!(delimited!(tag!("\""), is_not!("\""), tag!("\"")) | call!(rest))
>> ({
let separator: u8 = separator[0];
let mut f = ImapMailbox::default();
f.no_select = false;
f.is_subscribed = false;
for p in properties.split(|&b| b == b' ') {
use crate::backends::SpecialUsageMailbox;
if p.eq_ignore_ascii_case(b"\\NoSelect") {
f.no_select = true;
} else if p.eq_ignore_ascii_case(b"\\Sent") {
let _ = f.set_special_usage(SpecialUsageMailbox::Sent);
} else if p.eq_ignore_ascii_case(b"\\Junk") {
let _ = f.set_special_usage(SpecialUsageMailbox::Trash);
} else if p.eq_ignore_ascii_case(b"\\Drafts") {
let _ = f.set_special_usage(SpecialUsageMailbox::Drafts);
}
}
f.imap_path = String::from_utf8_lossy(path).into();
f.hash = get_path_hash!(&f.imap_path);
f.path = if separator == b'/' {
f.imap_path.clone()
} else {
f.imap_path.replace(separator as char, "/")
};
f.name = if let Some(pos) = f.imap_path.as_bytes().iter().rposition(|&c| c == separator) {
f.parent = Some(get_path_hash!(&f.imap_path[..pos]));
f.imap_path[pos + 1..].to_string()
} else {
f.imap_path.clone()
};
f.separator = separator;
debug!(f)
})
)
*/
} }
pub fn my_flags(input: &[u8]) -> IResult<&[u8], Flag> { pub fn my_flags(input: &[u8]) -> IResult<&[u8], Flag> {
@ -460,41 +416,6 @@ pub fn my_flags(input: &[u8]) -> IResult<&[u8], Flag> {
} }
} }
Ok((input, ret)) Ok((input, ret))
/*
my_flags<Flag>,
do_parse!(
flags: separated_list!(tag!(" "), preceded!(tag!("\\"), is_not!(")")))
>> ({
let mut ret = Flag::default();
for f in flags {
match f {
b"Answered" => {
ret.set(Flag::REPLIED, true);
}
b"Flagged" => {
ret.set(Flag::FLAGGED, true);
}
b"Deleted" => {
ret.set(Flag::TRASHED, true);
}
b"Seen" => {
ret.set(Flag::SEEN, true);
}
b"Draft" => {
ret.set(Flag::DRAFT, true);
}
f => {
debug!("unknown Flag token value: {}", unsafe {
std::str::from_utf8_unchecked(f)
});
}
}
}
ret
})
)
*/
} }
#[derive(Debug)] #[derive(Debug)]
@ -763,23 +684,13 @@ pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], Vec<(usize, (Fla
let (input, uid_flags) = permutation(( let (input, uid_flags) = permutation((
preceded( preceded(
tag("UID "), tag("UID "),
map_res(digit1, |s| { map_res(digit1, |s| usize::from_str(to_str!(s))),
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}),
), ),
preceded(tag("FLAGS "), delimited(tag("("), byte_flags, tag(")"))), preceded(tag("FLAGS "), delimited(tag("("), byte_flags, tag(")"))),
))(input.ltrim())?; ))(input.ltrim())?;
let (input, _) = tag(")\r\n")(input)?; let (input, _) = tag(")\r\n")(input)?;
Ok((input, (uid_flags.0, uid_flags.1))) Ok((input, (uid_flags.0, uid_flags.1)))
})(input) })(input)
/*
>> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit1, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")"))))
>> tag!(")\r\n")
>> ((uid_flags.0, uid_flags.1))
)
)
*/
} }
macro_rules! flags_to_imap_list { macro_rules! flags_to_imap_list {
@ -913,9 +824,7 @@ pub enum UntaggedResponse {
pub fn untagged_responses(input: &[u8]) -> IResult<&[u8], Option<UntaggedResponse>> { pub fn untagged_responses(input: &[u8]) -> IResult<&[u8], Option<UntaggedResponse>> {
let (input, _) = tag("* ")(input)?; let (input, _) = tag("* ")(input)?;
let (input, num) = map_res(digit1, |s| { let (input, num) = map_res(digit1, |s| usize::from_str(to_str!(s)))(input)?;
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
})(input)?;
let (input, _) = tag(" ")(input)?; let (input, _) = tag(" ")(input)?;
let (input, _tag) = take_until("\r\n")(input)?; let (input, _tag) = take_until("\r\n")(input)?;
let (input, _) = tag("\r\n")(input)?; let (input, _) = tag("\r\n")(input)?;
@ -926,49 +835,25 @@ pub fn untagged_responses(input: &[u8]) -> IResult<&[u8], Option<UntaggedRespons
b"EXISTS" => Some(Exists(num)), b"EXISTS" => Some(Exists(num)),
b"RECENT" => Some(Recent(num)), b"RECENT" => Some(Recent(num)),
_ if _tag.starts_with(b"FETCH ") => { _ if _tag.starts_with(b"FETCH ") => {
flags(unsafe { std::str::from_utf8_unchecked(&_tag[b"FETCH (FLAGS (".len()..]) }) let f = flags(unsafe {
.map(|(_, flags)| Fetch(num, flags)) std::str::from_utf8_unchecked(&_tag[b"FETCH (FLAGS (".len()..])
.map_err(|err| { })
debug!( .map(|(_, flags)| Fetch(num, flags));
"untagged_response malformed fetch: {} {}", if let Err(ref err) = f {
unsafe { std::str::from_utf8_unchecked(_tag) }, debug!(
err "untagged_response malformed fetch: {} {}",
) unsafe { std::str::from_utf8_unchecked(_tag) },
}) err
.ok() )
}
f.ok()
} }
_ => { _ => {
debug!("unknown untagged_response: {}", unsafe { debug!("unknown untagged_response: {}", to_str!(_tag));
std::str::from_utf8_unchecked(_tag)
});
None None
} }
} }
})) }))
/*
pub untagged_responses<>,
do_parse!(
tag!("* ")
>> num: map_res!(digit1, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })
>> tag!(" ")
>> tag: take_until!("\r\n")
>> tag!("\r\n")
>> ({
use UntaggedResponse::*;
match tag {
b"EXPUNGE" => Some(Expunge(num)),
b"EXISTS" => Some(Exists(num)),
b"RECENT" => Some(Recent(num)),
_ if tag.starts_with(b"FETCH ") => flags(
unsafe { std::str::from_utf8_unchecked(&tag[b"FETCH (FLAGS (".len()..]) }).map(|flags| Fetch(num, flags)).map_err(|err| debug!("untagged_response malformed fetch: {}", unsafe { std::str::from_utf8_unchecked(tag) })).ok(),
_ => {
debug!("unknown untagged_response: {}", unsafe { std::str::from_utf8_unchecked(tag) });
None
}
}
})
)
*/
} }
pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec<usize>> { pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec<usize>> {
@ -989,13 +874,6 @@ pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec<usize>> {
Ok((input, vec![])) Ok((input, vec![]))
}, },
))(input) ))(input)
/*
alt_complete!(do_parse!( tag!("* SEARCH ")
>> list: separated_nonempty_list_complete!(tag!(b" "), map_res!(is_not!(" \r\n"), |s: &[u8]| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }))
>> tag!("\r\n")
>> ({ list })) |
do_parse!(tag!("* SEARCH\r\n") >> ({ Vec::new() })))
*/
} }
pub fn search_results_raw<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { pub fn search_results_raw<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
@ -1011,15 +889,6 @@ pub fn search_results_raw<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
Ok((input, &b""[0..])) Ok((input, &b""[0..]))
}, },
))(input) ))(input)
/*
pub search_results_raw<&[u8]>,
alt_complete!(do_parse!( tag!("* SEARCH ")
>> list: take_until!("\r\n")
>> tag!("\r\n")
>> ({ list })) |
do_parse!(tag!("* SEARCH\r\n") >> ({ &b""[0..] })))
);
*/
} }
#[test] #[test]
@ -1532,3 +1401,55 @@ pub fn uid_fetch_envelopes_response(
pub fn bodystructure_has_attachments(input: &[u8]) -> bool { pub fn bodystructure_has_attachments(input: &[u8]) -> bool {
input.rfind(b" \"mixed\" ").is_some() || input.rfind(b" \"MIXED\" ").is_some() input.rfind(b" \"mixed\" ").is_some() || input.rfind(b" \"MIXED\" ").is_some()
} }
// mailbox = "INBOX" / astring
// ; INBOX is case-insensitive. All case variants of
// ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX
// ; not as an astring. An astring which consists of
// ; the case-insensitive sequence "I" "N" "B" "O" "X"
// ; is considered to be INBOX and not an astring.
// ; Refer to section 5.1 for further
// ; semantic details of mailbox names.
pub fn mailbox_token<'i>(input: &'i [u8]) -> IResult<&'i [u8], &'i str> {
let (input, astring) = astring_token(input)?;
if astring.eq_ignore_ascii_case(b"INBOX") {
return Ok((input, "INBOX"));
}
Ok((input, to_str!(astring)))
}
// astring = 1*ASTRING-CHAR / string
fn astring_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
alt((string_token, astring_char_tokens))(input)
}
// string = quoted / literal
fn string_token(input: &[u8]) -> IResult<&[u8], &[u8]> {
if let Ok((r, o)) = literal(input) {
return Ok((r, o));
}
if input.is_empty() || input[0] != b'"' {
return Err(nom::Err::Error((input, "string_token(): EOF").into()));
}
let mut i = 1;
while i < input.len() {
if input[i] == b'\"' && input[i - 1] != b'\\' {
return Ok((&input[i + 1..], &input[1..i]));
}
i += 1;
}
return Err(nom::Err::Error(
(input, "string_token(): not a quoted phrase").into(),
));
}
// ASTRING-CHAR = ATOM-CHAR / resp-specials
// atom = 1*ATOM-CHAR
// ATOM-CHAR = <any CHAR except atom-specials>
// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials / resp-specials
fn astring_char_tokens(input: &[u8]) -> IResult<&[u8], &[u8]> {
// FIXME
is_not(" ")(input)
}