Handle BYE responses explicitly.

In Session::logout(), ignore it. Fixes #210.
This commit is contained in:
Todd Mortimer 2021-07-25 21:21:07 -04:00
parent d86d1e228b
commit d6429512e8
2 changed files with 66 additions and 3 deletions

View file

@ -8,7 +8,7 @@ use std::str;
use std::sync::mpsc; use std::sync::mpsc;
use super::authenticator::Authenticator; 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::extensions;
use super::parse::*; use super::parse::*;
use super::types::*; use super::types::*;
@ -609,7 +609,18 @@ impl<T: Read + Write> Session<T> {
/// Logout informs the server that the client is done with the connection. /// Logout informs the server that the client is done with the connection.
pub fn logout(&mut self) -> Result<()> { 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 /// The [`CREATE` command](https://tools.ietf.org/html/rfc3501#section-6.3.3) creates a mailbox
@ -1337,7 +1348,7 @@ impl<T: Read + Write> Connection<T> {
)) => { )) => {
assert_eq!(tag.as_bytes(), match_tag.as_bytes()); assert_eq!(tag.as_bytes(), match_tag.as_bytes());
Some(match status { Some(match status {
Status::Bad | Status::No => Err(( Status::Bad | Status::No | Status::Bye => Err((
status, status,
information.map(|v| v.into_owned()), information.map(|v| v.into_owned()),
code.map(|v| v.into_owned()), code.map(|v| v.into_owned()),
@ -1376,6 +1387,13 @@ impl<T: Read + Write> Connection<T> {
.unwrap_or_else(|| "no explanation given".to_string()), .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)))), _ => 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] #[test]
fn rename() { fn rename() {
let response = b"a1 OK RENAME completed\r\n".to_vec(); let response = b"a1 OK RENAME completed\r\n".to_vec();

View file

@ -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<ResponseCode<'static>>,
}
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 /// A set of errors that can occur in the IMAP client
#[derive(Debug)] #[derive(Debug)]
#[non_exhaustive] #[non_exhaustive]
@ -71,6 +86,8 @@ pub enum Error {
Bad(Bad), Bad(Bad),
/// A NO response from the IMAP server. /// A NO response from the IMAP server.
No(No), No(No),
/// A BYE response from the IMAP server.
Bye(Bye),
/// The connection was terminated unexpectedly. /// The connection was terminated unexpectedly.
ConnectionLost, ConnectionLost,
/// Error parsing a server response. /// Error parsing a server response.
@ -148,6 +165,7 @@ impl fmt::Display for Error {
Error::Parse(ref e) => fmt::Display::fmt(e, f), Error::Parse(ref e) => fmt::Display::fmt(e, f),
Error::No(ref data) => write!(f, "No Response: {}", data), Error::No(ref data) => write!(f, "No Response: {}", data),
Error::Bad(ref data) => write!(f, "Bad 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::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), Error::Unexpected(ref r) => write!(f, "Unexpected Response: {:?}", r),
@ -171,6 +189,7 @@ impl StdError for Error {
Error::Validate(ref e) => e.description(), Error::Validate(ref e) => e.description(),
Error::Bad(_) => "Bad Response", Error::Bad(_) => "Bad Response",
Error::No(_) => "No Response", Error::No(_) => "No Response",
Error::Bye(_) => "Bye 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", Error::Unexpected(_) => "Unexpected Response",