diff --git a/src/client.rs b/src/client.rs index d9d705c..0bc5b29 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1064,12 +1064,15 @@ impl Session { mailbox_name: S1, data_items: S2, ) -> Result { + 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` diff --git a/src/error.rs b/src/error.rs index 4ed0ea1..16afbf5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 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", } } diff --git a/src/parse.rs b/src/parse.rs index 8a2a1cc..fc56674 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -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, +) -> Result { + 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>( lines: &[u8], unsolicited: &mut mpsc::Sender, diff --git a/tests/imap_integration.rs b/tests/imap_integration.rs index bd17a21..18292d3 100644 --- a/tests/imap_integration.rs +++ b/tests/imap_integration.rs @@ -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); +}