melib/imap: add UidFetchResponse struct and parser

Add handwritten parser for UID FETCH responses and use it for all UID
FETCH calls.
async
Manos Pitsidianakis 2019-12-11 00:05:41 +02:00
parent 569127fac5
commit 504b658f68
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
4 changed files with 257 additions and 66 deletions

View File

@ -227,13 +227,17 @@ impl MailBackend for ImapType {
response.len(),
response.lines().collect::<Vec<&str>>().len()
);
match protocol_parser::uid_fetch_envelopes_response(response.as_bytes())
.to_full_result()
.map_err(MeliError::from)
{
Ok(v) => {
match protocol_parser::uid_fetch_responses(&response) {
Ok((_, v)) => {
debug!("responses len is {}", v.len());
for (uid, flags, mut env) in v {
for UidFetchResponse {
uid,
flags,
envelope,
..
} in v
{
let mut env = envelope.unwrap();
let mut h = DefaultHasher::new();
h.write_usize(uid);
h.write(folder_path.as_bytes());

View File

@ -88,30 +88,17 @@ impl BackendOp for ImapOp {
response.len(),
response.lines().collect::<Vec<&str>>().len()
);
match protocol_parser::uid_fetch_response(response.as_bytes())
.to_full_result()
.map_err(MeliError::from)
{
Ok(mut v) => {
if v.len() != 1 {
debug!("responses len is {}", v.len());
/* TODO: Trigger cache invalidation here. */
return Err(MeliError::new(format!(
"message with UID {} was not found",
self.uid
)));
}
let (uid, flags, b) = v.remove(0);
assert_eq!(uid, self.uid);
if let Some((flags, _)) = flags {
self.flags.set(Some(flags));
cache.flags = Some(flags);
}
cache.bytes = Some(unsafe { std::str::from_utf8_unchecked(b).to_string() });
}
Err(e) => return Err(e),
let UidFetchResponse {
uid, flags, body, ..
} = protocol_parser::uid_fetch_response(&response)?.1;
assert_eq!(uid, self.uid);
assert!(body.is_some());
if let Some((flags, _)) = flags {
self.flags.set(Some(flags));
cache.flags = Some(flags);
}
cache.bytes =
Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() });
self.bytes = cache.bytes.clone();
}
}

View File

@ -5,6 +5,7 @@ use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
pub type ImapParseResult<'a, T> = Result<(&'a str, T)>;
pub struct ImapLineIterator<'a> {
slice: &'a str,
}
@ -141,22 +142,219 @@ named!(
)
);
#[derive(Debug)]
pub struct UidFetchResponse<'a> {
pub uid: UID,
pub flags: Option<(Flag, Vec<String>)>,
pub body: Option<&'a [u8]>,
pub envelope: Option<Envelope>,
}
pub fn uid_fetch_response(input: &str) -> ImapParseResult<UidFetchResponse<'_>> {
macro_rules! should_start_with {
($input:expr, $tag:literal) => {
if !$input.starts_with($tag) {
return Err(MeliError::new(format!(
"Expected `{}` but got `{:.50}`",
$tag, &$input
)));
}
};
}
should_start_with!(input, "* ");
let mut i = "* ".len();
macro_rules! bounds {
() => {
if i == input.len() {
return Err(MeliError::new(format!(
"Expected more input. Got: `{:.50}`",
input
)));
}
};
(break) => {
if i == input.len() {
break;
}
};
}
macro_rules! eat_whitespace {
() => {
while (input.as_bytes()[i] as char).is_whitespace() {
i += 1;
bounds!();
}
};
(break) => {
while (input.as_bytes()[i] as char).is_whitespace() {
i += 1;
bounds!(break);
}
};
}
while (input.as_bytes()[i] as char).is_numeric() {
i += 1;
bounds!();
}
eat_whitespace!();
should_start_with!(input[i..], "FETCH (");
i += "FETCH (".len();
let mut ret = UidFetchResponse {
uid: 0,
flags: None,
body: None,
envelope: None,
};
let mut has_attachments = false;
while i < input.len() {
eat_whitespace!(break);
if input[i..].starts_with("UID ") {
i += "UID ".len();
if let IResult::Done(rest, uid) = take_while!(input[i..].as_bytes(), call!(is_digit)) {
i += input.len() - i - rest.len();
ret.uid = usize::from_str(unsafe { std::str::from_utf8_unchecked(uid) }).unwrap();
} else {
return debug!(Err(MeliError::new(format!(
"217Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
input
))));
}
} else if input[i..].starts_with("FLAGS (") {
i += "FLAGS (".len();
if let IResult::Done(rest, flags) = flags(&input[i..]) {
ret.flags = Some(flags);
i += (input.len() - i - rest.len()) + 1;
} else {
return debug!(Err(MeliError::new(format!(
"228Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
input
))));
}
} else if input[i..].starts_with("RFC822 {") {
i += "RFC822 ".len();
if let IResult::Done(rest, body) = length_bytes!(
input[i..].as_bytes(),
delimited!(
tag!("{"),
map_res!(digit, |s| {
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}),
tag!("}\r\n")
)
) {
ret.body = Some(body);
i += input.len() - i - rest.len();
} else {
return debug!(Err(MeliError::new(format!(
"248Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
input
))));
}
} else if input[i..].starts_with("ENVELOPE (") {
i += "ENVELOPE ".len();
if let IResult::Done(rest, envelope) = envelope(input[i..].as_bytes()) {
ret.envelope = Some(envelope);
i += input.len() - i - rest.len();
} else {
return debug!(Err(MeliError::new(format!(
"264Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
&input[i..]
))));
}
} else if input[i..].starts_with("BODYSTRUCTURE ") {
i += "BODYSTRUCTURE ".len();
let mut struct_ptr = i;
let mut parenth_level = 0;
let mut inside_quote = false;
while struct_ptr != input.len() {
if !inside_quote {
if input.as_bytes()[struct_ptr] == b'(' {
parenth_level += 1;
} else if input.as_bytes()[struct_ptr] == b')' {
if parenth_level == 0 {
return debug!(Err(MeliError::new(format!(
"280Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
&input[struct_ptr..]
))));
}
parenth_level -= 1;
if parenth_level == 0 {
struct_ptr += 1;
break;
}
} else if input.as_bytes()[struct_ptr] == b'"' {
inside_quote = true;
}
} else if input.as_bytes()[struct_ptr] == b'\"'
&& (struct_ptr == 0 || (input.as_bytes()[struct_ptr - 1] != b'\\'))
{
inside_quote = false;
}
struct_ptr += 1;
}
has_attachments = bodystructure_has_attachments(&input.as_bytes()[i..struct_ptr]);
i = struct_ptr;
} else if input[i..].starts_with(")\r\n") {
i += ")\r\n".len();
break;
} else {
debug!(
"Got unexpected token while parsing UID FETCH response:\n`{}`\n",
input
);
return debug!(Err(MeliError::new(format!(
"Got unexpected token while parsing UID FETCH response: `{:.40}`",
&input[i..]
))));
}
}
if let Some(env) = ret.envelope.as_mut() {
env.set_has_attachments(has_attachments);
}
Ok((&input[i..], ret))
}
pub fn uid_fetch_responses(mut input: &str) -> ImapParseResult<Vec<UidFetchResponse<'_>>> {
let mut ret = Vec::new();
while let Ok((rest, el)) = uid_fetch_response(input) {
input = rest;
ret.push(el);
}
if !input.is_empty() && ret.is_empty() {
return Err(MeliError::new(format!(
"310Unexpected input while parsing UID FETCH responses: `{:.40}`",
input
)));
}
Ok((input, ret))
}
/*
*
* "* 1 FETCH (FLAGS (\Seen) UID 1 RFC822.HEADER {5224} "
*/
named!(
pub uid_fetch_response<Vec<(usize, Option<(Flag, Vec<String>)>, &[u8])>>,
pub uid_fetch_response_<Vec<(usize, Option<(Flag, Vec<String>)>, &[u8])>>,
many0!(
do_parse!(
tag!("* ")
>> take_while!(call!(is_digit))
>> tag!(" FETCH (")
>> uid_flags: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), opt!(preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")")))))
>> is_not!("{")
>> body: length_bytes!(delimited!(tag!("{"), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }), tag!("}\r\n")))
>> result: permutation!(preceded!(ws!(tag!("UID ")), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) })), opt!(preceded!(ws!(tag!("FLAGS ")), delimited!(tag!("("), byte_flags, tag!(")")))),
ws!(length_bytes!(delimited!(tag!("{"), map_res!(digit, |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }), tag!("}\r\n")))))
>> tag!(")\r\n")
>> ((uid_flags.0, uid_flags.1, body))
>> ((result.0, result.1, result.2))
)
)
);

View File

@ -303,14 +303,14 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
conn.read_response(&mut response)
);
debug!(&response);
match protocol_parser::uid_fetch_response(response.as_bytes())
.to_full_result()
.map_err(MeliError::from)
{
Ok(v) => {
match protocol_parser::uid_fetch_responses(&response) {
Ok((_, v)) => {
let len = v.len();
let mut ctr = 0;
for (uid, flags, b) in v {
for UidFetchResponse {
uid, flags, body, ..
} in v
{
work_context
.set_status
.send((
@ -318,9 +318,10 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
format!("parsing {}/{} envelopes..", ctr, len),
))
.unwrap();
if let Ok(mut env) =
Envelope::from_bytes(&b, flags.as_ref().map(|&(f, _)| f))
{
if let Ok(mut env) = Envelope::from_bytes(
body.unwrap(),
flags.as_ref().map(|&(f, _)| f),
) {
ctr += 1;
uid_store
.hash_index
@ -412,14 +413,14 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
)
conn.read_response(&mut response)
);
match protocol_parser::uid_fetch_response(response.as_bytes())
.to_full_result()
.map_err(MeliError::from)
{
Ok(v) => {
match protocol_parser::uid_fetch_responses(&response) {
Ok((_, v)) => {
let len = v.len();
let mut ctr = 0;
for (uid, flags, b) in v {
for UidFetchResponse {
uid, flags, body, ..
} in v
{
work_context
.set_status
.send((
@ -431,9 +432,10 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
ctr += 1;
continue;
}
if let Ok(mut env) =
Envelope::from_bytes(&b, flags.as_ref().map(|&(f, _)| f))
{
if let Ok(mut env) = Envelope::from_bytes(
body.unwrap(),
flags.as_ref().map(|&(f, _)| f),
) {
ctr += 1;
uid_store
.hash_index
@ -587,14 +589,14 @@ fn examine_updates(
conn.read_response(&mut response)
);
debug!(&response);
match protocol_parser::uid_fetch_response(response.as_bytes())
.to_full_result()
.map_err(MeliError::from)
{
Ok(v) => {
for (uid, flags, b) in v {
match protocol_parser::uid_fetch_responses(&response) {
Ok((_, v)) => {
for UidFetchResponse {
uid, flags, body, ..
} in v
{
if let Ok(mut env) = Envelope::from_bytes(
&b,
body.unwrap(),
flags.as_ref().map(|&(f, _)| f),
) {
uid_store
@ -663,14 +665,14 @@ fn examine_updates(
)
conn.read_response(&mut response)
);
match protocol_parser::uid_fetch_response(response.as_bytes())
.to_full_result()
.map_err(MeliError::from)
{
Ok(v) => {
for (uid, flags, b) in v {
match protocol_parser::uid_fetch_responses(&response) {
Ok((_, v)) => {
for UidFetchResponse {
uid, flags, body, ..
} in v
{
if let Ok(mut env) =
Envelope::from_bytes(&b, flags.as_ref().map(|&(f, _)| f))
Envelope::from_bytes(body.unwrap(), flags.as_ref().map(|&(f, _)| f))
{
uid_store
.hash_index