Skip to content

Commit 55cd646

Browse files
authored
Support parsing STATUS responses. (v2) (#192)
Fixes #185.
1 parent 1055dd6 commit 55cd646

File tree

4 files changed

+85
-3
lines changed

4 files changed

+85
-3
lines changed

src/client.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,12 +1064,15 @@ impl<T: Read + Write> Session<T> {
10641064
mailbox_name: S1,
10651065
data_items: S2,
10661066
) -> Result<Mailbox> {
1067+
let mailbox_name = mailbox_name.as_ref();
10671068
self.run_command_and_read_response(&format!(
10681069
"STATUS {} {}",
1069-
validate_str(mailbox_name.as_ref())?,
1070+
validate_str(mailbox_name)?,
10701071
data_items.as_ref()
10711072
))
1072-
.and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
1073+
.and_then(|lines| {
1074+
parse_status(&lines[..], mailbox_name, &mut self.unsolicited_responses_tx)
1075+
})
10731076
}
10741077

10751078
/// This method returns a handle that lets you use the [`IDLE`

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ pub enum Error {
8484
/// or an unsolicited response that could not be converted into a local type in
8585
/// [`UnsolicitedResponse`](crate::types::UnsolicitedResponse).
8686
Unexpected(Response<'static>),
87+
/// In response to a STATUS command, the server sent OK without actually sending any STATUS
88+
/// responses first.
89+
MissingStatusResponse,
8790
}
8891

8992
impl From<IoError> for Error {
@@ -148,6 +151,7 @@ impl fmt::Display for Error {
148151
Error::ConnectionLost => f.write_str("Connection Lost"),
149152
Error::Append => f.write_str("Could not append mail to mailbox"),
150153
Error::Unexpected(ref r) => write!(f, "Unexpected Response: {:?}", r),
154+
Error::MissingStatusResponse => write!(f, "Missing STATUS Response"),
151155
}
152156
}
153157
}
@@ -170,6 +174,7 @@ impl StdError for Error {
170174
Error::ConnectionLost => "Connection lost",
171175
Error::Append => "Could not append mail to mailbox",
172176
Error::Unexpected(_) => "Unexpected Response",
177+
Error::MissingStatusResponse => "Missing STATUS Response",
173178
}
174179
}
175180

src/parse.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use imap_proto::{MailboxDatum, Response, ResponseCode};
1+
use imap_proto::{MailboxDatum, Response, ResponseCode, StatusAttribute};
22
use lazy_static::lazy_static;
33
use regex::Regex;
44
use std::collections::HashSet;
@@ -315,6 +315,53 @@ pub fn parse_mailbox(
315315
}
316316
}
317317

318+
pub fn parse_status(
319+
mut lines: &[u8],
320+
mailbox_name: &str,
321+
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
322+
) -> Result<Mailbox> {
323+
let mut mailbox = Mailbox::default();
324+
let mut got_anything = false;
325+
while !lines.is_empty() {
326+
match imap_proto::parser::parse_response(lines) {
327+
Ok((
328+
rest,
329+
Response::MailboxData(MailboxDatum::Status {
330+
mailbox: their_mailbox_name,
331+
status,
332+
}),
333+
)) if their_mailbox_name == mailbox_name => {
334+
lines = rest;
335+
got_anything = true;
336+
for attr in status {
337+
match attr {
338+
StatusAttribute::HighestModSeq(v) => mailbox.highest_mod_seq = Some(v),
339+
StatusAttribute::Messages(v) => mailbox.exists = v,
340+
StatusAttribute::Recent(v) => mailbox.recent = v,
341+
StatusAttribute::UidNext(v) => mailbox.uid_next = Some(v),
342+
StatusAttribute::UidValidity(v) => mailbox.uid_validity = Some(v),
343+
StatusAttribute::Unseen(v) => mailbox.unseen = Some(v),
344+
_ => {} // needed because StatusAttribute is #[non_exhaustive]
345+
}
346+
}
347+
}
348+
Ok((rest, data)) => {
349+
lines = rest;
350+
if let Some(resp) = try_handle_unilateral(data, unsolicited) {
351+
return Err(resp.into());
352+
}
353+
}
354+
_ => {
355+
return Err(Error::Parse(ParseError::Invalid(lines.to_vec())));
356+
}
357+
}
358+
}
359+
if !got_anything {
360+
return Err(Error::MissingStatusResponse);
361+
}
362+
Ok(mailbox)
363+
}
364+
318365
fn parse_ids_with<T: Extend<u32>>(
319366
lines: &[u8],
320367
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,

tests/imap_integration.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use lettre::Transport;
99
use std::net::TcpStream;
1010

1111
use crate::imap::extensions::sort::{SortCharset, SortCriterion};
12+
use crate::imap::types::Mailbox;
1213

1314
fn tls() -> native_tls::TlsConnector {
1415
native_tls::TlsConnector::builder()
@@ -448,3 +449,29 @@ fn append_with_flags_and_date() {
448449
let inbox = c.search("ALL").unwrap();
449450
assert_eq!(inbox.len(), 0);
450451
}
452+
453+
#[test]
454+
fn status() {
455+
let mut s = session("readonly-test@localhost");
456+
457+
// Test all valid fields except HIGHESTMODSEQ, which apparently
458+
// isn't supported by the IMAP server used for this test.
459+
let mb = s.status("INBOX", "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)").unwrap();
460+
assert_eq!(mb.flags, Vec::new());
461+
assert_eq!(mb.exists, 0);
462+
assert_eq!(mb.recent, 0);
463+
assert!(mb.unseen.is_some());
464+
assert_eq!(mb.permanent_flags, Vec::new());
465+
assert!(mb.uid_next.is_some());
466+
assert!(mb.uid_validity.is_some());
467+
assert_eq!(mb.highest_mod_seq, None);
468+
assert_eq!(mb.is_read_only, false);
469+
470+
// If we only request one field, we should only get one field
471+
// back. (A server could legally send an unsolicited STATUS
472+
// response, but this one won't.)
473+
let mb = s.status("INBOX", "(MESSAGES)").unwrap();
474+
let mut expected = Mailbox::default();
475+
expected.exists = 0;
476+
assert_eq!(mb, expected);
477+
}

0 commit comments

Comments
 (0)