Handle unilateral server responses

Fixes #81.
This commit is contained in:
Jon Gjengset 2018-07-21 12:29:43 -04:00
parent 4c4fd89232
commit 72925cf1d8
No known key found for this signature in database
GPG key ID: D64AC9D67176DC71
2 changed files with 51 additions and 3 deletions

View file

@ -298,6 +298,12 @@ impl<T: Read + Write> Client<T> {
}
/// 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<Mailbox> {
self.run_command_and_read_response(&format!("SELECT {}", validate_str(mailbox_name)?))
.and_then(|lines| parse_mailbox(&lines[..]))
@ -309,12 +315,21 @@ impl<T: Read + Write> Client<T> {
.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<Vec<Fetch>> {
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<Vec<Fetch>> {
self.run_command_and_read_response(&format!("UID FETCH {} {}", uid_set, query))
.and_then(|lines| parse_fetches(lines))
@ -472,6 +487,12 @@ impl<T: Read + Write> Client<T> {
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<Vec<u8>> {
self.run_command(untagged_command)?;
self.read_response()

View file

@ -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<String> {
enum MapOrNot<T> {
Map(T),
Not(Response<'static>),
#[allow(dead_code)]
Ignore,
}
unsafe fn parse_many<T, F>(lines: Vec<u8>, mut map: F) -> ZeroCopyResult<Vec<T>>
@ -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));
}
}