From d6429512e880d2f692fb7ee9648d86535775cd2f Mon Sep 17 00:00:00 2001 From: Todd Mortimer Date: Sun, 25 Jul 2021 21:21:07 -0400 Subject: [PATCH] Handle BYE responses explicitly. In Session::logout(), ignore it. Fixes #210. --- src/client.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- src/error.rs | 19 +++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/client.rs b/src/client.rs index 2b1400e..c88f048 100644 --- a/src/client.rs +++ b/src/client.rs @@ -8,7 +8,7 @@ use std::str; use std::sync::mpsc; use super::authenticator::Authenticator; -use super::error::{Bad, Error, No, ParseError, Result, ValidateError}; +use super::error::{Bad, Bye, Error, No, ParseError, Result, ValidateError}; use super::extensions; use super::parse::*; use super::types::*; @@ -609,7 +609,18 @@ impl Session { /// Logout informs the server that the client is done with the connection. pub fn logout(&mut self) -> Result<()> { - self.run_command_and_check_ok("LOGOUT") + // Check for OK or BYE. + // According to the RFC: + // https://datatracker.ietf.org/doc/html/rfc3501#section-6.1.3 + // We should get an untagged BYE and a tagged OK. + // Apparently some servers send a tagged BYE (imap.wp.pl #210) + // instead, so we just treat it like OK since we are logging out + // anyway and this avoids returning an error on logout. + match self.run_command_and_check_ok("LOGOUT") { + Ok(_) => Ok(()), + Err(Error::Bye(_)) => Ok(()), + resp => resp, + } } /// The [`CREATE` command](https://tools.ietf.org/html/rfc3501#section-6.3.3) creates a mailbox @@ -1337,7 +1348,7 @@ impl Connection { )) => { assert_eq!(tag.as_bytes(), match_tag.as_bytes()); Some(match status { - Status::Bad | Status::No => Err(( + Status::Bad | Status::No | Status::Bye => Err(( status, information.map(|v| v.into_owned()), code.map(|v| v.into_owned()), @@ -1376,6 +1387,13 @@ impl Connection { .unwrap_or_else(|| "no explanation given".to_string()), })); } + Status::Bye => { + break Err(Error::Bye(Bye { + code, + information: expl + .unwrap_or_else(|| "no explanation given".to_string()), + })); + } _ => break Err(Error::Parse(ParseError::Invalid(data.split_off(0)))), } } @@ -1570,6 +1588,32 @@ mod tests { ); } + #[test] + fn logout_with_untagged_bye() { + let response = b"* BYE Logging out\r\na1 OK Logout completed.\r\n".to_vec(); + let command = format!("a1 LOGOUT\r\n"); + let mock_stream = MockStream::new(response); + let mut session = mock_session!(mock_stream); + session.logout().unwrap(); + assert!( + session.stream.get_ref().written_buf == command.as_bytes().to_vec(), + "Invalid logout command" + ); + } + + #[test] + fn logout_with_tagged_bye() { + let response = b"a1 BYE IMAP4rev1 Server logging out\r\n".to_vec(); + let command = format!("a1 LOGOUT\r\n"); + let mock_stream = MockStream::new(response); + let mut session = mock_session!(mock_stream); + session.logout().unwrap(); + assert!( + session.stream.get_ref().written_buf == command.as_bytes().to_vec(), + "Invalid logout command" + ); + } + #[test] fn rename() { let response = b"a1 OK RENAME completed\r\n".to_vec(); diff --git a/src/error.rs b/src/error.rs index 2dda68c..0ea316c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -52,6 +52,21 @@ impl fmt::Display for No { } } +/// A BYE response from the server, which indicates it is going to hang up on us. +#[derive(Debug)] +#[non_exhaustive] +pub struct Bye { + /// Human-redable message included with the response. + pub information: String, + /// A more specific error status code included with the response. + pub code: Option>, +} + +impl fmt::Display for Bye { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.information) + } +} /// A set of errors that can occur in the IMAP client #[derive(Debug)] #[non_exhaustive] @@ -71,6 +86,8 @@ pub enum Error { Bad(Bad), /// A NO response from the IMAP server. No(No), + /// A BYE response from the IMAP server. + Bye(Bye), /// The connection was terminated unexpectedly. ConnectionLost, /// Error parsing a server response. @@ -148,6 +165,7 @@ impl fmt::Display for Error { Error::Parse(ref e) => fmt::Display::fmt(e, f), Error::No(ref data) => write!(f, "No Response: {}", data), Error::Bad(ref data) => write!(f, "Bad Response: {}", data), + Error::Bye(ref data) => write!(f, "Bye Response: {}", data), 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), @@ -171,6 +189,7 @@ impl StdError for Error { Error::Validate(ref e) => e.description(), Error::Bad(_) => "Bad Response", Error::No(_) => "No Response", + Error::Bye(_) => "Bye Response", Error::ConnectionLost => "Connection lost", Error::Append => "Could not append mail to mailbox", Error::Unexpected(_) => "Unexpected Response",