(geronimo-mail) 04/05: GERONIMO-6904 - A garbled IMAP response leaves the connection poolable and poisons later commands
[email protected] Sat, 18 Jul 2026 19:12:30 +0000
| Newsgroups | gmane.comp.java.geronimo.cvs |
|---|---|
| Message-ID | <[email protected]> |
This is an automated email from the ASF dual-hosted git repository. rzo1 pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/geronimo-mail.git commit 2392bdffde8de3243f8f1a0468ddf43bd3894bff Author: Richard Zowalla <[email protected]> AuthorDate: Sat Jul 18 21:10:28 2026 +0200 GERONIMO-6904 - A garbled IMAP response leaves the connection poolable and poisons later commands --- .../mail/store/imap/connection/IMAPConnection.java | 13 +++- .../IMAPConnectionBrokenResponseTest.java | 86 ++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/store/imap/connection/IMAPConnection.java b/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/store/imap/connection/IMAPConnection.java index 1d1192f..cc0ae9f 100644 --- a/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/store/imap/connection/IMAPConnection.java +++ b/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/store/imap/connection/IMAPConnection.java @@ -399,7 +399,18 @@ public class IMAPConnection extends MailConnection { public IMAPTaggedResponse receiveResponse() throws MessagingException { while (true) { // read and parse a response from the server. - IMAPResponse response = reader.readResponse(); + IMAPResponse response; + try { + response = reader.readResponse(); + } catch (MessagingException e) { + // a read or parsing failure leaves the stream position somewhere in the + // middle of a response, so this connection can no longer be trusted to + // stay in sync with the server. Close it down and mark it so it is not + // returned to the connection pool, then let the failure propagate. + setClosed(); + closeServerConnection(); + throw e; + } // The response set is terminated by either a continuation response or a // tagged response (we only have a single command active at one time). if (response instanceof IMAPTaggedResponse) { diff --git a/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/test/java/org/apache/geronimo/mail/store/imap/connection/IMAPConnectionBrokenResponseTest.java b/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/test/java/org/apache/geronimo/mail/store/imap/connection/IMAPConnectionBrokenResponseTest.java new file mode 100644 index 0000000..1df014c --- /dev/null +++ b/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/test/java/org/apache/geronimo/mail/store/imap/connection/IMAPConnectionBrokenResponseTest.java @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.mail.store.imap.connection; + +import java.io.ByteArrayInputStream; +import java.util.Properties; + +import jakarta.mail.MessagingException; +import jakarta.mail.Session; + +import org.apache.geronimo.mail.util.ProtocolProperties; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Regression test for the broken-conn-containment defect: when + * readResponse() fails (garbled data or unexpected EOF), the stream position + * of the connection is undefined mid-response. Such a connection used to + * remain poolable, so one garbled exchange could poison a later, unrelated + * command. receiveResponse() must mark the connection closed (and shut the + * server connection down) before propagating the failure, so the pool + * discards it via the releaseClosedConnection path. + */ +public class IMAPConnectionBrokenResponseTest { + + private IMAPConnection connectionReading(final byte[] data) { + final ProtocolProperties props = new ProtocolProperties( + Session.getInstance(new Properties()), "imap", false, 143); + final IMAPConnection connection = new IMAPConnection(props, null); + // inject the response reader directly; the connection was never + // actually connected, so there is no socket to clean up. + connection.reader = new IMAPResponseStream(new ByteArrayInputStream(data)); + return connection; + } + + @Test + public void testGarbledResponseMarksConnectionClosed() throws Exception { + // an untagged response whose payload cannot be parsed ("* )" is the + // shape seen in the transient TCK flake) + final IMAPConnection connection = connectionReading("* )\r\n".getBytes("ISO8859-1")); + assertFalse(connection.isClosed(), "connection must start out poolable"); + + assertThrows(MessagingException.class, connection::receiveResponse, + "a garbled response must surface as a MessagingException"); + assertTrue(connection.isClosed(), + "a connection that failed mid-response must not be poolable"); + } + + @Test + public void testPrematureEndOfStreamMarksConnectionClosed() throws Exception { + // EOF in the middle of a response line + final IMAPConnection connection = connectionReading("* OK incomplete".getBytes("ISO8859-1")); + + assertThrows(MessagingException.class, connection::receiveResponse, + "an EOF mid-response must surface as a MessagingException"); + assertTrue(connection.isClosed(), + "a connection whose stream hit EOF mid-response must not be poolable"); + } + + @Test + public void testCleanResponseLeavesConnectionPoolable() throws Exception { + final IMAPConnection connection = connectionReading("a1 OK completed\r\n".getBytes("ISO8859-1")); + + final IMAPTaggedResponse response = connection.receiveResponse(); + assertTrue(response.isOK()); + assertFalse(connection.isClosed(), + "a successfully parsed exchange must leave the connection poolable"); + } +}