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 fa9d5b56356f376a71a448ed9f87f658c902a0bb Author: Benoit TELLIER <btell...@linagora.com> AuthorDate: Wed Oct 9 17:03:33 2024 +0200 JAMES-2182 Partial implementation for shared folders in IMAP Several issues remains to address: - [ ] List + myrights needs to keep FQDN in the response - [ ] #user shall not include personal mailboxes filter them out? (post filtering?) - [ ] Using #user in mailbox name pattern shall be permitted - [ ] Remove duplicate results... --- .../imapmailbox/suite/ListingWithSharingTest.java | 8 ++- .../james/imap/scripts/ListWithSharedMailbox.test | 64 ++++++++++++++++++++-- .../org/apache/james/imap/main/PathConverter.java | 6 +- .../apache/james/imap/processor/ListProcessor.java | 39 +++++++++---- .../james/imap/processor/StatusProcessor.java | 9 +-- 5 files changed, 103 insertions(+), 23 deletions(-) diff --git a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/ListingWithSharingTest.java b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/ListingWithSharingTest.java index 95eedde1d1..c496b3724f 100644 --- a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/ListingWithSharingTest.java +++ b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/ListingWithSharingTest.java @@ -33,8 +33,10 @@ import org.junit.jupiter.api.Test; public abstract class ListingWithSharingTest implements ImapTestConstants { public static final Username OTHER_USER_NAME = Username.of("Boby"); + public static final Username YET_ANOTHER_USER_NAME = Username.of("Diana"); public static final String OTHER_USER_PASSWORD = "password"; public static final MailboxPath OTHER_USER_SHARED_MAILBOX = MailboxPath.forUser(OTHER_USER_NAME, "sharedMailbox"); + public static final MailboxPath YET_ANOTHER_USER_SHARED_MAILBOX = MailboxPath.forUser(YET_ANOTHER_USER_NAME, "sharedMailbox"); public static final MailboxPath OTHER_USER_SHARED_MAILBOX_CHILD = MailboxPath.forUser(OTHER_USER_NAME, "sharedMailbox.child"); protected abstract ImapHostSystem createImapHostSystem(); @@ -57,8 +59,10 @@ public abstract class ListingWithSharingTest implements ImapTestConstants { scriptedTestProtocol .withMailbox(OTHER_USER_SHARED_MAILBOX) .withMailbox(OTHER_USER_SHARED_MAILBOX_CHILD) - .withRights(OTHER_USER_SHARED_MAILBOX, USER, MailboxACL.Rfc4314Rights.fromSerializedRfc4314Rights("r")) - .withRights(OTHER_USER_SHARED_MAILBOX_CHILD, USER, MailboxACL.Rfc4314Rights.fromSerializedRfc4314Rights("r")) + .withMailbox(YET_ANOTHER_USER_SHARED_MAILBOX) + .withRights(OTHER_USER_SHARED_MAILBOX, USER, MailboxACL.Rfc4314Rights.fromSerializedRfc4314Rights("rl")) + .withRights(OTHER_USER_SHARED_MAILBOX_CHILD, USER, MailboxACL.Rfc4314Rights.fromSerializedRfc4314Rights("rl")) + .withRights(YET_ANOTHER_USER_SHARED_MAILBOX, USER, MailboxACL.Rfc4314Rights.fromSerializedRfc4314Rights("rl")) .withLocale(Locale.US) .run("ListWithSharedMailbox"); } diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListWithSharedMailbox.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListWithSharedMailbox.test index eba7f0dd17..29243651e5 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListWithSharedMailbox.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/ListWithSharedMailbox.test @@ -17,16 +17,68 @@ # under the License. # ################################################################ -# Shouw that #private.Boby.sharedMailbox and it's child are not displayed - +# Can list other users delegated mailbox C: a0 LIST "" "*" +SUB { S: \* LIST \(\\HasNoChildren\) \"\.\" \"INBOX\" +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.diana.sharedMailbox\" +S: \* LIST \(\\HasChildren\) \".\" \"#user.boby.sharedMailbox\" +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.boby.sharedMailbox.child\" +} S: a0 OK LIST completed. -C: a1 LIST "#private" "%" -S: \* LIST \(\\HasNoChildren\) \"\.\" \"#private.INBOX\" +C: a1 LIST "" "*" RETURN (MYRIGHTS) +# TODO MYRIGHTS status response should keep FQDN +SUB { +S: \* LIST \(\\HasNoChildren\) \"\.\" \"INBOX\" +S: \* MYRIGHTS \"INBOX\" \"aeiklprstwx\" +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.diana.sharedMailbox\" +S: \* MYRIGHTS \"sharedMailbox\" \"lr\" +S: \* LIST \(\\HasChildren\) \".\" \"#user.boby.sharedMailbox\" +S: \* MYRIGHTS \"sharedMailbox\" \"lr\" +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.boby.sharedMailbox.child\" +S: \* MYRIGHTS \"sharedMailbox.child\" \"lr\" +} S: a1 OK LIST completed. -C: a3 LIST "#private" sharedMailbox* -S: a3 OK LIST completed. +C: a2 LIST "" "*" RETURN (STATUS (UNSEEN RECENT)) +SUB { +S: \* LIST \(\\HasNoChildren\) \"\.\" \"INBOX\" +S: \* STATUS \"INBOX\" \(RECENT 0 UNSEEN 0\) +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.diana.sharedMailbox\" +S: \* STATUS \"#user.diana.sharedMailbox\" \(RECENT 0 UNSEEN 0\) +S: \* LIST \(\\HasChildren\) \".\" \"#user.boby.sharedMailbox\" +S: \* STATUS \"#user.boby.sharedMailbox\" \(RECENT 0 UNSEEN 0\) +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.boby.sharedMailbox.child\" +S: \* STATUS \"#user.boby.sharedMailbox.child\" \(RECENT 0 UNSEEN 0\) +} +S: a2 OK LIST completed. + +C: a3 STATUS #user.boby.sharedMailbox (UNSEEN RECENT) +S: \* STATUS \"#user.boby.sharedMailbox\" \(RECENT 0 UNSEEN 0\) +S: a3 OK STATUS completed. + +C: a4 LIST "#user.diana" "*" +# TODO why double results +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.diana.sharedMailbox\" +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.diana.sharedMailbox\" +S: a4 OK LIST completed. + +C: a7 LIST "#user" "*" +# TODO personal mailboxes shall not be included +SUB { +S: \* LIST \(\\HasNoChildren\) \"\.\" \"#private.INBOX\" +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.diana.sharedMailbox\" +S: \* LIST \(\\HasChildren\) \".\" \"#user.boby.sharedMailbox\" +S: \* LIST \(\\HasNoChildren\) \".\" \"#user.boby.sharedMailbox.child\" +} +S: a7 OK LIST completed. + +# When looking up private namespace the shared mailboxes are not included +C: a8 LIST "#private" "%" +S: \* LIST \(\\HasNoChildren\) \"\.\" \"#private.INBOX\" +S: a8 OK LIST completed. + +C: a9 LIST "#private" sharedMailbox* +S: a9 OK LIST completed. diff --git a/protocols/imap/src/main/java/org/apache/james/imap/main/PathConverter.java b/protocols/imap/src/main/java/org/apache/james/imap/main/PathConverter.java index 5ea5c5fe79..8a53728fa7 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/main/PathConverter.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/main/PathConverter.java @@ -94,7 +94,11 @@ public interface PathConverter { String mailboxName = Joiner.on(session.getPathDelimiter()).join(Iterables.skip(mailboxPathParts, 1)); return new MailboxPath(MailboxConstants.USER_NAMESPACE, session.getUser(), sanitizeMailboxName(mailboxName)); } else if (namespace.equalsIgnoreCase("#user")) { - Preconditions.checkArgument(mailboxPathParts.size() > 2, "Expecting at least 2 parts"); + if (mailboxPathParts.size() == 1) { + // May be generated by some List commands. + String mailboxName = Joiner.on(session.getPathDelimiter()).join(Iterables.skip(mailboxPathParts, 1)); + return new MailboxPath(MailboxConstants.USER_NAMESPACE, null, sanitizeMailboxName(mailboxName)); + } String username = mailboxPathParts.get(USER); String unescapedUsername = username.replace("__", ".") .replace("_-", "_"); 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 aa6f19cd5e..a568665e50 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 @@ -191,18 +191,11 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess return Mono.empty(); } - // If the mailboxPattern is fully qualified, ignore the - // reference name. - 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 + request.getMailboxPattern()).charAt(0) != MailboxConstants.NAMESPACE_PREFIX_CHAR); + boolean isRelative = ((request.getBaseReferenceName() + request.getMailboxPattern()).charAt(0) != MailboxConstants.NAMESPACE_PREFIX_CHAR); - MailboxQuery mailboxQuery = mailboxQuery(computeBasePath(session, finalReferencename, isRelative), - request.getMailboxPattern(), mailboxSession); + MailboxQuery mailboxQuery = mailboxQuery(request.getBaseReferenceName(), request.getMailboxPattern(), mailboxSession, session, isRelative); if (request.selectSubscribed()) { return processWithSubscribed(session, request, responder, mailboxSession, isRelative, mailboxQuery); @@ -337,7 +330,33 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess return metaData.getResolvedAcls().getEntries().get(entryKey); } - private MailboxQuery mailboxQuery(MailboxPath basePath, String mailboxName, MailboxSession mailboxSession) { + private MailboxQuery mailboxQuery(String finalReferencename, String mailboxName, MailboxSession mailboxSession, + ImapSession session, boolean isRelative) { + if (finalReferencename.isEmpty()) { + if (mailboxName.equals("*")) { + return MailboxQuery.builder() + .matchesAllMailboxNames() + .build(); + } + return MailboxQuery.builder() + .expression(new PrefixedRegex( + "", + ModifiedUtf7.decodeModifiedUTF7(mailboxName), + mailboxSession.getPathDelimiter())) + .build(); + } + + MailboxPath basePath = computeBasePath(session, finalReferencename, isRelative); + if (basePath.getNamespace().equals(MailboxConstants.USER_NAMESPACE) + && basePath.getUser() == null) { + return MailboxQuery.builder() + .namespace(MailboxConstants.USER_NAMESPACE) + .expression(new PrefixedRegex( + basePath.getName(), + ModifiedUtf7.decodeModifiedUTF7(mailboxName), + mailboxSession.getPathDelimiter())) + .build(); + } if (basePath.getNamespace().equals(MailboxConstants.USER_NAMESPACE) && basePath.getUser().equals(mailboxSession.getUser()) && basePath.getName().isEmpty() diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java index a4aafb278e..038d41e553 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java @@ -74,6 +74,7 @@ import reactor.core.publisher.Mono; */ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> implements CapabilityImplementingProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(StatusProcessor.class); + public static final boolean RELATIVE = true; private final PathConverter.Factory pathConverterFactory; @@ -125,7 +126,7 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp Mono<MailboxStatusResponse> sendStatus(MessageManager mailbox, StatusDataItems statusDataItems, Responder responder, ImapSession session, MailboxSession mailboxSession) { return retrieveMetadata(mailbox, statusDataItems, mailboxSession) - .flatMap(metaData -> computeStatusResponse(mailbox, statusDataItems, metaData, mailboxSession) + .flatMap(metaData -> computeStatusResponse(mailbox, statusDataItems, metaData, mailboxSession, session) .doOnNext(response -> { // Enable CONDSTORE as this is a CONDSTORE enabling command if (response.getHighestModSeq() != null) { @@ -164,8 +165,8 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp private Mono<MailboxStatusResponse> computeStatusResponse(MessageManager mailbox, StatusDataItems statusDataItems, MessageManager.MailboxMetaData metaData, - MailboxSession session) { - return iterateMailbox(statusDataItems, mailbox, session) + MailboxSession mailboxSession, ImapSession session) { + return iterateMailbox(statusDataItems, mailbox, mailboxSession) .map(maybeIterationResult -> { Optional<Long> appendLimit = appendLimit(statusDataItems); Long messages = messages(statusDataItems, metaData); @@ -180,7 +181,7 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp maybeIterationResult.flatMap(result -> result.getSize(statusDataItems)).orElse(null), maybeIterationResult.flatMap(result -> result.getDeleted(statusDataItems)).orElse(null), maybeIterationResult.flatMap(result -> result.getDeletedStorage(statusDataItems)).orElse(null), - messages, recent, uidNext, highestModSeq, uidValidity, unseen, mailbox.getMailboxPath().getName(), mailboxId); + messages, recent, uidNext, highestModSeq, uidValidity, unseen, pathConverterFactory.forSession(session).mailboxName(RELATIVE, mailbox.getMailboxPath(), mailboxSession), mailboxId); }); } --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org