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