From 72925cf1d81e0812375ce7a09ccb99b401078298 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Sat, 21 Jul 2018 12:29:43 -0400 Subject: [PATCH] Handle unilateral server responses Fixes #81. --- src/client.rs | 23 ++++++++++++++++++++++- src/parse.rs | 31 +++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/client.rs b/src/client.rs index 4384c42..7008735 100644 --- a/src/client.rs +++ b/src/client.rs @@ -298,6 +298,12 @@ impl Client { } /// Selects a mailbox + /// + /// Note that the server *is* allowed to unilaterally send things to the client for messages in + /// a selected mailbox whose status has changed. See the note on [unilateral server responses + /// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). This means that if you use + /// [`Client::run_command_and_read_response`], you *may* see additional untagged `RECENT`, + /// `EXISTS`, `FETCH`, and `EXPUNGE` responses! pub fn select(&mut self, mailbox_name: &str) -> Result { self.run_command_and_read_response(&format!("SELECT {}", validate_str(mailbox_name)?)) .and_then(|lines| parse_mailbox(&lines[..])) @@ -309,12 +315,21 @@ impl Client { .and_then(|lines| parse_mailbox(&lines[..])) } - /// Fetch retreives data associated with a message in the mailbox. + /// Fetch retreives data associated with a set of messages in the mailbox. + /// + /// Note that the server *is* allowed to unilaterally include `FETCH` responses for other + /// messages in the selected mailbox whose status has changed. See the note on [unilateral + /// server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). pub fn fetch(&mut self, sequence_set: &str, query: &str) -> ZeroCopyResult> { self.run_command_and_read_response(&format!("FETCH {} {}", sequence_set, query)) .and_then(|lines| parse_fetches(lines)) } + /// Fetch retreives data associated with a set of messages by UID in the mailbox. + /// + /// Note that the server *is* allowed to unilaterally include `FETCH` responses for other + /// messages in the selected mailbox whose status has changed. See the note on [unilateral + /// server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). pub fn uid_fetch(&mut self, uid_set: &str, query: &str) -> ZeroCopyResult> { self.run_command_and_read_response(&format!("UID FETCH {} {}", uid_set, query)) .and_then(|lines| parse_fetches(lines)) @@ -472,6 +487,12 @@ impl Client { self.write_line(command.into_bytes().as_slice()) } + /// Run a raw IMAP command and read back its response. + /// + /// Note that the server *is* allowed to unilaterally send things to the client for messages in + /// a selected mailbox whose status has changed. See the note on [unilateral server responses + /// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). This means that you *may* see + /// additional untagged `RECENT`, `EXISTS`, `FETCH`, and `EXPUNGE` responses! pub fn run_command_and_read_response(&mut self, untagged_command: &str) -> Result> { self.run_command(untagged_command)?; self.read_response() diff --git a/src/parse.rs b/src/parse.rs index 7eb0ceb..8e68dce 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,4 +1,4 @@ -use imap_proto::{self, Response}; +use imap_proto::{self, MailboxDatum, Response}; use nom::IResult; use regex::Regex; @@ -19,6 +19,8 @@ pub fn parse_authenticate_response(line: String) -> Result { enum MapOrNot { Map(T), Not(Response<'static>), + #[allow(dead_code)] + Ignore, } unsafe fn parse_many(lines: Vec, mut map: F) -> ZeroCopyResult> @@ -38,7 +40,20 @@ where match map(resp) { MapOrNot::Map(t) => things.push(t), - MapOrNot::Not(resp) => break Err(resp.into()), + MapOrNot::Not(resp) => { + // check if this is simply a unilateral server response + // (see Section 7 of RFC 3501): + match resp { + Response::MailboxData(MailboxDatum::Recent { .. }) + | Response::MailboxData(MailboxDatum::Exists { .. }) + | Response::Fetch(..) + | Response::Expunge(..) => { + continue; + } + resp => break Err(resp.into()), + } + } + MapOrNot::Ignore => continue, } } _ => { @@ -264,4 +279,16 @@ mod tests { assert_eq!(fetches[1].uid, None); assert_eq!(fetches[1].rfc822(), None); } + + #[test] + fn parse_fetches_w_unilateral() { + // https://github.com/mattnenterprise/rust-imap/issues/81 + let lines = b"\ + * 37 FETCH (UID 74)\r\n\ + * 1 RECENT\r\n"; + let fetches = parse_fetches(lines.to_vec()).unwrap(); + assert_eq!(fetches.len(), 1); + assert_eq!(fetches[0].message, 37); + assert_eq!(fetches[0].uid, Some(74)); + } }