Update imap-proto and nom dependencies.
Add support for HIGHESTMODSEQ (RFC 4551) and VANISHED (RFC 7162), which allows users to quickly synchronize to a mailbox by fetching only changes since the last known highest mod sequence.
This commit is contained in:
parent
b11b08954c
commit
c49e78b4d0
5 changed files with 111 additions and 13 deletions
|
|
@ -29,8 +29,8 @@ default = ["tls"]
|
|||
native-tls = { version = "0.2.2", optional = true }
|
||||
regex = "1.0"
|
||||
bufstream = "0.1"
|
||||
imap-proto = "0.10.0"
|
||||
nom = "5.0"
|
||||
imap-proto = "0.12.0"
|
||||
nom = "6.0"
|
||||
base64 = "0.12"
|
||||
chrono = "0.4"
|
||||
lazy_static = "1.4"
|
||||
|
|
|
|||
|
|
@ -1252,10 +1252,10 @@ impl<T: Read + Write> Connection<T> {
|
|||
};
|
||||
|
||||
let break_with = {
|
||||
use imap_proto::{parse_response, Response, Status};
|
||||
use imap_proto::{Response, Status};
|
||||
let line = &data[line_start..];
|
||||
|
||||
match parse_response(line) {
|
||||
match imap_proto::parser::parse_response(line) {
|
||||
Ok((
|
||||
_,
|
||||
Response::Done {
|
||||
|
|
@ -1606,6 +1606,7 @@ mod tests {
|
|||
permanent_flags: vec![],
|
||||
uid_next: Some(2),
|
||||
uid_validity: Some(1257842737),
|
||||
highest_mod_seq: None,
|
||||
};
|
||||
let mailbox_name = "INBOX";
|
||||
let command = format!("a1 EXAMINE {}\r\n", quote!(mailbox_name));
|
||||
|
|
@ -1652,6 +1653,7 @@ mod tests {
|
|||
],
|
||||
uid_next: Some(2),
|
||||
uid_validity: Some(1257842737),
|
||||
highest_mod_seq: None,
|
||||
};
|
||||
let mailbox_name = "INBOX";
|
||||
let command = format!("a1 SELECT {}\r\n", quote!(mailbox_name));
|
||||
|
|
|
|||
78
src/parse.rs
78
src/parse.rs
|
|
@ -44,7 +44,7 @@ where
|
|||
break Ok(things);
|
||||
}
|
||||
|
||||
match imap_proto::parse_response(lines) {
|
||||
match imap_proto::parser::parse_response(lines) {
|
||||
Ok((rest, resp)) => {
|
||||
lines = rest;
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ pub fn parse_capabilities(
|
|||
let f = |mut lines| {
|
||||
let mut caps = HashSet::new();
|
||||
loop {
|
||||
match imap_proto::parse_response(lines) {
|
||||
match imap_proto::parser::parse_response(lines) {
|
||||
Ok((rest, Response::Capabilities(c))) => {
|
||||
lines = rest;
|
||||
caps.extend(c);
|
||||
|
|
@ -179,7 +179,7 @@ pub fn parse_noop(
|
|||
break Ok(());
|
||||
}
|
||||
|
||||
match imap_proto::parse_response(lines) {
|
||||
match imap_proto::parser::parse_response(lines) {
|
||||
Ok((rest, data)) => {
|
||||
lines = rest;
|
||||
if let Some(resp) = handle_unilateral(data, unsolicited) {
|
||||
|
|
@ -200,7 +200,7 @@ pub fn parse_mailbox(
|
|||
let mut mailbox = Mailbox::default();
|
||||
|
||||
loop {
|
||||
match imap_proto::parse_response(lines) {
|
||||
match imap_proto::parser::parse_response(lines) {
|
||||
Ok((rest, Response::Data { status, code, .. })) => {
|
||||
lines = rest;
|
||||
|
||||
|
|
@ -212,6 +212,9 @@ pub fn parse_mailbox(
|
|||
|
||||
use imap_proto::ResponseCode;
|
||||
match code {
|
||||
Some(ResponseCode::HighestModSeq(seq)) => {
|
||||
mailbox.highest_mod_seq = Some(seq);
|
||||
}
|
||||
Some(ResponseCode::UidValidity(uid)) => {
|
||||
mailbox.uid_validity = Some(uid);
|
||||
}
|
||||
|
|
@ -254,7 +257,8 @@ pub fn parse_mailbox(
|
|||
}
|
||||
MailboxDatum::List { .. }
|
||||
| MailboxDatum::MetadataSolicited { .. }
|
||||
| MailboxDatum::MetadataUnsolicited { .. } => {}
|
||||
| MailboxDatum::MetadataUnsolicited { .. }
|
||||
| MailboxDatum::Search { .. } => {}
|
||||
}
|
||||
}
|
||||
Ok((rest, Response::Expunge(n))) => {
|
||||
|
|
@ -286,8 +290,8 @@ pub fn parse_ids(
|
|||
break Ok(ids);
|
||||
}
|
||||
|
||||
match imap_proto::parse_response(lines) {
|
||||
Ok((rest, Response::IDs(c))) => {
|
||||
match imap_proto::parser::parse_response(lines) {
|
||||
Ok((rest, Response::MailboxData(MailboxDatum::Search(c)))) => {
|
||||
lines = rest;
|
||||
ids.extend(c);
|
||||
}
|
||||
|
|
@ -331,6 +335,11 @@ fn handle_unilateral<'a>(
|
|||
Response::Expunge(n) => {
|
||||
unsolicited.send(UnsolicitedResponse::Expunge(n)).unwrap();
|
||||
}
|
||||
Response::Vanished { earlier, uids } => {
|
||||
unsolicited
|
||||
.send(UnsolicitedResponse::Vanished { earlier, uids })
|
||||
.unwrap();
|
||||
}
|
||||
res => {
|
||||
return Some(res);
|
||||
}
|
||||
|
|
@ -547,4 +556,59 @@ mod tests {
|
|||
let ids: HashSet<u32> = ids.iter().cloned().collect();
|
||||
assert_eq!(ids, HashSet::<u32>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_vanished_test() {
|
||||
// VANISHED can appear if the user has enabled QRESYNC (RFC 7162), in response to
|
||||
// SELECT/EXAMINE (QRESYNC); UID FETCH (VANISHED); or EXPUNGE commands. In the first
|
||||
// two cases the VANISHED respone will be a different type than the expected response
|
||||
// and so goes into the unsolicited respones channel. In the last case, VANISHED is
|
||||
// explicitly a response to an EXPUNGE command, but the semantics of EXPUNGE (one ID
|
||||
// per response, multiple responses) vs VANISHED (a sequence-set of UIDs in a single
|
||||
// response) are different enough that is isn't obvious what parse_expunge() should do.
|
||||
// If we do nothing special, then the VANISHED response ends up in the unsolicited
|
||||
// responses channel, which is at least consistent with the other cases where VANISHED
|
||||
// can show up.
|
||||
let lines = b"* VANISHED 3\r\n";
|
||||
let (mut send, recv) = mpsc::channel();
|
||||
let resp = parse_expunge(lines.to_vec(), &mut send).unwrap();
|
||||
assert!(resp.is_empty());
|
||||
|
||||
match recv.try_recv().unwrap() {
|
||||
UnsolicitedResponse::Vanished { earlier, uids } => {
|
||||
assert!(!earlier);
|
||||
assert_eq!(uids.len(), 1);
|
||||
assert_eq!(*uids[0].start(), 3);
|
||||
assert_eq!(*uids[0].end(), 3);
|
||||
}
|
||||
what => panic!("Unexpected response in unsolicited responses: {:?}", what),
|
||||
}
|
||||
assert!(recv.try_recv().is_err());
|
||||
|
||||
// test VANISHED mixed with FETCH
|
||||
let lines = b"* VANISHED (EARLIER) 3:8,12,50:60\r\n\
|
||||
* 49 FETCH (UID 117 FLAGS (\\Seen \\Answered) MODSEQ (90060115194045001))\r\n";
|
||||
|
||||
let fetches = parse_fetches(lines.to_vec(), &mut send).unwrap();
|
||||
match recv.try_recv().unwrap() {
|
||||
UnsolicitedResponse::Vanished { earlier, uids } => {
|
||||
assert!(earlier);
|
||||
assert_eq!(uids.len(), 3);
|
||||
assert_eq!(*uids[0].start(), 3);
|
||||
assert_eq!(*uids[0].end(), 8);
|
||||
assert_eq!(*uids[1].start(), 12);
|
||||
assert_eq!(*uids[1].end(), 12);
|
||||
assert_eq!(*uids[2].start(), 50);
|
||||
assert_eq!(*uids[2].end(), 60);
|
||||
}
|
||||
what => panic!("Unexpected response in unsolicited responses: {:?}", what),
|
||||
}
|
||||
assert!(recv.try_recv().is_err());
|
||||
assert_eq!(fetches.len(), 1);
|
||||
assert_eq!(fetches[0].message, 49);
|
||||
assert_eq!(fetches[0].flags(), &[Flag::Seen, Flag::Answered]);
|
||||
assert_eq!(fetches[0].uid, Some(117));
|
||||
assert_eq!(fetches[0].body(), None);
|
||||
assert_eq!(fetches[0].header(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ pub struct Mailbox {
|
|||
/// The unique identifier validity value. See [`Uid`] for more details. If this is missing,
|
||||
/// the server does not support unique identifiers.
|
||||
pub uid_validity: Option<u32>,
|
||||
|
||||
/// The highest mod sequence for this mailboxr. Used with
|
||||
/// [Conditional STORE](https://tools.ietf.org/html/rfc4551#section-3.1.1).
|
||||
pub highest_mod_seq: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for Mailbox {
|
||||
|
|
@ -47,6 +51,7 @@ impl Default for Mailbox {
|
|||
permanent_flags: Vec::new(),
|
||||
uid_next: None,
|
||||
uid_validity: None,
|
||||
highest_mod_seq: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,14 +61,15 @@ impl fmt::Display for Mailbox {
|
|||
write!(
|
||||
f,
|
||||
"flags: {:?}, exists: {}, recent: {}, unseen: {:?}, permanent_flags: {:?},\
|
||||
uid_next: {:?}, uid_validity: {:?}",
|
||||
uid_next: {:?}, uid_validity: {:?}, highest_mod_seq: {:?}",
|
||||
self.flags,
|
||||
self.exists,
|
||||
self.recent,
|
||||
self.unseen,
|
||||
self.permanent_flags,
|
||||
self.uid_next,
|
||||
self.uid_validity
|
||||
self.uid_validity,
|
||||
self.highest_mod_seq,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,6 +277,32 @@ pub enum UnsolicitedResponse {
|
|||
/// sequence numbers 9, 8, 7, 6, and 5.
|
||||
// TODO: the spec doesn't seem to say anything about when these may be received as unsolicited?
|
||||
Expunge(Seq),
|
||||
|
||||
/// An unsolicited [`VANISHED` response](https://tools.ietf.org/html/rfc7162#section-3.2.10)
|
||||
/// that reports a sequence-set of `UID`s that have been expunged from the mailbox.
|
||||
///
|
||||
/// The `VANISHED` response is similar to the `EXPUNGE` response and can be sent wherever
|
||||
/// an `EXPUNGE` response can be sent. It can only be sent by the server if the client
|
||||
/// has enabled [`QRESYNC`](https://tools.ietf.org/html/rfc7162).
|
||||
///
|
||||
/// The `VANISHED` response has two forms, one with the `EARLIER` tag which is used to
|
||||
/// respond to a `UID FETCH` or `SELECT/EXAMINE` command, and one without an `EARLIER`
|
||||
/// tag, which is used to announce removals within an already selected mailbox.
|
||||
///
|
||||
/// If using `QRESYNC`, the client can fetch new, updated and deleted `UID`s in a
|
||||
/// single round trip by including the `(CHANGEDSINCE <MODSEQ> VANISHED)`
|
||||
/// modifier to the `UID SEARCH` command, as described in
|
||||
/// [RFC7162](https://tools.ietf.org/html/rfc7162#section-3.1.4). For example
|
||||
/// `UID FETCH 1:* (UID FLAGS) (CHANGEDSINCE 1234 VANISHED)` would return `FETCH`
|
||||
/// results for all `UID`s added or modified since `MODSEQ` `1234`. Deleted `UID`s
|
||||
/// will be present as a `VANISHED` response in the `Session::unsolicited_responses`
|
||||
/// channel.
|
||||
Vanished {
|
||||
/// Whether the `EARLIER` tag was set on the response
|
||||
earlier: bool,
|
||||
/// The list of `UID`s which have been removed
|
||||
uids: Vec<std::ops::RangeInclusive<u32>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// This type wraps an input stream and a type that was constructed by parsing that input stream,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue