Skip to content

Commit d642951

Browse files
committed
Handle BYE responses explicitly.
In Session::logout(), ignore it. Fixes #210.
1 parent d86d1e2 commit d642951

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

src/client.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::str;
88
use std::sync::mpsc;
99

1010
use super::authenticator::Authenticator;
11-
use super::error::{Bad, Error, No, ParseError, Result, ValidateError};
11+
use super::error::{Bad, Bye, Error, No, ParseError, Result, ValidateError};
1212
use super::extensions;
1313
use super::parse::*;
1414
use super::types::*;
@@ -609,7 +609,18 @@ impl<T: Read + Write> Session<T> {
609609

610610
/// Logout informs the server that the client is done with the connection.
611611
pub fn logout(&mut self) -> Result<()> {
612-
self.run_command_and_check_ok("LOGOUT")
612+
// Check for OK or BYE.
613+
// According to the RFC:
614+
// https://datatracker.ietf.org/doc/html/rfc3501#section-6.1.3
615+
// We should get an untagged BYE and a tagged OK.
616+
// Apparently some servers send a tagged BYE (imap.wp.pl #210)
617+
// instead, so we just treat it like OK since we are logging out
618+
// anyway and this avoids returning an error on logout.
619+
match self.run_command_and_check_ok("LOGOUT") {
620+
Ok(_) => Ok(()),
621+
Err(Error::Bye(_)) => Ok(()),
622+
resp => resp,
623+
}
613624
}
614625

615626
/// 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> {
13371348
)) => {
13381349
assert_eq!(tag.as_bytes(), match_tag.as_bytes());
13391350
Some(match status {
1340-
Status::Bad | Status::No => Err((
1351+
Status::Bad | Status::No | Status::Bye => Err((
13411352
status,
13421353
information.map(|v| v.into_owned()),
13431354
code.map(|v| v.into_owned()),
@@ -1376,6 +1387,13 @@ impl<T: Read + Write> Connection<T> {
13761387
.unwrap_or_else(|| "no explanation given".to_string()),
13771388
}));
13781389
}
1390+
Status::Bye => {
1391+
break Err(Error::Bye(Bye {
1392+
code,
1393+
information: expl
1394+
.unwrap_or_else(|| "no explanation given".to_string()),
1395+
}));
1396+
}
13791397
_ => break Err(Error::Parse(ParseError::Invalid(data.split_off(0)))),
13801398
}
13811399
}
@@ -1570,6 +1588,32 @@ mod tests {
15701588
);
15711589
}
15721590

1591+
#[test]
1592+
fn logout_with_untagged_bye() {
1593+
let response = b"* BYE Logging out\r\na1 OK Logout completed.\r\n".to_vec();
1594+
let command = format!("a1 LOGOUT\r\n");
1595+
let mock_stream = MockStream::new(response);
1596+
let mut session = mock_session!(mock_stream);
1597+
session.logout().unwrap();
1598+
assert!(
1599+
session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1600+
"Invalid logout command"
1601+
);
1602+
}
1603+
1604+
#[test]
1605+
fn logout_with_tagged_bye() {
1606+
let response = b"a1 BYE IMAP4rev1 Server logging out\r\n".to_vec();
1607+
let command = format!("a1 LOGOUT\r\n");
1608+
let mock_stream = MockStream::new(response);
1609+
let mut session = mock_session!(mock_stream);
1610+
session.logout().unwrap();
1611+
assert!(
1612+
session.stream.get_ref().written_buf == command.as_bytes().to_vec(),
1613+
"Invalid logout command"
1614+
);
1615+
}
1616+
15731617
#[test]
15741618
fn rename() {
15751619
let response = b"a1 OK RENAME completed\r\n".to_vec();

src/error.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ impl fmt::Display for No {
5252
}
5353
}
5454

55+
/// A BYE response from the server, which indicates it is going to hang up on us.
56+
#[derive(Debug)]
57+
#[non_exhaustive]
58+
pub struct Bye {
59+
/// Human-redable message included with the response.
60+
pub information: String,
61+
/// A more specific error status code included with the response.
62+
pub code: Option<ResponseCode<'static>>,
63+
}
64+
65+
impl fmt::Display for Bye {
66+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67+
write!(f, "{}", self.information)
68+
}
69+
}
5570
/// A set of errors that can occur in the IMAP client
5671
#[derive(Debug)]
5772
#[non_exhaustive]
@@ -71,6 +86,8 @@ pub enum Error {
7186
Bad(Bad),
7287
/// A NO response from the IMAP server.
7388
No(No),
89+
/// A BYE response from the IMAP server.
90+
Bye(Bye),
7491
/// The connection was terminated unexpectedly.
7592
ConnectionLost,
7693
/// Error parsing a server response.
@@ -148,6 +165,7 @@ impl fmt::Display for Error {
148165
Error::Parse(ref e) => fmt::Display::fmt(e, f),
149166
Error::No(ref data) => write!(f, "No Response: {}", data),
150167
Error::Bad(ref data) => write!(f, "Bad Response: {}", data),
168+
Error::Bye(ref data) => write!(f, "Bye Response: {}", data),
151169
Error::ConnectionLost => f.write_str("Connection Lost"),
152170
Error::Append => f.write_str("Could not append mail to mailbox"),
153171
Error::Unexpected(ref r) => write!(f, "Unexpected Response: {:?}", r),
@@ -171,6 +189,7 @@ impl StdError for Error {
171189
Error::Validate(ref e) => e.description(),
172190
Error::Bad(_) => "Bad Response",
173191
Error::No(_) => "No Response",
192+
Error::Bye(_) => "Bye Response",
174193
Error::ConnectionLost => "Connection lost",
175194
Error::Append => "Could not append mail to mailbox",
176195
Error::Unexpected(_) => "Unexpected Response",

0 commit comments

Comments
 (0)