Handle BYE responses explicitly.
In Session::logout(), ignore it. Fixes #210.
This commit is contained in:
parent
d86d1e228b
commit
d6429512e8
2 changed files with 66 additions and 3 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
19
src/error.rs
19
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<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",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue