From 9f38539814205dff7eb453357845435fd3df6e14 Mon Sep 17 00:00:00 2001 From: Psilon Date: Wed, 2 Sep 2026 15:51:00 +0300 Subject: [PATCH] fix: do not fail LOGIN command when receiving a NO response for another request Normally the response should have been read by the time login command is sent, so this should not happen. This can still happen if the future reading the response was dropped or if the server is violating the standard and e.g. duplicates responses. --- src/client.rs | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/client.rs b/src/client.rs index 1bd56d4..948ecdc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -214,26 +214,24 @@ impl Client { information, tag, } = res.parsed() + && *tag == id { ok_or_unauth_client_err!( self.check_status_ok(status, code.as_ref(), information.as_deref()), self ); - if *tag == id { - let capabilities = - if let Some(imap_proto::types::ResponseCode::Capabilities(capabilities)) = - code - { - use crate::types::{Capabilities, Capability}; - let capability_set: HashSet = - capabilities.iter().map(Capability::from).collect(); - Some(Capabilities(capability_set)) - } else { - None - }; - return Ok((Session::new(self.conn), capabilities)); - } + let capabilities = + if let Some(imap_proto::types::ResponseCode::Capabilities(capabilities)) = code + { + use crate::types::{Capabilities, Capability}; + let capability_set: HashSet = + capabilities.iter().map(Capability::from).collect(); + Some(Capabilities(capability_set)) + } else { + None + }; + return Ok((Session::new(self.conn), capabilities)); } } } @@ -1658,6 +1656,22 @@ mod tests { } } + #[cfg_attr(feature = "runtime-tokio", tokio::test)] + #[cfg_attr(feature = "runtime-async-std", async_std::test)] + async fn login_ignores_completion_for_other_command_tag() { + let response = b"A9999 NO Other command rejected\r\n\ + A0001 OK Logged in\r\n" + .to_vec(); + let client = mock_client!(MockStream::new(response)); + + let result = client.login("username", "password").await; + + assert!( + result.is_ok(), + "LOGIN must use only its matching completion" + ); + } + #[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg_attr(feature = "runtime-async-std", async_std::test)] async fn login_with_capabilities() {