(geronimo-mail) 06/07: GERONIMO-6900 - Folder.close(false) loses \Deleted messages - use UNSELECT when the server supports RFC 3691

[email protected] Sat, 18 Jul 2026 18:49:05 +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 f1ceceedf80ea4633b04493d8792ac27096c4427
Author: Richard Zowalla <[email protected]>
AuthorDate: Sat Jul 18 20:48:28 2026 +0200

    GERONIMO-6900 - Folder.close(false) loses \Deleted messages - use UNSELECT when the server supports RFC 3691
---
 .../geronimo/mail/store/imap/IMAPFolder.java       | 22 +++++++++-----
 .../mail/store/imap/connection/IMAPConnection.java | 14 +++++++++
 .../mail/store/imap/IMAPTckRegressionTest.java     | 35 ++++++++++++++++++++++
 geronimo-mail_2.1_tck/src/tck/geronimo.jtx         |  7 -----
 4 files changed, 64 insertions(+), 14 deletions(-)

diff --git a/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/store/imap/IMAPFolder.java b/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/store/imap/IMAPFolder.java
index 1309793..a362a78 100644
--- a/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/store/imap/IMAPFolder.java
+++ b/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/main/java/org/apache/geronimo/mail/store/imap/IMAPFolder.java
@@ -737,15 +737,23 @@ public class IMAPFolder extends Folder implements UIDFolder, IMAPUntaggedRespons
                 // The CLOSE operation depends on what mode was used to select the mailbox.
                 // If we're open in READ-WRITE mode, we used a SELECT operation.  When CLOSE
                 // is issued, any deleted messages will be expunged.  If we've been asked not
-                // to expunge the messages, we have a problem.  The solution is to reselect the
-                // mailbox using EXAMINE, which will not expunge messages when closed.
-                if (mode == READ_WRITE && !expunge) {
-                    // we can ignore the result...we're just switching modes.
-                    currentConnection.openMailbox(fullname, true);
+                // to expunge the messages, we have a problem.  If the server supports the
+                // UNSELECT extension (RFC 3691), that deselects the mailbox without an
+                // implicit expunge.  Otherwise we fall back to reselecting the mailbox
+                // using EXAMINE before issuing CLOSE.  NB:  the fallback is not reliable
+                // on all servers (e.g., Apache James expunges on CLOSE even after EXAMINE).
+                if (mode == READ_WRITE && !expunge && currentConnection.hasCapability("UNSELECT")) {
+                    currentConnection.unselectMailbox();
                 }
+                else {
+                    if (mode == READ_WRITE && !expunge) {
+                        // we can ignore the result...we're just switching modes.
+                        currentConnection.openMailbox(fullname, true);
+                    }
 
-                // have this close the selected mailbox
-                currentConnection.closeMailbox();
+                    // have this close the selected mailbox
+                    currentConnection.closeMailbox();
+                }
             }
             currentConnection.removeResponseHandler(this);
             // we need to release the connection to the Store once we're closed
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 97deeba..9351115 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
@@ -459,6 +459,20 @@ public class IMAPConnection extends MailConnection {
     }
 
 
+    /**
+     * Deselect a mailbox without expunging any messages flagged
+     * \Deleted (RFC 3691).  Only usable when the server advertises
+     * the UNSELECT capability.
+     *
+     * @exception MessagingException
+     */
+    public void unselectMailbox() throws MessagingException {
+        // We can just send the command and generally ignore the
+        // status response.
+        sendCommand("UNSELECT");
+    }
+
+
     /**
      * Authenticate with the server, if necessary (or possible).
      *
diff --git a/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/test/java/org/apache/geronimo/mail/store/imap/IMAPTckRegressionTest.java b/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/test/java/org/apache/geronimo/mail/store/imap/IMAPTckRegressionTest.java
index 40ef672..b9bfc46 100644
--- a/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/test/java/org/apache/geronimo/mail/store/imap/IMAPTckRegressionTest.java
+++ b/geronimo-mail_2.1_impl/geronimo-mail_2.1_provider/src/test/java/org/apache/geronimo/mail/store/imap/IMAPTckRegressionTest.java
@@ -164,6 +164,41 @@ public class IMAPTckRegressionTest extends AbstractProtocolTest {
         }
     }
 
+    /**
+     * Folder#appendMessages_Test (unit 3): close(false) must not expunge
+     * messages flagged \Deleted.  The EXAMINE+CLOSE trick used for
+     * close-without-expunge does not work on James (it expunges on CLOSE
+     * anyway); the server advertises UNSELECT which must be used instead.
+     */
+    @Test
+    public void testCloseWithoutExpungeKeepsDeletedMessages() throws Exception {
+        start();
+        createMailboxWithMessage("test1");
+        server.appendToUserMailbox("test1", readMessageResource("/messages/simple.msg"));
+
+        final Store store = connect();
+        try {
+            final Folder folder = store.getDefaultFolder().getFolder("test1");
+            folder.open(Folder.READ_WRITE);
+            assertTrue(folder.getMessageCount() == 2, "test setup must provide two messages");
+            folder.getMessage(1).setFlag(Flags.Flag.DELETED, true);
+            folder.close(false);
+
+            final Folder reopened = store.getDefaultFolder().getFolder("test1");
+            reopened.open(Folder.READ_ONLY);
+            try {
+                assertTrue(reopened.getMessageCount() == 2,
+                        "close(false) must not expunge messages flagged \\Deleted");
+                assertTrue(reopened.getMessage(1).isSet(Flags.Flag.DELETED),
+                        "the \\Deleted flag must survive close(false)");
+            } finally {
+                reopened.close(false);
+            }
+        } finally {
+            store.close();
+        }
+    }
+
     /**
      * MimeMessage#getContentLanguage_Test: a message whose BODYSTRUCTURE
      * carries no language information used to trigger a NullPointerException;
diff --git a/geronimo-mail_2.1_tck/src/tck/geronimo.jtx b/geronimo-mail_2.1_tck/src/tck/geronimo.jtx
index 460fa6b..95b54cc 100644
--- a/geronimo-mail_2.1_tck/src/tck/geronimo.jtx
+++ b/geronimo-mail_2.1_tck/src/tck/geronimo.jtx
@@ -29,13 +29,6 @@
 SignatureTest.html
 
 
-# Folder.close(false) must not expunge: the client issues CLOSE, which per
-# RFC 3501 always expunges \Deleted messages; not expunging requires UNSELECT
-# (or dropping the selection another way). Surfaced once the APPEND deadlock
-# was fixed (appendMessages_Test unit 3 appends \Deleted-flagged messages and
-# expects them to survive close(false)).
-javasoft/sqe/tests/jakarta/mail/Folder/testlist.html#appendMessages_Test
-
 # Remaining baseline failures (284 passed / 31 failed after the
 # IMAPFolder.renameTo fix removed a ~90-test cascade). Distinct defects:
 # folder create/delete (topdog), permanent flags, list/listSubscribed,