You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/03/22 02:03:29 UTC

[james-project] branch master updated (acb10d3 -> b2260d9)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from acb10d3  JAMES-3715 netty-all dependency in protocols-imap4 is no longer needed for testing
     new 94365ff  [FIX] UidValidity generate fails for Long.MIN_VALUE
     new 6417a48  JAMES-3722 Tests for QRESYNC
     new 677c003  JAMES-3722 Don't mix highest modseq and highest modseq for which deletion occurs
     new 908d064  JAMES-3722 SELECT + QRESYNC cosmetic fix
     new f5fa4d4  JAMES-3722 Avoid sending empty VANISHED responses
     new 72596a0  JAMES-3722 Remove no longer used parameters in AbstractSelectionProcessor
     new 5ee4973  JAMES-3722 More tests for QRESYNC
     new c0ac4ca  JAMES-3722 IMAP FETCH parsing should support several modifiers
     new e0e4f90  JAMES-3722 IMAP SELECT should return CLOSED responses upon implicit selected mailbox changes
     new 63f93c4  JAMES-3722 Test IMAP CLOSE in regard of QRESYNC
     new 80ab117  JAMES-3722 Test IMAP SELECT optional parameters in regard of QRESYNC
     new 824e8f6  JAMES-3722 SELECT + QRESYNC did not comply with formal syntax
     new 3c4c46c  JAMES-3722 Tests for IMAP SELECT QRESYNC known sequences application
     new e33ef69  JAMES-3722 Fix for IMAP SELECT QRESYNC known sequences application
     new 7a72dca  JAMES-3722 Refactor range filtering
     new 179c8c7  JAMES-3722 IMAP SELECT QRESYNC remove TODO
     new 3bfdddc  JAMES-3722 IMAP SELECT QRESYNC remove TODO
     new 389310b  JAMES-3722 IMAP SELECT QRESYNC another small code extraction
     new a5356f8  JAMES-3722 Add a test: enable QRESYNC with a mailbox selected
     new 4d9962a  JAMES-3722 Manage SocketChannel in IMAP QRESYNC test with JUNIT
     new 0263fa7  JAMES-3722 Few tests for IMAP CONDSTORE RFC-4551
     new cea3297  JAMES-3722 SELECT do not supports CONDSTORE to be immediately followed by a ')'
     new 41421a5  JAMES-3722 CONDSTORE/QRESYNC tests fixes
     new b66294d  JAMES-3722 Parse an arbitrary number of IMAP FETCH modifiers
     new b2260d9  JAMES-3722 Apply some review comments

The 25 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/james/mailbox/model/UidValidity.java    |   8 +-
 .../james/mailbox/model/UidValidityTest.java       |   6 +
 .../james/imap/decode/ImapRequestLineReader.java   |   2 +-
 .../parser/AbstractSelectionCommandParser.java     |  20 +-
 .../imap/decode/parser/FetchCommandParser.java     |  40 +-
 .../imap/processor/AbstractMailboxProcessor.java   |  59 +-
 .../imap/processor/AbstractSelectionProcessor.java | 152 ++---
 .../james/imap/processor/fetch/FetchProcessor.java |   2 +-
 .../james/imapserver/netty/IMAPServerTest.java     | 760 +++++++++++++++++++++
 9 files changed, 887 insertions(+), 162 deletions(-)

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 20/25: JAMES-3722 Manage SocketChannel in IMAP QRESYNC test with JUNIT

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 4d9962ae7a7ebfaa7a31f5485937f8943af8db80
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Mar 9 17:13:00 2022 +0700

    JAMES-3722 Manage SocketChannel in IMAP QRESYNC test with JUNIT
---
 .../james/imapserver/netty/IMAPServerTest.java     | 328 ++++++++-------------
 1 file changed, 131 insertions(+), 197 deletions(-)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 058884e..0aa7f20 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1437,7 +1437,6 @@ class IMAPServerTest {
     @Nested
     class QResync {
         IMAPServer imapServer;
-        private int port;
         private MailboxSession mailboxSession;
         private MessageManager inbox;
         private SocketChannel clientConnection;
@@ -1445,7 +1444,7 @@ class IMAPServerTest {
         @BeforeEach
         void beforeEach() throws Exception {
             imapServer = createImapServer("imapServer.xml");
-            port = imapServer.getListenAddresses().get(0).getPort();
+            int port = imapServer.getListenAddresses().get(0).getPort();
             mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
             memoryIntegrationResources.getMailboxManager()
                 .createMailbox(MailboxPath.inbox(USER), mailboxSession);
@@ -1454,11 +1453,16 @@ class IMAPServerTest {
             clientConnection = SocketChannel.open();
             clientConnection.connect(new InetSocketAddress(LOCALHOST_IP, port));
             setUpTestingData();
+
+            clientConnection = SocketChannel.open();
+            clientConnection.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(clientConnection);
         }
 
         @AfterEach
-        void tearDown() {
+        void tearDown() throws Exception {
             imapServer.destroy();
+            clientConnection.close();
         }
 
         @Test
@@ -1470,17 +1474,13 @@ class IMAPServerTest {
                 .getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .getMailboxEntity().getUidValidity();
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d 88 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d 88 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("VANISHED"))
                 .isEmpty();
         }
@@ -1500,17 +1500,13 @@ class IMAPServerTest {
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .delete(ImmutableList.of(MessageUid.of(10)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
                 .hasSize(1);
         }
@@ -1530,17 +1526,13 @@ class IMAPServerTest {
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .delete(ImmutableList.of(MessageUid.of(10)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
                 .hasSize(1);
         }
@@ -1561,17 +1553,13 @@ class IMAPServerTest {
                 MessageUid.of(25), MessageUid.of(26),
                 MessageUid.of(32)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 5:11,28:36 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 5:11,28:36 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10:11,32"))
                 .hasSize(1);
         }
@@ -1592,21 +1580,17 @@ class IMAPServerTest {
                 MessageUid.of(25), MessageUid.of(26),
                 MessageUid.of(32)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
             // MSN 1 => UID 2 MATCH
             // MSN 13 => UID 17 MATCH
             // MSN 28 => UID 30 MISMATCH stored value is 34
             // Thus we know we can skip resynchronisation for UIDs up to 17
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 1:37 (1,13,28 2,17,30)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 1:37 (1,13,28 2,17,30)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 25:26,32"))
                 .hasSize(1);
         }
@@ -1627,21 +1611,17 @@ class IMAPServerTest {
                 MessageUid.of(25), MessageUid.of(26),
                 MessageUid.of(32)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
             // MSN 1 => UID 2 MATCH
             // MSN 13 => UID 17 MATCH
             // MSN 28 => UID 32 MISMATCH stored value is 34 (32 not being stored)
             // Thus we know we can skip resynchronisation for UIDs up to 17
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 1:37 (1,13,28 2,17,32)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 1:37 (1,13,28 2,17,32)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 25:26,32"))
                 .hasSize(1);
         }
@@ -1661,17 +1641,13 @@ class IMAPServerTest {
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .delete(ImmutableList.of(MessageUid.of(10)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
                 .hasSize(1);
         }
@@ -1691,17 +1667,13 @@ class IMAPServerTest {
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .delete(ImmutableList.of(MessageUid.of(10)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
                 .hasSize(1);
         }
@@ -1722,17 +1694,13 @@ class IMAPServerTest {
                     MessageUid.of(25), MessageUid.of(26),
                     MessageUid.of(32)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10:12,25:26,32"))
                 .hasSize(1);
         }
@@ -1753,18 +1721,14 @@ class IMAPServerTest {
                     MessageUid.of(25), MessageUid.of(26),
                     MessageUid.of(32)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap("I00104 SELECT INBOX\r\n".getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap("I00104 SELECT INBOX\r\n".getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed."));
 
-            server.write(ByteBuffer.wrap(("a2 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(("a2 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
 
-            List<String> replies = readStringUntil(server, s -> s.contains("a2 OK ENABLE completed."));
+            List<String> replies = readStringUntil(clientConnection, s -> s.contains("a2 OK ENABLE completed."));
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(replies)
                     .filteredOn(s -> s.contains("* OK [HIGHESTMODSEQ 41] Highest"))
@@ -1783,16 +1747,12 @@ class IMAPServerTest {
 
         @Test
         void fetchShouldAllowChangedSinceModifier() throws Exception {
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
@@ -1800,34 +1760,30 @@ class IMAPServerTest {
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
                 .getHighestModSeq();
-            server.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 1:37 (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 1:37 (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK FETCH completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK FETCH completed.")))
                 .filteredOn(s -> s.contains("* 10 FETCH (MODSEQ (39) FLAGS (\\Answered \\Recent) UID 10)"))
                 .hasSize(1);
         }
 
         @Test
         void fetchShouldNotReturnChangedItemsOutOfRange() throws Exception {
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             inbox.setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
                 .getHighestModSeq();
-            server.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 12:37 (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 12:37 (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK FETCH completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK FETCH completed.")))
                 .filteredOn(s -> s.contains("FLAGS")) // No FLAGS FETCH responses
                 .hasSize(1);
         }
@@ -1836,16 +1792,12 @@ class IMAPServerTest {
         void fetchShouldSupportVanishedModifiedWithEarlierTag() throws Exception {
             inbox.delete(ImmutableList.of(MessageUid.of(14)), mailboxSession);
 
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
@@ -1855,25 +1807,21 @@ class IMAPServerTest {
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
                 .getHighestModSeq();
-            server.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 12:37 (FLAGS) (CHANGEDSINCE %d VANISHED)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 12:37 (FLAGS) (CHANGEDSINCE %d VANISHED)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK FETCH completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK FETCH completed.")))
                 .filteredOn(s -> s.contains("* VANISHED (EARLIER) 14"))
                 .hasSize(1);
         }
 
         @Test
         void unsolicitedNotificationsShouldBeSent() throws Exception {
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
@@ -1885,25 +1833,21 @@ class IMAPServerTest {
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
                 .getHighestModSeq();
-            server.write(ByteBuffer.wrap(String.format("I00104 NOOP\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(String.format("I00104 NOOP\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK NOOP completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK NOOP completed.")))
                 .filteredOn(s -> s.contains("* VANISHED 14"))
                 .hasSize(1);
         }
 
         @Test
         void expungeShouldReturnVanishedWhenQResyncIsActive() throws Exception {
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
@@ -1918,25 +1862,21 @@ class IMAPServerTest {
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
 
-            server.write(ByteBuffer.wrap(("I00104 EXPUNGE\r\n").getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(("I00104 EXPUNGE\r\n").getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [HIGHESTMODSEQ 44] EXPUNGE completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [HIGHESTMODSEQ 44] EXPUNGE completed.")))
                 .filteredOn(s -> s.contains("* VANISHED 10:12,25:26,31"))
                 .hasSize(1);
         }
 
         @Test
         void uidExpungeShouldReturnExpungededWhenQResyncIsActive() throws Exception {
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
-
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
@@ -1951,9 +1891,9 @@ class IMAPServerTest {
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
 
-            server.write(ByteBuffer.wrap(("I00104 UID EXPUNGE 1:37\r\n").getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(("I00104 UID EXPUNGE 1:37\r\n").getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [HIGHESTMODSEQ 44] EXPUNGE completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK [HIGHESTMODSEQ 44] EXPUNGE completed.")))
                 .filteredOn(s -> s.contains("* VANISHED 10:12,25:26,31"))
                 .hasSize(1);
         }
@@ -1962,20 +1902,17 @@ class IMAPServerTest {
         void implicitMailboxSelectionChangesShouldReturnClosedNotifications() throws Exception {
             memoryIntegrationResources.getMailboxManager()
                 .createMailbox(MailboxPath.forUser(USER, "other"), mailboxSession);
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
 
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
-            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
-            server.write(ByteBuffer.wrap(("a3 SELECT other\r\n").getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(("a3 SELECT other\r\n").getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("a3 OK [READ-WRITE] SELECT completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("a3 OK [READ-WRITE] SELECT completed.")))
                 .filteredOn(s -> s.contains("* OK [CLOSED]"))
                 .hasSize(1);
         }
@@ -1985,21 +1922,18 @@ class IMAPServerTest {
             // See https://www.rfc-editor.org/errata_search.php?rfc=5162
             memoryIntegrationResources.getMailboxManager()
                 .createMailbox(MailboxPath.forUser(USER, "other"), mailboxSession);
-            SocketChannel server = SocketChannel.open();
-            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
-            readBytes(server);
 
-            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+            clientConnection.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a1 OK ENABLE completed."));
 
-            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
-            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
-            server.write(ByteBuffer.wrap(("a3 CLOSE\r\n").getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap(("a3 CLOSE\r\n").getBytes(StandardCharsets.UTF_8)));
 
-            assertThat(readStringUntil(server, s -> s.contains("a3 OK CLOSE completed.")))
+            assertThat(readStringUntil(clientConnection, s -> s.contains("a3 OK CLOSE completed.")))
                 .isNotNull();
         }
     }

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 16/25: JAMES-3722 IMAP SELECT QRESYNC remove TODO

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 179c8c700bb3755b210984936515f4db15c21076
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 15:01:56 2022 +0700

    JAMES-3722 IMAP SELECT QRESYNC remove TODO
    
    Known sequence trick already act in practice as a decent limitation for sent
    vanished responses
---
 .../org/apache/james/imap/processor/AbstractSelectionProcessor.java    | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index dae539c..07d0bc4 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -238,9 +238,6 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
             }
         }
 
-        // TODO: Reconsider if we can do something to make the handling better. Maybe at least cache the triplets for the expunged
-        //       while have the server running. This could maybe allow us to not return every expunged message all the time
-        //
         //      As we don't store the <<MSN, UID>, <MODSEQ>> in a permanent way its the best to just ignore it here.
         //
         //      From RFC5162 4.1. Server Implementations That Don't Store Extra State

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 19/25: JAMES-3722 Add a test: enable QRESYNC with a mailbox selected

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit a5356f892f6dadb6e1e6ea3d15293a44a2ac5747
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 15:17:36 2022 +0700

    JAMES-3722 Add a test: enable QRESYNC with a mailbox selected
---
 .../james/imapserver/netty/IMAPServerTest.java     | 42 ++++++++++++++++++++--
 1 file changed, 39 insertions(+), 3 deletions(-)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 5c0c424..058884e 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -39,9 +39,6 @@ import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.function.Predicate;
-import java.util.List;
-import java.util.Properties;
-import java.util.function.Predicate;
 import java.util.stream.IntStream;
 
 import javax.mail.FetchProfile;
@@ -92,6 +89,7 @@ import org.apache.james.server.core.configuration.Configuration;
 import org.apache.james.server.core.filesystem.FileSystemImpl;
 import org.apache.james.util.ClassLoaderUtils;
 import org.apache.james.utils.TestIMAPClient;
+import org.assertj.core.api.SoftAssertions;
 import org.awaitility.Awaitility;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -1739,6 +1737,44 @@ class IMAPServerTest {
                 .hasSize(1);
         }
 
+        @Test
+        void enableQRESYNCShouldReturnHighestModseq() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            inbox.delete(ImmutableList.of(MessageUid.of(10), MessageUid.of(11), MessageUid.of(12),
+                    MessageUid.of(25), MessageUid.of(26),
+                    MessageUid.of(32)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap("I00104 SELECT INBOX\r\n".getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed."));
+
+            server.write(ByteBuffer.wrap(("a2 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            List<String> replies = readStringUntil(server, s -> s.contains("a2 OK ENABLE completed."));
+            SoftAssertions.assertSoftly(softly -> {
+                softly.assertThat(replies)
+                    .filteredOn(s -> s.contains("* OK [HIGHESTMODSEQ 41] Highest"))
+                    .hasSize(1);
+                softly.assertThat(replies)
+                    .filteredOn(s -> s.contains("* ENABLED QRESYNC"))
+                    .hasSize(1);
+            });
+        }
+
         private void setUpTestingData() {
             IntStream.range(0, 37)
                 .forEach(Throwing.intConsumer(i -> inbox.appendMessage(MessageManager.AppendCommand.builder()

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 22/25: JAMES-3722 SELECT do not supports CONDSTORE to be immediately followed by a ')'

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit cea3297660a8f3282783febab504b95c43a1225d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Mar 10 09:08:14 2022 +0700

    JAMES-3722 SELECT do not supports CONDSTORE to be immediately followed by a ')'
    
    This behaviour did not comply with the example of RFC-4551 section 3.7
---
 .../apache/james/imap/decode/parser/AbstractSelectionCommandParser.java | 2 +-
 .../src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java | 2 --
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/AbstractSelectionCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/AbstractSelectionCommandParser.java
index d06f2f1..f73afb9 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/AbstractSelectionCommandParser.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/AbstractSelectionCommandParser.java
@@ -67,7 +67,7 @@ public abstract class AbstractSelectionCommandParser extends AbstractImapCommand
             switch (n) {
             case 'C':
                 // It starts with C so it should be CONDSTORE
-                request.consumeWord(StringMatcherCharacterValidator.ignoreCase(CONDSTORE));
+                request.consumeWord(StringMatcherCharacterValidator.ignoreCase(CONDSTORE), true);
                 condstore = true;
                 break;
             case 'Q':
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 32f889c..89d40d3 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1569,7 +1569,6 @@ class IMAPServerTest {
                 .hasSize(1);
         }
 
-        @Disabled("JAMES-3722 SELECT do not supports CONDSTORE to be immediately followed by a ')'")
         @Test
         void selectShouldAcceptCondstore() throws Exception {
             clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
@@ -1581,7 +1580,6 @@ class IMAPServerTest {
                 .isNotNull();
         }
 
-        @Disabled("JAMES-3722 SELECT do not supports CONDSTORE to be immediately followed by a ')'")
         @Test
         void selectShouldEnableCondstore() throws Exception {
             clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 03/25: JAMES-3722 Don't mix highest modseq and highest modseq for which deletion occurs

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 677c0035b78b6eefe8fa460cf561f89a07543a2e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Mar 7 22:33:12 2022 +0700

    JAMES-3722 Don't mix highest modseq and highest modseq for which deletion occurs
    
    We did take the latests modseq as the 'latest modseq for which a deletion occured'
    which is not stored, thus we never return deletions unless there are new messages
    / flags changes  causing the modseq to change
---
 .../imap/processor/AbstractMailboxProcessor.java   | 65 ++++++++--------------
 .../imap/processor/AbstractSelectionProcessor.java |  2 +-
 .../james/imap/processor/fetch/FetchProcessor.java |  2 +-
 .../james/imapserver/netty/IMAPServerTest.java     |  5 +-
 4 files changed, 27 insertions(+), 47 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
index 786a7e7..ae3b12c 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
@@ -534,49 +534,32 @@ public abstract class AbstractMailboxProcessor<R extends ImapRequest> extends Ab
     /**
      * Send VANISHED responses if needed. 
      */
-    protected void respondVanished(SelectedMailbox selectedMailbox, List<MessageRange> ranges, long changedSince, MailboxMetaData metaData, Responder responder) throws MailboxException {
-        // RFC5162 4.2. Server Implementations Storing Minimal State
-        //  
-        //      A server that stores the HIGHESTMODSEQ value at the time of the last
-        //      EXPUNGE can omit the VANISHED response when a client provides a
-        //      MODSEQ value that is equal to, or higher than, the current value of
-        //      this datum, that is, when there have been no EXPUNGEs.
-        //
-        //      A client providing message sequence match data can reduce the scope
-        //      as above.  In the case where there have been no expunges, the server
-        //      can ignore this data.
-        if (metaData.getHighestModSeq().asLong() > changedSince) {
-            SearchQuery.Builder searchQuery = SearchQuery.builder();
-            SearchQuery.UidRange[] nRanges = new SearchQuery.UidRange[ranges.size()];
-            Set<MessageUid> vanishedUids = new HashSet<>();
-            for (int i = 0; i < ranges.size(); i++) {
-                MessageRange r = ranges.get(i);
-                SearchQuery.UidRange nr;
-                if (r.getType() == Type.ONE) {
-                    nr = new SearchQuery.UidRange(r.getUidFrom());
-                } else {
-                    nr = new SearchQuery.UidRange(r.getUidFrom(), r.getUidTo());
-                }
-                MessageUid from = nr.getLowValue();
-                MessageUid to = nr.getHighValue();
-                while (from.compareTo(to) <= 0) {
-                    MessageUid copy = from;
-                    selectedMailbox.msn(from).fold(
-                        () -> vanishedUids.add(copy),
-                        msn -> {
-                            // ignore still there
-                            return true;
-                        });
-                    from = from.next();
-                }
-                nRanges[i] = nr;
-
+    protected void respondVanished(SelectedMailbox selectedMailbox, List<MessageRange> ranges, Responder responder) throws MailboxException {
+        Set<MessageUid> vanishedUids = new HashSet<>();
+        for (int i = 0; i < ranges.size(); i++) {
+            MessageRange r = ranges.get(i);
+            SearchQuery.UidRange nr;
+            if (r.getType() == Type.ONE) {
+                nr = new SearchQuery.UidRange(r.getUidFrom());
+            } else {
+                nr = new SearchQuery.UidRange(r.getUidFrom(), r.getUidTo());
             }
-            UidRange[] vanishedIdRanges = uidRanges(MessageRange.toRanges(vanishedUids));
-            responder.respond(new VanishedResponse(vanishedIdRanges, true));
+            MessageUid from = nr.getLowValue();
+            MessageUid to = nr.getHighValue();
+            while (from.compareTo(to) <= 0) {
+                MessageUid copy = from;
+                selectedMailbox.msn(from).fold(
+                    () -> vanishedUids.add(copy),
+                    msn -> {
+                        // ignore still there
+                        return true;
+                    });
+                from = from.next();
+            }
+
         }
-        
-        
+        UidRange[] vanishedIdRanges = uidRanges(MessageRange.toRanges(vanishedUids));
+        responder.respond(new VanishedResponse(vanishedIdRanges, true));
     }
     
     
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index 2fa0ddd..051755c 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -261,7 +261,7 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
         //          expunges have not happened, or happen only toward the end of the
         //          mailbox.
         //
-        respondVanished(selected, ranges, modSeq, metaData, responder);
+        respondVanished(selected, ranges, responder);
     }
 
     @VisibleForTesting
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/fetch/FetchProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/fetch/FetchProcessor.java
index 74b02cc..8255b65 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/fetch/FetchProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/fetch/FetchProcessor.java
@@ -112,7 +112,7 @@ public class FetchProcessor extends AbstractMailboxProcessor<FetchRequest> {
             if (vanished) {
                 // TODO: From the QRESYNC RFC it seems ok to send the VANISHED responses after the FETCH Responses. 
                 //       If we do so we could prolly save one mailbox access which should give use some more speed up
-                respondVanished(session.getSelected(), ranges, changedSince, metaData.get(), responder);
+                respondVanished(session.getSelected(), ranges, responder);
             }
             processMessageRanges(session, mailbox, ranges, fetch, useUids, mailboxSession, responder);
 
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index df20e64..4799857 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1497,11 +1497,8 @@ class IMAPServerTest {
                 .isEmpty();
         }
 
-        @Disabled("JAMES-3722 We currently take the latests modseq as the 'latest modseq for which a deletion occured'" +
-            " which is not currently stored, thus we never return deletions unless there are new messages / flags changes" +
-            " causing the modseq to change")
         @Test
-        void selectShouldReturnDeletedMessagesWhenNoSubsequentModifucation() throws Exception {
+        void selectShouldReturnDeletedMessagesWhenNoSubsequentModification() throws Exception {
             MailboxSession mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
             memoryIntegrationResources.getMailboxManager()
                 .createMailbox(MailboxPath.inbox(USER), mailboxSession);

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 05/25: JAMES-3722 Avoid sending empty VANISHED responses

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit f5fa4d40b97827d280d00062b09a5c999e87c0d3
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Mar 7 22:39:09 2022 +0700

    JAMES-3722 Avoid sending empty VANISHED responses
    
    We currently return empty vanished response whose syntax is invalid.
    
    We don't match RFC-3501 sequence-set formal syntax that guaranty at least one element
---
 .../org/apache/james/imap/processor/AbstractMailboxProcessor.java     | 4 +++-
 .../test/java/org/apache/james/imapserver/netty/IMAPServerTest.java   | 2 --
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
index 919445f..3d1c892 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
@@ -551,7 +551,9 @@ public abstract class AbstractMailboxProcessor<R extends ImapRequest> extends Ab
 
         }
         UidRange[] vanishedIdRanges = uidRanges(MessageRange.toRanges(vanishedUids));
-        responder.respond(new VanishedResponse(vanishedIdRanges, true));
+        if (vanishedIdRanges.length > 0) {
+            responder.respond(new VanishedResponse(vanishedIdRanges, true));
+        }
     }
     
     
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 4799857..b568b38 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1461,8 +1461,6 @@ class IMAPServerTest {
             imapServer.destroy();
         }
 
-        @Disabled("JAMES-3722 We currently return empty vanished response whose syntax is invalid." +
-            "We don't match RFC-3501 sequence-set formal syntax that guaranty at least one element")
         @Test
         void selectShouldNotAnswerEmptyVanishedResponses() throws Exception {
             MailboxSession mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 21/25: JAMES-3722 Few tests for IMAP CONDSTORE RFC-4551

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 0263fa7795f1ae76e14bc19316e3ebb5c7cf4e0d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Mar 10 09:04:20 2022 +0700

    JAMES-3722 Few tests for IMAP CONDSTORE RFC-4551
---
 .../james/imapserver/netty/IMAPServerTest.java     | 312 +++++++++++++++++++--
 1 file changed, 281 insertions(+), 31 deletions(-)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 0aa7f20..32f889c 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -19,8 +19,11 @@
 
 package org.apache.james.imapserver.netty;
 
+import static javax.mail.Flags.Flag.ANSWERED;
 import static javax.mail.Folder.READ_WRITE;
 import static org.apache.james.jmap.JMAPTestingConstants.LOCALHOST_IP;
+import static org.apache.james.mailbox.MessageManager.FlagsUpdateMode.REPLACE;
+import static org.apache.james.mailbox.MessageManager.MailboxMetaData.FetchGroup.NO_COUNT;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -1414,6 +1417,253 @@ class IMAPServerTest {
         }
     }
 
+    @Nested
+    class CondStore {
+        IMAPServer imapServer;
+        private MailboxSession mailboxSession;
+        private MessageManager inbox;
+        private SocketChannel clientConnection;
+
+        @BeforeEach
+        void beforeEach() throws Exception {
+            imapServer = createImapServer("imapServer.xml");
+            int port = imapServer.getListenAddresses().get(0).getPort();
+            mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
+            memoryIntegrationResources.getMailboxManager()
+                .createMailbox(MailboxPath.inbox(USER), mailboxSession);
+            inbox = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession);
+            setUpTestingData();
+
+            clientConnection = SocketChannel.open();
+            clientConnection.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(clientConnection);
+        }
+
+        @AfterEach
+        void tearDown() throws Exception {
+            imapServer.destroy();
+            clientConnection.close();
+        }
+
+        private void setUpTestingData() {
+            IntStream.range(0, 37)
+                .forEach(Throwing.intConsumer(i -> inbox.appendMessage(MessageManager.AppendCommand.builder()
+                    .build("MIME-Version: 1.0\r\n\r\nCONTENT\r\n"), mailboxSession)));
+        }
+
+        @Test
+        void fetchShouldSupportChangedSince() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(14)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
+
+            ModSeq highestModSeq = inbox.getMetaData(false, mailboxSession, NO_COUNT).getHighestModSeq();
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(2)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(35)), mailboxSession);
+
+            clientConnection.write(ByteBuffer.wrap(("a2 NOOP\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK NOOP completed."));
+
+            clientConnection.write(ByteBuffer.wrap(String.format("a3 UID FETCH 1:* (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            List<String> replies = readStringUntil(clientConnection, s -> s.contains("a3 OK FETCH completed."));
+            SoftAssertions.assertSoftly(softly -> {
+                softly.assertThat(replies)
+                    .filteredOn(s -> s.contains("* 2 FETCH (MODSEQ (41) FLAGS (\\Answered \\Recent) UID 2)"))
+                    .hasSize(1);
+                softly.assertThat(replies)
+                    .filteredOn(s -> s.contains("* 25 FETCH (MODSEQ (42) FLAGS (\\Answered \\Recent) UID 25)"))
+                    .hasSize(1);
+                softly.assertThat(replies)
+                    .filteredOn(s -> s.contains("* 35 FETCH (MODSEQ (43) FLAGS (\\Answered \\Recent) UID 35)"))
+                    .hasSize(1);
+
+                softly.assertThat(replies)
+                    .filteredOn(s -> s.contains("* 14 FETCH (MODSEQ (39) FLAGS (\\Answered \\Recent) UID 14)"))
+                    .isEmpty();
+                softly.assertThat(replies)
+                    .filteredOn(s -> s.contains("* 31 FETCH (MODSEQ (40) FLAGS (\\Answered \\Recent) UID 31)"))
+                    .isEmpty();
+            });
+        }
+
+        @Test
+        void searchShouldSupportModSeq() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(14)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
+
+            ModSeq highestModSeq = inbox.getMetaData(false, mailboxSession, NO_COUNT).getHighestModSeq();
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(2)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(35)), mailboxSession);
+
+            clientConnection.write(ByteBuffer.wrap(("a2 NOOP\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK NOOP completed."));
+
+            clientConnection.write(ByteBuffer.wrap(String.format("A150 SEARCH MODSEQ %d\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(clientConnection, s -> s.contains("A150 OK SEARCH completed.")))
+                .filteredOn(s -> s.contains("* SEARCH 2 25 31 35 (MODSEQ 43)"))
+                .hasSize(1);
+        }
+
+        @Test
+        void searchShouldSupportModSeqWithFlagRestrictions() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(14)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
+
+            ModSeq highestModSeq = inbox.getMetaData(false, mailboxSession, NO_COUNT).getHighestModSeq();
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(2)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(35)), mailboxSession);
+
+            clientConnection.write(ByteBuffer.wrap(("a2 NOOP\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK NOOP completed."));
+
+            clientConnection.write(ByteBuffer.wrap(String.format("a SEARCH MODSEQ \"/flags/\\\\draft\" all %d\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            // Restrictions are not applied however
+            assertThat(readStringUntil(clientConnection, s -> s.contains("a OK SEARCH completed.")))
+                .filteredOn(s -> s.contains("* SEARCH 2 25 31 35 (MODSEQ 43)"))
+                .hasSize(1);
+        }
+
+        @Test
+        void statusShouldAcceptHighestModSeqItem() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(2)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(35)), mailboxSession);
+            ModSeq highestModSeq = inbox.getMetaData(false, mailboxSession, NO_COUNT).getHighestModSeq();
+
+            clientConnection.write(ByteBuffer.wrap(("A042 STATUS INBOX (HIGHESTMODSEQ)\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(clientConnection, s -> s.contains("A042 OK STATUS completed.")))
+                .filteredOn(s -> s.contains(String.format("* STATUS \"INBOX\" (HIGHESTMODSEQ %d)", highestModSeq.asLong())))
+                .hasSize(1);
+        }
+
+        @Disabled("JAMES-3722 SELECT do not supports CONDSTORE to be immediately followed by a ')'")
+        @Test
+        void selectShouldAcceptCondstore() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            clientConnection.write(ByteBuffer.wrap(("A142 SELECT INBOX (CONDSTORE)\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(clientConnection, s -> s.contains("A142 OK [READ-WRITE] SELECT completed.")))
+                .isNotNull();
+        }
+
+        @Disabled("JAMES-3722 SELECT do not supports CONDSTORE to be immediately followed by a ')'")
+        @Test
+        void selectShouldEnableCondstore() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            clientConnection.write(ByteBuffer.wrap(("A142 SELECT INBOX (CONDSTORE)\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            readStringUntil(clientConnection, s -> s.contains("A142 OK [READ-WRITE] SELECT completed."));
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(35)), mailboxSession);
+
+            clientConnection.write(ByteBuffer.wrap(("a2 NOOP\r\n").getBytes(StandardCharsets.UTF_8)));
+            assertThat(readStringUntil(clientConnection, s -> s.contains("a2 OK NOOP completed.")))
+                .filteredOn(s -> s.contains("* 35 FETCH (MODSEQ (39) FLAGS (\\Answered \\Recent))"))
+                .hasSize(1);
+        }
+
+        @Test
+        void storeShouldSucceedWhenUnchangedSinceIsNotExceeded() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            clientConnection.write(ByteBuffer.wrap(("A142 SELECT INBOX (CONDSTORE)\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            readStringUntil(clientConnection, s -> s.contains("A142 OK [READ-WRITE] SELECT completed."));
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(35)), mailboxSession);
+            ModSeq highestModSeq = inbox.getMetaData(false, mailboxSession, NO_COUNT).getHighestModSeq();
+
+            clientConnection.write(ByteBuffer.wrap(("a2 NOOP\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK NOOP completed."));
+
+            clientConnection.write(ByteBuffer.wrap((String.format("a103 UID STORE 35 (UNCHANGEDSINCE %d) +FLAGS.SILENT (\\Seen)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8))));
+            assertThat(readStringUntil(clientConnection, s -> s.contains("a103 OK STORE completed.")))
+                .filteredOn(s -> s.contains("* 35 FETCH (MODSEQ (40) UID 35)"))
+                .hasSize(1);
+        }
+
+        @Test
+        void storeShouldFailWhenUnchangedSinceIsExceeded() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            clientConnection.write(ByteBuffer.wrap(("A142 SELECT INBOX (CONDSTORE)\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            readStringUntil(clientConnection, s -> s.contains("A142 OK [READ-WRITE] SELECT completed."));
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(35)), mailboxSession);
+            ModSeq highestModSeq = inbox.getMetaData(false, mailboxSession, NO_COUNT).getHighestModSeq();
+
+            clientConnection.write(ByteBuffer.wrap(("a2 NOOP\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK NOOP completed."));
+
+            clientConnection.write(ByteBuffer.wrap((String.format("a103 UID STORE 35 (UNCHANGEDSINCE %d) +FLAGS.SILENT (\\Seen)\r\n", highestModSeq.asLong() - 1).getBytes(StandardCharsets.UTF_8))));
+            assertThat(readStringUntil(clientConnection, s -> s.contains("a103 OK [MODIFIED 0 35] STORE failed.")))
+                .isNotNull();
+        }
+
+        @Test
+        void storeShouldFailWhenSomeMessagesDoNotMatch() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(clientConnection);
+
+            clientConnection.write(ByteBuffer.wrap(("A142 SELECT INBOX (CONDSTORE)\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            readStringUntil(clientConnection, s -> s.contains("A142 OK [READ-WRITE] SELECT completed."));
+
+            ModSeq highestModSeq = inbox.getMetaData(false, mailboxSession, NO_COUNT).getHighestModSeq();
+
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(7)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(9)), mailboxSession);
+
+            clientConnection.write(ByteBuffer.wrap(("a2 NOOP\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(clientConnection, s -> s.contains("a2 OK NOOP completed."));
+
+            clientConnection.write(ByteBuffer.wrap((String.format("a103 UID STORE 5,7,9 (UNCHANGEDSINCE %d) +FLAGS.SILENT (\\Seen)\r\n", highestModSeq.asLong() - 1).getBytes(StandardCharsets.UTF_8))));
+            assertThat(readStringUntil(clientConnection, s -> s.contains("a103 OK [MODIFIED 0 5,7,9] STORE failed.")))
+                .filteredOn(s -> s.contains("FETCH"))
+                .isEmpty();
+        }
+    }
+
     private byte[] readBytes(SocketChannel channel) throws IOException {
         ByteBuffer line = ByteBuffer.allocate(1024);
         channel.read(line);
@@ -1490,7 +1740,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1516,7 +1766,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1542,7 +1792,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1569,7 +1819,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1600,7 +1850,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1631,7 +1881,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1657,7 +1907,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1683,7 +1933,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1710,7 +1960,7 @@ class IMAPServerTest {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
 
             UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
@@ -1755,10 +2005,10 @@ class IMAPServerTest {
             readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+                .setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
             clientConnection.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 1:37 (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
@@ -1776,10 +2026,10 @@ class IMAPServerTest {
             clientConnection.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
             readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
-            inbox.setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+            inbox.setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
             clientConnection.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 12:37 (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
@@ -1800,12 +2050,12 @@ class IMAPServerTest {
             readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+                .setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+                .setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
             clientConnection.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 12:37 (FLAGS) (CHANGEDSINCE %d VANISHED)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
@@ -1824,14 +2074,14 @@ class IMAPServerTest {
             readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+                .setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+                .setFlags(new Flags(ANSWERED), REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
 
             inbox.delete(ImmutableList.of(MessageUid.of(14)), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
             clientConnection.write(ByteBuffer.wrap(String.format("I00104 NOOP\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
@@ -1850,17 +2100,17 @@ class IMAPServerTest {
             readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(11)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(11)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(12)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(12)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(26)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(26)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
 
             clientConnection.write(ByteBuffer.wrap(("I00104 EXPUNGE\r\n").getBytes(StandardCharsets.UTF_8)));
 
@@ -1879,17 +2129,17 @@ class IMAPServerTest {
             readStringUntil(clientConnection, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
 
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(11)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(11)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(12)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(12)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(26)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(26)), mailboxSession);
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
+                .setFlags(new Flags(Flags.Flag.DELETED), REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
 
             clientConnection.write(ByteBuffer.wrap(("I00104 UID EXPUNGE 1:37\r\n").getBytes(StandardCharsets.UTF_8)));
 

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 15/25: JAMES-3722 Refactor range filtering

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 7a72dca58839552f0f78a23fd7e13d3a01dc0ab1
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 15:00:49 2022 +0700

    JAMES-3722 Refactor range filtering
    
    Method extraction and functional style...
---
 .../imap/processor/AbstractSelectionProcessor.java | 29 ++++++++++++++--------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index 0fcb04c..dae539c 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -20,8 +20,10 @@
 package org.apache.james.imap.processor;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.events.EventBus;
@@ -278,21 +280,28 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
             .reduce((t3_1, t3_2) -> t3_2)
             .orElse(MessageUid.MIN_VALUE);
 
-        // Ok now its time to filter out the IdRanges which we are not interested in
-        List<UidRange> filteredUidSet = new ArrayList<>();
-        for (UidRange r : uidSet) {
-            if (r.getLowVal().compareTo(firstKnownUid) < 0) {
-                if (r.getHighVal().compareTo(firstKnownUid) > 0) {
-                    filteredUidSet.add(new UidRange(firstKnownUid, r.getHighVal()));
-                }
+        return filter(uidSet, firstKnownUid);
+    }
+
+    private UidRange[] filter(UidRange[] uidSet, MessageUid lowerBound) {
+        return Arrays.stream(uidSet)
+            .flatMap(range -> filter(range, lowerBound))
+            .collect(ImmutableList.toImmutableList())
+            .toArray(UidRange[]::new);
+    }
+
+    private Stream<UidRange> filter(UidRange range, MessageUid lowerBound) {
+        if (range.getLowVal().compareTo(lowerBound) < 0) {
+            if (range.getHighVal().compareTo(lowerBound) > 0) {
+                return Stream.of(new UidRange(lowerBound, range.getHighVal()));
             } else {
-                filteredUidSet.add(r);
+                return Stream.empty();
             }
+        } else {
+            return Stream.of(range);
         }
-        return filteredUidSet.toArray(UidRange[]::new);
     }
 
-
     private void highestModSeq(Responder responder, MailboxMetaData metaData) {
         final StatusResponse untaggedOk;
         if (metaData.isModSeqPermanent()) {

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 01/25: [FIX] UidValidity generate fails for Long.MIN_VALUE

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 94365fff39276b1e8c469f9204d832691ea5dc42
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Mar 7 21:30:43 2022 +0700

    [FIX] UidValidity generate fails for Long.MIN_VALUE
---
 .../src/main/java/org/apache/james/mailbox/model/UidValidity.java | 8 ++++----
 .../test/java/org/apache/james/mailbox/model/UidValidityTest.java | 6 ++++++
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/UidValidity.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/UidValidity.java
index 1c1401c..4867851 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/UidValidity.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/UidValidity.java
@@ -38,14 +38,14 @@ public class UidValidity {
      *
      * See https://issues.apache.org/jira/browse/JAMES-3074 for details
      */
-    public static  UidValidity generate() {
+    public static UidValidity generate() {
         return fromSupplier(RANDOM::nextLong);
     }
 
     @VisibleForTesting
     static UidValidity fromSupplier(Supplier<Long> longSupplier) {
-        long randomValue = Math.abs(longSupplier.get());
-        long sanitizedRandomValue = 1 + (randomValue % (UPPER_EXCLUSIVE_BOUND - 1));
+        long randomValue = longSupplier.get();
+        long sanitizedRandomValue = Math.abs(1 + (randomValue % (UPPER_EXCLUSIVE_BOUND - 1)));
         return of(sanitizedRandomValue);
     }
 
@@ -56,7 +56,7 @@ public class UidValidity {
      * Strongly favor uses of  {@link #ofValid(long)}
      */
     public static UidValidity of(long uidValidity) {
-        Preconditions.checkArgument(isValid(uidValidity), "uidValidity needs to be a non-zero unsigned 32-bit integer");
+        Preconditions.checkArgument(isValid(uidValidity), "uidValidity needs to be a non-zero unsigned 32-bit integer, got %s", uidValidity);
         return new UidValidity(uidValidity);
     }
 
diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/model/UidValidityTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/model/UidValidityTest.java
index 34fc0e5..fac5900 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/model/UidValidityTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/model/UidValidityTest.java
@@ -82,6 +82,12 @@ class UidValidityTest {
     }
 
     @Test
+    void fromSupplierShouldNotThrowWhenMinValue() {
+        assertThatCode(() -> UidValidity.fromSupplier(() -> Long.MIN_VALUE))
+            .doesNotThrowAnyException();
+    }
+
+    @Test
     void fromSupplierShouldNotThrowWhenUpperBoundInclusiveIsGenerated() {
         assertThatCode(() -> UidValidity.fromSupplier(() -> 4294967295L))
             .doesNotThrowAnyException();

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 17/25: JAMES-3722 IMAP SELECT QRESYNC remove TODO

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 3bfdddc2b620daa3acdac949478182cb4a00c811
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 15:02:42 2022 +0700

    JAMES-3722 IMAP SELECT QRESYNC remove TODO
    
    RFC says: no need to handle CONSTORE here
---
 .../java/org/apache/james/imap/processor/AbstractSelectionProcessor.java | 1 -
 1 file changed, 1 deletion(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index 07d0bc4..735fbee 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -387,7 +387,6 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
             sessionMailbox = session.getSelected();
             
         } else {
-            // TODO: Check if we need to handle CONDSTORE there too 
             sessionMailbox = currentMailbox;
         }
         final MailboxMetaData metaData = mailbox.getMetaData(!openReadOnly, mailboxSession, MailboxMetaData.FetchGroup.FIRST_UNSEEN);

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 07/25: JAMES-3722 More tests for QRESYNC

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 5ee497397b65abdc2abcb8e0a98f61cde21bb579
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 08:31:47 2022 +0700

    JAMES-3722 More tests for QRESYNC
---
 .../james/imapserver/netty/IMAPServerTest.java     | 268 +++++++++++++++++++--
 1 file changed, 244 insertions(+), 24 deletions(-)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index b568b38..686c624 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -45,6 +45,7 @@ import java.util.function.Predicate;
 import java.util.stream.IntStream;
 
 import javax.mail.FetchProfile;
+import javax.mail.Flags;
 import javax.mail.Folder;
 import javax.mail.Message;
 import javax.mail.Session;
@@ -62,7 +63,6 @@ import javax.net.ssl.X509TrustManager;
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.net.imap.AuthenticatingIMAPClient;
 import org.apache.commons.net.imap.IMAPReply;
 import org.apache.commons.net.imap.IMAPSClient;
@@ -78,6 +78,7 @@ import org.apache.james.mailbox.ModSeq;
 import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.model.UidValidity;
 import org.apache.james.mailbox.store.FakeAuthenticator;
 import org.apache.james.mailbox.store.FakeAuthorizator;
@@ -1454,6 +1455,7 @@ class IMAPServerTest {
 
             clientConnection = SocketChannel.open();
             clientConnection.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            setUpTestingData();
         }
 
         @AfterEach
@@ -1463,15 +1465,6 @@ class IMAPServerTest {
 
         @Test
         void selectShouldNotAnswerEmptyVanishedResponses() throws Exception {
-            MailboxSession mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
-            memoryIntegrationResources.getMailboxManager()
-                .createMailbox(MailboxPath.inbox(USER), mailboxSession);
-            IntStream.range(0, 37)
-                .forEach(Throwing.intConsumer(i -> memoryIntegrationResources.getMailboxManager()
-                    .getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                    .appendMessage(MessageManager.AppendCommand.builder()
-                        .build("MIME-Version: 1.0\r\n\r\nCONTENT\r\n"), mailboxSession)));
-
             memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
@@ -1486,8 +1479,7 @@ class IMAPServerTest {
             server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
             readBytes(server);
             server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
-            readBytes(server);
-            readBytes(server);
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
             server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d 88 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong()).getBytes(StandardCharsets.UTF_8)));
 
             assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
@@ -1497,17 +1489,7 @@ class IMAPServerTest {
 
         @Test
         void selectShouldReturnDeletedMessagesWhenNoSubsequentModification() throws Exception {
-            MailboxSession mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
-            memoryIntegrationResources.getMailboxManager()
-                .createMailbox(MailboxPath.inbox(USER), mailboxSession);
-            IntStream.range(0, 37)
-                .forEach(Throwing.intConsumer(i -> memoryIntegrationResources.getMailboxManager()
-                    .getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                    .appendMessage(MessageManager.AppendCommand.builder()
-                        .build("MIME-Version: 1.0\r\n\r\nCONTENT\r\n"), mailboxSession)));
-
-            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
-                .delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
@@ -1527,12 +1509,250 @@ class IMAPServerTest {
             server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
             readBytes(server);
             server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
+                .hasSize(1);
+        }
+
+        @Test
+        void selectShouldCombineIntoRangesWhenRespondingVanished() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            inbox.delete(ImmutableList.of(MessageUid.of(10), MessageUid.of(11), MessageUid.of(12),
+                    MessageUid.of(25), MessageUid.of(26),
+                    MessageUid.of(32)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
             readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
             readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
             server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
             assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
-                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10:12,25:26,32"))
+                .hasSize(1);
+        }
+
+        private void setUpTestingData() {
+            IntStream.range(0, 37)
+                .forEach(Throwing.intConsumer(i -> inbox.appendMessage(MessageManager.AppendCommand.builder()
+                    .build("MIME-Version: 1.0\r\n\r\nCONTENT\r\n"), mailboxSession)));
+        }
+
+        @Test
+        void fetchShouldAllowChangedSinceModifier() throws Exception {
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+            server.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 1:37 (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK FETCH completed.")))
+                .filteredOn(s -> s.contains("* 10 FETCH (MODSEQ (39) FLAGS (\\Answered \\Recent) UID 10)"))
+                .hasSize(1);
+        }
+
+        @Test
+        void fetchShouldNotReturnChangedItemsOutOfRange() throws Exception {
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+            inbox.setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+            server.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 12:37 (FLAGS) (CHANGEDSINCE %d)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK FETCH completed.")))
+                .filteredOn(s -> s.contains("FLAGS")) // No FLAGS FETCH responses
+                .hasSize(1);
+        }
+
+        @Disabled("JAMES-3722 IMAP stack failed to parse FETCH command with two modifiers and thus do" +
+            "not conform to the example of the RFC-5162")
+        @Test
+        void fetchShouldSupportVanishedModifiedWithEarlierTag() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.of(14)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+            server.write(ByteBuffer.wrap(String.format("I00104 UID FETCH 12:37 (FLAGS) (CHANGEDSINCE %d VANISHED)\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK FETCH completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 14"))
+                .hasSize(1);
+        }
+
+        @Test
+        void unsolicitedNotificationsShouldBeSent() throws Exception {
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.ANSWERED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+
+            inbox.delete(ImmutableList.of(MessageUid.of(14)), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+            server.write(ByteBuffer.wrap(String.format("I00104 NOOP\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK NOOP completed.")))
+                .filteredOn(s -> s.contains("* VANISHED 14"))
+                .hasSize(1);
+        }
+
+        @Test
+        void expungeShouldReturnVanishedWhenQResyncIsActive() throws Exception {
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(11)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(12)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(26)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
+
+            server.write(ByteBuffer.wrap(("I00104 EXPUNGE\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [HIGHESTMODSEQ 44] EXPUNGE completed.")))
+                .filteredOn(s -> s.contains("* VANISHED 10:12,25:26,31"))
+                .hasSize(1);
+        }
+
+        @Test
+        void uidExpungeShouldReturnExpungededWhenQResyncIsActive() throws Exception {
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(10)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(11)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(12)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(25)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(26)), mailboxSession);
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .setFlags(new Flags(Flags.Flag.DELETED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.one(MessageUid.of(31)), mailboxSession);
+
+            server.write(ByteBuffer.wrap(("I00104 UID EXPUNGE 1:37\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [HIGHESTMODSEQ 44] EXPUNGE completed.")))
+                .filteredOn(s -> s.contains("* VANISHED 10:12,25:26,31"))
+                .hasSize(1);
+        }
+
+        @Disabled("JAMES-3722 Closed notifications were never sent upon implicit selected mailbox changes")
+        @Test
+        void implicitMailboxSelectionChangesShouldReturnClosedNotifications() throws Exception {
+            memoryIntegrationResources.getMailboxManager()
+                .createMailbox(MailboxPath.forUser(USER, "other"), mailboxSession);
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+            server.write(ByteBuffer.wrap(("a3 SELECT other\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("a3 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* OK [CLOSED]"))
                 .hasSize(1);
         }
     }

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 04/25: JAMES-3722 SELECT + QRESYNC cosmetic fix

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 908d064f6c9966e7d23da23ca2a08127283e7dbf
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Mar 7 22:37:14 2022 +0700

    JAMES-3722 SELECT + QRESYNC cosmetic fix
    
    Avoid intermediate search API data representation for ranges to check for QRESYNC
---
 .../james/imap/processor/AbstractMailboxProcessor.java     | 14 +++-----------
 1 file changed, 3 insertions(+), 11 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
index ae3b12c..919445f 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java
@@ -61,7 +61,6 @@ import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.model.MessageRange.Type;
-import org.apache.james.mailbox.model.SearchQuery;
 import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.metrics.api.TimeMetric;
 import org.slf4j.Logger;
@@ -536,16 +535,9 @@ public abstract class AbstractMailboxProcessor<R extends ImapRequest> extends Ab
      */
     protected void respondVanished(SelectedMailbox selectedMailbox, List<MessageRange> ranges, Responder responder) throws MailboxException {
         Set<MessageUid> vanishedUids = new HashSet<>();
-        for (int i = 0; i < ranges.size(); i++) {
-            MessageRange r = ranges.get(i);
-            SearchQuery.UidRange nr;
-            if (r.getType() == Type.ONE) {
-                nr = new SearchQuery.UidRange(r.getUidFrom());
-            } else {
-                nr = new SearchQuery.UidRange(r.getUidFrom(), r.getUidTo());
-            }
-            MessageUid from = nr.getLowValue();
-            MessageUid to = nr.getHighValue();
+        for (MessageRange range : ranges) {
+            MessageUid from = range.getUidFrom();
+            MessageUid to = range.getUidTo();
             while (from.compareTo(to) <= 0) {
                 MessageUid copy = from;
                 selectedMailbox.msn(from).fold(

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 24/25: JAMES-3722 Parse an arbitrary number of IMAP FETCH modifiers

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit b66294df3d4282f51b39744e290e288f537940f3
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Mar 17 16:34:52 2022 +0700

    JAMES-3722 Parse an arbitrary number of IMAP FETCH modifiers
---
 .../james/imap/decode/parser/FetchCommandParser.java      | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/FetchCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/FetchCommandParser.java
index f006dd2..b878f6f 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/FetchCommandParser.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/FetchCommandParser.java
@@ -84,10 +84,9 @@ public class FetchCommandParser extends AbstractUidCommandParser {
             next = nextNonSpaceChar(request);
             if (next == '(') {
                 request.consumeChar('(');
-                parseModifier(request, fetch);
-                nextNonSpaceChar(request);
-                parseModifier(request, fetch);
-                nextNonSpaceChar(request);
+                while (parseModifier(request, fetch)) {
+                    nextNonSpaceChar(request);
+                }
                 request.consumeChar(')');
             }
         } else {
@@ -97,21 +96,21 @@ public class FetchCommandParser extends AbstractUidCommandParser {
         return fetch.build();
     }
 
-    private void parseModifier(ImapRequestLineReader request, FetchData.Builder fetch) throws DecodingException {
+    private boolean parseModifier(ImapRequestLineReader request, FetchData.Builder fetch) throws DecodingException {
         char next = request.nextChar();
         switch (next) {
         case 'C':
             // Now check for the CHANGEDSINCE option which is part of CONDSTORE
             request.consumeWord(StringMatcherCharacterValidator.ignoreCase(CHANGEDSINCE));
             fetch.changedSince(request.number(true));
-            break;
+            return true;
         case 'V':
             // Check for the VANISHED option which is part of QRESYNC
             request.consumeWord(StringMatcherCharacterValidator.ignoreCase(VANISHED), true);
             fetch.vanished(true);
-            break;
+            return true;
         default:
-            break;
+            return false;
         }
     }
 

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 18/25: JAMES-3722 IMAP SELECT QRESYNC another small code extraction

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 389310bd1799de3cd750a0d18674e0ca07109ebb
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 15:09:49 2022 +0700

    JAMES-3722 IMAP SELECT QRESYNC another small code extraction
---
 .../imap/processor/AbstractSelectionProcessor.java | 40 ++++++++++++----------
 1 file changed, 22 insertions(+), 18 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index 735fbee..ed6293f 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -22,6 +22,7 @@ package org.apache.james.imap.processor;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
@@ -63,6 +64,7 @@ import org.apache.james.metrics.api.MetricFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.fge.lambdas.Throwing;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 
@@ -168,23 +170,15 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
                 //  The server sends the client any pending flag changes (using FETCH
                 //  responses that MUST contain UIDs) and expunges those that have
                 //  occurred in this mailbox since the provided modification sequence.
-                UidRange[] uidSet = request.getUidSet();
-
-                if (uidSet == null) {
-                    // See mailbox had some messages stored before, if not we don't need to query at all
-                    MessageUid uidNext = metaData.getUidNext();
-                    if (!uidNext.isFirst()) {
-                        // Use UIDNEXT -1 as max uid as stated in the QRESYNC RFC
-                        uidSet = new UidRange[] {new UidRange(MessageUid.MIN_VALUE, uidNext.previous())};
-                    }
-                }
-                
-                if (uidSet != null) {
-                    respondVanished(session, responder, knownSequences, knownUids, selected, uidSet);
-                }
+
+                uidSet(request, metaData)
+                    .ifPresent(Throwing.<UidRange[]>consumer(
+                        uidSet -> respondVanished(session, responder, knownSequences, knownUids, selected, uidSet))
+                        .sneakyThrow());
+
                 taggedOk(responder, request, metaData, HumanReadableText.SELECT);
             } else {
-                
+
                 taggedOk(responder, request, metaData, HumanReadableText.QRESYNC_UIDVALIDITY_MISMATCH);
             }
         } else {
@@ -196,6 +190,19 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
         SearchResUtil.resetSavedSequenceSet(session);
     }
 
+    private Optional<UidRange[]> uidSet(AbstractMailboxSelectionRequest request, MailboxMetaData metaData) {
+        return Optional.ofNullable(request.getUidSet())
+            .or(() -> {
+                // See mailbox had some messages stored before, if not we don't need to query at all
+                MessageUid uidNext = metaData.getUidNext();
+                if (!uidNext.isFirst()) {
+                    // Use UIDNEXT -1 as max uid as stated in the QRESYNC RFC
+                    return Optional.of(new UidRange[] {new UidRange(MessageUid.MIN_VALUE, uidNext.previous())});
+                }
+                return Optional.empty();
+            });
+    }
+
     private void respondVanished(ImapSession session, Responder responder, IdRange[] knownSequences, UidRange[] knownUids, SelectedMailbox selected, UidRange[] uidSet) throws MailboxException {
         // RFC5162 3.1. QRESYNC Parameter to SELECT/EXAMINE
         //
@@ -342,8 +349,6 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
             });
         }
         return true;
-
-
     }
 
     private void uidValidity(Responder responder, MailboxMetaData metaData) {
@@ -394,7 +399,6 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
         return metaData;
     }
 
-
     private void addRecent(MailboxMetaData metaData, SelectedMailbox sessionMailbox) {
         final List<MessageUid> recentUids = metaData.getRecent();
         for (MessageUid uid : recentUids) {

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 09/25: JAMES-3722 IMAP SELECT should return CLOSED responses upon implicit selected mailbox changes

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit e0e4f90775bc4b23e27ef394c072b1b1e40bc96a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 08:33:59 2022 +0700

    JAMES-3722 IMAP SELECT should return CLOSED responses upon implicit selected mailbox changes
---
 .../james/imap/processor/AbstractSelectionProcessor.java      | 11 ++++++-----
 .../org/apache/james/imapserver/netty/IMAPServerTest.java     |  1 -
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index d6ad78a..5dfa2dc 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -116,7 +116,7 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
             return;
         }
 
-        final MailboxMetaData metaData = selectMailbox(fullMailboxPath, session);
+        final MailboxMetaData metaData = selectMailbox(fullMailboxPath, session, responder);
         final SelectedMailbox selected = session.getSelected();
         MessageUid firstUnseen = metaData.getFirstUnseen();
         
@@ -136,7 +136,7 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
                 LOGGER.info("Unable to uid for unseen message {} in mailbox {}", firstUnseen, selected.getMailboxId().serialize());
                 break;
             }
-            firstUnseen = selectMailbox(fullMailboxPath, session).getFirstUnseen();
+            firstUnseen = selectMailbox(fullMailboxPath, session, responder).getFirstUnseen();
             retryCount++;
             
         }
@@ -384,7 +384,7 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
         responder.respond(existsResponse);
     }
 
-    private MailboxMetaData selectMailbox(MailboxPath mailboxPath, ImapSession session) throws MailboxException {
+    private MailboxMetaData selectMailbox(MailboxPath mailboxPath, ImapSession session, Responder responder) throws MailboxException {
         final MailboxManager mailboxManager = getMailboxManager();
         final MailboxSession mailboxSession = session.getMailboxSession();
         final MessageManager mailbox = mailboxManager.getMailbox(mailboxPath, mailboxSession);
@@ -398,8 +398,9 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
             // Response with the CLOSE return-code when the currently selected mailbox is closed implicitly using the SELECT/EXAMINE command on another mailbox
             //
             // See rfc5162 3.7. CLOSED Response Code
-            if (currentMailbox != null) {
-                getStatusResponseFactory().untaggedOk(HumanReadableText.QRESYNC_CLOSED, ResponseCode.closed());
+            if (currentMailbox != null && EnableProcessor.getEnabledCapabilities(session).contains(ImapConstants.SUPPORTS_QRESYNC)) {
+                responder.respond(getStatusResponseFactory()
+                    .untaggedOk(HumanReadableText.QRESYNC_CLOSED, ResponseCode.closed()));
             }
             session.selected(new SelectedMailboxImpl(getMailboxManager(), eventBus, session, mailbox));
 
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 8b470b5..fb2d92b 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1731,7 +1731,6 @@ class IMAPServerTest {
                 .hasSize(1);
         }
 
-        @Disabled("JAMES-3722 Closed notifications were never sent upon implicit selected mailbox changes")
         @Test
         void implicitMailboxSelectionChangesShouldReturnClosedNotifications() throws Exception {
             memoryIntegrationResources.getMailboxManager()

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 25/25: JAMES-3722 Apply some review comments

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit b2260d9ab8cee87fce03ce798dc60fa019132da3
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Mar 21 14:39:08 2022 +0700

    JAMES-3722 Apply some review comments
---
 .../org/apache/james/imap/processor/AbstractSelectionProcessor.java   | 1 -
 .../test/java/org/apache/james/imapserver/netty/IMAPServerTest.java   | 4 ++--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index ed6293f..6646efc 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -290,7 +290,6 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
     private UidRange[] filter(UidRange[] uidSet, MessageUid lowerBound) {
         return Arrays.stream(uidSet)
             .flatMap(range -> filter(range, lowerBound))
-            .collect(ImmutableList.toImmutableList())
             .toArray(UidRange[]::new);
     }
 
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index d1a12ef..06356b4 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1844,7 +1844,7 @@ class IMAPServerTest {
         }
 
         @Test
-        void knownUidSetShouldTorelateDeletedMessages() throws Exception {
+        void knownUidSetShouldTolerateDeletedMessages() throws Exception {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
 
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
@@ -2081,7 +2081,7 @@ class IMAPServerTest {
             ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
                 .getMetaData(false, mailboxSession, NO_COUNT)
                 .getHighestModSeq();
-            clientConnection.write(ByteBuffer.wrap(String.format("I00104 NOOP\r\n", highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+            clientConnection.write(ByteBuffer.wrap("I00104 NOOP\r\n".getBytes(StandardCharsets.UTF_8)));
 
             assertThat(readStringUntil(clientConnection, s -> s.contains("I00104 OK NOOP completed.")))
                 .filteredOn(s -> s.contains("* VANISHED 14"))

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 02/25: JAMES-3722 Tests for QRESYNC

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 6417a48eaaa9f5a250d2a31e0ef6834aae8006cf
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Mar 7 22:22:49 2022 +0700

    JAMES-3722 Tests for QRESYNC
---
 .../james/imapserver/netty/IMAPServerTest.java     | 116 +++++++++++++++++++++
 1 file changed, 116 insertions(+)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index f6a2e8c..df20e64 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -39,6 +39,10 @@ import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.function.Predicate;
+import java.util.List;
+import java.util.Properties;
+import java.util.function.Predicate;
+import java.util.stream.IntStream;
 
 import javax.mail.FetchProfile;
 import javax.mail.Folder;
@@ -58,6 +62,7 @@ import javax.net.ssl.X509TrustManager;
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.net.imap.AuthenticatingIMAPClient;
 import org.apache.commons.net.imap.IMAPReply;
 import org.apache.commons.net.imap.IMAPSClient;
@@ -68,9 +73,12 @@ import org.apache.james.imap.processor.main.DefaultImapProcessorFactory;
 import org.apache.james.jwt.OidcTokenFixture;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.ModSeq;
 import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.UidValidity;
 import org.apache.james.mailbox.store.FakeAuthenticator;
 import org.apache.james.mailbox.store.FakeAuthorizator;
 import org.apache.james.mailbox.store.StoreSubscriptionManager;
@@ -95,6 +103,7 @@ import org.mockserver.integration.ClientAndServer;
 import org.mockserver.model.HttpRequest;
 import org.mockserver.model.HttpResponse;
 
+import com.github.fge.lambdas.Throwing;
 import com.google.common.collect.ImmutableList;
 import com.sun.mail.imap.IMAPFolder;
 
@@ -1425,4 +1434,111 @@ class IMAPServerTest {
             }
         }
     }
+
+    @Nested
+    class QResync {
+        IMAPServer imapServer;
+        private int port;
+        private MailboxSession mailboxSession;
+        private MessageManager inbox;
+        private SocketChannel clientConnection;
+
+        @BeforeEach
+        void beforeEach() throws Exception {
+            imapServer = createImapServer("imapServer.xml");
+            port = imapServer.getListenAddresses().get(0).getPort();
+            mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
+            memoryIntegrationResources.getMailboxManager()
+                .createMailbox(MailboxPath.inbox(USER), mailboxSession);
+            inbox = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession);
+
+            clientConnection = SocketChannel.open();
+            clientConnection.connect(new InetSocketAddress(LOCALHOST_IP, port));
+        }
+
+        @AfterEach
+        void tearDown() {
+            imapServer.destroy();
+        }
+
+        @Disabled("JAMES-3722 We currently return empty vanished response whose syntax is invalid." +
+            "We don't match RFC-3501 sequence-set formal syntax that guaranty at least one element")
+        @Test
+        void selectShouldNotAnswerEmptyVanishedResponses() throws Exception {
+            MailboxSession mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
+            memoryIntegrationResources.getMailboxManager()
+                .createMailbox(MailboxPath.inbox(USER), mailboxSession);
+            IntStream.range(0, 37)
+                .forEach(Throwing.intConsumer(i -> memoryIntegrationResources.getMailboxManager()
+                    .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                    .appendMessage(MessageManager.AppendCommand.builder()
+                        .build("MIME-Version: 1.0\r\n\r\nCONTENT\r\n"), mailboxSession)));
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            readBytes(server);
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d 88 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("VANISHED"))
+                .isEmpty();
+        }
+
+        @Disabled("JAMES-3722 We currently take the latests modseq as the 'latest modseq for which a deletion occured'" +
+            " which is not currently stored, thus we never return deletions unless there are new messages / flags changes" +
+            " causing the modseq to change")
+        @Test
+        void selectShouldReturnDeletedMessagesWhenNoSubsequentModifucation() throws Exception {
+            MailboxSession mailboxSession = memoryIntegrationResources.getMailboxManager().createSystemSession(USER);
+            memoryIntegrationResources.getMailboxManager()
+                .createMailbox(MailboxPath.inbox(USER), mailboxSession);
+            IntStream.range(0, 37)
+                .forEach(Throwing.intConsumer(i -> memoryIntegrationResources.getMailboxManager()
+                    .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                    .appendMessage(MessageManager.AppendCommand.builder()
+                        .build("MIME-Version: 1.0\r\n\r\nCONTENT\r\n"), mailboxSession)));
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .delete(ImmutableList.of(MessageUid.of(10)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            readBytes(server);
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
+                .hasSize(1);
+        }
+    }
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 11/25: JAMES-3722 Test IMAP SELECT optional parameters in regard of QRESYNC

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 80ab117c1eac248b547b8482677f78be3f2f27d6
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 09:19:35 2022 +0700

    JAMES-3722 Test IMAP SELECT optional parameters in regard of QRESYNC
---
 .../james/imapserver/netty/IMAPServerTest.java     | 126 +++++++++++++++++++++
 1 file changed, 126 insertions(+)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index e4979ea..e5d78ef 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1517,6 +1517,132 @@ class IMAPServerTest {
                 .hasSize(1);
         }
 
+        @Disabled("JAMES-3722 SELECT + QRESYNC did not comply with formal syntax: sequence match data can theorically" +
+            "be specified without known uids. CF:" +
+            "   select-param        =  \"QRESYNC\" SP \"(\" uidvalidity SP" +
+            "           mod-sequence-value [SP known-uids]" +
+            "           [SP seq-match-data]")
+        @Test
+        void selectShouldReturnDeletedMessagesWhenSequenceMatchDataAndNoKnownUid() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .delete(ImmutableList.of(MessageUid.of(10)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
+                .hasSize(1);
+        }
+
+        @Test
+        void selectShouldReturnDeletedMessagesWhenKnownUidSet() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            inbox.delete(ImmutableList.of(MessageUid.of(10), MessageUid.of(11), MessageUid.of(12),
+                MessageUid.of(25), MessageUid.of(26),
+                MessageUid.of(32)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 5:11,28:36 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
+                .hasSize(1);
+        }
+
+        @Test
+        void selectShouldReturnDeletedMessagesWhenNoSequenceMatchData() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .delete(ImmutableList.of(MessageUid.of(10)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 2:37))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
+                .hasSize(1);
+        }
+
+        @Test
+        void selectShouldReturnDeletedMessagesWhenNoSequenceMatchDataAndKnownUid() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .delete(ImmutableList.of(MessageUid.of(10)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
+                .hasSize(1);
+        }
+
         @Test
         void selectShouldCombineIntoRangesWhenRespondingVanished() throws Exception {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 06/25: JAMES-3722 Remove no longer used parameters in AbstractSelectionProcessor

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 72596a00ec6e226e80fbe37a33ab27c2fe75700f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Mar 7 23:40:34 2022 +0700

    JAMES-3722 Remove no longer used parameters in AbstractSelectionProcessor
---
 .../james/imap/processor/AbstractSelectionProcessor.java      | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index 051755c..d6ad78a 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -98,9 +98,7 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
     }
 
     private void respond(ImapSession session, MailboxPath fullMailboxPath, AbstractMailboxSelectionRequest request, Responder responder) throws MailboxException {
-
         ClientSpecifiedUidValidity lastKnownUidValidity = request.getLastKnownUidValidity();
-        Long modSeq = request.getKnownModSeq();
         IdRange[] knownSequences = request.getKnownSequenceSet();
         UidRange[] knownUids = request.getKnownUidSet();
 
@@ -157,10 +155,6 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
         // The same is true if none are given ;)
         if (metaData.isModSeqPermanent() && !lastKnownUidValidity.isUnknown()) {
             if (lastKnownUidValidity.correspondsTo(metaData.getUidValidity())) {
-                
-                final MailboxManager mailboxManager = getMailboxManager();
-                final MailboxSession mailboxSession = session.getMailboxSession();
-                final MessageManager mailbox = mailboxManager.getMailbox(fullMailboxPath, mailboxSession);
 
                 //  If the provided UIDVALIDITY matches that of the selected mailbox, the
                 //  server then checks the last known modification sequence.
@@ -168,7 +162,6 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
                 //  The server sends the client any pending flag changes (using FETCH
                 //  responses that MUST contain UIDs) and expunges those that have
                 //  occurred in this mailbox since the provided modification sequence.
-
                 UidRange[] uidSet = request.getUidSet();
 
                 if (uidSet == null) {
@@ -181,7 +174,7 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
                 }
                 
                 if (uidSet != null) {
-                    respondVanished(session, responder, modSeq, knownSequences, knownUids, metaData, selected, mailboxSession, mailbox, uidSet);
+                    respondVanished(session, responder, knownSequences, knownUids, selected, uidSet);
                 }
                 taggedOk(responder, request, metaData, HumanReadableText.SELECT);
             } else {
@@ -197,7 +190,7 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
         SearchResUtil.resetSavedSequenceSet(session);
     }
 
-    private void respondVanished(ImapSession session, Responder responder, Long modSeq, IdRange[] knownSequences, UidRange[] knownUids, MailboxMetaData metaData, SelectedMailbox selected, MailboxSession mailboxSession, MessageManager mailbox, UidRange[] uidSet) throws MailboxException {
+    private void respondVanished(ImapSession session, Responder responder, IdRange[] knownSequences, UidRange[] knownUids, SelectedMailbox selected, UidRange[] uidSet) throws MailboxException {
         // RFC5162 3.1. QRESYNC Parameter to SELECT/EXAMINE
         //
         // Message sequence match data:

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 12/25: JAMES-3722 SELECT + QRESYNC did not comply with formal syntax

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 824e8f6ec3fbfd30651738f2ade1c693f36f0e85
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 09:21:24 2022 +0700

    JAMES-3722 SELECT + QRESYNC did not comply with formal syntax
    
    sequence match data can theorically be specified without known uids.
    
    CF:
    select-param        =  \"QRESYNC\" SP \"(\" uidvalidity SP" +
                            mod-sequence-value [SP known-uids]
                            [SP seq-match-data]
---
 .../decode/parser/AbstractSelectionCommandParser.java  | 18 ++++++++++++------
 .../apache/james/imapserver/netty/IMAPServerTest.java  |  5 -----
 2 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/AbstractSelectionCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/AbstractSelectionCommandParser.java
index 8959299..d06f2f1 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/AbstractSelectionCommandParser.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/AbstractSelectionCommandParser.java
@@ -35,9 +35,12 @@ import org.apache.james.imap.message.request.AbstractMailboxSelectionRequest;
 import org.apache.james.imap.message.request.AbstractMailboxSelectionRequest.ClientSpecifiedUidValidity;
 import org.apache.james.mailbox.MessageUid;
 
+import com.google.common.base.CharMatcher;
+
 public abstract class AbstractSelectionCommandParser extends AbstractImapCommandParser {
     private static final String CONDSTORE = ImapConstants.SUPPORTS_CONDSTORE.asString();
     private static final String QRESYNC = ImapConstants.SUPPORTS_QRESYNC.asString();
+    private static final CharMatcher NUMERIC = CharMatcher.inRange('0', '9');
 
     public AbstractSelectionCommandParser(ImapCommand command, StatusResponseFactory statusResponseFactory) {
         super(command, statusResponseFactory);
@@ -89,15 +92,18 @@ public abstract class AbstractSelectionCommandParser extends AbstractImapCommand
                        
                     // Consume the SP
                     request.consumeChar(' ');
-                    uidSet = request.parseUidRange();
-                    
-                    // Check for *
-                    checkUidRanges(uidSet, false);
+                    if (NUMERIC.matches(request.nextChar())) {
+                        uidSet = request.parseUidRange();
+                        // Check for *
+                        checkUidRanges(uidSet, false);
+                    }
                     
                     nc = request.nextChar();
-                    if (nc == ' ')  {
+                    if (nc == ' ') {
                         request.consumeChar(' ');
-                        
+                    }
+                    nc = request.nextChar();
+                    if (nc == '(') {
                         // This is enclosed in () so remove (
                         request.consumeChar('(');
                         knownSequenceSet = request.parseIdRange();
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index e5d78ef..54ad3d6 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1517,11 +1517,6 @@ class IMAPServerTest {
                 .hasSize(1);
         }
 
-        @Disabled("JAMES-3722 SELECT + QRESYNC did not comply with formal syntax: sequence match data can theorically" +
-            "be specified without known uids. CF:" +
-            "   select-param        =  \"QRESYNC\" SP \"(\" uidvalidity SP" +
-            "           mod-sequence-value [SP known-uids]" +
-            "           [SP seq-match-data]")
         @Test
         void selectShouldReturnDeletedMessagesWhenSequenceMatchDataAndNoKnownUid() throws Exception {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 23/25: JAMES-3722 CONDSTORE/QRESYNC tests fixes

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 41421a5e6546e6739d15d8ba4c5077a97d71dca2
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Mar 14 17:12:42 2022 +0700

    JAMES-3722 CONDSTORE/QRESYNC tests fixes
---
 .../test/java/org/apache/james/imapserver/netty/IMAPServerTest.java   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 89d40d3..d1a12ef 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1441,8 +1441,8 @@ class IMAPServerTest {
 
         @AfterEach
         void tearDown() throws Exception {
-            imapServer.destroy();
             clientConnection.close();
+            imapServer.destroy();
         }
 
         private void setUpTestingData() {
@@ -1709,8 +1709,8 @@ class IMAPServerTest {
 
         @AfterEach
         void tearDown() throws Exception {
-            imapServer.destroy();
             clientConnection.close();
+            imapServer.destroy();
         }
 
         @Test

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 13/25: JAMES-3722 Tests for IMAP SELECT QRESYNC known sequences application

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 3c4c46cd4659afd6eb495e52467efed1c2b1fa9f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 14:49:32 2022 +0700

    JAMES-3722 Tests for IMAP SELECT QRESYNC known sequences application
    
    Known sequence sets are buggy and never restrict vanished replies
    
    Known sequence sets are a way to restrict the scope of vanished
    responses for servers not storing deletion sequences.
---
 .../james/imapserver/netty/IMAPServerTest.java     | 79 +++++++++++++++++++++-
 1 file changed, 78 insertions(+), 1 deletion(-)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 54ad3d6..aa00b8d 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -63,6 +63,7 @@ import javax.net.ssl.X509TrustManager;
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.net.imap.AuthenticatingIMAPClient;
 import org.apache.commons.net.imap.IMAPReply;
 import org.apache.commons.net.imap.IMAPSClient;
@@ -1574,7 +1575,83 @@ class IMAPServerTest {
             server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 5:11,28:36 (1,10,28 2,11,29)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
 
             assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
-                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10"))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 10:11,32"))
+                .hasSize(1);
+        }
+
+        @Disabled("JAMES-3722 Known sequence sets are buggy and never restrict vanished replies." +
+            "Known sequence sets are a way to restrict the scope of vanished responses for servers " +
+            "not storing deletion sequences.")
+        @Test
+        void knownUidSetShouldBeUsedToRestrictVanishedResponses() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            inbox.delete(ImmutableList.of(MessageUid.of(10), MessageUid.of(11), MessageUid.of(12),
+                MessageUid.of(25), MessageUid.of(26),
+                MessageUid.of(32)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            // MSN 1 => UID 2 MATCH
+            // MSN 13 => UID 17 MATCH
+            // MSN 28 => UID 30 MISMATCH stored value is 34
+            // Thus we know we can skip resynchronisation for UIDs up to 17
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 1:37 (1,13,28 2,17,30)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 25:26,32"))
+                .hasSize(1);
+        }
+
+        @Disabled("JAMES-3722 Known sequence sets are buggy and never restrict vanished replies." +
+            "Known sequence sets are a way to restrict the scope of vanished responses for servers " +
+            "not storing deletion sequences.")
+        @Test
+        void knownUidSetShouldTorelateDeletedMessages() throws Exception {
+            inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
+
+            ModSeq highestModSeq = memoryIntegrationResources.getMailboxManager().getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMetaData(false, mailboxSession, MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
+                .getHighestModSeq();
+
+            UidValidity uidValidity = memoryIntegrationResources.getMailboxManager()
+                .getMailbox(MailboxPath.inbox(USER), mailboxSession)
+                .getMailboxEntity().getUidValidity();
+
+            inbox.delete(ImmutableList.of(MessageUid.of(10), MessageUid.of(11), MessageUid.of(12),
+                MessageUid.of(25), MessageUid.of(26),
+                MessageUid.of(32)), mailboxSession);
+
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+            // MSN 1 => UID 2 MATCH
+            // MSN 13 => UID 17 MATCH
+            // MSN 28 => UID 32 MISMATCH stored value is 34 (32 not being stored)
+            // Thus we know we can skip resynchronisation for UIDs up to 17
+            server.write(ByteBuffer.wrap(String.format("I00104 SELECT INBOX (QRESYNC (%d %d 1:37 (1,13,28 2,17,32)))\r\n", uidValidity.asLong(), highestModSeq.asLong()).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("I00104 OK [READ-WRITE] SELECT completed.")))
+                .filteredOn(s -> s.contains("* VANISHED (EARLIER) 25:26,32"))
                 .hasSize(1);
         }
 

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 14/25: JAMES-3722 Fix for IMAP SELECT QRESYNC known sequences application

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit e33ef693b603cc674e177fb5f0f59493129c5d7b
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 14:54:15 2022 +0700

    JAMES-3722 Fix for IMAP SELECT QRESYNC known sequences application
    
    Known sequence sets are buggy and never restrict vanished replies
    
    Known sequence sets are a way to restrict the scope of vanished
    responses for servers not storing deletion sequences.
    
    Applying correctly the known sequence trick will dramatically reduce
    the amount of 'VANISHED' notifications sent in a real world scenario,
    saving network, bandwidth and processing time.
---
 .../imap/processor/AbstractSelectionProcessor.java | 82 ++++++++--------------
 .../james/imapserver/netty/IMAPServerTest.java     |  7 --
 2 files changed, 28 insertions(+), 61 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
index 5dfa2dc..0fcb04c 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/AbstractSelectionProcessor.java
@@ -21,7 +21,9 @@ package org.apache.james.imap.processor;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.IntStream;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.events.EventBus;
 import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.api.ImapMessage;
@@ -62,6 +64,8 @@ import org.slf4j.LoggerFactory;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 
+import io.vavr.Tuple;
+
 abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequest> extends AbstractMailboxProcessor<R> implements PermitEnableCapabilityProcessor {
     private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSelectionProcessor.class);
     private static final List<Capability> CAPS = ImmutableList.of(ImapConstants.SUPPORTS_QRESYNC, ImapConstants.SUPPORTS_CONDSTORE);
@@ -259,63 +263,33 @@ abstract class AbstractSelectionProcessor<R extends AbstractMailboxSelectionRequ
 
     @VisibleForTesting
     UidRange[] recomputeUidSet(IdRange[] knownSequences, UidRange[] knownUids, SelectedMailbox selected, UidRange[] uidSet) {
-        // Add all uids which are contained in the knownuidsset to a List so we can later access them via the index
-        List<MessageUid> knownUidsList = new ArrayList<>();
-        for (UidRange range : knownUids) {
-            for (MessageUid uid : range) {
-                knownUidsList.add(uid);
-            }
-        }
-
-        // loop over the known sequences and check the UID for MSN X again the known UID X
-        MessageUid firstUid = MessageUid.MIN_VALUE;
-        int index = 0;
-        for (IdRange knownSequence : knownSequences) {
-            boolean done = false;
-            for (Long msn : knownSequence) {
-
-                // Check if we have uids left to check against
-                if (knownUidsList.size() > index) {
-                    int msnAsInt = msn.intValue();
-                    MessageUid knownUid = knownUidsList.get(index);
-
-                    // Check if the uid match if not we are done here
-                    done = selected.uid(msnAsInt)
-                        .filter(selectedUid -> selectedUid.equals(knownUid))
-                        .isPresent();
-                    if (done) {
-                        break;
-                    } else {
-                        firstUid = knownUid;
-                    }
-                    index += 1;
-                } else {
-                    done = true;
-                    break;
-                }
-            }
-
-            // We found the first uid to start with
-            if (done) {
-                firstUid = firstUid.next();
-
-                // Ok now its time to filter out the IdRanges which we are not interested in
-                List<UidRange> filteredUidSet = new ArrayList<>();
-                for (UidRange r : uidSet) {
-                    if (r.getLowVal().compareTo(firstUid) < 0) {
-                        if (r.getHighVal().compareTo(firstUid) > 0) {
-                            filteredUidSet.add(new UidRange(firstUid, r.getHighVal()));
-                        }
-                    } else {
-                        filteredUidSet.add(r);
-                    }
+        int size = Math.min(knownSequences.length, knownUids.length);
+        MessageUid firstKnownUid = IntStream.range(0, size)
+            .mapToObj(i -> Pair.of(knownSequences[i], knownUids[i]))
+            // We are guarantied of the ranges being individual items
+            .map(pair -> Pair.of(pair.getLeft().getLowVal(), pair.getRight().getLowVal()))
+            // T3(MSN, UID, MATCH)
+            .map(pair -> Tuple.of(pair.getLeft(), pair.getRight(),
+                selected.uid(pair.getLeft().intValue())
+                    .filter(selectedUid -> selectedUid.equals(pair.getRight()))
+                    .isPresent()))
+            .takeWhile(t3 -> t3._3)
+            .map(t3 -> t3._2)
+            .reduce((t3_1, t3_2) -> t3_2)
+            .orElse(MessageUid.MIN_VALUE);
+
+        // Ok now its time to filter out the IdRanges which we are not interested in
+        List<UidRange> filteredUidSet = new ArrayList<>();
+        for (UidRange r : uidSet) {
+            if (r.getLowVal().compareTo(firstKnownUid) < 0) {
+                if (r.getHighVal().compareTo(firstKnownUid) > 0) {
+                    filteredUidSet.add(new UidRange(firstKnownUid, r.getHighVal()));
                 }
-                uidSet = filteredUidSet.toArray(UidRange[]::new);
-
-                break;
+            } else {
+                filteredUidSet.add(r);
             }
         }
-        return uidSet;
+        return filteredUidSet.toArray(UidRange[]::new);
     }
 
 
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index aa00b8d..5c0c424 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -63,7 +63,6 @@ import javax.net.ssl.X509TrustManager;
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.net.imap.AuthenticatingIMAPClient;
 import org.apache.commons.net.imap.IMAPReply;
 import org.apache.commons.net.imap.IMAPSClient;
@@ -1579,9 +1578,6 @@ class IMAPServerTest {
                 .hasSize(1);
         }
 
-        @Disabled("JAMES-3722 Known sequence sets are buggy and never restrict vanished replies." +
-            "Known sequence sets are a way to restrict the scope of vanished responses for servers " +
-            "not storing deletion sequences.")
         @Test
         void knownUidSetShouldBeUsedToRestrictVanishedResponses() throws Exception {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);
@@ -1617,9 +1613,6 @@ class IMAPServerTest {
                 .hasSize(1);
         }
 
-        @Disabled("JAMES-3722 Known sequence sets are buggy and never restrict vanished replies." +
-            "Known sequence sets are a way to restrict the scope of vanished responses for servers " +
-            "not storing deletion sequences.")
         @Test
         void knownUidSetShouldTorelateDeletedMessages() throws Exception {
             inbox.delete(ImmutableList.of(MessageUid.MIN_VALUE), mailboxSession);

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 08/25: JAMES-3722 IMAP FETCH parsing should support several modifiers

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit c0ac4ca3058b5258ae54cbe3cabc9919875db6c6
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 08:32:42 2022 +0700

    JAMES-3722 IMAP FETCH parsing should support several modifiers
---
 .../james/imap/decode/ImapRequestLineReader.java   |  2 +-
 .../imap/decode/parser/FetchCommandParser.java     | 43 +++++++++++-----------
 .../james/imapserver/netty/IMAPServerTest.java     |  2 -
 3 files changed, 23 insertions(+), 24 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestLineReader.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestLineReader.java
index 045ebec..85ea774 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestLineReader.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestLineReader.java
@@ -351,7 +351,7 @@ public abstract class ImapRequestLineReader {
         return consumeWord(validator, false);
     }
 
-    private String consumeWord(CharacterValidator validator, boolean stripParen) throws DecodingException {
+    public String consumeWord(CharacterValidator validator, boolean stripParen) throws DecodingException {
         StringBuilder atom = new StringBuilder();
 
         char next = nextWordChar();
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/FetchCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/FetchCommandParser.java
index d9b2fc2..f006dd2 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/FetchCommandParser.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/FetchCommandParser.java
@@ -81,39 +81,40 @@ public class FetchCommandParser extends AbstractUidCommandParser {
             }
             request.consumeChar(')');
             
-            
             next = nextNonSpaceChar(request);
             if (next == '(') {
                 request.consumeChar('(');
-
-                next = request.nextChar();
-                switch (next) {
-                case 'C':
-                    // Now check for the CHANGEDSINCE option which is part of CONDSTORE
-                    request.consumeWord(StringMatcherCharacterValidator.ignoreCase(CHANGEDSINCE));
-                    fetch.changedSince(request.number(true));
-                    break;
-                case 'V':
-                    // Check for the VANISHED option which is part of QRESYNC
-                    request.consumeWord(StringMatcherCharacterValidator.ignoreCase(VANISHED));
-                    fetch.vanished(true);
-                    break;
-                default:
-                    break;
-                }
-               
-                
+                parseModifier(request, fetch);
+                nextNonSpaceChar(request);
+                parseModifier(request, fetch);
+                nextNonSpaceChar(request);
                 request.consumeChar(')');
-
             }
         } else {
             addNextElement(request, fetch);
-
         }
 
         return fetch.build();
     }
 
+    private void parseModifier(ImapRequestLineReader request, FetchData.Builder fetch) throws DecodingException {
+        char next = request.nextChar();
+        switch (next) {
+        case 'C':
+            // Now check for the CHANGEDSINCE option which is part of CONDSTORE
+            request.consumeWord(StringMatcherCharacterValidator.ignoreCase(CHANGEDSINCE));
+            fetch.changedSince(request.number(true));
+            break;
+        case 'V':
+            // Check for the VANISHED option which is part of QRESYNC
+            request.consumeWord(StringMatcherCharacterValidator.ignoreCase(VANISHED), true);
+            fetch.vanished(true);
+            break;
+        default:
+            break;
+        }
+    }
+
     private void addNextElement(ImapRequestLineReader reader, FetchData.Builder fetch) throws DecodingException {
         // String name = element.toString();
         String name = readWord(reader, " [)\r\n");
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 686c624..8b470b5 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1605,8 +1605,6 @@ class IMAPServerTest {
                 .hasSize(1);
         }
 
-        @Disabled("JAMES-3722 IMAP stack failed to parse FETCH command with two modifiers and thus do" +
-            "not conform to the example of the RFC-5162")
         @Test
         void fetchShouldSupportVanishedModifiedWithEarlierTag() throws Exception {
             inbox.delete(ImmutableList.of(MessageUid.of(14)), mailboxSession);

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 10/25: JAMES-3722 Test IMAP CLOSE in regard of QRESYNC

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 63f93c47ffff2476b4e25854c88d4f9e09f5bc02
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Mar 8 08:49:57 2022 +0700

    JAMES-3722 Test IMAP CLOSE in regard of QRESYNC
---
 .../james/imapserver/netty/IMAPServerTest.java     | 23 ++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index fb2d92b..e4979ea 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -1752,5 +1752,28 @@ class IMAPServerTest {
                 .filteredOn(s -> s.contains("* OK [CLOSED]"))
                 .hasSize(1);
         }
+
+        @Test
+        void closeShouldNotReturnHighestModseqWhenUsingQResync() throws Exception {
+            // See https://www.rfc-editor.org/errata_search.php?rfc=5162
+            memoryIntegrationResources.getMailboxManager()
+                .createMailbox(MailboxPath.forUser(USER, "other"), mailboxSession);
+            SocketChannel server = SocketChannel.open();
+            server.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(server);
+
+            server.write(ByteBuffer.wrap(String.format("a0 LOGIN %s %s\r\n", USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+            readBytes(server);
+            server.write(ByteBuffer.wrap(("a1 ENABLE QRESYNC\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a1 OK ENABLE completed."));
+
+            server.write(ByteBuffer.wrap(("a2 SELECT INBOX\r\n").getBytes(StandardCharsets.UTF_8)));
+            readStringUntil(server, s -> s.contains("a2 OK [READ-WRITE] SELECT completed."));
+
+            server.write(ByteBuffer.wrap(("a3 CLOSE\r\n").getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(readStringUntil(server, s -> s.contains("a3 OK CLOSE completed.")))
+                .isNotNull();
+        }
     }
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org