summaryrefslogtreecommitdiffstats
path: root/melib/src
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-12-11 00:05:41 +0200
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-12-11 00:05:41 +0200
commit504b658f687e9ddfcd4459a0913fbe2e5f4068d1 (patch)
treeeb831ed3cca9a70952057e89c62eafa999112556 /melib/src
parent569127fac519da98cb08e6789e5d2344ec807d36 (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.rs16
-rw-r--r--melib/src/backends/imap/operations.rs33
-rw-r--r--melib/src/backends/imap/protocol_parser.rs208
-rw-r--r--melib/src/backends/imap/watch.rs66
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