You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2022/12/05 02:51:43 UTC

[james-project] branch master updated (390be9e9e3 -> a886ba7f0c)

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

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


    from 390be9e9e3 [FIX] Leak in ActiveMQHealthCheck
     new 96f872531f JAMES-3754 - IMAP support List command extension RFC-5258 - SUBSCRIBED & RECURSIVEMATCH
     new a886ba7f0c JAMES-3754 - IMAP support List command extension RFC-5258 - REMOTE

The 2 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:
 .../mpt/imapmailbox/suite/AuthenticatedState.java  |   5 +
 .../james/mpt/imapmailbox/suite/SelectedInbox.java |   7 +
 .../apache/james/imap/scripts/ListSubscribed.test  | 136 +++++++++++++++
 .../org/apache/james/imap/api/ImapConstants.java   |   3 +
 .../imap/decode/parser/ListCommandParser.java      | 190 ++++++++++++++++++++-
 .../james/imap/encode/ListingEncodingUtils.java    |  57 +++++++
 .../james/imap/message/request/ListRequest.java    |  54 +++++-
 .../james/imap/message/request/XListRequest.java   |   5 +-
 .../james/imap/message/response/ListResponse.java  | 126 +++++++++++++-
 .../james/imap/processor/DefaultProcessor.java     |   4 +-
 .../apache/james/imap/processor/ListProcessor.java | 176 ++++++++++++++-----
 .../james/imap/processor/XListProcessor.java       |   8 +-
 .../james/imap/encode/ListResponseEncoderTest.java |   4 +-
 .../imap/encode/ListingEncodingUtilsTest.java      |  24 +--
 14 files changed, 737 insertions(+), 62 deletions(-)
 create mode 100644 mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListSubscribed.test


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


[james-project] 01/02: JAMES-3754 - IMAP support List command extension RFC-5258 - SUBSCRIBED & RECURSIVEMATCH

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

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

commit 96f872531f04e486aaa2deb8f6bc2f53d4cab5a7
Author: Tung Van TRAN <vt...@linagora.com>
AuthorDate: Thu Nov 24 08:35:12 2022 +0700

    JAMES-3754 - IMAP support List command extension RFC-5258 - SUBSCRIBED & RECURSIVEMATCH
---
 .../mpt/imapmailbox/suite/AuthenticatedState.java  |   5 +
 .../james/mpt/imapmailbox/suite/SelectedInbox.java |   7 +
 .../apache/james/imap/scripts/ListSubscribed.test  | 132 ++++++++++++++
 .../org/apache/james/imap/api/ImapConstants.java   |   3 +
 .../imap/decode/parser/ListCommandParser.java      | 190 ++++++++++++++++++++-
 .../james/imap/encode/ListingEncodingUtils.java    |  57 +++++++
 .../james/imap/message/request/ListRequest.java    |  51 +++++-
 .../james/imap/message/request/XListRequest.java   |   5 +-
 .../james/imap/message/response/ListResponse.java  | 126 +++++++++++++-
 .../james/imap/processor/DefaultProcessor.java     |   4 +-
 .../apache/james/imap/processor/ListProcessor.java | 171 ++++++++++++++-----
 .../james/imap/processor/XListProcessor.java       |   8 +-
 .../james/imap/encode/ListResponseEncoderTest.java |   4 +-
 .../imap/encode/ListingEncodingUtilsTest.java      |  24 +--
 14 files changed, 725 insertions(+), 62 deletions(-)

diff --git a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/AuthenticatedState.java b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/AuthenticatedState.java
index f7c3f4592b..1c1606d174 100644
--- a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/AuthenticatedState.java
+++ b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/AuthenticatedState.java
@@ -99,6 +99,11 @@ public abstract class AuthenticatedState extends BasicImapCommands {
         simpleScriptedTestProtocol.run("ListMailboxes");
     }
 
+    @Test
+    public void testListSubscribed() throws Exception {
+        simpleScriptedTestProtocol.run("ListSubscribed");
+    }
+
     @Test
     public void testStatusUS() throws Exception {
         simpleScriptedTestProtocol.run("Status");
diff --git a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/SelectedInbox.java b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/SelectedInbox.java
index 3971d04561..2c33f9581a 100644
--- a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/SelectedInbox.java
+++ b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/SelectedInbox.java
@@ -109,6 +109,13 @@ public abstract class SelectedInbox implements ImapTestConstants {
             .run("ListMailboxes");
     }
 
+    @Test
+    public void testListSubscribed() throws Exception {
+        simpleScriptedTestProtocol
+            .withLocale(Locale.US)
+            .run("ListSubscribed");
+    }
+
     @Test
     public void testStatusUS() throws Exception {
         simpleScriptedTestProtocol
diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListSubscribed.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListSubscribed.test
new file mode 100644
index 0000000000..7c317a7194
--- /dev/null
+++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListSubscribed.test
@@ -0,0 +1,132 @@
+################################################################
+# Licensed to the Apache Software Foundation (ASF) under one   #
+# or more contributor license agreements.  See the NOTICE file #
+# distributed with this work for additional information        #
+# regarding copyright ownership.  The ASF licenses this file   #
+# to you under the Apache License, Version 2.0 (the            #
+# "License"); you may not use this file except in compliance   #
+# with the License.  You may obtain a copy of the License at   #
+#                                                              #
+#   http://www.apache.org/licenses/LICENSE-2.0                 #
+#                                                              #
+# Unless required by applicable law or agreed to in writing,   #
+# software distributed under the License is distributed on an  #
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       #
+# KIND, either express or implied.  See the License for the    #
+# specific language governing permissions and limitations      #
+# under the License.                                           #
+################################################################
+# Create a few folders
+C: 10 CREATE listtest
+S: 10 OK \[MAILBOXID \(.+\)\] CREATE completed.
+C: 11 CREATE listtest.subfolder
+S: 11 OK \[MAILBOXID \(.+\)\] CREATE completed.
+C: 12 CREATE listtest1
+S: 12 OK \[MAILBOXID \(.+\)\] CREATE completed.
+C: 13 CREATE listtest1.subfolder1
+S: 13 OK \[MAILBOXID \(.+\)\] CREATE completed.
+
+# List select option: SUBSCRIBE
+C: a02 SUBSCRIBE listtest
+S: a02 OK SUBSCRIBE completed.
+C: A01 LIST (SUBSCRIBED) "" "*"
+SUB {
+S: \* LIST \(\\HasChildren \\Subscribed\) \"\.\" "listtest"
+}
+S: A01 OK LIST completed.
+
+# SUBSCRIBE attribute should work on child
+C: a022 SUBSCRIBE listtest.subfolder
+S: a022 OK SUBSCRIBE completed.
+C: A012 LIST (SUBSCRIBED) "" "*"
+SUB {
+S: \* LIST \(\\HasChildren \\Subscribed\) \"\.\" "listtest"
+S: \* LIST \(\\HasNoChildren \\Subscribed\) \"\.\" "listtest.subfolder"
+}
+S: A012 OK LIST completed.
+C: A013 UNSUBSCRIBE listtest.subfolder
+S: A013 OK UNSUBSCRIBE completed.
+
+# SUBSCRIBE should work when parent search
+C: A10 LIST (SUBSCRIBED) "" "%"
+SUB {
+S: \* LIST \(\\HasChildren \\Subscribed\) \"\.\" "listtest"
+}
+S: A10 OK LIST completed.
+
+C: A12 SUBSCRIBE listtest.subfolder
+S: A12 OK SUBSCRIBE completed.
+C: A13 LIST (SUBSCRIBED) "" "%"
+SUB {
+S: \* LIST \(\\HasChildren \\Subscribed\) \"\.\" "listtest"
+}
+S: A13 OK LIST completed.
+
+C: A14 UNSUBSCRIBE listtest.subfolder
+S: A14 OK UNSUBSCRIBE completed.
+C: A15 UNSUBSCRIBE listtest
+S: A15 OK UNSUBSCRIBE completed.
+C: A16 LIST (SUBSCRIBED) "" "%"
+S: A16 OK LIST completed.
+
+# RECURSIVEMATCH should work with SUBSCRIBED
+C: A21 SUBSCRIBE listtest.subfolder
+S: A21 OK SUBSCRIBE completed.
+C: A22 LIST (SUBSCRIBED RECURSIVEMATCH) "" "%"
+SUB {
+S: \* LIST \(\\HasChildren\) \"\.\" "listtest" \(\"CHILDINFO\" \(\"SUBSCRIBED\"\)\)
+}
+S: A22 OK LIST completed.
+
+C: A231 CREATE another1
+S: A231 OK \[MAILBOXID \(.+\)\] CREATE completed.
+C: A232 SUBSCRIBE listtest1.subfolder1
+S: A232 OK SUBSCRIBE completed.
+C: A233 SUBSCRIBE another1
+S: A233 OK SUBSCRIBE completed.
+
+C: A24 LIST (RECURSIVEMATCH SUBSCRIBED) "" "*1"
+SUB {
+S: \* LIST \(\\HasChildren\) \"\.\" "listtest1" \(\"CHILDINFO\" \(\"SUBSCRIBED\"\)\)
+S: \* LIST \(\\HasNoChildren \\Subscribed\) \"\.\" "listtest1.subfolder1"
+S: \* LIST \(\\HasNoChildren \\Subscribed\) \"\.\" "another1"
+}
+S: A24 OK LIST completed.
+
+C: A25 UNSUBSCRIBE listtest1.subfolder1
+S: A25 OK UNSUBSCRIBE completed.
+C: A14 UNSUBSCRIBE listtest.subfolder
+S: A14 OK UNSUBSCRIBE completed.
+C: A15 UNSUBSCRIBE another1
+S: A15 OK UNSUBSCRIBE completed.
+C: A16 DELETE another1
+S: A16 OK DELETE completed.
+
+# Response should has `NonExistent` attribute when the subscribed mailbox is not exist
+C: a03 SUBSCRIBE subscribeNonExistent
+S: a03 OK SUBSCRIBE completed.
+C: A04 LIST (SUBSCRIBED) "" "*"
+SUB {
+S: \* LIST \(\\Subscribed \\NonExistent\) \"\.\" "subscribeNonExistent"
+}
+S: A04 OK LIST completed.
+
+# Response should has `NonExistent` attribute when subscribed on children
+C: a05 SUBSCRIBE subscribeNonExistent.child1
+S: a05 OK SUBSCRIBE completed.
+C: A06 LIST (SUBSCRIBED) "" "*"
+SUB {
+S: \* LIST \(\\Subscribed \\NonExistent\) \"\.\" "subscribeNonExistent"
+S: \* LIST \(\\Subscribed \\NonExistent\) \"\.\" "subscribeNonExistent.child1"
+}
+S: A06 OK LIST completed.
+
+C: a07 SUBSCRIBE subscribeNonExistent2.child2
+S: a07 OK SUBSCRIBE completed.
+C: A08 LIST (SUBSCRIBED) "" "*"
+SUB {
+S: \* LIST \(\\Subscribed \\NonExistent\) \"\.\" "subscribeNonExistent"
+S: \* LIST \(\\Subscribed \\NonExistent\) \"\.\" "subscribeNonExistent.child1"
+S: \* LIST \(\\Subscribed \\NonExistent\) \"\.\" "subscribeNonExistent2.child2"
+}
+S: A08 OK LIST completed.
\ No newline at end of file
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java b/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
index 211c4c9d1d..c1029b683e 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
@@ -141,6 +141,9 @@ public interface ImapConstants {
 
     byte[] NAME_ATTRIBUTE_HAS_NO_CHILDREN = "\\HasNoChildren".getBytes(US_ASCII);
 
+    byte[] NAME_ATTRIBUTE_SUBSCRIBED = "\\Subscribed".getBytes(US_ASCII);
+
+    byte[] NAME_ATTRIBUTE_NON_EXISTENT = "\\NonExistent".getBytes(US_ASCII);
 
     char BACK_SLASH = '\\';
 
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ListCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ListCommandParser.java
index 1d5bb5fba5..f147718763 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ListCommandParser.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ListCommandParser.java
@@ -18,23 +18,32 @@
  ****************************************************************/
 package org.apache.james.imap.decode.parser;
 
+import java.util.EnumSet;
+import java.util.Optional;
+
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.imap.api.ImapCommand;
 import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.api.ImapMessage;
 import org.apache.james.imap.api.Tag;
+import org.apache.james.imap.api.display.HumanReadableText;
+import org.apache.james.imap.api.message.StatusDataItems;
 import org.apache.james.imap.api.message.response.StatusResponseFactory;
 import org.apache.james.imap.api.process.ImapSession;
 import org.apache.james.imap.decode.DecodingException;
 import org.apache.james.imap.decode.ImapRequestLineReader;
 import org.apache.james.imap.decode.ImapRequestLineReader.AtomCharValidator;
 import org.apache.james.imap.message.request.ListRequest;
+import org.apache.james.imap.message.request.ListRequest.ListReturnOption;
+import org.apache.james.imap.message.request.ListRequest.ListSelectOption;
 
 /**
  * Parse LIST commands
  */
 public class ListCommandParser extends AbstractUidCommandParser {
+
     private static class ListCharValidator extends AtomCharValidator {
-        public static ImapRequestLineReader.CharacterValidator INSTANCE = new ListCharValidator();
+        public static final ImapRequestLineReader.CharacterValidator INSTANCE = new ListCharValidator();
 
         @Override
         public boolean isValid(char chr) {
@@ -74,13 +83,190 @@ public class ListCommandParser extends AbstractUidCommandParser {
 
     @Override
     protected ImapMessage decode(ImapRequestLineReader request, Tag tag, boolean useUids, ImapSession session) throws DecodingException {
+        EnumSet<ListSelectOption> listOptions = parseSelectOptions(request);
         String referenceName = request.mailbox();
         String mailboxPattern = listMailbox(request);
+
+        Pair<EnumSet<ListReturnOption>, Optional<StatusDataItems>> listReturnOptions = parseReturnOptions(request);
         request.eol();
-        return createMessage(referenceName, mailboxPattern, tag);
+
+        if (listOptions.isEmpty() && listReturnOptions.getLeft().isEmpty()) {
+            return createMessage(referenceName, mailboxPattern, tag);
+        }
+        return new ListRequest(ImapConstants.LIST_COMMAND, referenceName, mailboxPattern, tag, listOptions, listReturnOptions.getLeft(), listReturnOptions.getRight());
     }
 
     protected ImapMessage createMessage(String referenceName, String mailboxPattern, Tag tag) {
         return new ListRequest(referenceName, mailboxPattern, tag);
     }
+
+    private EnumSet<ListSelectOption> parseSelectOptions(ImapRequestLineReader request) throws DecodingException {
+        EnumSet<ListSelectOption> listOptions = EnumSet.noneOf(ListSelectOption.class);
+        if (request.nextWordChar() != '(') {
+            return listOptions;
+        }
+
+        request.consumeChar('(');
+        request.nextWordChar();
+
+        while (request.nextChar() != ')') {
+            listOptions.add(parseListSelectOption(request));
+            request.nextWordChar();
+        }
+        request.consumeChar(')');
+        return listOptions;
+    }
+
+    private ListSelectOption parseListSelectOption(ImapRequestLineReader request) throws DecodingException {
+        char c = request.nextWordChar();
+        if (c == 'r' || c == 'R') {
+            return readR(request);
+        }
+        if (c == 's' || c == 'S') {
+            return readSubscribed(request);
+        }
+        throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS,
+            "Unknown select option: '" + request.consumeWord(ImapRequestLineReader.NoopCharValidator.INSTANCE) + "'");
+    }
+
+    private Pair<ListReturnOption, Optional<StatusDataItems>> readS(ImapRequestLineReader request) throws DecodingException {
+        request.consume();
+        char c = request.nextWordChar();
+        if (c == 'T' || c == 't') {
+            return readStatus(request);
+        } else {
+            return Pair.of(readReturnSubscribed(request), Optional.empty());
+        }
+    }
+
+    private Pair<ListReturnOption, Optional<StatusDataItems>> readStatus(ImapRequestLineReader request) throws DecodingException {
+        // 'S' is already consummed
+        assertChar(request, 'T', 't');
+        assertChar(request, 'A', 'a');
+        assertChar(request, 'T', 't');
+        assertChar(request, 'U', 'u');
+        assertChar(request, 'S', 's');
+        return Pair.of(ListReturnOption.STATUS, Optional.empty());
+    }
+
+    private ListSelectOption readSubscribed(ImapRequestLineReader request) throws DecodingException {
+        consumeSubscribed(request);
+        return ListSelectOption.SUBSCRIBED;
+    }
+
+    private ListSelectOption readR(ImapRequestLineReader request) throws DecodingException {
+        request.consume();
+        char c2 = request.nextChar();
+        if (c2 == 'e' || c2 == 'E') {
+            request.consume();
+            char c3 = request.nextChar();
+            if (c3 == 'm' || c3 == 'M') {
+                return readRemote(request);
+            } else {
+                return readRecursivematch(request);
+            }
+        }
+        throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS,
+            "Unknown select option: '" + request.consumeWord(ImapRequestLineReader.NoopCharValidator.INSTANCE) + "'");
+    }
+
+    private ListSelectOption readRemote(ImapRequestLineReader request) throws DecodingException {
+        assertChar(request, 'M', 'm');
+        assertChar(request, 'O', 'o');
+        assertChar(request, 'T', 't');
+        assertChar(request, 'E', 'e');
+        return ListSelectOption.REMOTE;
+    }
+
+    private ListSelectOption readRecursivematch(ImapRequestLineReader request) throws DecodingException {
+        assertChar(request, 'C', 'c');
+        assertChar(request, 'U', 'u');
+        assertChar(request, 'R', 'r');
+        assertChar(request, 'S', 's');
+        assertChar(request, 'I', 'i');
+        assertChar(request, 'V', 'v');
+        assertChar(request, 'E', 'e');
+        assertChar(request, 'M', 'm');
+        assertChar(request, 'A', 'a');
+        assertChar(request, 'T', 't');
+        assertChar(request, 'C', 'c');
+        assertChar(request, 'H', 'h');
+        return ListSelectOption.RECURSIVEMATCH;
+    }
+
+    private Pair<EnumSet<ListReturnOption>, Optional<StatusDataItems>> parseReturnOptions(ImapRequestLineReader request) throws DecodingException {
+        if (request.nextWordCharLenient().isPresent()) {
+            String returnKey = request.consumeWord(AtomCharValidator.INSTANCE);
+            if ("RETURN".equalsIgnoreCase(returnKey)) {
+                EnumSet<ListReturnOption> listReturnOptions = EnumSet.noneOf(ListReturnOption.class);
+                request.nextWordChar();
+                request.consumeChar('(');
+                request.nextWordChar();
+
+                Optional<StatusDataItems> statusDataItems = Optional.empty();
+                while (request.nextChar() != ')') {
+                    Pair<ListReturnOption, Optional<StatusDataItems>> listReturnOption = parseListReturnOption(request);
+                    listReturnOptions.add(listReturnOption.getLeft());
+                    if (listReturnOption.getRight().isPresent()) {
+                        statusDataItems = listReturnOption.getRight();
+                    }
+                    request.nextWordChar();
+                }
+                request.consumeChar(')');
+                return Pair.of(listReturnOptions, statusDataItems);
+            } else {
+                throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Unknown '" + returnKey + "' option'");
+            }
+        }
+        return Pair.of(EnumSet.noneOf(ListReturnOption.class), Optional.empty());
+    }
+
+    private Pair<ListReturnOption, Optional<StatusDataItems>> parseListReturnOption(ImapRequestLineReader request) throws DecodingException {
+        char c = request.nextWordChar();
+        if (c == 'c' || c == 'C') {
+            return Pair.of(readChildren(request), Optional.empty());
+        }
+        if (c == 's' || c == 'S') {
+            return readS(request);
+        }
+        throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS,
+            "Unknown return option: '" + request.consumeWord(ImapRequestLineReader.NoopCharValidator.INSTANCE) + "'");
+    }
+
+    private ListReturnOption readChildren(ImapRequestLineReader request) throws DecodingException {
+        assertChar(request, 'C', 'c');
+        assertChar(request, 'H', 'h');
+        assertChar(request, 'I', 'i');
+        assertChar(request, 'L', 'l');
+        assertChar(request, 'D', 'd');
+        assertChar(request, 'R', 'r');
+        assertChar(request, 'E', 'e');
+        assertChar(request, 'N', 'n');
+        return ListReturnOption.CHILDREN;
+    }
+
+    private ListReturnOption readReturnSubscribed(ImapRequestLineReader request) throws DecodingException {
+        consumeSubscribed(request);
+        return ListReturnOption.SUBSCRIBED;
+    }
+
+    private void consumeSubscribed(ImapRequestLineReader request) throws DecodingException {
+        assertChar(request, 'S', 's');
+        assertChar(request, 'U', 'u');
+        assertChar(request, 'B', 'b');
+        assertChar(request, 'S', 's');
+        assertChar(request, 'C', 'c');
+        assertChar(request, 'R', 'r');
+        assertChar(request, 'I', 'i');
+        assertChar(request, 'B', 'b');
+        assertChar(request, 'E', 'e');
+        assertChar(request, 'D', 'd');
+    }
+
+    private void assertChar(ImapRequestLineReader reader, char c, char cUp) throws DecodingException {
+        char c2 = reader.consume();
+        if (c2 != c && c2 != cUp) {
+            throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Unexpected token in select option. Expecting " + c + " got " + c2);
+        }
+    }
 }
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/encode/ListingEncodingUtils.java b/protocols/imap/src/main/java/org/apache/james/imap/encode/ListingEncodingUtils.java
index c1482af8b9..f0b22e2f24 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/encode/ListingEncodingUtils.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/encode/ListingEncodingUtils.java
@@ -19,18 +19,26 @@
 
 package org.apache.james.imap.encode;
 
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static org.apache.james.imap.api.ImapConstants.NAME_ATTRIBUTE_NON_EXISTENT;
+import static org.apache.james.imap.api.ImapConstants.NAME_ATTRIBUTE_SUBSCRIBED;
+
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
 
 import org.apache.james.imap.api.ImapCommand;
 import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.api.process.MailboxType;
 import org.apache.james.imap.message.response.AbstractListingResponse;
+import org.apache.james.imap.message.response.ListResponse;
 import org.apache.james.mailbox.model.MailboxMetaData;
 
 import com.google.common.collect.ImmutableList;
 
 public class ListingEncodingUtils {
+    public static final byte[] NAME_CHILDINFO_EXTENDED = "\"CHILDINFO\"".getBytes(US_ASCII);
+    public static final byte[] NAME_CHILDINFO_SUBSCRIBED = "\"SUBSCRIBED\"".getBytes(US_ASCII);
 
     public static void encodeListingResponse(ImapCommand command, ImapResponseComposer composer, AbstractListingResponse response) throws IOException {
         composer.untagged();
@@ -42,6 +50,10 @@ public class ListingEncodingUtils {
         composer.closeParen();
         writeDelimiter(composer, response.getHierarchyDelimiter());
         composer.mailbox(response.getName());
+
+        if (response instanceof ListResponse) {
+            writeChildInfos(returnChildInfosAsString(((ListResponse) response).getChildInfos()), composer);
+        }
         composer.end();
     }
 
@@ -53,6 +65,19 @@ public class ListingEncodingUtils {
         }
     }
 
+    private static void writeChildInfos(ImmutableList<byte[]> childInfos, ImapResponseComposer composer) throws IOException {
+        if (!childInfos.isEmpty()) {
+            composer.openParen();
+            composer.message(NAME_CHILDINFO_EXTENDED);
+            composer.openParen();
+            for (byte[] childInfo : childInfos) {
+                composer.message(childInfo);
+            }
+            composer.closeParen();
+            composer.closeParen();
+        }
+    }
+
     private static ImmutableList<byte[]> getNameAttributes(AbstractListingResponse response) {
         ImmutableList.Builder<byte[]> builder = ImmutableList.builder();
 
@@ -60,6 +85,12 @@ public class ListingEncodingUtils {
         childrenAsString(response.getChildren(), builder);
         mailboxAttributeAsString(response.getType(), builder);
 
+        if (response instanceof ListResponse) {
+            ListResponse listResponse = (ListResponse) response;
+            returnSubscribedAsString(listResponse.isReturnSubscribed(), builder);
+            returnNonExistentAsString(listResponse.isReturnNonExistent(), builder);
+        }
+
         return builder.build();
     }
 
@@ -97,4 +128,30 @@ public class ListingEncodingUtils {
         }
         return builder;
     }
+
+    private static ImmutableList.Builder<byte[]> returnSubscribedAsString(boolean returnSubscribed, ImmutableList.Builder<byte[]> builder) {
+        if (returnSubscribed) {
+            return builder.add(NAME_ATTRIBUTE_SUBSCRIBED);
+        }
+        return builder;
+    }
+
+    private static ImmutableList.Builder<byte[]> returnNonExistentAsString(boolean returnNonExistent, ImmutableList.Builder<byte[]> builder) {
+        if (returnNonExistent) {
+            return builder.add(NAME_ATTRIBUTE_NON_EXISTENT);
+        }
+        return builder;
+    }
+
+    private static ImmutableList<byte[]> returnChildInfosAsString(EnumSet<ListResponse.ChildInfo> childInfos) {
+        ImmutableList.Builder<byte[]> builder = ImmutableList.builder();
+        if (childInfos != null && !childInfos.isEmpty()) {
+            childInfos.forEach(childInfo -> {
+                if (childInfo == ListResponse.ChildInfo.SUBSCRIBED) {
+                    builder.add(NAME_CHILDINFO_SUBSCRIBED);
+                }
+            });
+        }
+        return builder.build();
+    }
 }
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/request/ListRequest.java b/protocols/imap/src/main/java/org/apache/james/imap/message/request/ListRequest.java
index 798d747200..24de0dc4f0 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/message/request/ListRequest.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/request/ListRequest.java
@@ -18,24 +18,52 @@
  ****************************************************************/
 package org.apache.james.imap.message.request;
 
+import java.util.EnumSet;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
 import org.apache.james.imap.api.ImapCommand;
 import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.api.Tag;
+import org.apache.james.imap.api.message.StatusDataItems;
 
 import com.google.common.base.MoreObjects;
 
 public class ListRequest extends AbstractImapRequest {
+
+    // https://www.rfc-editor.org/rfc/rfc5258.html
+    public enum ListSelectOption {
+        REMOTE,
+        RECURSIVEMATCH,
+        SUBSCRIBED
+    }
+
+    // https://www.rfc-editor.org/rfc/rfc5258.html
+    public enum ListReturnOption {
+        CHILDREN,
+        SUBSCRIBED,
+        // https://www.rfc-editor.org/rfc/rfc5819.html LIST STATUS
+        STATUS
+    }
+
     private final String baseReferenceName;
     private final String mailboxPattern;
+    private final EnumSet<ListSelectOption> selectOptions;
+    private final EnumSet<ListReturnOption> returnOptions;
+    private final Optional<StatusDataItems> statusDataItems;
 
     public ListRequest(String referenceName, String mailboxPattern, Tag tag) {
-        this(ImapConstants.LIST_COMMAND, referenceName, mailboxPattern, tag);
+        this(ImapConstants.LIST_COMMAND, referenceName, mailboxPattern, tag, EnumSet.noneOf(ListSelectOption.class), EnumSet.noneOf(ListReturnOption.class), Optional.empty());
     }
 
-    public ListRequest(ImapCommand imapCommand, String referenceName, String mailboxPattern, Tag tag) {
+    public ListRequest(ImapCommand imapCommand, String referenceName, String mailboxPattern, Tag tag,
+                       EnumSet<ListSelectOption> selectOptions, EnumSet<ListReturnOption> returnOptions, Optional<StatusDataItems> statusDataItems) {
         super(tag, imapCommand);
         this.baseReferenceName = referenceName;
         this.mailboxPattern = mailboxPattern;
+        this.selectOptions = selectOptions;
+        this.returnOptions = returnOptions;
+        this.statusDataItems = statusDataItems;
     }
 
     public final String getBaseReferenceName() {
@@ -46,11 +74,30 @@ public class ListRequest extends AbstractImapRequest {
         return mailboxPattern;
     }
 
+    public EnumSet<ListSelectOption> getSelectOptions() {
+        return selectOptions;
+    }
+
+    public EnumSet<ListReturnOption> getReturnOptions() {
+        return returnOptions;
+    }
+
+    public boolean selectSubscribed() {
+        return getSelectOptions().contains(ListSelectOption.SUBSCRIBED);
+    }
+
+    public Optional<StatusDataItems> getStatusDataItems() {
+        return statusDataItems;
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
             .add("baseReferenceName", baseReferenceName)
             .add("mailboxPattern", mailboxPattern)
+            .add("selectOptions", selectOptions)
+            .add("returnOptions", returnOptions)
+            .add("statusDataItem", statusDataItems.orElse(null))
             .toString();
     }
 }
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/request/XListRequest.java b/protocols/imap/src/main/java/org/apache/james/imap/message/request/XListRequest.java
index d6472e1dd7..24ea823816 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/message/request/XListRequest.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/request/XListRequest.java
@@ -20,6 +20,9 @@ package org.apache.james.imap.message.request;
 
 import static org.apache.james.imap.api.ImapConstants.XLIST_COMMAND;
 
+import java.util.EnumSet;
+import java.util.Optional;
+
 import org.apache.james.imap.api.Tag;
 
 /**
@@ -27,6 +30,6 @@ import org.apache.james.imap.api.Tag;
  */
 public class XListRequest extends ListRequest {
     public XListRequest(String referenceName, String mailboxPattern, Tag tag) {
-        super(XLIST_COMMAND, referenceName, mailboxPattern, tag);
+        super(XLIST_COMMAND, referenceName, mailboxPattern, tag, EnumSet.noneOf(ListSelectOption.class), EnumSet.noneOf(ListReturnOption.class), Optional.empty());
     }
 }
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/response/ListResponse.java b/protocols/imap/src/main/java/org/apache/james/imap/message/response/ListResponse.java
index cde9f35b9e..cc0513b76e 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/message/response/ListResponse.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/response/ListResponse.java
@@ -18,15 +18,139 @@
  ****************************************************************/
 package org.apache.james.imap.message.response;
 
+
+import static org.apache.james.imap.processor.ListProcessor.RETURN_NON_EXISTENT;
+import static org.apache.james.imap.processor.ListProcessor.RETURN_SUBSCRIBED;
+
+import java.util.EnumSet;
+
 import org.apache.james.imap.api.message.response.ImapResponseMessage;
 import org.apache.james.imap.api.process.MailboxType;
+import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxMetaData;
+import org.apache.james.mailbox.model.MailboxPath;
+
+import com.google.common.collect.ImmutableSet;
 
 /**
  * Values an IMAP4rev1 <code>LIST</code> response.
  */
 public final class ListResponse extends AbstractListingResponse implements ImapResponseMessage {
-    public ListResponse(MailboxMetaData.Children children, MailboxMetaData.Selectability selectability, String name, char hierarchyDelimiter) {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private MailboxMetaData.Children children;
+        private MailboxMetaData.Selectability selectability;
+        private String name;
+        private char hierarchyDelimiter;
+        private boolean returnSubscribed;
+        private boolean returnNonExistent;
+        private ImmutableSet.Builder<ChildInfo> childInfos;
+
+        public Builder() {
+            this.childInfos = ImmutableSet.builder();
+        }
+
+        public Builder children(MailboxMetaData.Children children) {
+            this.children = children;
+            return this;
+        }
+
+        public Builder selectability(MailboxMetaData.Selectability selectability) {
+            this.selectability = selectability;
+            return this;
+        }
+
+        public Builder name(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder hierarchyDelimiter(char hierarchyDelimiter) {
+            this.hierarchyDelimiter = hierarchyDelimiter;
+            return this;
+        }
+
+        public Builder returnSubscribed(boolean returnSubscribed) {
+            this.returnSubscribed = returnSubscribed;
+            return this;
+        }
+
+        public Builder returnNonExistent(boolean returnNonExistent) {
+            this.returnNonExistent = returnNonExistent;
+            return this;
+        }
+
+        public Builder childInfos(ChildInfo... childInfos) {
+            this.childInfos.add(childInfos);
+            return this;
+        }
+
+        public Builder forMetaData(MailboxMetaData mailboxMetaData) {
+            return children(mailboxMetaData.inferiors())
+                .selectability(mailboxMetaData.getSelectability())
+                .hierarchyDelimiter(mailboxMetaData.getHierarchyDelimiter())
+                .returnNonExistent(!RETURN_NON_EXISTENT);
+        }
+
+        public Builder nonExitingSubscribedMailbox(MailboxPath subscribedPath) {
+            return name(subscribedPath.getName())
+                .children(MailboxMetaData.Children.CHILDREN_ALLOWED_BUT_UNKNOWN)
+                .selectability(MailboxMetaData.Selectability.NONE)
+                .hierarchyDelimiter(MailboxConstants.DEFAULT_DELIMITER)
+                .returnSubscribed(RETURN_SUBSCRIBED)
+                .returnNonExistent(RETURN_NON_EXISTENT);
+        }
+
+        private EnumSet<ChildInfo> buildChildInfos() {
+            ImmutableSet<ChildInfo> childInfoImmutableSet = childInfos.build();
+            if (childInfoImmutableSet.isEmpty()) {
+                return EnumSet.noneOf(ChildInfo.class);
+            } else {
+                return EnumSet.copyOf(childInfoImmutableSet);
+            }
+        }
+
+        public ListResponse build() {
+
+            return new ListResponse(children, selectability, name, hierarchyDelimiter, returnSubscribed,
+                returnNonExistent, buildChildInfos());
+        }
+    }
+
+
+    // https://www.rfc-editor.org/rfc/rfc5258.html
+    public enum ChildInfo {
+        SUBSCRIBED
+    }
+
+    private boolean returnSubscribed;
+    private boolean returnNonExistent;
+
+    private EnumSet<ChildInfo> childInfos;
+
+    public ListResponse(MailboxMetaData.Children children, MailboxMetaData.Selectability selectability,
+                        String name, char hierarchyDelimiter, boolean returnSubscribed, boolean returnNonExistent,
+                        EnumSet<ChildInfo> childInfos) {
         super(children, selectability, name, hierarchyDelimiter, MailboxType.OTHER);
+        this.returnSubscribed = returnSubscribed;
+        this.returnNonExistent = returnNonExistent;
+        this.childInfos = childInfos;
+    }
+
+    public boolean isReturnSubscribed() {
+        return returnSubscribed;
+    }
+
+    public boolean isReturnNonExistent() {
+        return returnNonExistent;
+    }
+
+    public EnumSet<ChildInfo> getChildInfos() {
+        return childInfos;
     }
+
 }
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
index 2d29aa4070..7812c1fe6a 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java
@@ -80,8 +80,8 @@ public class DefaultProcessor implements ImapProcessor {
         builder.add(new IdleProcessor(mailboxManager, statusResponseFactory, metricFactory));
         builder.add(new StatusProcessor(mailboxManager, statusResponseFactory, metricFactory));
         builder.add(new LSubProcessor(mailboxManager, subscriptionManager, statusResponseFactory, metricFactory));
-        builder.add(new XListProcessor(mailboxManager, statusResponseFactory, mailboxTyper, metricFactory));
-        builder.add(new ListProcessor<>(mailboxManager, statusResponseFactory, metricFactory));
+        builder.add(new XListProcessor(mailboxManager, statusResponseFactory, mailboxTyper, metricFactory, subscriptionManager));
+        builder.add(new ListProcessor<>(mailboxManager, statusResponseFactory, metricFactory, subscriptionManager));
         builder.add(new SearchProcessor(mailboxManager, statusResponseFactory, metricFactory));
         SelectProcessor selectProcessor = new SelectProcessor(mailboxManager, eventBus, statusResponseFactory, metricFactory);
         builder.add(selectProcessor);
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java
index 00823f1dfe..15aa1a6584 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java
@@ -19,10 +19,21 @@
 
 package org.apache.james.imap.processor;
 
+import static org.apache.james.imap.message.request.ListRequest.ListSelectOption.RECURSIVEMATCH;
 import static org.apache.james.mailbox.MailboxManager.MailboxSearchFetchType.Minimal;
 
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.imap.api.display.HumanReadableText;
 import org.apache.james.imap.api.display.ModifiedUtf7;
+import org.apache.james.imap.api.message.Capability;
 import org.apache.james.imap.api.message.response.ImapResponseMessage;
 import org.apache.james.imap.api.message.response.StatusResponseFactory;
 import org.apache.james.imap.api.process.ImapSession;
@@ -32,6 +43,7 @@ import org.apache.james.imap.message.request.ListRequest;
 import org.apache.james.imap.message.response.ListResponse;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.SubscriptionManager;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxMetaData;
@@ -45,17 +57,34 @@ import org.apache.james.util.ReactorUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.fge.lambdas.Throwing;
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
-public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcessor<T> {
+public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcessor<T> implements CapabilityImplementingProcessor {
+    public static final boolean RETURN_SUBSCRIBED = true;
+    public static final boolean RETURN_NON_EXISTENT = true;
     private static final Logger LOGGER = LoggerFactory.getLogger(ListProcessor.class);
+    private static final List<Capability> CAPA = ImmutableList.of(Capability.of("LIST-EXTENDED"), Capability.of("LIST-STATUS"));
 
-    public ListProcessor(MailboxManager mailboxManager, StatusResponseFactory factory, MetricFactory metricFactory) {
-        this((Class<T>) ListRequest.class, mailboxManager, factory, metricFactory);
+    private final SubscriptionManager subscriptionManager;
+
+    public ListProcessor(MailboxManager mailboxManager, StatusResponseFactory factory,
+                         MetricFactory metricFactory, SubscriptionManager subscriptionManager) {
+        this((Class<T>) ListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager);
     }
 
-    public ListProcessor(Class<T> clazz, MailboxManager mailboxManager, StatusResponseFactory factory, MetricFactory metricFactory) {
+    public ListProcessor(Class<T> clazz, MailboxManager mailboxManager, StatusResponseFactory factory,
+                         MetricFactory metricFactory, SubscriptionManager subscriptionManager) {
         super(clazz, mailboxManager, factory, metricFactory);
+        this.subscriptionManager = subscriptionManager;
+    }
+
+    @Override
+    public List<Capability> getImplementedCapabilities(ImapSession session) {
+        return CAPA;
     }
 
     /**
@@ -73,29 +102,29 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess
 
     @Override
     protected Mono<Void> processRequestReactive(T request, ImapSession session, Responder responder) {
-        String baseReferenceName = request.getBaseReferenceName();
-        String mailboxPatternString = request.getMailboxPattern();
         MailboxSession mailboxSession = session.getMailboxSession();
 
-        return respond(session, responder, baseReferenceName, mailboxPatternString, mailboxSession)
+        return respond(session, responder, request, mailboxSession)
             .then(Mono.fromRunnable(() -> okComplete(request, responder)))
             .onErrorResume(MailboxException.class, e -> {
                 no(request, responder, HumanReadableText.SEARCH_FAILED);
-                return ReactorUtils.logAsMono(() -> LOGGER.error("List failed for mailboxName {}", mailboxPatternString, e));
+                return ReactorUtils.logAsMono(() -> LOGGER.error("List failed for mailboxName {}", request.getMailboxPattern(), e));
             })
             .then();
     }
 
-    private Mono<Void> respond(ImapSession session, Responder responder, String baseReferenceName, String mailboxPatternString, MailboxSession mailboxSession) {
-        if (mailboxPatternString.length() == 0) {
-            return Mono.fromRunnable(() -> respondNamespace(baseReferenceName, responder, mailboxSession));
+    private Mono<Void> respond(ImapSession session, Responder responder, T request, MailboxSession mailboxSession) {
+        if (request.getMailboxPattern().length() == 0) {
+            return Mono.fromRunnable(() -> respondNamespace(request.getBaseReferenceName(), responder, mailboxSession));
         } else {
-            return respondMailboxList(baseReferenceName, mailboxPatternString, session, responder, mailboxSession);
+            return respondMailboxList(request, session, responder, mailboxSession);
         }
     }
 
-    protected ImapResponseMessage createResponse(MailboxMetaData.Children children, MailboxMetaData.Selectability selectability, String name, char hierarchyDelimiter, MailboxType type) {
-        return new ListResponse(children, selectability, name, hierarchyDelimiter);
+    protected ImapResponseMessage createResponse(MailboxMetaData.Children children, MailboxMetaData.Selectability selectability, String name,
+                                                 char hierarchyDelimiter, MailboxType type) {
+        return new ListResponse(children, selectability, name, hierarchyDelimiter, !RETURN_SUBSCRIBED,
+            !RETURN_NON_EXISTENT, EnumSet.noneOf(ListResponse.ChildInfo.class));
     }
 
     private void respondNamespace(String referenceName, Responder responder, MailboxSession mailboxSession) {
@@ -116,7 +145,7 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess
             // A qualified reference name - get the root element
             int firstDelimiter = referenceName.indexOf(mailboxSession.getPathDelimiter());
             if (firstDelimiter == -1) {
-                 return referenceName;
+                return referenceName;
             } else {
                 return referenceName.substring(0, firstDelimiter);
             }
@@ -127,24 +156,102 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess
         }
     }
 
-    private Mono<Void> respondMailboxList(String referenceName, String mailboxName, ImapSession session, Responder responder, MailboxSession mailboxSession) {
+    private Mono<Void> respondMailboxList(T request, ImapSession session,
+                                          Responder responder, MailboxSession mailboxSession) {
         // If the mailboxPattern is fully qualified, ignore the
         // reference name.
-        String finalReferencename = referenceName;
-        if (mailboxName.charAt(0) == MailboxConstants.NAMESPACE_PREFIX_CHAR) {
+        String finalReferencename = request.getBaseReferenceName();
+        if (request.getMailboxPattern().charAt(0) == MailboxConstants.NAMESPACE_PREFIX_CHAR) {
             finalReferencename = "";
         }
         // Is the interpreted (combined) pattern relative?
         // Should the namespace section be returned or not?
-        boolean isRelative = ((finalReferencename + mailboxName).charAt(0) != MailboxConstants.NAMESPACE_PREFIX_CHAR);
+        boolean isRelative = ((finalReferencename + request.getMailboxPattern()).charAt(0) != MailboxConstants.NAMESPACE_PREFIX_CHAR);
+
+        MailboxQuery mailboxQuery = mailboxQuery(computeBasePath(session, finalReferencename, isRelative),
+            request.getMailboxPattern(), mailboxSession);
 
-        MailboxPath basePath = computeBasePath(session, finalReferencename, isRelative);
+        if (request.selectSubscribed()) {
+            return processWithSubscribed(request, responder, mailboxSession, isRelative, mailboxQuery);
+        } else {
+            return processWithoutSubscribed(session, responder, mailboxSession, isRelative, mailboxQuery);
+        }
+    }
 
-        return getMailboxManager().search(mailboxQuery(basePath, mailboxName, mailboxSession), Minimal, mailboxSession)
-            .doOnNext(metaData -> processResult(responder, isRelative, metaData, getMailboxType(session, metaData.getPath())))
+    private Mono<Void> processWithoutSubscribed(ImapSession session, Responder responder, MailboxSession mailboxSession, boolean isRelative, MailboxQuery mailboxQuery) {
+        return getMailboxManager().search(mailboxQuery, Minimal, mailboxSession)
+            .doOnNext(metaData -> responder.respond(
+                createResponse(metaData.inferiors(),
+                    metaData.getSelectability(),
+                    mailboxName(isRelative, metaData.getPath(), metaData.getHierarchyDelimiter()),
+                    metaData.getHierarchyDelimiter(),
+                    getMailboxType(session, metaData.getPath()))))
             .then();
     }
 
+    private Mono<Void> processWithSubscribed(T request, Responder responder, MailboxSession mailboxSession, boolean isRelative, MailboxQuery mailboxQuery) {
+        return Mono.zip(getMailboxManager().search(mailboxQuery, Minimal, mailboxSession).collectList()
+                    .map(searchedResultList -> searchedResultList.stream().collect(Collectors.toMap(MailboxMetaData::getPath, Function.identity()))),
+                Flux.from(Throwing.supplier(() -> subscriptionManager.subscriptionsReactive(mailboxSession)).get()).collectList())
+            .map(tuple -> getListResponseForSelectSubscribed(tuple.getT1(), tuple.getT2(), request, mailboxSession, isRelative, mailboxQuery))
+            .flatMapIterable(list -> list)
+            .doOnNext(pathAndResponse -> responder.respond(pathAndResponse.getRight()))
+            .then();
+    }
+
+    private List<Pair<MailboxPath, ListResponse>> getListResponseForSelectSubscribed(Map<MailboxPath, MailboxMetaData> searchedResultMap, List<MailboxPath> allSubscribedSearch,
+                                                                                     ListRequest listRequest, MailboxSession mailboxSession, boolean relative, MailboxQuery mailboxQuery) {
+        ImmutableList.Builder<Pair<MailboxPath, ListResponse>> responseBuilders = ImmutableList.builder();
+        List<Pair<MailboxPath, ListResponse>> listRecursiveMatch = listRecursiveMatch(searchedResultMap, allSubscribedSearch, mailboxSession, relative, listRequest);
+        responseBuilders.addAll(listRecursiveMatch);
+        Set<MailboxPath> listRecursiveMatchPath = listRecursiveMatch.stream().map(Pair::getKey).collect(Collectors.toUnmodifiableSet());
+
+        allSubscribedSearch.stream()
+            .filter(subscribed -> !listRecursiveMatchPath.contains(subscribed))
+            .filter(mailboxQuery::isPathMatch)
+            .map(subscribed -> buildListResponse(searchedResultMap, mailboxSession, relative, subscribed))
+            .forEach(responseBuilders::add);
+
+        return responseBuilders.build();
+    }
+
+    private Pair<MailboxPath, ListResponse> buildListResponse(Map<MailboxPath, MailboxMetaData> searchedResultMap, MailboxSession mailboxSession, boolean relative, MailboxPath subscribed) {
+        return Pair.of(subscribed, Optional.ofNullable(searchedResultMap.get(subscribed))
+            .map(mailboxMetaData -> ListResponse.builder()
+                .returnSubscribed(RETURN_SUBSCRIBED)
+                .forMetaData(mailboxMetaData)
+                .name(mailboxName(relative, subscribed, mailboxMetaData.getHierarchyDelimiter()))
+                .returnNonExistent(!RETURN_NON_EXISTENT))
+            .orElseGet(() -> ListResponse.builder().nonExitingSubscribedMailbox(subscribed))
+            .build());
+    }
+
+    private List<Pair<MailboxPath, ListResponse>> listRecursiveMatch(Map<MailboxPath, MailboxMetaData> searchedResultMap,
+                                                                     List<MailboxPath> allSubscribedSearch, MailboxSession mailboxSession,
+                                                                     boolean relative, ListRequest listRequest) {
+        if (!listRequest.getSelectOptions().contains(RECURSIVEMATCH)) {
+            return List.of();
+        }
+
+        Set<MailboxPath> allSubscribedSearchParent = allSubscribedSearch.stream()
+            .flatMap(mailboxPath -> mailboxPath.getParents(mailboxSession.getPathDelimiter()).stream())
+            .collect(Collectors.toSet());
+
+        return searchedResultMap.entrySet().stream()
+            .filter(pair -> allSubscribedSearchParent.contains(pair.getKey()))
+            .map(pair -> {
+                MailboxMetaData metaData = pair.getValue();
+                ListResponse listResponse = ListResponse.builder()
+                    .forMetaData(metaData)
+                    .name(mailboxName(relative, metaData.getPath(), metaData.getHierarchyDelimiter()))
+                    .childInfos(ListResponse.ChildInfo.SUBSCRIBED)
+                    .returnSubscribed(allSubscribedSearch.contains(pair.getKey()))
+                    .build();
+                return Pair.of(pair.getKey(), listResponse);
+            })
+            .collect(Collectors.toList());
+    }
+
     private MailboxQuery mailboxQuery(MailboxPath basePath, String mailboxName, MailboxSession mailboxSession) {
         if (basePath.getNamespace().equals(MailboxConstants.USER_NAMESPACE)
             && basePath.getUser().equals(mailboxSession.getUser())
@@ -175,27 +282,13 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess
         }
     }
 
-    private void processResult(Responder responder, boolean relative, MailboxMetaData listResult, MailboxType mailboxType) {
-        String mailboxName = mailboxName(relative, listResult.getPath(), listResult.getHierarchyDelimiter());
-
-        ImapResponseMessage response =
-            createResponse(
-                listResult.inferiors(),
-                listResult.getSelectability(),
-                mailboxName,
-                listResult.getHierarchyDelimiter(),
-                mailboxType);
-        responder.respond(response);
-    }
 
     /**
      * retrieve mailboxType for specified mailboxPath using provided
      * MailboxTyper
-     * 
-     * @param session
-     *            current imap session
-     * @param path
-     *            mailbox's path
+     *
+     * @param session current imap session
+     * @param path    mailbox's path
      * @return MailboxType value
      */
     protected MailboxType getMailboxType(ImapSession session, MailboxPath path) {
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/XListProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/XListProcessor.java
index f658b20bd7..e164522dc7 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/XListProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/XListProcessor.java
@@ -32,6 +32,7 @@ import org.apache.james.imap.api.process.MailboxTyper;
 import org.apache.james.imap.message.request.XListRequest;
 import org.apache.james.imap.message.response.XListResponse;
 import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.SubscriptionManager;
 import org.apache.james.mailbox.model.MailboxMetaData;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.metrics.api.MetricFactory;
@@ -47,8 +48,8 @@ public class XListProcessor extends ListProcessor<XListRequest> implements Capab
     private final MailboxTyper mailboxTyper;
 
     public XListProcessor(MailboxManager mailboxManager, StatusResponseFactory factory, MailboxTyper mailboxTyper,
-            MetricFactory metricFactory) {
-        super(XListRequest.class, mailboxManager, factory, metricFactory);
+                          MetricFactory metricFactory, SubscriptionManager subscriptionManager) {
+        super(XListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager);
         this.mailboxTyper = mailboxTyper;
     }
 
@@ -63,7 +64,8 @@ public class XListProcessor extends ListProcessor<XListRequest> implements Capab
     }
 
     @Override
-    protected ImapResponseMessage createResponse(MailboxMetaData.Children children, MailboxMetaData.Selectability selectability, String name, char hierarchyDelimiter, MailboxType type) {
+    protected ImapResponseMessage createResponse(MailboxMetaData.Children children, MailboxMetaData.Selectability selectability,
+                                                 String name, char hierarchyDelimiter, MailboxType type) {
         return new XListResponse(children, selectability, name, hierarchyDelimiter, type);
     }
 
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/encode/ListResponseEncoderTest.java b/protocols/imap/src/test/java/org/apache/james/imap/encode/ListResponseEncoderTest.java
index adab9cc83a..8cbb86f9d2 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/encode/ListResponseEncoderTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/encode/ListResponseEncoderTest.java
@@ -21,6 +21,8 @@ package org.apache.james.imap.encode;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.EnumSet;
+
 import org.apache.james.imap.encode.base.ByteImapResponseWriter;
 import org.apache.james.imap.encode.base.ImapResponseComposerImpl;
 import org.apache.james.imap.message.response.ListResponse;
@@ -45,7 +47,7 @@ class ListResponseEncoderTest {
 
     @Test
     void encoderShouldIncludeListCommand() throws Exception {
-        encoder.encode(new ListResponse(MailboxMetaData.Children.HAS_CHILDREN, MailboxMetaData.Selectability.NONE, "name", '.'), composer);
+        encoder.encode(new ListResponse(MailboxMetaData.Children.HAS_CHILDREN, MailboxMetaData.Selectability.NONE, "name", '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)), composer);
         assertThat(writer.getString()).startsWith("* LIST");
     }
 }
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/encode/ListingEncodingUtilsTest.java b/protocols/imap/src/test/java/org/apache/james/imap/encode/ListingEncodingUtilsTest.java
index 9995f78a67..1d3728f430 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/encode/ListingEncodingUtilsTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/encode/ListingEncodingUtilsTest.java
@@ -22,6 +22,8 @@ import static org.apache.james.imap.api.ImapConstants.LIST_COMMAND;
 import static org.apache.james.imap.api.ImapConstants.XLIST_COMMAND;
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.EnumSet;
+
 import org.apache.james.imap.api.process.MailboxType;
 import org.apache.james.imap.encode.base.ByteImapResponseWriter;
 import org.apache.james.imap.encode.base.ImapResponseComposerImpl;
@@ -41,7 +43,7 @@ public class ListingEncodingUtilsTest  {
 
     @Test
     void encodeShouldWriteNilDelimiterWhenUnassigned() throws Exception {
-        ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, ((char) Character.UNASSIGNED));
+        ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, ((char) Character.UNASSIGNED), false, false, EnumSet.noneOf(ListResponse.ChildInfo.class));
 
         ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input);
         assertThat(writer.getString()).isEqualTo("* LIST (\\HasChildren) NIL \"mailbox\"\r\n");
@@ -49,7 +51,7 @@ public class ListingEncodingUtilsTest  {
 
     @Test
     void encodeShouldWriteAnyDelimiter() throws Exception {
-        ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, '#');
+        ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, '#', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class));
 
         ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input);
         assertThat(writer.getString()).isEqualTo("* LIST (\\HasChildren) \"#\" \"mailbox\"\r\n");
@@ -57,7 +59,7 @@ public class ListingEncodingUtilsTest  {
 
     @Test
     void encodeShouldNotIncludeAttributeWhenNone() throws Exception {
-        ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, MailboxMetaData.Selectability.NONE, nameParameter, '.');
+        ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, MailboxMetaData.Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class));
 
         ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input);
         assertThat(writer.getString()).isEqualTo("* LIST () \".\" \"mailbox\"\r\n");
@@ -65,23 +67,23 @@ public class ListingEncodingUtilsTest  {
 
     @Test
     void encodeShouldAddHasChildrenToAttributes() throws Exception {
-        ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, '.');
-            
+        ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class));
+
         ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input);
         assertThat(writer.getString()).isEqualTo("* LIST (\\HasChildren) \".\" \"mailbox\"\r\n");
     }
-    
+
     @Test
     void encodeShouldAddHasNoChildrenToAttributes() throws Exception {
-        ListResponse input = new ListResponse(Children.HAS_NO_CHILDREN, Selectability.NONE, nameParameter, '.');
-            
+        ListResponse input = new ListResponse(Children.HAS_NO_CHILDREN, Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class));
+
         ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input);
         assertThat(writer.getString()).isEqualTo("* LIST (\\HasNoChildren) \".\" \"mailbox\"\r\n");
     }
 
     @Test
     void encodeShouldAddSeveralAttributes() throws Exception {
-        ListResponse input = new ListResponse(Children.NO_INFERIORS, Selectability.NOSELECT, nameParameter, '.');
+        ListResponse input = new ListResponse(Children.NO_INFERIORS, Selectability.NOSELECT, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class));
 
         ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input);
         assertThat(writer.getString()).isEqualTo("* LIST (\\Noselect \\Noinferiors) \".\" \"mailbox\"\r\n");
@@ -89,7 +91,7 @@ public class ListingEncodingUtilsTest  {
 
     @Test
     void encodeShouldAddMarkedAttribute() throws Exception {
-        ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.MARKED, nameParameter, '.');
+        ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.MARKED, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class));
 
         ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input);
         assertThat(writer.getString()).isEqualTo("* LIST (\\Marked) \".\" \"mailbox\"\r\n");
@@ -97,7 +99,7 @@ public class ListingEncodingUtilsTest  {
 
     @Test
     void encodeShouldAddUnmarkedAttribute() throws Exception {
-        ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.UNMARKED, nameParameter, '.');
+        ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.UNMARKED, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class));
 
         ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input);
         assertThat(writer.getString()).isEqualTo("* LIST (\\Unmarked) \".\" \"mailbox\"\r\n");


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


[james-project] 02/02: JAMES-3754 - IMAP support List command extension RFC-5258 - REMOTE

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

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

commit a886ba7f0c4300c5a874a064274d3d6861520c25
Author: Tung Van TRAN <vt...@linagora.com>
AuthorDate: Mon Nov 28 18:20:45 2022 +0700

    JAMES-3754 - IMAP support List command extension RFC-5258 - REMOTE
---
 .../main/resources/org/apache/james/imap/scripts/ListSubscribed.test | 4 ++++
 .../main/java/org/apache/james/imap/message/request/ListRequest.java | 5 ++++-
 .../src/main/java/org/apache/james/imap/processor/ListProcessor.java | 5 +++++
 3 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListSubscribed.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListSubscribed.test
index 7c317a7194..28b745fc26 100644
--- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListSubscribed.test
+++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListSubscribed.test
@@ -102,6 +102,10 @@ S: A15 OK UNSUBSCRIBE completed.
 C: A16 DELETE another1
 S: A16 OK DELETE completed.
 
+# REMOTE should return empty (not yet support)
+C: A31 LIST (REMOTE) "" "*"
+S: A31 OK LIST completed.
+
 # Response should has `NonExistent` attribute when the subscribed mailbox is not exist
 C: a03 SUBSCRIBE subscribeNonExistent
 S: a03 OK SUBSCRIBE completed.
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/request/ListRequest.java b/protocols/imap/src/main/java/org/apache/james/imap/message/request/ListRequest.java
index 24de0dc4f0..fb1e18e794 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/message/request/ListRequest.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/request/ListRequest.java
@@ -20,7 +20,6 @@ package org.apache.james.imap.message.request;
 
 import java.util.EnumSet;
 import java.util.Optional;
-import java.util.stream.Collectors;
 
 import org.apache.james.imap.api.ImapCommand;
 import org.apache.james.imap.api.ImapConstants;
@@ -86,6 +85,10 @@ public class ListRequest extends AbstractImapRequest {
         return getSelectOptions().contains(ListSelectOption.SUBSCRIBED);
     }
 
+    public boolean selectRemote() {
+        return getSelectOptions().contains(ListSelectOption.REMOTE);
+    }
+
     public Optional<StatusDataItems> getStatusDataItems() {
         return statusDataItems;
     }
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java
index 15aa1a6584..c4bb28302f 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java
@@ -158,6 +158,11 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess
 
     private Mono<Void> respondMailboxList(T request, ImapSession session,
                                           Responder responder, MailboxSession mailboxSession) {
+        if (request.selectRemote()) {
+            // https://www.rfc-editor.org/rfc/rfc5258.html. NOT YET SUPPORT `REMOTE`
+            return Mono.empty();
+        }
+
         // If the mailboxPattern is fully qualified, ignore the
         // reference name.
         String finalReferencename = request.getBaseReferenceName();


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