diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-12-11 00:05:41 +0200 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2019-12-11 00:05:41 +0200 |
commit | 504b658f687e9ddfcd4459a0913fbe2e5f4068d1 (patch) | |
tree | eb831ed3cca9a70952057e89c62eafa999112556 /melib/src | |
parent | 569127fac519da98cb08e6789e5d2344ec807d36 (diff) |
melib/imap: add UidFetchResponse struct and parser
Add handwritten parser for UID FETCH responses and use it for all UID
FETCH calls.
Diffstat (limited to 'melib/src')
-rw-r--r-- | melib/src/backends/imap.rs | 16 | ||||
-rw-r--r-- | melib/src/backends/imap/operations.rs | 33 | ||||
-rw-r--r-- | melib/src/backends/imap/protocol_parser.rs | 208 | ||||
-rw-r--r-- | melib/src/backends/imap/watch.rs | 66 |
4 files changed, 257 insertions, 66 deletions
diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index f9f77148..269c9e59 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -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()); diff --git a/melib/src/backends/imap/operations.rs b/melib/src/backends/imap/operations.rs index d3e8847d..0414c1aa 100644 --- a/melib/src/backends/imap/operations.rs +++ b/melib/src/backends/imap/operations.rs @@ -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(); } } diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index 77a9bac4..9fa8b3bd 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -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)) ) ) ); diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs index 7dcbc337..df631f21 100644 --- a/melib/src/backends/imap/watch.rs +++ b/melib/src/backends/imap/watch.rs @@ -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 |