Merge pull request #186 from mordak/idle-responses
Pass IDLE responses to caller.
This commit is contained in:
commit
382c025513
7 changed files with 463 additions and 209 deletions
|
|
@ -21,7 +21,7 @@ default = ["tls"]
|
||||||
native-tls = { version = "0.2.2", optional = true }
|
native-tls = { version = "0.2.2", optional = true }
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
bufstream = "0.1"
|
bufstream = "0.1"
|
||||||
imap-proto = "0.14.0"
|
imap-proto = "0.14.1"
|
||||||
nom = { version = "6.0", default-features = false }
|
nom = { version = "6.0", default-features = false }
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
chrono = { version = "0.4", default-features = false }
|
chrono = { version = "0.4", default-features = false }
|
||||||
|
|
@ -31,6 +31,7 @@ lazy_static = "1.4"
|
||||||
lettre = "0.9"
|
lettre = "0.9"
|
||||||
lettre_email = "0.9"
|
lettre_email = "0.9"
|
||||||
rustls-connector = "0.13.0"
|
rustls-connector = "0.13.0"
|
||||||
|
structopt = "0.3"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "basic"
|
name = "basic"
|
||||||
|
|
@ -40,6 +41,10 @@ required-features = ["default"]
|
||||||
name = "gmail_oauth2"
|
name = "gmail_oauth2"
|
||||||
required-features = ["default"]
|
required-features = ["default"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "idle"
|
||||||
|
required-features = ["default"]
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "imap_integration"
|
name = "imap_integration"
|
||||||
required-features = ["default"]
|
required-features = ["default"]
|
||||||
|
|
|
||||||
84
examples/idle.rs
Normal file
84
examples/idle.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
use native_tls::TlsConnector;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(name = "idle")]
|
||||||
|
struct Opt {
|
||||||
|
// The server name to connect to
|
||||||
|
#[structopt(short, long)]
|
||||||
|
server: String,
|
||||||
|
|
||||||
|
// The port to use
|
||||||
|
#[structopt(short, long, default_value = "993")]
|
||||||
|
port: u16,
|
||||||
|
|
||||||
|
// The account username
|
||||||
|
#[structopt(short, long)]
|
||||||
|
username: String,
|
||||||
|
|
||||||
|
// The account password. In a production system passwords
|
||||||
|
// would normally be in a config or fetched at runtime from
|
||||||
|
// a password manager or user prompt and not passed on the
|
||||||
|
// command line.
|
||||||
|
#[structopt(short = "w", long)]
|
||||||
|
password: String,
|
||||||
|
|
||||||
|
// The mailbox to IDLE on
|
||||||
|
#[structopt(short, long, default_value = "INBOX")]
|
||||||
|
mailbox: String,
|
||||||
|
|
||||||
|
#[structopt(
|
||||||
|
short = "x",
|
||||||
|
long,
|
||||||
|
help = "The number of responses to receive before exiting",
|
||||||
|
default_value = "5"
|
||||||
|
)]
|
||||||
|
max_responses: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
|
let ssl_conn = TlsConnector::builder().build().unwrap();
|
||||||
|
let client = imap::connect((opt.server.clone(), opt.port), opt.server, &ssl_conn)
|
||||||
|
.expect("Could not connect to imap server");
|
||||||
|
let mut imap = client
|
||||||
|
.login(opt.username, opt.password)
|
||||||
|
.expect("Could not authenticate");
|
||||||
|
|
||||||
|
// Turn on debug output so we can see the actual traffic coming
|
||||||
|
// from the server and how it is handled in our callback.
|
||||||
|
// This wouldn't be turned on in a production build, but is helpful
|
||||||
|
// in examples and for debugging.
|
||||||
|
imap.debug = true;
|
||||||
|
|
||||||
|
imap.select(opt.mailbox).expect("Could not select mailbox");
|
||||||
|
|
||||||
|
let idle = imap.idle().expect("Could not IDLE");
|
||||||
|
|
||||||
|
// Implement a trivial counter that causes the IDLE callback to end the IDLE
|
||||||
|
// after a fixed number of responses.
|
||||||
|
//
|
||||||
|
// A threaded client could use channels or shared data to interact with the
|
||||||
|
// rest of the program and update mailbox state, decide to exit the IDLE, etc.
|
||||||
|
let mut num_responses = 0;
|
||||||
|
let max_responses = opt.max_responses;
|
||||||
|
let idle_result = idle.wait_keepalive_while(|response| {
|
||||||
|
num_responses += 1;
|
||||||
|
println!("IDLE response #{}: {:?}", num_responses, response);
|
||||||
|
if num_responses >= max_responses {
|
||||||
|
// Stop IDLE
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
// Continue IDLE
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match idle_result {
|
||||||
|
Ok(()) => println!("IDLE finished normally"),
|
||||||
|
Err(e) => println!("IDLE finished with error {:?}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
imap.logout().expect("Could not log out");
|
||||||
|
}
|
||||||
12
src/error.rs
12
src/error.rs
|
|
@ -76,6 +76,10 @@ pub enum Error {
|
||||||
Validate(ValidateError),
|
Validate(ValidateError),
|
||||||
/// Error appending an e-mail.
|
/// Error appending an e-mail.
|
||||||
Append,
|
Append,
|
||||||
|
/// An unexpected response was received. This could be a response from a command,
|
||||||
|
/// or an unsolicited response that could not be converted into a local type in
|
||||||
|
/// [`UnsolicitedResponse`].
|
||||||
|
Unexpected(Response<'static>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<IoError> for Error {
|
impl From<IoError> for Error {
|
||||||
|
|
@ -112,7 +116,7 @@ impl From<TlsError> for Error {
|
||||||
|
|
||||||
impl<'a> From<Response<'a>> for Error {
|
impl<'a> From<Response<'a>> for Error {
|
||||||
fn from(err: Response<'a>) -> Error {
|
fn from(err: Response<'a>) -> Error {
|
||||||
Error::Parse(ParseError::Unexpected(format!("{:?}", err)))
|
Error::Unexpected(err.into_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,6 +134,7 @@ impl fmt::Display for Error {
|
||||||
Error::Bad(ref data) => write!(f, "Bad Response: {}", data),
|
Error::Bad(ref data) => write!(f, "Bad Response: {}", data),
|
||||||
Error::ConnectionLost => f.write_str("Connection Lost"),
|
Error::ConnectionLost => f.write_str("Connection Lost"),
|
||||||
Error::Append => f.write_str("Could not append mail to mailbox"),
|
Error::Append => f.write_str("Could not append mail to mailbox"),
|
||||||
|
Error::Unexpected(ref r) => write!(f, "Unexpected Response: {:?}", r),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +154,7 @@ impl StdError for Error {
|
||||||
Error::No(_) => "No Response",
|
Error::No(_) => "No Response",
|
||||||
Error::ConnectionLost => "Connection lost",
|
Error::ConnectionLost => "Connection lost",
|
||||||
Error::Append => "Could not append mail to mailbox",
|
Error::Append => "Could not append mail to mailbox",
|
||||||
|
Error::Unexpected(_) => "Unexpected Response",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,8 +176,6 @@ impl StdError for Error {
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
/// Indicates an error parsing the status response. Such as OK, NO, and BAD.
|
/// Indicates an error parsing the status response. Such as OK, NO, and BAD.
|
||||||
Invalid(Vec<u8>),
|
Invalid(Vec<u8>),
|
||||||
/// An unexpected response was encountered.
|
|
||||||
Unexpected(String),
|
|
||||||
/// The client could not find or decode the server's authentication challenge.
|
/// The client could not find or decode the server's authentication challenge.
|
||||||
Authentication(String, Option<DecodeError>),
|
Authentication(String, Option<DecodeError>),
|
||||||
/// The client received data that was not UTF-8 encoded.
|
/// The client received data that was not UTF-8 encoded.
|
||||||
|
|
@ -182,7 +186,6 @@ impl fmt::Display for ParseError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
ParseError::Invalid(_) => f.write_str("Unable to parse status response"),
|
ParseError::Invalid(_) => f.write_str("Unable to parse status response"),
|
||||||
ParseError::Unexpected(_) => f.write_str("Encountered unexpected parse response"),
|
|
||||||
ParseError::Authentication(_, _) => {
|
ParseError::Authentication(_, _) => {
|
||||||
f.write_str("Unable to parse authentication response")
|
f.write_str("Unable to parse authentication response")
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +198,6 @@ impl StdError for ParseError {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
ParseError::Invalid(_) => "Unable to parse status response",
|
ParseError::Invalid(_) => "Unable to parse status response",
|
||||||
ParseError::Unexpected(_) => "Encountered unexpected parsed response",
|
|
||||||
ParseError::Authentication(_, _) => "Unable to parse authentication response",
|
ParseError::Authentication(_, _) => "Unable to parse authentication response",
|
||||||
ParseError::DataNotUtf8(_, _) => "Unable to parse data as UTF-8 text",
|
ParseError::DataNotUtf8(_, _) => "Unable to parse data as UTF-8 text",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
use crate::client::Session;
|
use crate::client::Session;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
use crate::parse::parse_idle;
|
||||||
|
use crate::types::UnsolicitedResponse;
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use native_tls::TlsStream;
|
use native_tls::TlsStream;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
|
@ -13,13 +15,36 @@ use std::time::Duration;
|
||||||
///
|
///
|
||||||
/// The handle blocks using the [`IDLE` command](https://tools.ietf.org/html/rfc2177#section-3)
|
/// The handle blocks using the [`IDLE` command](https://tools.ietf.org/html/rfc2177#section-3)
|
||||||
/// specificed in [RFC 2177](https://tools.ietf.org/html/rfc2177) until the underlying server state
|
/// specificed in [RFC 2177](https://tools.ietf.org/html/rfc2177) until the underlying server state
|
||||||
/// changes in some way. While idling does inform the client what changes happened on the server,
|
/// changes in some way.
|
||||||
/// this implementation will currently just block until _anything_ changes, and then notify the
|
///
|
||||||
|
/// Each of the `wait` functions takes a callback function which receives any responses
|
||||||
|
/// that arrive on the channel while IDLE. The callback function implements whatever
|
||||||
|
/// logic is needed to handle the IDLE response, and then returns a boolean
|
||||||
|
/// to continue idling (`true`) or stop (`false`).
|
||||||
|
/// For users that want the IDLE to exit on any change (the behavior proior to version 3.0),
|
||||||
|
/// a convenience callback function [`stop_on_any`] is provided.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use native_tls::TlsConnector;
|
||||||
|
/// use imap::extensions::idle;
|
||||||
|
/// let ssl_conn = TlsConnector::builder().build().unwrap();
|
||||||
|
/// let client = imap::connect(("example.com", 993), "example.com", &ssl_conn)
|
||||||
|
/// .expect("Could not connect to imap server");
|
||||||
|
/// let mut imap = client.login("user@example.com", "password")
|
||||||
|
/// .expect("Could not authenticate");
|
||||||
|
/// imap.select("INBOX")
|
||||||
|
/// .expect("Could not select mailbox");
|
||||||
|
///
|
||||||
|
/// let idle = imap.idle().expect("Could not IDLE");
|
||||||
|
///
|
||||||
|
/// // Exit on any mailbox change
|
||||||
|
/// let result = idle.wait_keepalive_while(idle::stop_on_any);
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// Note that the server MAY consider a client inactive if it has an IDLE command running, and if
|
/// Note that the server MAY consider a client inactive if it has an IDLE command running, and if
|
||||||
/// such a server has an inactivity timeout it MAY log the client off implicitly at the end of its
|
/// such a server has an inactivity timeout it MAY log the client off implicitly at the end of its
|
||||||
/// timeout period. Because of that, clients using IDLE are advised to terminate the IDLE and
|
/// timeout period. Because of that, clients using IDLE are advised to terminate the IDLE and
|
||||||
/// re-issue it at least every 29 minutes to avoid being logged off. [`Handle::wait_keepalive`]
|
/// re-issue it at least every 29 minutes to avoid being logged off. [`Handle::wait_keepalive_while`]
|
||||||
/// does this. This still allows a client to receive immediate mailbox updates even though it need
|
/// does this. This still allows a client to receive immediate mailbox updates even though it need
|
||||||
/// only "poll" at half hour intervals.
|
/// only "poll" at half hour intervals.
|
||||||
///
|
///
|
||||||
|
|
@ -40,11 +65,16 @@ pub enum WaitOutcome {
|
||||||
MailboxChanged,
|
MailboxChanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A convenience function to always cause the IDLE handler to exit on any change.
|
||||||
|
pub fn stop_on_any(_response: UnsolicitedResponse) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Must be implemented for a transport in order for a `Session` using that transport to support
|
/// Must be implemented for a transport in order for a `Session` using that transport to support
|
||||||
/// operations with timeouts.
|
/// operations with timeouts.
|
||||||
///
|
///
|
||||||
/// Examples of where this is useful is for `Handle::wait_keepalive` and
|
/// Examples of where this is useful is for `Handle::wait_keepalive_while` and
|
||||||
/// `Handle::wait_timeout`.
|
/// `Handle::wait_timeout_while`.
|
||||||
pub trait SetReadTimeout {
|
pub trait SetReadTimeout {
|
||||||
/// Set the timeout for subsequent reads to the given one.
|
/// Set the timeout for subsequent reads to the given one.
|
||||||
///
|
///
|
||||||
|
|
@ -99,58 +129,102 @@ impl<'a, T: Read + Write + 'a> Handle<'a, T> {
|
||||||
|
|
||||||
/// Internal helper that doesn't consume self.
|
/// Internal helper that doesn't consume self.
|
||||||
///
|
///
|
||||||
/// This is necessary so that we can keep using the inner `Session` in `wait_keepalive`.
|
/// This is necessary so that we can keep using the inner `Session` in `wait_keepalive_while`.
|
||||||
fn wait_inner(&mut self, reconnect: bool) -> Result<WaitOutcome> {
|
fn wait_inner<F>(&mut self, reconnect: bool, mut callback: F) -> Result<WaitOutcome>
|
||||||
|
where
|
||||||
|
F: FnMut(UnsolicitedResponse) -> bool,
|
||||||
|
{
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
loop {
|
let result = loop {
|
||||||
let result = match self.session.readline(&mut v).map(|_| ()) {
|
match self.session.readline(&mut v) {
|
||||||
Err(Error::Io(ref e))
|
Err(Error::Io(ref e))
|
||||||
if e.kind() == io::ErrorKind::TimedOut
|
if e.kind() == io::ErrorKind::TimedOut
|
||||||
|| e.kind() == io::ErrorKind::WouldBlock =>
|
|| e.kind() == io::ErrorKind::WouldBlock =>
|
||||||
{
|
{
|
||||||
if reconnect {
|
break Ok(WaitOutcome::TimedOut);
|
||||||
self.terminate()?;
|
|
||||||
self.init()?;
|
|
||||||
return self.wait_inner(reconnect);
|
|
||||||
}
|
|
||||||
Ok(WaitOutcome::TimedOut)
|
|
||||||
}
|
}
|
||||||
Ok(()) => Ok(WaitOutcome::MailboxChanged),
|
Ok(_len) => {
|
||||||
Err(r) => Err(r),
|
// Handle Dovecot's imap_idle_notify_interval message
|
||||||
}?;
|
if v.eq_ignore_ascii_case(b"* OK Still here\r\n") {
|
||||||
|
v.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match parse_idle(&v) {
|
||||||
|
// Something went wrong parsing.
|
||||||
|
(_rest, Some(Err(r))) => break Err(r),
|
||||||
|
// Complete response. We expect rest to be empty.
|
||||||
|
(rest, Some(Ok(response))) => {
|
||||||
|
if !callback(response) {
|
||||||
|
break Ok(WaitOutcome::MailboxChanged);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle Dovecot's imap_idle_notify_interval message
|
// Assert on partial parse in debug builds - we expect
|
||||||
if v.eq_ignore_ascii_case(b"* OK Still here\r\n") {
|
// to always parse all or none of the input buffer.
|
||||||
v.clear();
|
// On release builds, we still do the right thing.
|
||||||
} else {
|
debug_assert!(
|
||||||
break Ok(result);
|
rest.is_empty(),
|
||||||
|
"Unexpected partial parse: input: {:?}, output: {:?}",
|
||||||
|
v,
|
||||||
|
rest,
|
||||||
|
);
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
v.clear();
|
||||||
|
} else {
|
||||||
|
let used = v.len() - rest.len();
|
||||||
|
v.drain(0..used);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Incomplete parse - do nothing and read more.
|
||||||
|
(_rest, None) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(r) => break Err(r),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reconnect on timeout if needed
|
||||||
|
match (reconnect, result) {
|
||||||
|
(true, Ok(WaitOutcome::TimedOut)) => {
|
||||||
|
self.terminate()?;
|
||||||
|
self.init()?;
|
||||||
|
self.wait_inner(reconnect, callback)
|
||||||
}
|
}
|
||||||
|
(_, result) => result,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until the selected mailbox changes.
|
/// Block until the given callback returns `false`, or until a response
|
||||||
pub fn wait(mut self) -> Result<()> {
|
/// arrives that is not explicitly handled by [`UnsolicitedResponse`].
|
||||||
self.wait_inner(true).map(|_| ())
|
pub fn wait_while<F>(mut self, callback: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(UnsolicitedResponse) -> bool,
|
||||||
|
{
|
||||||
|
self.wait_inner(true, callback).map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: SetReadTimeout + Read + Write + 'a> Handle<'a, T> {
|
impl<'a, T: SetReadTimeout + Read + Write + 'a> Handle<'a, T> {
|
||||||
/// Set the keep-alive interval to use when `wait_keepalive` is called.
|
/// Set the keep-alive interval to use when `wait_keepalive_while` is called.
|
||||||
///
|
///
|
||||||
/// The interval defaults to 29 minutes as dictated by RFC 2177.
|
/// The interval defaults to 29 minutes as dictated by RFC 2177.
|
||||||
pub fn set_keepalive(&mut self, interval: Duration) {
|
pub fn set_keepalive(&mut self, interval: Duration) {
|
||||||
self.keepalive = interval;
|
self.keepalive = interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until the selected mailbox changes.
|
/// Block until the given callback returns `false`, or until a response
|
||||||
|
/// arrives that is not explicitly handled by [`UnsolicitedResponse`].
|
||||||
///
|
///
|
||||||
/// This method differs from [`Handle::wait`] in that it will periodically refresh the IDLE
|
/// This method differs from [`Handle::wait_while`] in that it will periodically refresh the IDLE
|
||||||
/// connection, to prevent the server from timing out our connection. The keepalive interval is
|
/// connection, to prevent the server from timing out our connection. The keepalive interval is
|
||||||
/// set to 29 minutes by default, as dictated by RFC 2177, but can be changed using
|
/// set to 29 minutes by default, as dictated by RFC 2177, but can be changed using
|
||||||
/// [`Handle::set_keepalive`].
|
/// [`Handle::set_keepalive`].
|
||||||
///
|
///
|
||||||
/// This is the recommended method to use for waiting.
|
/// This is the recommended method to use for waiting.
|
||||||
pub fn wait_keepalive(self) -> Result<()> {
|
pub fn wait_keepalive_while<F>(self, callback: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(UnsolicitedResponse) -> bool,
|
||||||
|
{
|
||||||
// The server MAY consider a client inactive if it has an IDLE command
|
// The server MAY consider a client inactive if it has an IDLE command
|
||||||
// running, and if such a server has an inactivity timeout it MAY log
|
// running, and if such a server has an inactivity timeout it MAY log
|
||||||
// the client off implicitly at the end of its timeout period. Because
|
// the client off implicitly at the end of its timeout period. Because
|
||||||
|
|
@ -159,26 +233,33 @@ impl<'a, T: SetReadTimeout + Read + Write + 'a> Handle<'a, T> {
|
||||||
// This still allows a client to receive immediate mailbox updates even
|
// This still allows a client to receive immediate mailbox updates even
|
||||||
// though it need only "poll" at half hour intervals.
|
// though it need only "poll" at half hour intervals.
|
||||||
let keepalive = self.keepalive;
|
let keepalive = self.keepalive;
|
||||||
self.timed_wait(keepalive, true).map(|_| ())
|
self.timed_wait(keepalive, true, callback).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until the selected mailbox changes, or until the given amount of time has expired.
|
/// Block until the given given amount of time has elapsed, the given callback
|
||||||
#[deprecated(note = "use wait_with_timeout instead")]
|
/// returns `false`, or until a response arrives that is not explicitly handled
|
||||||
pub fn wait_timeout(self, timeout: Duration) -> Result<()> {
|
/// by [`UnsolicitedResponse`].
|
||||||
self.wait_with_timeout(timeout).map(|_| ())
|
pub fn wait_with_timeout_while<F>(self, timeout: Duration, callback: F) -> Result<WaitOutcome>
|
||||||
|
where
|
||||||
|
F: FnMut(UnsolicitedResponse) -> bool,
|
||||||
|
{
|
||||||
|
self.timed_wait(timeout, false, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until the selected mailbox changes, or until the given amount of time has expired.
|
fn timed_wait<F>(
|
||||||
pub fn wait_with_timeout(self, timeout: Duration) -> Result<WaitOutcome> {
|
mut self,
|
||||||
self.timed_wait(timeout, false)
|
timeout: Duration,
|
||||||
}
|
reconnect: bool,
|
||||||
|
callback: F,
|
||||||
fn timed_wait(mut self, timeout: Duration, reconnect: bool) -> Result<WaitOutcome> {
|
) -> Result<WaitOutcome>
|
||||||
|
where
|
||||||
|
F: FnMut(UnsolicitedResponse) -> bool,
|
||||||
|
{
|
||||||
self.session
|
self.session
|
||||||
.stream
|
.stream
|
||||||
.get_mut()
|
.get_mut()
|
||||||
.set_read_timeout(Some(timeout))?;
|
.set_read_timeout(Some(timeout))?;
|
||||||
let res = self.wait_inner(reconnect);
|
let res = self.wait_inner(reconnect, callback);
|
||||||
let _ = self.session.stream.get_mut().set_read_timeout(None).is_ok();
|
let _ = self.session.stream.get_mut().set_read_timeout(None).is_ok();
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
src/parse.rs
78
src/parse.rs
|
|
@ -2,6 +2,7 @@ use imap_proto::{MailboxDatum, Response, ResponseCode};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use super::error::{Error, ParseError, Result};
|
use super::error::{Error, ParseError, Result};
|
||||||
|
|
@ -105,12 +106,9 @@ pub fn parse_fetches(
|
||||||
|
|
||||||
// set some common fields eaglery
|
// set some common fields eaglery
|
||||||
for attr in &fetch.fetch {
|
for attr in &fetch.fetch {
|
||||||
use imap_proto::AttributeValue;
|
|
||||||
match attr {
|
match attr {
|
||||||
AttributeValue::Flags(flags) => {
|
AttributeValue::Flags(flags) => {
|
||||||
fetch
|
fetch.flags.extend(Flag::from_strs(flags));
|
||||||
.flags
|
|
||||||
.extend(flags.iter().map(|f| Flag::from(f.to_string())));
|
|
||||||
}
|
}
|
||||||
AttributeValue::Uid(uid) => fetch.uid = Some(*uid),
|
AttributeValue::Uid(uid) => fetch.uid = Some(*uid),
|
||||||
AttributeValue::Rfc822Size(sz) => fetch.size = Some(*sz),
|
AttributeValue::Rfc822Size(sz) => fetch.size = Some(*sz),
|
||||||
|
|
@ -270,9 +268,7 @@ pub fn parse_mailbox(
|
||||||
mailbox.unseen = Some(n);
|
mailbox.unseen = Some(n);
|
||||||
}
|
}
|
||||||
Some(ResponseCode::PermanentFlags(flags)) => {
|
Some(ResponseCode::PermanentFlags(flags)) => {
|
||||||
mailbox
|
mailbox.permanent_flags.extend(Flag::from_strs(flags));
|
||||||
.permanent_flags
|
|
||||||
.extend(flags.into_iter().map(String::from).map(Flag::from));
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -296,9 +292,7 @@ pub fn parse_mailbox(
|
||||||
mailbox.recent = r;
|
mailbox.recent = r;
|
||||||
}
|
}
|
||||||
MailboxDatum::Flags(flags) => {
|
MailboxDatum::Flags(flags) => {
|
||||||
mailbox
|
mailbox.flags.extend(Flag::from_strs(flags));
|
||||||
.flags
|
|
||||||
.extend(flags.into_iter().map(String::from).map(Flag::from));
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +344,21 @@ pub fn parse_ids(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a single unsolicited response from IDLE responses.
|
||||||
|
pub fn parse_idle(lines: &[u8]) -> (&[u8], Option<Result<UnsolicitedResponse>>) {
|
||||||
|
match imap_proto::parser::parse_response(lines) {
|
||||||
|
Ok((rest, response)) => match UnsolicitedResponse::try_from(response) {
|
||||||
|
Ok(unsolicited) => (rest, Some(Ok(unsolicited))),
|
||||||
|
Err(res) => (rest, Some(Err(res.into()))),
|
||||||
|
},
|
||||||
|
Err(nom::Err::Incomplete(_)) => (lines, None),
|
||||||
|
Err(_) => (
|
||||||
|
lines,
|
||||||
|
Some(Err(Error::Parse(ParseError::Invalid(lines.to_vec())))),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this is simply a unilateral server response (see Section 7 of RFC 3501).
|
// Check if this is simply a unilateral server response (see Section 7 of RFC 3501).
|
||||||
//
|
//
|
||||||
// Returns `None` if the response was handled, `Some(res)` if not.
|
// Returns `None` if the response was handled, `Some(res)` if not.
|
||||||
|
|
@ -357,52 +366,13 @@ pub(crate) fn try_handle_unilateral<'a>(
|
||||||
res: Response<'a>,
|
res: Response<'a>,
|
||||||
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
unsolicited: &mut mpsc::Sender<UnsolicitedResponse>,
|
||||||
) -> Option<Response<'a>> {
|
) -> Option<Response<'a>> {
|
||||||
match res {
|
match UnsolicitedResponse::try_from(res) {
|
||||||
Response::MailboxData(MailboxDatum::Status { mailbox, status }) => {
|
Ok(response) => {
|
||||||
unsolicited
|
unsolicited.send(response).ok();
|
||||||
.send(UnsolicitedResponse::Status {
|
None
|
||||||
mailbox: mailbox.into(),
|
|
||||||
attributes: status,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Response::MailboxData(MailboxDatum::Recent(n)) => {
|
|
||||||
unsolicited.send(UnsolicitedResponse::Recent(n)).unwrap();
|
|
||||||
}
|
|
||||||
Response::MailboxData(MailboxDatum::Flags(flags)) => {
|
|
||||||
unsolicited
|
|
||||||
.send(UnsolicitedResponse::Flags(
|
|
||||||
flags
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| Flag::from(s.to_string()))
|
|
||||||
.collect(),
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Response::MailboxData(MailboxDatum::Exists(n)) => {
|
|
||||||
unsolicited.send(UnsolicitedResponse::Exists(n)).unwrap();
|
|
||||||
}
|
|
||||||
Response::Expunge(n) => {
|
|
||||||
unsolicited.send(UnsolicitedResponse::Expunge(n)).unwrap();
|
|
||||||
}
|
|
||||||
Response::MailboxData(MailboxDatum::MetadataUnsolicited { mailbox, values }) => {
|
|
||||||
unsolicited
|
|
||||||
.send(UnsolicitedResponse::Metadata {
|
|
||||||
mailbox: mailbox.to_string(),
|
|
||||||
metadata_entries: values.iter().map(|s| s.to_string()).collect(),
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Response::Vanished { earlier, uids } => {
|
|
||||||
unsolicited
|
|
||||||
.send(UnsolicitedResponse::Vanished { earlier, uids })
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
res => {
|
|
||||||
return Some(res);
|
|
||||||
}
|
}
|
||||||
|
Err(unhandled) => Some(unhandled),
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
116
src/types/mod.rs
116
src/types/mod.rs
|
|
@ -168,6 +168,13 @@ impl Flag<'static> {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to transform Strings into owned Flags
|
||||||
|
pub fn from_strs<S: ToString>(
|
||||||
|
v: impl IntoIterator<Item = S>,
|
||||||
|
) -> impl Iterator<Item = Flag<'static>> {
|
||||||
|
v.into_iter().map(|s| Flag::from(s.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Display for Flag<'a> {
|
impl<'a> fmt::Display for Flag<'a> {
|
||||||
|
|
@ -220,113 +227,8 @@ pub use self::capabilities::Capabilities;
|
||||||
mod deleted;
|
mod deleted;
|
||||||
pub use self::deleted::Deleted;
|
pub use self::deleted::Deleted;
|
||||||
|
|
||||||
/// re-exported from imap_proto;
|
mod unsolicited_response;
|
||||||
pub use imap_proto::StatusAttribute;
|
pub use self::unsolicited_response::{AttributeValue, UnsolicitedResponse};
|
||||||
|
|
||||||
/// Responses that the server sends that are not related to the current command.
|
|
||||||
/// [RFC 3501](https://tools.ietf.org/html/rfc3501#section-7) states that clients need to be able
|
|
||||||
/// to accept any response at any time. These are the ones we've encountered in the wild.
|
|
||||||
///
|
|
||||||
/// Note that `Recent`, `Exists` and `Expunge` responses refer to the currently `SELECT`ed folder,
|
|
||||||
/// so the user must take care when interpreting these.
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum UnsolicitedResponse {
|
|
||||||
/// An unsolicited [`STATUS response`](https://tools.ietf.org/html/rfc3501#section-7.2.4).
|
|
||||||
Status {
|
|
||||||
/// The mailbox that this status response is for.
|
|
||||||
mailbox: String,
|
|
||||||
/// The attributes of this mailbox.
|
|
||||||
attributes: Vec<StatusAttribute>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// An unsolicited [`RECENT` response](https://tools.ietf.org/html/rfc3501#section-7.3.2)
|
|
||||||
/// indicating the number of messages with the `\Recent` flag set. This response occurs if the
|
|
||||||
/// size of the mailbox changes (e.g., new messages arrive).
|
|
||||||
///
|
|
||||||
/// > Note: It is not guaranteed that the message sequence
|
|
||||||
/// > numbers of recent messages will be a contiguous range of
|
|
||||||
/// > the highest n messages in the mailbox (where n is the
|
|
||||||
/// > value reported by the `RECENT` response). Examples of
|
|
||||||
/// > situations in which this is not the case are: multiple
|
|
||||||
/// > clients having the same mailbox open (the first session
|
|
||||||
/// > to be notified will see it as recent, others will
|
|
||||||
/// > probably see it as non-recent), and when the mailbox is
|
|
||||||
/// > re-ordered by a non-IMAP agent.
|
|
||||||
/// >
|
|
||||||
/// > The only reliable way to identify recent messages is to
|
|
||||||
/// > look at message flags to see which have the `\Recent` flag
|
|
||||||
/// > set, or to do a `SEARCH RECENT`.
|
|
||||||
Recent(u32),
|
|
||||||
|
|
||||||
/// An unsolicited [`EXISTS` response](https://tools.ietf.org/html/rfc3501#section-7.3.1) that
|
|
||||||
/// reports the number of messages in the mailbox. This response occurs if the size of the
|
|
||||||
/// mailbox changes (e.g., new messages arrive).
|
|
||||||
Exists(u32),
|
|
||||||
|
|
||||||
/// An unsolicited [`EXPUNGE` response](https://tools.ietf.org/html/rfc3501#section-7.4.1) that
|
|
||||||
/// reports that the specified message sequence number has been permanently removed from the
|
|
||||||
/// mailbox. The message sequence number for each successive message in the mailbox is
|
|
||||||
/// immediately decremented by 1, and this decrement is reflected in message sequence numbers
|
|
||||||
/// in subsequent responses (including other untagged `EXPUNGE` responses).
|
|
||||||
///
|
|
||||||
/// The EXPUNGE response also decrements the number of messages in the mailbox; it is not
|
|
||||||
/// necessary to send an `EXISTS` response with the new value.
|
|
||||||
///
|
|
||||||
/// As a result of the immediate decrement rule, message sequence numbers that appear in a set
|
|
||||||
/// of successive `EXPUNGE` responses depend upon whether the messages are removed starting
|
|
||||||
/// from lower numbers to higher numbers, or from higher numbers to lower numbers. For
|
|
||||||
/// example, if the last 5 messages in a 9-message mailbox are expunged, a "lower to higher"
|
|
||||||
/// server will send five untagged `EXPUNGE` responses for message sequence number 5, whereas a
|
|
||||||
/// "higher to lower server" will send successive untagged `EXPUNGE` responses for message
|
|
||||||
/// 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 METADATA response (https://tools.ietf.org/html/rfc5464#section-4.4.2)
|
|
||||||
/// that reports a change in a server or mailbox annotation.
|
|
||||||
Metadata {
|
|
||||||
/// Mailbox name for which annotations were changed.
|
|
||||||
mailbox: String,
|
|
||||||
/// List of annotations that were changed.
|
|
||||||
metadata_entries: Vec<String>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// 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>>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// An unsolicited [`FLAGS` response](https://tools.ietf.org/html/rfc3501#section-7.2.6) that
|
|
||||||
/// identifies the flags (at a minimum, the system-defined flags) that are applicable in the
|
|
||||||
/// mailbox. Flags other than the system flags can also exist, depending on server
|
|
||||||
/// implementation.
|
|
||||||
///
|
|
||||||
/// See [`Flag`] for details.
|
|
||||||
// TODO: the spec doesn't seem to say anything about when these may be received as unsolicited?
|
|
||||||
Flags(Vec<Flag<'static>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This type wraps an input stream and a type that was constructed by parsing that input stream,
|
/// This type wraps an input stream and a type that was constructed by parsing that input stream,
|
||||||
/// which allows the parsed type to refer to data in the underlying stream instead of copying it.
|
/// which allows the parsed type to refer to data in the underlying stream instead of copying it.
|
||||||
|
|
|
||||||
210
src/types/unsolicited_response.rs
Normal file
210
src/types/unsolicited_response.rs
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use super::{Flag, Seq};
|
||||||
|
|
||||||
|
/// re-exported from imap_proto;
|
||||||
|
pub use imap_proto::AttributeValue;
|
||||||
|
pub use imap_proto::ResponseCode;
|
||||||
|
pub use imap_proto::StatusAttribute;
|
||||||
|
use imap_proto::{MailboxDatum, Response, Status};
|
||||||
|
|
||||||
|
/// Responses that the server sends that are not related to the current command.
|
||||||
|
/// [RFC 3501](https://tools.ietf.org/html/rfc3501#section-7) states that clients need to be able
|
||||||
|
/// to accept any response at any time.
|
||||||
|
///
|
||||||
|
/// Not all possible responses are explicitly enumerated here because in practice only
|
||||||
|
/// some types of responses are delivered as unsolicited responses. If you encounter an
|
||||||
|
/// unsolicited response in the wild that is not handled here, please
|
||||||
|
/// [open an issue](https://github.com/jonhoo/rust-imap/issues) and let us know!
|
||||||
|
///
|
||||||
|
/// Note that `Recent`, `Exists` and `Expunge` responses refer to the currently `SELECT`ed folder,
|
||||||
|
/// so the user must take care when interpreting these.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum UnsolicitedResponse {
|
||||||
|
/// An unsolicited `BYE` response.
|
||||||
|
///
|
||||||
|
/// The `BYE` response may have an optional `ResponseCode` that provides additional
|
||||||
|
/// information, per [RFC3501](https://tools.ietf.org/html/rfc3501#section-7.1.5).
|
||||||
|
Bye {
|
||||||
|
/// Optional response code.
|
||||||
|
code: Option<ResponseCode<'static>>,
|
||||||
|
/// Information text that may be presented to the user.
|
||||||
|
information: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An unsolicited [`EXISTS` response](https://tools.ietf.org/html/rfc3501#section-7.3.1) that
|
||||||
|
/// reports the number of messages in the mailbox. This response occurs if the size of the
|
||||||
|
/// mailbox changes (e.g., new messages arrive).
|
||||||
|
Exists(u32),
|
||||||
|
|
||||||
|
/// An unsolicited [`EXPUNGE` response](https://tools.ietf.org/html/rfc3501#section-7.4.1) that
|
||||||
|
/// reports that the specified message sequence number has been permanently removed from the
|
||||||
|
/// mailbox. The message sequence number for each successive message in the mailbox is
|
||||||
|
/// immediately decremented by 1, and this decrement is reflected in message sequence numbers
|
||||||
|
/// in subsequent responses (including other untagged `EXPUNGE` responses).
|
||||||
|
///
|
||||||
|
/// The EXPUNGE response also decrements the number of messages in the mailbox; it is not
|
||||||
|
/// necessary to send an `EXISTS` response with the new value.
|
||||||
|
///
|
||||||
|
/// As a result of the immediate decrement rule, message sequence numbers that appear in a set
|
||||||
|
/// of successive `EXPUNGE` responses depend upon whether the messages are removed starting
|
||||||
|
/// from lower numbers to higher numbers, or from higher numbers to lower numbers. For
|
||||||
|
/// example, if the last 5 messages in a 9-message mailbox are expunged, a "lower to higher"
|
||||||
|
/// server will send five untagged `EXPUNGE` responses for message sequence number 5, whereas a
|
||||||
|
/// "higher to lower server" will send successive untagged `EXPUNGE` responses for message
|
||||||
|
/// 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 `FETCH` response.
|
||||||
|
///
|
||||||
|
/// The server may unilaterally send `FETCH` responses, as described in
|
||||||
|
/// [RFC3501](https://tools.ietf.org/html/rfc3501#section-7.4.2).
|
||||||
|
Fetch {
|
||||||
|
/// Message identifier.
|
||||||
|
id: u32,
|
||||||
|
/// Attribute values for this message.
|
||||||
|
attributes: Vec<AttributeValue<'static>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An unsolicited [`FLAGS` response](https://tools.ietf.org/html/rfc3501#section-7.2.6) that
|
||||||
|
/// identifies the flags (at a minimum, the system-defined flags) that are applicable in the
|
||||||
|
/// mailbox. Flags other than the system flags can also exist, depending on server
|
||||||
|
/// implementation.
|
||||||
|
///
|
||||||
|
/// See [`Flag`] for details.
|
||||||
|
// TODO: the spec doesn't seem to say anything about when these may be received as unsolicited?
|
||||||
|
Flags(Vec<Flag<'static>>),
|
||||||
|
|
||||||
|
/// An unsolicited METADATA response (https://tools.ietf.org/html/rfc5464#section-4.4.2)
|
||||||
|
/// that reports a change in a server or mailbox annotation.
|
||||||
|
Metadata {
|
||||||
|
/// Mailbox name for which annotations were changed.
|
||||||
|
mailbox: String,
|
||||||
|
/// List of annotations that were changed.
|
||||||
|
metadata_entries: Vec<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An unsolicited `OK` response.
|
||||||
|
///
|
||||||
|
/// The `OK` response may have an optional `ResponseCode` that provides additional
|
||||||
|
/// information, per [RFC3501](https://tools.ietf.org/html/rfc3501#section-7.1.1).
|
||||||
|
Ok {
|
||||||
|
/// Optional response code.
|
||||||
|
code: Option<ResponseCode<'static>>,
|
||||||
|
/// Information text that may be presented to the user.
|
||||||
|
information: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An unsolicited [`RECENT` response](https://tools.ietf.org/html/rfc3501#section-7.3.2)
|
||||||
|
/// indicating the number of messages with the `\Recent` flag set. This response occurs if the
|
||||||
|
/// size of the mailbox changes (e.g., new messages arrive).
|
||||||
|
///
|
||||||
|
/// > Note: It is not guaranteed that the message sequence
|
||||||
|
/// > numbers of recent messages will be a contiguous range of
|
||||||
|
/// > the highest n messages in the mailbox (where n is the
|
||||||
|
/// > value reported by the `RECENT` response). Examples of
|
||||||
|
/// > situations in which this is not the case are: multiple
|
||||||
|
/// > clients having the same mailbox open (the first session
|
||||||
|
/// > to be notified will see it as recent, others will
|
||||||
|
/// > probably see it as non-recent), and when the mailbox is
|
||||||
|
/// > re-ordered by a non-IMAP agent.
|
||||||
|
/// >
|
||||||
|
/// > The only reliable way to identify recent messages is to
|
||||||
|
/// > look at message flags to see which have the `\Recent` flag
|
||||||
|
/// > set, or to do a `SEARCH RECENT`.
|
||||||
|
Recent(u32),
|
||||||
|
|
||||||
|
/// An unsolicited [`STATUS response`](https://tools.ietf.org/html/rfc3501#section-7.2.4).
|
||||||
|
Status {
|
||||||
|
/// The mailbox that this status response is for.
|
||||||
|
mailbox: String,
|
||||||
|
/// The attributes of this mailbox.
|
||||||
|
attributes: Vec<StatusAttribute>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// 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>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to convert from a `imap_proto::Response`.
|
||||||
|
///
|
||||||
|
/// Not all `Response` variants are supported - only those which
|
||||||
|
/// are known or likely to be sent by a server as a unilateral response
|
||||||
|
/// during normal operations or during an IDLE session are implented.
|
||||||
|
///
|
||||||
|
/// If the conversion fails, the input `Reponse` is returned.
|
||||||
|
impl<'a> TryFrom<Response<'a>> for UnsolicitedResponse {
|
||||||
|
type Error = Response<'a>;
|
||||||
|
|
||||||
|
fn try_from(response: Response<'a>) -> Result<Self, Self::Error> {
|
||||||
|
match response {
|
||||||
|
Response::Data {
|
||||||
|
status: Status::Bye,
|
||||||
|
code,
|
||||||
|
information,
|
||||||
|
} => Ok(UnsolicitedResponse::Bye {
|
||||||
|
code: code.map(|c| c.into_owned()),
|
||||||
|
information: information.map(|s| s.to_string()),
|
||||||
|
}),
|
||||||
|
Response::Data {
|
||||||
|
status: Status::Ok,
|
||||||
|
code,
|
||||||
|
information,
|
||||||
|
} => Ok(UnsolicitedResponse::Ok {
|
||||||
|
code: code.map(|c| c.into_owned()),
|
||||||
|
information: information.map(|s| s.to_string()),
|
||||||
|
}),
|
||||||
|
Response::Expunge(n) => Ok(UnsolicitedResponse::Expunge(n)),
|
||||||
|
Response::Fetch(id, attributes) => Ok(UnsolicitedResponse::Fetch {
|
||||||
|
id,
|
||||||
|
attributes: attributes.into_iter().map(|a| a.into_owned()).collect(),
|
||||||
|
}),
|
||||||
|
Response::MailboxData(MailboxDatum::Exists(n)) => Ok(UnsolicitedResponse::Exists(n)),
|
||||||
|
Response::MailboxData(MailboxDatum::Flags(flags)) => {
|
||||||
|
Ok(UnsolicitedResponse::Flags(Flag::from_strs(flags).collect()))
|
||||||
|
}
|
||||||
|
Response::MailboxData(MailboxDatum::MetadataUnsolicited { mailbox, values }) => {
|
||||||
|
Ok(UnsolicitedResponse::Metadata {
|
||||||
|
mailbox: mailbox.to_string(),
|
||||||
|
metadata_entries: values.iter().map(|s| s.to_string()).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Response::MailboxData(MailboxDatum::Recent(n)) => Ok(UnsolicitedResponse::Recent(n)),
|
||||||
|
Response::MailboxData(MailboxDatum::Status { mailbox, status }) => {
|
||||||
|
Ok(UnsolicitedResponse::Status {
|
||||||
|
mailbox: mailbox.into(),
|
||||||
|
attributes: status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Response::Vanished { earlier, uids } => {
|
||||||
|
Ok(UnsolicitedResponse::Vanished { earlier, uids })
|
||||||
|
}
|
||||||
|
_ => Err(response),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue