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/12/07 10:40:04 UTC

[james-project] 07/08: JAMES-3754 SPECIAL-USE needs to be supported as a LIST selection option too

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 b4cb6a1bd4819092e63325c62dd598d427d6f1b9
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 2 23:38:18 2022 +0700

    JAMES-3754 SPECIAL-USE needs to be supported as a LIST selection option too
---
 .../org/apache/james/imap/scripts/XList.test       | 33 ++++++++++++++++++++++
 .../imap/decode/parser/ListCommandParser.java      | 30 ++++++++++++++------
 .../james/imap/message/request/ListRequest.java    |  4 ++-
 .../apache/james/imap/processor/ListProcessor.java | 23 +++++++++------
 4 files changed, 73 insertions(+), 17 deletions(-)

diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/XList.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/XList.test
index ba13b0da8a..62b07aa7a6 100644
--- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/XList.test
+++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/XList.test
@@ -52,3 +52,36 @@ S: \* LIST \(\\HasNoChildren \\Trash\) \"\.\" "Trash"
 S: \* LIST \(\\HasNoChildren \\Archive\) \"\.\" "Archive"
 }
 S: 10 OK LIST completed.
+
+C: 10 LIST (SPECIAL-USE) "" *
+SUB {
+S: \* LIST \(\\HasNoChildren \\Drafts\) \"\.\" "Drafts"
+S: \* LIST \(\\HasNoChildren \\Sent\) \"\.\" "Sent"
+S: \* LIST \(\\HasNoChildren \\Junk\) \"\.\" "Spam"
+S: \* LIST \(\\HasNoChildren \\Trash\) \"\.\" "Trash"
+S: \* LIST \(\\HasNoChildren \\Archive\) \"\.\" "Archive"
+}
+S: 10 OK LIST completed.
+
+C: A12 SUBSCRIBE Sent
+S: A12 OK SUBSCRIBE completed.
+C: A12 SUBSCRIBE Other
+S: A12 OK SUBSCRIBE completed.
+
+C: 10 LIST (SPECIAL-USE SUBSCRIBED) "" *
+SUB {
+S: \* LIST \(\\HasNoChildren \\Sent \\Subscribed\) \"\.\" "Sent"
+}
+S: 10 OK LIST completed.
+
+C: 10 LIST "" * RETURN (SPECIAL-USE SUBSCRIBED)
+SUB {
+S: \* LIST \(\\HasNoChildren\) \"\.\" "INBOX"
+S: \* LIST \(\\HasNoChildren \\Subscribed\) \"\.\" "Other"
+S: \* LIST \(\\HasNoChildren \\Drafts\) \"\.\" "Drafts"
+S: \* LIST \(\\HasNoChildren \\Sent \\Subscribed\) \"\.\" "Sent"
+S: \* LIST \(\\HasNoChildren \\Junk\) \"\.\" "Spam"
+S: \* LIST \(\\HasNoChildren \\Trash\) \"\.\" "Trash"
+S: \* LIST \(\\HasNoChildren \\Archive\) \"\.\" "Archive"
+}
+S: 10 OK LIST completed.
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 288f2ed285..5909b2dbe1 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
@@ -93,6 +93,9 @@ public class ListCommandParser extends AbstractUidCommandParser {
         if (listOptions.isEmpty() && listReturnOptions.getLeft().isEmpty()) {
             return createMessage(referenceName, mailboxPattern, tag);
         }
+        if (listOptions.contains(ListSelectOption.SPECIAL_USE)) {
+            listReturnOptions.getLeft().add(ListReturnOption.SPECIAL_USE);
+        }
         return new ListRequest(ImapConstants.LIST_COMMAND, referenceName, mailboxPattern, tag, listOptions, listReturnOptions.getLeft(), listReturnOptions.getRight());
     }
 
@@ -123,12 +126,24 @@ public class ListCommandParser extends AbstractUidCommandParser {
             return readR(request);
         }
         if (c == 's' || c == 'S') {
-            return readSubscribed(request);
+            return readSelectS(request);
         }
         throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS,
             "Unknown select option: '" + request.consumeWord(ImapRequestLineReader.NoopCharValidator.INSTANCE) + "'");
     }
 
+    private ListSelectOption readSelectS(ImapRequestLineReader request) throws DecodingException {
+        request.consume();
+        char c = request.nextChar();
+        if (c == 'u' || c == 'U') {
+            consumeSubscribed(request);
+            return ListSelectOption.SUBSCRIBED;
+        } else {
+            consumeSpecialUse(request);
+            return ListSelectOption.SPECIAL_USE;
+        }
+    }
+
     private Pair<ListReturnOption, Optional<StatusDataItems>> readS(ImapRequestLineReader request) throws DecodingException {
         request.consume();
         char c = request.nextWordChar();
@@ -152,6 +167,11 @@ public class ListCommandParser extends AbstractUidCommandParser {
     }
 
     private ListReturnOption readSpecialUse(ImapRequestLineReader request) throws DecodingException {
+        consumeSpecialUse(request);
+        return ListReturnOption.SPECIAL_USE;
+    }
+
+    private void consumeSpecialUse(ImapRequestLineReader request) throws DecodingException {
         // 'S' is already consummed
         assertChar(request, 'P', 'p');
         assertChar(request, 'E', 'e');
@@ -163,12 +183,6 @@ public class ListCommandParser extends AbstractUidCommandParser {
         assertChar(request, 'U', 'u');
         assertChar(request, 'S', 's');
         assertChar(request, 'E', 'e');
-        return ListReturnOption.SPECIAL_USE;
-    }
-
-    private ListSelectOption readSubscribed(ImapRequestLineReader request) throws DecodingException {
-        consumeSubscribed(request);
-        return ListSelectOption.SUBSCRIBED;
     }
 
     private ListSelectOption readR(ImapRequestLineReader request) throws DecodingException {
@@ -283,7 +297,7 @@ public class ListCommandParser extends AbstractUidCommandParser {
     }
 
     private void consumeSubscribed(ImapRequestLineReader request) throws DecodingException {
-        assertChar(request, 'S', 's');
+        // s is already consumed
         assertChar(request, 'U', 'u');
         assertChar(request, 'B', 'b');
         assertChar(request, 'S', 's');
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 bf3e601ae1..4729c90678 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
@@ -34,7 +34,9 @@ public class ListRequest extends AbstractImapRequest {
     public enum ListSelectOption {
         REMOTE,
         RECURSIVEMATCH,
-        SUBSCRIBED
+        SUBSCRIBED,
+        // https://www.rfc-editor.org/rfc/rfc6154.html
+        SPECIAL_USE
     }
 
     // https://www.rfc-editor.org/rfc/rfc5258.html
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 1fbe137595..8791fbcab8 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
@@ -20,6 +20,7 @@
 package org.apache.james.imap.processor;
 
 import static org.apache.james.imap.message.request.ListRequest.ListSelectOption.RECURSIVEMATCH;
+import static org.apache.james.imap.message.request.ListRequest.ListSelectOption.SPECIAL_USE;
 import static org.apache.james.mailbox.MailboxManager.MailboxSearchFetchType.Minimal;
 
 import java.util.EnumSet;
@@ -195,7 +196,7 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess
 
         if (request.selectSubscribed()) {
             return processWithSubscribed(session, request, responder, mailboxSession, isRelative, mailboxQuery);
-        } if (request.getReturnOptions().contains(ListRequest.ListReturnOption.SUBSCRIBED)) {
+        } else if (request.getReturnOptions().contains(ListRequest.ListReturnOption.SUBSCRIBED)) {
             return Flux.from(Throwing.supplier(() -> subscriptionManager.subscriptionsReactive(mailboxSession)).get())
                 .collect(ImmutableMap.toImmutableMap(path -> path, path -> path))
                 .flatMap(subscribed -> processWithoutSubscribed(session, request, responder, mailboxSession, isRelative, mailboxQuery, subscribed::containsKey));
@@ -207,13 +208,18 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess
     private Mono<Void> processWithoutSubscribed(ImapSession session, T request, Responder responder, MailboxSession mailboxSession,
                                                 boolean isRelative, MailboxQuery mailboxQuery, Predicate<MailboxPath> isSubscribed) {
         return getMailboxManager().search(mailboxQuery, Minimal, mailboxSession)
-            .doOnNext(metaData -> responder.respond(
-                createResponse(metaData.inferiors(),
-                    metaData.getSelectability(),
-                    mailboxName(isRelative, metaData.getPath(), metaData.getHierarchyDelimiter()),
-                    metaData.getHierarchyDelimiter(),
-                    getMailboxType(request, session, metaData.getPath()),
-                    isSubscribed.test(metaData.getPath()))))
+            .doOnNext(metaData -> {
+                MailboxType mailboxType = getMailboxType(request, session, metaData.getPath());
+                if (!request.getSelectOptions().contains(SPECIAL_USE) || mailboxType.getRfc6154attributeName() != null) {
+                    responder.respond(
+                        createResponse(metaData.inferiors(),
+                            metaData.getSelectability(),
+                            mailboxName(isRelative, metaData.getPath(), metaData.getHierarchyDelimiter()),
+                            metaData.getHierarchyDelimiter(),
+                            mailboxType,
+                            isSubscribed.test(metaData.getPath())));
+                }
+            })
             .doOnNext(metaData -> respondMyRights(request, responder, mailboxSession, metaData))
             .flatMap(metaData -> request.getStatusDataItems().map(statusDataItems -> statusProcessor.sendStatus(metaData.getPath(), statusDataItems, responder, session, mailboxSession)).orElse(Mono.empty()))
             .then();
@@ -243,6 +249,7 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess
             .filter(subscribed -> !listRecursiveMatchPath.contains(subscribed))
             .filter(mailboxQuery::isPathMatch)
             .map(subscribed -> buildListResponse(listRequest, searchedResultMap, session, relative, subscribed))
+            .filter(pair -> !listRequest.getSelectOptions().contains(SPECIAL_USE) || mailboxTyper.getMailboxType(session, pair.getKey()).getRfc6154attributeName() != null)
             .forEach(pair -> responseBuilders.add(Triple.of(pair.getLeft(), pair.getRight(), Optional.ofNullable(searchedResultMap.get(pair.getLeft())))));
 
         return responseBuilders.build();


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