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/11/16 01:28:41 UTC

[james-project] 01/03: JAMES-3852 Allow parsing escaped MailboxPath

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 11cd2ddd73add2589092ed2527fd3e7d0d371fe3
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Nov 11 16:10:18 2022 +0700

    JAMES-3852 Allow parsing escaped MailboxPath
    
    Can be used for storage under string form while still preserving arbitrary
    char in the username.
---
 .../apache/james/mailbox/model/MailboxPath.java    | 36 +++++++++++++++++++
 .../james/mailbox/model/MailboxPathTest.java       | 41 ++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
index dd7e0fe72a..b78d477128 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxPath.java
@@ -21,11 +21,13 @@ package org.apache.james.mailbox.model;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Predicate;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.translate.LookupTranslator;
 import org.apache.james.core.Username;
 import org.apache.james.mailbox.DefaultMailboxes;
 import org.apache.james.mailbox.MailboxSession;
@@ -34,6 +36,7 @@ import org.apache.james.mailbox.exception.MailboxNameException;
 import org.apache.james.mailbox.exception.TooLongMailboxNameException;
 
 import com.google.common.base.CharMatcher;
+import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
@@ -43,6 +46,12 @@ import com.google.common.collect.Iterables;
  * The path to a mailbox.
  */
 public class MailboxPath {
+
+    private static final Splitter PART_SPLITTER = Splitter.on(':');
+    private static final Joiner PARTS_JOINER = Joiner.on(':');
+    private static final LookupTranslator USERNAME_ESCAPER = new LookupTranslator(Map.of(":", "/;", "/", "//"));
+    private static final LookupTranslator USERNAME_UNESCAPER = new LookupTranslator(Map.of("/;", ":", "//", "/"));
+
     /**
      * Return a {@link MailboxPath} which represent the INBOX of the given
      * session
@@ -62,6 +71,29 @@ public class MailboxPath {
         return new MailboxPath(MailboxConstants.USER_NAMESPACE, username, mailboxName);
     }
 
+    /**
+     * Parses a MailboxPath from the result of MailboxPath::asEscapedString thus supporting the use of ':' character
+     * in the serialized form, in the user part.
+     */
+    public static Optional<MailboxPath> parseEscaped(String asEscapedString) {
+        List<String> parts = PART_SPLITTER.splitToList(asEscapedString);
+        if (parts.size() == 2) {
+            return Optional.of(new MailboxPath(parts.get(0), getUsername(parts), ""));
+        }
+        if (parts.size() == 3) {
+            return Optional.of(new MailboxPath(parts.get(0), getUsername(parts), parts.get(2)));
+        }
+        if (parts.size() > 3) {
+            return Optional.of(new MailboxPath(parts.get(0), getUsername(parts),
+                PARTS_JOINER.join(Iterables.skip(parts, 2))));
+        }
+        return Optional.empty();
+    }
+
+    private static Username getUsername(List<String> parts) {
+        return Username.of(USERNAME_UNESCAPER.translate(parts.get(1)));
+    }
+
     private static final String INVALID_CHARS = "%*";
     private static final CharMatcher INVALID_CHARS_MATCHER = CharMatcher.anyOf(INVALID_CHARS);
     // This is the size that all mailbox backend should support
@@ -214,6 +246,10 @@ public class MailboxPath {
         return namespace + ":" + user.asString() + ":" + name;
     }
 
+    public String asEscapedString() {
+        return namespace + ":" + USERNAME_ESCAPER.translate(user.asString()) + ":" + name;
+    }
+
     public boolean isInbox() {
         return DefaultMailboxes.INBOX.equalsIgnoreCase(name);
     }
diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/model/MailboxPathTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/model/MailboxPathTest.java
index 66fe741230..1a09420637 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/model/MailboxPathTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/model/MailboxPathTest.java
@@ -24,12 +24,17 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
+import java.util.stream.Stream;
+
 import org.apache.james.core.Username;
 import org.apache.james.mailbox.DefaultMailboxes;
 import org.apache.james.mailbox.exception.HasEmptyMailboxNameInHierarchyException;
 import org.apache.james.mailbox.exception.MailboxNameException;
 import org.apache.james.mailbox.exception.TooLongMailboxNameException;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import com.google.common.base.Strings;
 
@@ -37,6 +42,7 @@ import nl.jqno.equalsverifier.EqualsVerifier;
 
 class MailboxPathTest {
     private static final Username USER = Username.of("user");
+    private static final Username BUGGY_USER = Username.of("buggy:bob");
 
     @Test
     void shouldMatchBeanContract() {
@@ -45,6 +51,41 @@ class MailboxPathTest {
             .verify();
     }
 
+    static Stream<Arguments> parseShouldYieldCorrectResults() {
+        return Stream.of(
+            Arguments.of(MailboxPath.forUser(USER, "test")),
+            Arguments.of(MailboxPath.forUser(USER, "a:b")),
+            Arguments.of(MailboxPath.forUser(USER, "a;b")),
+            Arguments.of(MailboxPath.forUser(USER, "a;;b")),
+            Arguments.of(MailboxPath.forUser(USER, "a:b:c:")),
+            Arguments.of(MailboxPath.forUser(USER, "a:b:c:")),
+            Arguments.of(MailboxPath.forUser(USER, ":")),
+            Arguments.of(MailboxPath.forUser(USER, ";")),
+            Arguments.of(MailboxPath.forUser(USER, "")),
+            Arguments.of(MailboxPath.inbox(USER)),
+            Arguments.of(MailboxPath.inbox(Username.of("a;b"))),
+            Arguments.of(MailboxPath.inbox(Username.of(";"))),
+            Arguments.of(MailboxPath.inbox(Username.of(":"))),
+            Arguments.of(MailboxPath.inbox(Username.of("a;;a"))),
+            Arguments.of(MailboxPath.inbox(Username.of("a:::;:::;:;;;a"))),
+            Arguments.of(MailboxPath.inbox(Username.of("a::a"))),
+            Arguments.of(MailboxPath.inbox(Username.of("a/a"))),
+            Arguments.of(MailboxPath.inbox(Username.of("a//a"))),
+            Arguments.of(MailboxPath.inbox(Username.of("a/:a"))),
+            Arguments.of(MailboxPath.inbox(Username.of("a/;a"))),
+            Arguments.of(MailboxPath.inbox(Username.of("a/:::;;/:://:;//:/a"))),
+            Arguments.of(MailboxPath.inbox(BUGGY_USER)),
+            Arguments.of(new MailboxPath("#whatever", USER, "whatever")),
+            Arguments.of(new MailboxPath(null, USER, "whatever")));
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void parseShouldYieldCorrectResults(MailboxPath mailboxPath) {
+        assertThat(MailboxPath.parseEscaped(mailboxPath.asEscapedString()))
+            .contains(mailboxPath);
+    }
+
     @Test
     void asStringShouldFormatUser() {
         assertThat(MailboxPath.forUser(USER, "inbox.folder.subfolder").asString())


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