Support parsing STATUS responses. (v2) (#192)

Fixes #185.
This commit is contained in:
comex 2021-05-16 18:12:16 -04:00 committed by GitHub
parent 1055dd6e43
commit 55cd6465c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 3 deletions

View file

@ -1064,12 +1064,15 @@ impl<T: Read + Write> Session<T> {
mailbox_name: S1,
data_items: S2,
) -> Result<Mailbox> {
let mailbox_name = mailbox_name.as_ref();
self.run_command_and_read_response(&format!(
"STATUS {} {}",
validate_str(mailbox_name.as_ref())?,
validate_str(mailbox_name)?,
data_items.as_ref()
))
.and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
.and_then(|lines| {
parse_status(&lines[..], mailbox_name, &mut self.unsolicited_responses_tx)
})
}
/// This method returns a handle that lets you use the [`IDLE`

View file

@ -84,6 +84,9 @@ pub enum Error {
/// or an unsolicited response that could not be converted into a local type in
/// [`UnsolicitedResponse`](crate::types::UnsolicitedResponse).
Unexpected(Response<'static>),
/// In response to a STATUS command, the server sent OK without actually sending any STATUS
/// responses first.
MissingStatusResponse,
}
impl From<IoError> for Error {
@ -148,6 +151,7 @@ impl fmt::Display for Error {
Error::ConnectionLost => f.write_str("Connection Lost"),
Error::Append => f.write_str("Could not append mail to mailbox"),
Error::Unexpected(ref r) => write!(f, "Unexpected Response: {:?}", r),
Error::MissingStatusResponse => write!(f, "Missing STATUS Response"),
}
}
}
@ -170,6 +174,7 @@ impl StdError for Error {
Error::ConnectionLost => "Connection lost",
Error::Append => "Could not append mail to mailbox",
Error::Unexpected(_) => "Unexpected Response",
Error::MissingStatusResponse => "Missing STATUS Response",
}
}

View file

@ -1,4 +1,4 @@
use imap_proto::{MailboxDatum, Response, ResponseCode};
use imap_proto::{MailboxDatum, Response, ResponseCode, StatusAttribute};
use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashSet;
@ -315,6 +315,53 @@ pub fn parse_mailbox(
}
}
pub fn parse_status(
mut lines: &[u8],
mailbox_name: &str,
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
) -> Result<Mailbox> {
let mut mailbox = Mailbox::default();
let mut got_anything = false;
while !lines.is_empty() {
match imap_proto::parser::parse_response(lines) {
Ok((
rest,
Response::MailboxData(MailboxDatum::Status {
mailbox: their_mailbox_name,
status,
}),
)) if their_mailbox_name == mailbox_name => {
lines = rest;
got_anything = true;
for attr in status {
match attr {
StatusAttribute::HighestModSeq(v) => mailbox.highest_mod_seq = Some(v),
StatusAttribute::Messages(v) => mailbox.exists = v,
StatusAttribute::Recent(v) => mailbox.recent = v,
StatusAttribute::UidNext(v) => mailbox.uid_next = Some(v),
StatusAttribute::UidValidity(v) => mailbox.uid_validity = Some(v),
StatusAttribute::Unseen(v) => mailbox.unseen = Some(v),
_ => {} // needed because StatusAttribute is #[non_exhaustive]
}
}
}
Ok((rest, data)) => {
lines = rest;
if let Some(resp) = try_handle_unilateral(data, unsolicited) {
return Err(resp.into());
}
}
_ => {
return Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
}
}
}
if !got_anything {
return Err(Error::MissingStatusResponse);
}
Ok(mailbox)
}
fn parse_ids_with<T: Extend<u32>>(
lines: &[u8],
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,

View file

@ -9,6 +9,7 @@ use lettre::Transport;
use std::net::TcpStream;
use crate::imap::extensions::sort::{SortCharset, SortCriterion};
use crate::imap::types::Mailbox;
fn tls() -> native_tls::TlsConnector {
native_tls::TlsConnector::builder()
@ -448,3 +449,29 @@ fn append_with_flags_and_date() {
let inbox = c.search("ALL").unwrap();
assert_eq!(inbox.len(), 0);
}
#[test]
fn status() {
let mut s = session("readonly-test@localhost");
// Test all valid fields except HIGHESTMODSEQ, which apparently
// isn't supported by the IMAP server used for this test.
let mb = s.status("INBOX", "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)").unwrap();
assert_eq!(mb.flags, Vec::new());
assert_eq!(mb.exists, 0);
assert_eq!(mb.recent, 0);
assert!(mb.unseen.is_some());
assert_eq!(mb.permanent_flags, Vec::new());
assert!(mb.uid_next.is_some());
assert!(mb.uid_validity.is_some());
assert_eq!(mb.highest_mod_seq, None);
assert_eq!(mb.is_read_only, false);
// If we only request one field, we should only get one field
// back. (A server could legally send an unsolicited STATUS
// response, but this one won't.)
let mb = s.status("INBOX", "(MESSAGES)").unwrap();
let mut expected = Mailbox::default();
expected.exists = 0;
assert_eq!(mb, expected);
}