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 4fc47a15ad9a05c2a7f0e0977f0f621983b660b4 Author: Benoit Tellier <btell...@linagora.com> AuthorDate: Thu Nov 24 23:36:44 2022 +0700 JAMES-3754 Implement RFC-6154 IMAP LIST Extension for Special-Use Mailboxes --- .../org/apache/james/imap/scripts/XList.test | 17 +++++++++ .../imap/api/process/DefaultMailboxTyper.java | 1 + .../apache/james/imap/api/process/MailboxType.java | 25 +++++++++----- .../imap/decode/parser/ListCommandParser.java | 17 +++++++++ .../james/imap/encode/ListingEncodingUtils.java | 6 ++-- .../james/imap/message/request/ListRequest.java | 4 ++- .../message/response/AbstractListingResponse.java | 2 ++ .../james/imap/message/response/LSubResponse.java | 5 +++ .../james/imap/message/response/ListResponse.java | 19 +++++++--- .../james/imap/message/response/XListResponse.java | 4 +++ .../james/imap/processor/DefaultProcessor.java | 2 +- .../apache/james/imap/processor/ListProcessor.java | 40 ++++++++++++++-------- .../james/imap/processor/XListProcessor.java | 7 ++-- .../james/imap/encode/ListResponseEncoderTest.java | 5 ++- .../imap/encode/ListingEncodingUtilsTest.java | 16 ++++----- .../pages/architecture/implemented-standards.adoc | 1 + src/site/xdoc/protocols/imap4.xml | 1 + 17 files changed, 126 insertions(+), 46 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 57ed556f05..ba13b0da8a 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 @@ -35,3 +35,20 @@ S: \* XLIST \(\\HasNoChildren \\Spam\) \"\.\" "Spam" S: \* XLIST \(\\HasNoChildren \\Trash\) \"\.\" "Trash" } S: 10 OK XLIST completed. + +C: 13 CREATE Archive +S: 13 OK \[MAILBOXID \(.+\)\] CREATE completed. +C: 13 CREATE Other +S: 13 OK \[MAILBOXID \(.+\)\] CREATE completed. + +C: 10 LIST "" * RETURN (SPECIAL-USE) +SUB { +S: \* LIST \(\\HasNoChildren\) \"\.\" "INBOX" +S: \* LIST \(\\HasNoChildren\) \"\.\" "Other" +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. diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/process/DefaultMailboxTyper.java b/protocols/imap/src/main/java/org/apache/james/imap/api/process/DefaultMailboxTyper.java index fd7d5ba3f4..8611ff5b52 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/api/process/DefaultMailboxTyper.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/api/process/DefaultMailboxTyper.java @@ -30,6 +30,7 @@ public class DefaultMailboxTyper implements MailboxTyper { private static final ImmutableMap<Role, MailboxType> ROLES_TO_MAILBOX_TYPE = ImmutableMap.of( Role.INBOX, MailboxType.INBOX, Role.SENT, MailboxType.SENT, + Role.ARCHIVE, MailboxType.ARCHIVE, Role.SPAM, MailboxType.SPAM, Role.DRAFTS, MailboxType.DRAFTS, Role.TRASH, MailboxType.TRASH); diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/process/MailboxType.java b/protocols/imap/src/main/java/org/apache/james/imap/api/process/MailboxType.java index 15253042e2..d9db674ab0 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/api/process/MailboxType.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/api/process/MailboxType.java @@ -24,22 +24,29 @@ package org.apache.james.imap.api.process; */ public enum MailboxType { - INBOX("\\Inbox"), - DRAFTS("\\Drafts"), - TRASH("\\Trash"), - SPAM("\\Spam"), - SENT("\\Sent"), - STARRED("\\Starred"), - ALLMAIL("\\AllMail"), - OTHER(null); + INBOX("\\Inbox", null), + DRAFTS("\\Drafts", "\\Drafts"), + TRASH("\\Trash", "\\Trash"), + SPAM("\\Spam", "\\Junk"), + SENT("\\Sent", "\\Sent"), + STARRED("\\Starred", "\\Flagged"), + ALLMAIL("\\AllMail", "\\All"), + ARCHIVE(null, "\\Archive"), + OTHER(null, null); private final String attributeName; + private final String rfc6154attributeName; - MailboxType(String attributeName) { + MailboxType(String attributeName, String rfc6154attributeName) { this.attributeName = attributeName; + this.rfc6154attributeName = rfc6154attributeName; } public String getAttributeName() { return attributeName; } + + public String getRfc6154attributeName() { + return rfc6154attributeName; + } } 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 c913e12b4c..288f2ed285 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 @@ -134,6 +134,8 @@ public class ListCommandParser extends AbstractUidCommandParser { char c = request.nextWordChar(); if (c == 'T' || c == 't') { return readStatus(request); + } else if (c == 'P' || c == 'p') { + return Pair.of(readSpecialUse(request), Optional.empty()); } else { return Pair.of(readReturnSubscribed(request), Optional.empty()); } @@ -149,6 +151,21 @@ public class ListCommandParser extends AbstractUidCommandParser { return Pair.of(ListReturnOption.STATUS, Optional.of(StatusCommandParser.statusDataItems(request))); } + private ListReturnOption readSpecialUse(ImapRequestLineReader request) throws DecodingException { + // 'S' is already consummed + assertChar(request, 'P', 'p'); + assertChar(request, 'E', 'e'); + assertChar(request, 'C', 'c'); + assertChar(request, 'I', 'i'); + assertChar(request, 'A', 'a'); + assertChar(request, 'L', 'l'); + assertChar(request, '-', '-'); + 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; 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 f0b22e2f24..122780802e 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 @@ -29,7 +29,6 @@ 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; @@ -83,7 +82,7 @@ public class ListingEncodingUtils { selectabilityAsString(response.getSelectability(), builder); childrenAsString(response.getChildren(), builder); - mailboxAttributeAsString(response.getType(), builder); + mailboxAttributeAsString(response.getTypeAsString(), builder); if (response instanceof ListResponse) { ListResponse listResponse = (ListResponse) response; @@ -121,8 +120,7 @@ public class ListingEncodingUtils { } } - private static ImmutableList.Builder<byte[]> mailboxAttributeAsString(MailboxType type, ImmutableList.Builder<byte[]> builder) { - String attributeName = type.getAttributeName(); + private static ImmutableList.Builder<byte[]> mailboxAttributeAsString(String attributeName, ImmutableList.Builder<byte[]> builder) { if (attributeName != null) { return builder.add(attributeName.getBytes(StandardCharsets.US_ASCII)); } 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 37045a001c..bf3e601ae1 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 @@ -44,7 +44,9 @@ public class ListRequest extends AbstractImapRequest { // https://www.rfc-editor.org/rfc/rfc5819.html LIST STATUS STATUS, // https://www.rfc-editor.org/rfc/rfc8440.html - MYRIGHTS + MYRIGHTS, + // https://www.rfc-editor.org/rfc/rfc6154.html + SPECIAL_USE } private final String baseReferenceName; diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/response/AbstractListingResponse.java b/protocols/imap/src/main/java/org/apache/james/imap/message/response/AbstractListingResponse.java index c50a882d32..bcf899b8d5 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/message/response/AbstractListingResponse.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/message/response/AbstractListingResponse.java @@ -65,6 +65,8 @@ public abstract class AbstractListingResponse { return type; } + public abstract String getTypeAsString(); + public MailboxMetaData.Children getChildren() { return children; } diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/response/LSubResponse.java b/protocols/imap/src/main/java/org/apache/james/imap/message/response/LSubResponse.java index a4b1aa4a7f..9622a2dde4 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/message/response/LSubResponse.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/message/response/LSubResponse.java @@ -36,4 +36,9 @@ public final class LSubResponse extends AbstractListingResponse implements ImapR } return MailboxMetaData.Selectability.NONE; } + + @Override + public String getTypeAsString() { + return getType().getRfc6154attributeName(); + } } 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 cc0513b76e..ba8c6f785f 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 @@ -48,6 +48,7 @@ public final class ListResponse extends AbstractListingResponse implements ImapR private char hierarchyDelimiter; private boolean returnSubscribed; private boolean returnNonExistent; + private MailboxType mailboxType; private ImmutableSet.Builder<ChildInfo> childInfos; public Builder() { @@ -89,6 +90,11 @@ public final class ListResponse extends AbstractListingResponse implements ImapR return this; } + public Builder mailboxType(MailboxType mailboxType) { + this.mailboxType = mailboxType; + return this; + } + public Builder forMetaData(MailboxMetaData mailboxMetaData) { return children(mailboxMetaData.inferiors()) .selectability(mailboxMetaData.getSelectability()) @@ -102,7 +108,8 @@ public final class ListResponse extends AbstractListingResponse implements ImapR .selectability(MailboxMetaData.Selectability.NONE) .hierarchyDelimiter(MailboxConstants.DEFAULT_DELIMITER) .returnSubscribed(RETURN_SUBSCRIBED) - .returnNonExistent(RETURN_NON_EXISTENT); + .returnNonExistent(RETURN_NON_EXISTENT) + .mailboxType(MailboxType.OTHER); } private EnumSet<ChildInfo> buildChildInfos() { @@ -117,7 +124,7 @@ public final class ListResponse extends AbstractListingResponse implements ImapR public ListResponse build() { return new ListResponse(children, selectability, name, hierarchyDelimiter, returnSubscribed, - returnNonExistent, buildChildInfos()); + returnNonExistent, buildChildInfos(), mailboxType); } } @@ -134,8 +141,8 @@ public final class ListResponse extends AbstractListingResponse implements ImapR 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); + EnumSet<ChildInfo> childInfos, MailboxType mailboxType) { + super(children, selectability, name, hierarchyDelimiter, mailboxType); this.returnSubscribed = returnSubscribed; this.returnNonExistent = returnNonExistent; this.childInfos = childInfos; @@ -153,4 +160,8 @@ public final class ListResponse extends AbstractListingResponse implements ImapR return childInfos; } + @Override + public String getTypeAsString() { + return getType().getRfc6154attributeName(); + } } diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/response/XListResponse.java b/protocols/imap/src/main/java/org/apache/james/imap/message/response/XListResponse.java index df4b392aae..268ad0e9a3 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/message/response/XListResponse.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/message/response/XListResponse.java @@ -32,4 +32,8 @@ public class XListResponse extends AbstractListingResponse implements ImapRespon super(children, selectability, name, hierarchyDelimiter, type); } + @Override + public String getTypeAsString() { + return getType().getAttributeName(); + } } 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 40d153c8c0..6913be379a 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 @@ -83,7 +83,7 @@ public class DefaultProcessor implements ImapProcessor { builder.add(statusProcessor); builder.add(new LSubProcessor(mailboxManager, subscriptionManager, statusResponseFactory, metricFactory)); builder.add(new XListProcessor(mailboxManager, statusResponseFactory, mailboxTyper, metricFactory, subscriptionManager)); - builder.add(new ListProcessor<>(mailboxManager, statusResponseFactory, metricFactory, subscriptionManager, statusProcessor)); + builder.add(new ListProcessor<>(mailboxManager, statusResponseFactory, metricFactory, subscriptionManager, statusProcessor, mailboxTyper)); 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 2e69aa0d44..fd44a047ed 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 @@ -39,6 +39,7 @@ 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; import org.apache.james.imap.api.process.MailboxType; +import org.apache.james.imap.api.process.MailboxTyper; import org.apache.james.imap.main.PathConverter; import org.apache.james.imap.message.request.ListRequest; import org.apache.james.imap.message.response.ListResponse; @@ -70,23 +71,29 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess 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"), Capability.of("LIST-MYRIGHTS")); + private static final List<Capability> CAPA = ImmutableList.of( + Capability.of("LIST-EXTENDED"), + Capability.of("LIST-STATUS"), + Capability.of("LIST-MYRIGHTS"), + Capability.of("SPECIAL-USE")); private final SubscriptionManager subscriptionManager; private final StatusProcessor statusProcessor; + protected final MailboxTyper mailboxTyper; public ListProcessor(MailboxManager mailboxManager, StatusResponseFactory factory, MetricFactory metricFactory, SubscriptionManager subscriptionManager, - StatusProcessor statusProcessor) { - this((Class<T>) ListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager, statusProcessor); + StatusProcessor statusProcessor, MailboxTyper mailboxTyper) { + this((Class<T>) ListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager, statusProcessor, mailboxTyper); } public ListProcessor(Class<T> clazz, MailboxManager mailboxManager, StatusResponseFactory factory, MetricFactory metricFactory, SubscriptionManager subscriptionManager, - StatusProcessor statusProcessor) { + StatusProcessor statusProcessor, MailboxTyper mailboxTyper) { super(clazz, mailboxManager, factory, metricFactory); this.subscriptionManager = subscriptionManager; this.statusProcessor = statusProcessor; + this.mailboxTyper = mailboxTyper; } @Override @@ -131,7 +138,7 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess 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)); + !RETURN_NON_EXISTENT, EnumSet.noneOf(ListResponse.ChildInfo.class), type); } private void respondNamespace(String referenceName, Responder responder, MailboxSession mailboxSession) { @@ -197,7 +204,7 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess metaData.getSelectability(), mailboxName(isRelative, metaData.getPath(), metaData.getHierarchyDelimiter()), metaData.getHierarchyDelimiter(), - getMailboxType(session, metaData.getPath())))) + getMailboxType(request, session, 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(); @@ -207,7 +214,7 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess 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)) + .map(tuple -> getListResponseForSelectSubscribed(session, tuple.getT1(), tuple.getT2(), request, mailboxSession, isRelative, mailboxQuery)) .flatMapIterable(list -> list) .doOnNext(pathAndResponse -> responder.respond(pathAndResponse.getMiddle())) .doOnNext(pathAndResponse -> pathAndResponse.getRight().ifPresent(mailboxMetaData -> respondMyRights(request, responder, mailboxSession, mailboxMetaData))) @@ -215,10 +222,10 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess .then(); } - private List<Triple<MailboxPath, ListResponse, Optional<MailboxMetaData>>> getListResponseForSelectSubscribed(Map<MailboxPath, MailboxMetaData> searchedResultMap, List<MailboxPath> allSubscribedSearch, + private List<Triple<MailboxPath, ListResponse, Optional<MailboxMetaData>>> getListResponseForSelectSubscribed(ImapSession session, Map<MailboxPath, MailboxMetaData> searchedResultMap, List<MailboxPath> allSubscribedSearch, ListRequest listRequest, MailboxSession mailboxSession, boolean relative, MailboxQuery mailboxQuery) { ImmutableList.Builder<Triple<MailboxPath, ListResponse, Optional<MailboxMetaData>>> responseBuilders = ImmutableList.builder(); - List<Pair<MailboxPath, ListResponse>> listRecursiveMatch = listRecursiveMatch(searchedResultMap, allSubscribedSearch, mailboxSession, relative, listRequest); + List<Pair<MailboxPath, ListResponse>> listRecursiveMatch = listRecursiveMatch(session, searchedResultMap, allSubscribedSearch, mailboxSession, relative, listRequest); listRecursiveMatch.forEach(pair -> responseBuilders.add(Triple.of(pair.getLeft(), pair.getRight(), Optional.ofNullable(searchedResultMap.get(pair.getLeft()))))); Set<MailboxPath> listRecursiveMatchPath = listRecursiveMatch.stream().map(Pair::getKey).collect(Collectors.toUnmodifiableSet()); @@ -226,24 +233,25 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess allSubscribedSearch.stream() .filter(subscribed -> !listRecursiveMatchPath.contains(subscribed)) .filter(mailboxQuery::isPathMatch) - .map(subscribed -> buildListResponse(searchedResultMap, mailboxSession, relative, subscribed)) + .map(subscribed -> buildListResponse(listRequest, searchedResultMap, session, relative, subscribed)) .forEach(pair -> responseBuilders.add(Triple.of(pair.getLeft(), pair.getRight(), Optional.ofNullable(searchedResultMap.get(pair.getLeft()))))); return responseBuilders.build(); } - private Pair<MailboxPath, ListResponse> buildListResponse(Map<MailboxPath, MailboxMetaData> searchedResultMap, MailboxSession mailboxSession, boolean relative, MailboxPath subscribed) { + private Pair<MailboxPath, ListResponse> buildListResponse(ListRequest listRequest, Map<MailboxPath, MailboxMetaData> searchedResultMap, ImapSession session, 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)) + .returnNonExistent(!RETURN_NON_EXISTENT) + .mailboxType(getMailboxType(listRequest, session, mailboxMetaData.getPath()))) .orElseGet(() -> ListResponse.builder().nonExitingSubscribedMailbox(subscribed)) .build()); } - private List<Pair<MailboxPath, ListResponse>> listRecursiveMatch(Map<MailboxPath, MailboxMetaData> searchedResultMap, + private List<Pair<MailboxPath, ListResponse>> listRecursiveMatch(ImapSession session, Map<MailboxPath, MailboxMetaData> searchedResultMap, List<MailboxPath> allSubscribedSearch, MailboxSession mailboxSession, boolean relative, ListRequest listRequest) { if (!listRequest.getSelectOptions().contains(RECURSIVEMATCH)) { @@ -263,6 +271,7 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess .name(mailboxName(relative, metaData.getPath(), metaData.getHierarchyDelimiter())) .childInfos(ListResponse.ChildInfo.SUBSCRIBED) .returnSubscribed(allSubscribedSearch.contains(pair.getKey())) + .mailboxType(getMailboxType(listRequest, session, metaData.getPath())) .build(); return Pair.of(pair.getKey(), listResponse); }) @@ -323,7 +332,10 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess * @param path mailbox's path * @return MailboxType value */ - protected MailboxType getMailboxType(ImapSession session, MailboxPath path) { + protected MailboxType getMailboxType(ListRequest listRequest, ImapSession session, MailboxPath path) { + if (listRequest.getReturnOptions().contains(ListRequest.ListReturnOption.SPECIAL_USE)) { + return mailboxTyper.getMailboxType(session, path); + } return MailboxType.OTHER; } 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 7bab0ff87e..5ae2066be6 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 @@ -29,6 +29,7 @@ import org.apache.james.imap.api.message.response.StatusResponseFactory; import org.apache.james.imap.api.process.ImapSession; import org.apache.james.imap.api.process.MailboxType; import org.apache.james.imap.api.process.MailboxTyper; +import org.apache.james.imap.message.request.ListRequest; import org.apache.james.imap.message.request.XListRequest; import org.apache.james.imap.message.response.XListResponse; import org.apache.james.mailbox.MailboxManager; @@ -45,12 +46,10 @@ import com.google.common.collect.ImmutableList; public class XListProcessor extends ListProcessor<XListRequest> implements CapabilityImplementingProcessor { private static final List<Capability> XLIST_CAPS = ImmutableList.of(SUPPORTS_XLIST); - private final MailboxTyper mailboxTyper; public XListProcessor(MailboxManager mailboxManager, StatusResponseFactory factory, MailboxTyper mailboxTyper, MetricFactory metricFactory, SubscriptionManager subscriptionManager) { - super(XListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager, null); - this.mailboxTyper = mailboxTyper; + super(XListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager, null, mailboxTyper); } @Override @@ -70,7 +69,7 @@ public class XListProcessor extends ListProcessor<XListRequest> implements Capab } @Override - protected MailboxType getMailboxType(ImapSession session, MailboxPath path) { + protected MailboxType getMailboxType(ListRequest request, ImapSession session, MailboxPath path) { return mailboxTyper.getMailboxType(session, path); } } 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 8cbb86f9d2..d6559d7dda 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 @@ -23,6 +23,7 @@ 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; import org.apache.james.imap.message.response.ListResponse; @@ -47,7 +48,9 @@ class ListResponseEncoderTest { @Test void encoderShouldIncludeListCommand() throws Exception { - encoder.encode(new ListResponse(MailboxMetaData.Children.HAS_CHILDREN, MailboxMetaData.Selectability.NONE, "name", '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)), composer); + encoder.encode(new ListResponse(MailboxMetaData.Children.HAS_CHILDREN, MailboxMetaData.Selectability.NONE, + "name", '.', false, + false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER), 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 1d3728f430..ab748e79a8 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 @@ -43,7 +43,7 @@ public class ListingEncodingUtilsTest { @Test void encodeShouldWriteNilDelimiterWhenUnassigned() throws Exception { - ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, ((char) Character.UNASSIGNED), false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)); + ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, ((char) Character.UNASSIGNED), false, false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER); ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input); assertThat(writer.getString()).isEqualTo("* LIST (\\HasChildren) NIL \"mailbox\"\r\n"); @@ -51,7 +51,7 @@ public class ListingEncodingUtilsTest { @Test void encodeShouldWriteAnyDelimiter() throws Exception { - ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, '#', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)); + ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, '#', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER); ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input); assertThat(writer.getString()).isEqualTo("* LIST (\\HasChildren) \"#\" \"mailbox\"\r\n"); @@ -59,7 +59,7 @@ public class ListingEncodingUtilsTest { @Test void encodeShouldNotIncludeAttributeWhenNone() throws Exception { - ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, MailboxMetaData.Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)); + ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, MailboxMetaData.Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER); ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input); assertThat(writer.getString()).isEqualTo("* LIST () \".\" \"mailbox\"\r\n"); @@ -67,7 +67,7 @@ public class ListingEncodingUtilsTest { @Test void encodeShouldAddHasChildrenToAttributes() throws Exception { - ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)); + ListResponse input = new ListResponse(Children.HAS_CHILDREN, Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER); ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input); assertThat(writer.getString()).isEqualTo("* LIST (\\HasChildren) \".\" \"mailbox\"\r\n"); @@ -75,7 +75,7 @@ public class ListingEncodingUtilsTest { @Test void encodeShouldAddHasNoChildrenToAttributes() throws Exception { - ListResponse input = new ListResponse(Children.HAS_NO_CHILDREN, Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)); + ListResponse input = new ListResponse(Children.HAS_NO_CHILDREN, Selectability.NONE, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER); ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input); assertThat(writer.getString()).isEqualTo("* LIST (\\HasNoChildren) \".\" \"mailbox\"\r\n"); @@ -83,7 +83,7 @@ public class ListingEncodingUtilsTest { @Test void encodeShouldAddSeveralAttributes() throws Exception { - ListResponse input = new ListResponse(Children.NO_INFERIORS, Selectability.NOSELECT, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)); + ListResponse input = new ListResponse(Children.NO_INFERIORS, Selectability.NOSELECT, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER); ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input); assertThat(writer.getString()).isEqualTo("* LIST (\\Noselect \\Noinferiors) \".\" \"mailbox\"\r\n"); @@ -91,7 +91,7 @@ public class ListingEncodingUtilsTest { @Test void encodeShouldAddMarkedAttribute() throws Exception { - ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.MARKED, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)); + ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.MARKED, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER); ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input); assertThat(writer.getString()).isEqualTo("* LIST (\\Marked) \".\" \"mailbox\"\r\n"); @@ -99,7 +99,7 @@ public class ListingEncodingUtilsTest { @Test void encodeShouldAddUnmarkedAttribute() throws Exception { - ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.UNMARKED, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class)); + ListResponse input = new ListResponse(Children.CHILDREN_ALLOWED_BUT_UNKNOWN, Selectability.UNMARKED, nameParameter, '.', false, false, EnumSet.noneOf(ListResponse.ChildInfo.class), MailboxType.OTHER); ListingEncodingUtils.encodeListingResponse(LIST_COMMAND, composer, input); assertThat(writer.getString()).isEqualTo("* LIST (\\Unmarked) \".\" \"mailbox\"\r\n"); diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/architecture/implemented-standards.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/architecture/implemented-standards.adoc index 398180a13a..7ed8e6fd5a 100644 --- a/server/apps/distributed-app/docs/modules/ROOT/pages/architecture/implemented-standards.adoc +++ b/server/apps/distributed-app/docs/modules/ROOT/pages/architecture/implemented-standards.adoc @@ -71,6 +71,7 @@ The following IMAP specifications are implemented: - link:https://www.rfc-editor.org/rfc/rfc5258.html[RFC-5258] IMAP LIST Command Extensions - link:https://www.rfc-editor.org/rfc/rfc5819.html[RFC-5819] IMAP4 Extension for Returning STATUS Information in Extended LIST - link:https://www.rfc-editor.org/rfc/rfc8440.html[RFC-8440] IMAP4 Extension for Returning MYRIGHTS Information in Extended LIST + - link:https://www.rfc-editor.org/rfc/rfc8440.html[RFC-6154] IMAP LIST Extension for Special-Use Mailboxes Partially implemented specifications: diff --git a/src/site/xdoc/protocols/imap4.xml b/src/site/xdoc/protocols/imap4.xml index 1af6934fd2..96c1be2cab 100644 --- a/src/site/xdoc/protocols/imap4.xml +++ b/src/site/xdoc/protocols/imap4.xml @@ -66,6 +66,7 @@ <li>IMAP LIST Command Extensions (link:https://www.rfc-editor.org/rfc/rfc5258.html)</li> <li>IMAP4 Extension for Returning STATUS Information in Extended LIST (https://www.rfc-editor.org/rfc/rfc5819.html)</li> <li>IMAP4 Extension for Returning MYRIGHTS Information in Extended LIST (https://www.rfc-editor.org/rfc/rfc8440.html)</li> + <li>IMAP LIST Extension for Special-Use Mailboxes (https://www.rfc-editor.org/rfc/rfc6154.html)</li> </ul> <p>We follow RFC2683 recommendations for our implementations:</p> <ul> --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org