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


The following commit(s) were added to refs/heads/master by this push:
     new 256c89dec7 JAMES-3945 Subaddressing - case sensitivity and subfolders 
(#2435)
256c89dec7 is described below

commit 256c89dec70a8aa4db0a807b42b2602b4716cc20
Author: florentos17 <fazav...@linagora.com>
AuthorDate: Mon Oct 7 08:36:09 2024 +0200

    JAMES-3945 Subaddressing - case sensitivity and subfolders (#2435)
---
 .../model/search/ExactNameCaseInsensitive.java     |  70 ++++++++++++++
 .../search/PrefixedWildcardCaseInsensitive.java    |  69 ++++++++++++++
 .../model/search/ExactNameCaseInsensitiveTest.java |  88 +++++++++++++++++
 .../PrefixedWildcardCaseInsensitiveTest.java       | 105 +++++++++++++++++++++
 .../james/mailets/configuration/Constants.java     |  10 +-
 .../apache/james/mailets/SubAddressingTest.java    | 102 +++++++++++++++++++-
 .../james/transport/mailets/SubAddressing.java     |  49 ++++++++--
 .../james/transport/mailets/SubAddressingTest.java |  76 ++++++++-------
 8 files changed, 513 insertions(+), 56 deletions(-)

diff --git 
a/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitive.java
 
b/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitive.java
new file mode 100644
index 0000000000..8acced2857
--- /dev/null
+++ 
b/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitive.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailbox.model.search;
+
+import java.util.Objects;
+
+import com.google.common.base.Preconditions;
+
+public class ExactNameCaseInsensitive implements MailboxNameExpression {
+
+    private final String name;
+
+    public ExactNameCaseInsensitive(String name) {
+        Preconditions.checkNotNull(name);
+        this.name = name;
+    }
+
+    @Override
+    public boolean isExpressionMatch(String mailboxName) {
+        Preconditions.checkNotNull(mailboxName);
+        return name.equalsIgnoreCase(mailboxName);
+    }
+
+    @Override
+    public String getCombinedName() {
+        return name;
+    }
+
+    @Override
+    public boolean isWild() {
+        return false;
+    }
+
+    @Override
+    public MailboxNameExpression includeChildren() {
+        return new PrefixedWildcardCaseInsensitive(name);
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof ExactNameCaseInsensitive) {
+            ExactNameCaseInsensitive exactName = (ExactNameCaseInsensitive) o;
+
+            return Objects.equals(this.name, exactName.name);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(name);
+    }
+}
diff --git 
a/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitive.java
 
b/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitive.java
new file mode 100644
index 0000000000..85e490d775
--- /dev/null
+++ 
b/mailbox/api/src/main/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitive.java
@@ -0,0 +1,69 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailbox.model.search;
+
+import java.util.Objects;
+
+import com.google.common.base.Preconditions;
+
+public class PrefixedWildcardCaseInsensitive implements MailboxNameExpression {
+
+    private final String prefix;
+
+    public PrefixedWildcardCaseInsensitive(String prefix) {
+        Preconditions.checkNotNull(prefix);
+        this.prefix = prefix;
+    }
+
+    @Override
+    public boolean isExpressionMatch(String name) {
+        return name.toLowerCase().startsWith(prefix.toLowerCase());
+    }
+
+    @Override
+    public String getCombinedName() {
+        return prefix + FREEWILDCARD;
+    }
+
+    @Override
+    public boolean isWild() {
+        return true;
+    }
+
+    @Override
+    public MailboxNameExpression includeChildren() {
+        return this;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof PrefixedWildcardCaseInsensitive) {
+            PrefixedWildcardCaseInsensitive that = 
(PrefixedWildcardCaseInsensitive) o;
+
+            return Objects.equals(this.prefix, that.prefix);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(prefix);
+    }
+}
diff --git 
a/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitiveTest.java
 
b/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitiveTest.java
new file mode 100644
index 0000000000..1c46759d40
--- /dev/null
+++ 
b/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/ExactNameCaseInsensitiveTest.java
@@ -0,0 +1,88 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailbox.model.search;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class ExactNameCaseInsensitiveTest {
+    public static final String NAME = "toto";
+    public static final String NAME_DIFFERENT_CASE_1 = "Toto";
+    public static final String NAME_DIFFERENT_CASE_2 = "TOTO";
+
+    @Test
+    void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(ExactNameCaseInsensitive.class)
+            .verify();
+    }
+
+    @Test
+    void constructorShouldThrowOnNullName() {
+        assertThatThrownBy(() -> new ExactNameCaseInsensitive(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    void isWildShouldReturnFalse() {
+        assertThat(new ExactNameCaseInsensitive(NAME).isWild())
+            .isFalse();
+    }
+
+    @Test
+    void getCombinedNameShouldReturnName() {
+        assertThat(new ExactNameCaseInsensitive(NAME).getCombinedName())
+            .isEqualTo(NAME);
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnTrueWhenName() {
+        assertThat(new ExactNameCaseInsensitive(NAME).isExpressionMatch(NAME))
+            .isTrue();
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnTrueWhenNameDifferentCase1() {
+        assertThat(new 
ExactNameCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_1))
+                .isTrue();
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnTrueWhenNameDifferentCase2() {
+        assertThat(new 
ExactNameCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_2))
+                .isTrue();
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnFalseWhenOtherValue() {
+        assertThat(new 
ExactNameCaseInsensitive(NAME).isExpressionMatch("other"))
+            .isFalse();
+    }
+
+    @Test
+    void isExpressionMatchShouldThrowOnNullValue() {
+        assertThatThrownBy(() -> new 
ExactNameCaseInsensitive(NAME).isExpressionMatch(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+}
\ No newline at end of file
diff --git 
a/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitiveTest.java
 
b/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitiveTest.java
new file mode 100644
index 0000000000..597632c91c
--- /dev/null
+++ 
b/mailbox/api/src/test/java/org/apache/james/mailbox/model/search/PrefixedWildcardCaseInsensitiveTest.java
@@ -0,0 +1,105 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailbox.model.search;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class PrefixedWildcardCaseInsensitiveTest {
+    public static final String NAME = "toto";
+    public static final String NAME_DIFFERENT_CASE_1 = "Toto";
+    public static final String NAME_DIFFERENT_CASE_2 = "TOTO";
+
+    @Test
+    void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(PrefixedWildcardCaseInsensitive.class)
+            .verify();
+    }
+
+    @Test
+    void constructorShouldThrowOnNullName() {
+        assertThatThrownBy(() -> new PrefixedWildcardCaseInsensitive(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    void isWildShouldReturnTrue() {
+        assertThat(new PrefixedWildcardCaseInsensitive(NAME).isWild())
+            .isTrue();
+    }
+
+    @Test
+    void getCombinedNameShouldReturnName() {
+        assertThat(new PrefixedWildcardCaseInsensitive(NAME).getCombinedName())
+            .isEqualTo(NAME + MailboxNameExpression.FREEWILDCARD);
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnTrueWhenName() {
+        assertThat(new 
PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME))
+            .isTrue();
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnTrueWhenNameWithDifferentCase1() {
+        assertThat(new 
PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_1))
+            .isTrue();
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnTrueWhenNameWithDifferentCase2() {
+        assertThat(new 
PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_2))
+            .isTrue();
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnTrueWhenNameAndPostfix() {
+        assertThat(new 
PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME + "any"))
+            .isTrue();
+    }
+
+    @Test
+    void 
isExpressionMatchShouldReturnTrueWhenNameWithDifferentCase1AndPostfix() {
+        assertThat(new 
PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_1 + 
"any"))
+            .isTrue();
+    }
+
+    @Test
+    void 
isExpressionMatchShouldReturnTrueWhenNameWithDifferentCase2AndPostfix() {
+        assertThat(new 
PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(NAME_DIFFERENT_CASE_2 + 
"any"))
+            .isTrue();
+    }
+
+    @Test
+    void isExpressionMatchShouldReturnFalseWhenOtherValue() {
+        assertThat(new 
PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch("other"))
+            .isFalse();
+    }
+
+    @Test
+    void isExpressionMatchShouldThrowOnNullValue() {
+        assertThatThrownBy(() -> new 
PrefixedWildcardCaseInsensitive(NAME).isExpressionMatch(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+}
\ No newline at end of file
diff --git 
a/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/Constants.java
 
b/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/Constants.java
index c5bdaf4b13..4e2ebcc1f2 100644
--- 
a/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/Constants.java
+++ 
b/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/Constants.java
@@ -42,9 +42,9 @@ public class Constants {
     public static final String DEFAULT_DOMAIN = "james.org";
     public static final String LOCALHOST_IP = "127.0.0.1";
     public static final String PASSWORD = "secret";
-    public static final String FROM = "user@" + DEFAULT_DOMAIN;
-    public static final String FROM2 = "user4@" + DEFAULT_DOMAIN;
-    public static final String RECIPIENT = "user2@" + DEFAULT_DOMAIN;
-    public static final String ALIAS = "user2alias@" + DEFAULT_DOMAIN;
-    public static final String RECIPIENT2 = "user3@" + DEFAULT_DOMAIN;
+    public static final String FROM = "from@" + DEFAULT_DOMAIN;
+    public static final String FROM2 = "from2@" + DEFAULT_DOMAIN;
+    public static final String RECIPIENT = "recipient@" + DEFAULT_DOMAIN;
+    public static final String ALIAS = "recipientalias@" + DEFAULT_DOMAIN;
+    public static final String RECIPIENT2 = "recipient2@" + DEFAULT_DOMAIN;
 }
diff --git 
a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/SubAddressingTest.java
 
b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/SubAddressingTest.java
index 55f656cc00..fa1225cd1f 100644
--- 
a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/SubAddressingTest.java
+++ 
b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/SubAddressingTest.java
@@ -27,6 +27,7 @@ import static 
org.apache.james.mailets.configuration.Constants.FROM2;
 import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP;
 import static org.apache.james.mailets.configuration.Constants.PASSWORD;
 import static org.apache.james.mailets.configuration.Constants.RECIPIENT;
+import static org.apache.james.mailets.configuration.Constants.RECIPIENT2;
 import static 
org.apache.james.mailets.configuration.Constants.awaitAtMostOneMinute;
 
 import java.io.File;
@@ -55,6 +56,8 @@ import org.junit.jupiter.api.io.TempDir;
 
 class SubAddressingTest {
     private static final String TARGETED_MAILBOX = "any";
+    private static final String TARGETED_MAILBOX_LOWER = TARGETED_MAILBOX;
+    private static final String TARGETED_MAILBOX_UPPER = "ANY";
 
     @RegisterExtension
     public TestIMAPClient testIMAPClient = new TestIMAPClient();
@@ -78,6 +81,7 @@ class SubAddressingTest {
         DataProbe dataProbe = jamesServer.getProbe(DataProbeImpl.class);
         dataProbe.addDomain(DEFAULT_DOMAIN);
         dataProbe.addUser(RECIPIENT, PASSWORD);
+        dataProbe.addUser(RECIPIENT2, PASSWORD);
         dataProbe.addUser(FROM, PASSWORD);
         dataProbe.addUser(FROM2, PASSWORD);
 
@@ -100,7 +104,95 @@ class SubAddressingTest {
 
         // do not create mailbox
 
-        sendSubAddressedMail();
+        sendSubAddressedMail(TARGETED_MAILBOX);
+        awaitSubAddressedMail(MailboxConstants.INBOX);
+    }
+
+
+    @Test
+    void 
subAddressedEmailShouldBeDeliveredInINBOXWhenSpecifiedFolderExistsForAnotherUser(@TempDir
 File temporaryFolder) throws Exception {
+        setup(temporaryFolder);
+
+        // create mailbox for recipient 1
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX);
+        testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX + " " + 
"anyone" + " p");
+
+        // send to recipient 2
+        messageSender.connect(LOCALHOST_IP, 
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+                .authenticate(FROM, PASSWORD)
+                .sendMessage(FROM, "recipient2+" + TARGETED_MAILBOX + "@" + 
DEFAULT_DOMAIN);
+
+        testIMAPClient
+                .connect(LOCALHOST_IP, 
jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+                .login(RECIPIENT2, PASSWORD)
+                .select(MailboxConstants.INBOX)
+                .awaitMessage(awaitAtMostOneMinute);
+    }
+
+    @Test
+    void 
subAddressedEmailShouldBeDeliveredInSpecifiedFolderWhenItExistsInUpperCase(@TempDir
 File temporaryFolder) throws Exception {
+        setup(temporaryFolder);
+
+        // create mailbox
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_UPPER);
+        testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_UPPER + " " + 
"anyone" + " p");
+
+        sendSubAddressedMail(TARGETED_MAILBOX_LOWER);
+        awaitSubAddressedMail(TARGETED_MAILBOX_UPPER);
+    }
+
+    @Test
+    void 
subAddressedEmailShouldBeDeliveredInSpecifiedFolderWhenItExistsInLowerCase(@TempDir
 File temporaryFolder) throws Exception {
+        setup(temporaryFolder);
+
+        // create mailbox
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_LOWER);
+        testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_LOWER + " " + 
"anyone" + " p");
+
+        sendSubAddressedMail(TARGETED_MAILBOX_UPPER);
+        awaitSubAddressedMail(TARGETED_MAILBOX_LOWER);
+    }
+
+    @Test
+    void 
subAddressedEmailShouldBeDeliveredInSpecifiedFolderWithCorrectLowerCaseWhenSeveralCasesExist(@TempDir
 File temporaryFolder) throws Exception {
+        setup(temporaryFolder);
+
+        // create mailboxes
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_LOWER);
+        testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_LOWER + " " + 
"anyone" + " p");
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_UPPER);
+        testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_UPPER + " " + 
"anyone" + " p");
+
+        sendSubAddressedMail(TARGETED_MAILBOX_LOWER);
+        awaitSubAddressedMail(TARGETED_MAILBOX_LOWER);
+    }
+
+    @Test
+    void 
subAddressedEmailShouldBeDeliveredInSpecifiedFolderWithCorrectUpperCaseWhenSeveralCasesExist(@TempDir
 File temporaryFolder) throws Exception {
+        setup(temporaryFolder);
+
+        // create mailboxes
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_LOWER);
+        testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_LOWER + " " + 
"anyone" + " p");
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_UPPER);
+        testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_UPPER + " " + 
"anyone" + " p");
+
+        sendSubAddressedMail(TARGETED_MAILBOX_UPPER);
+        awaitSubAddressedMail(TARGETED_MAILBOX_UPPER);
+    }
+
+    @Test
+    void 
subAddressedEmailShouldBeDeliveredInINBOXWhenSpecifiedFolderExistsWithCorrectCaseButNoRightAndOtherCaseButRight(@TempDir
 File temporaryFolder) throws Exception {
+        setup(temporaryFolder);
+
+        // create mailbox with incorrect case and give posting right
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_LOWER);
+        testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX_LOWER + " " + 
"anyone" + " p");
+
+        // create mailbox with correct case but don't give posting right
+        testIMAPClient.sendCommand("CREATE " + TARGETED_MAILBOX_UPPER);
+
+        sendSubAddressedMail(TARGETED_MAILBOX_UPPER);
         awaitSubAddressedMail(MailboxConstants.INBOX);
     }
 
@@ -114,7 +206,7 @@ class SubAddressingTest {
         // do not give posting rights
         testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX + " " + 
"anyone" + " -p");
 
-        sendSubAddressedMail();
+        sendSubAddressedMail(TARGETED_MAILBOX);
         awaitSubAddressedMail(MailboxConstants.INBOX);
     }
 
@@ -127,14 +219,14 @@ class SubAddressingTest {
         // give posting rights for anyone
         testIMAPClient.sendCommand("SETACL " + TARGETED_MAILBOX + " " + 
"anyone" + " +p");
 
-        sendSubAddressedMail();
+        sendSubAddressedMail(TARGETED_MAILBOX);
         awaitSubAddressedMail(TARGETED_MAILBOX);
     }
 
-    private void sendSubAddressedMail() throws IOException, 
NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+    private void sendSubAddressedMail(String targetMailbox) throws 
IOException, NoSuchAlgorithmException, InvalidKeyException, 
InvalidKeySpecException {
         messageSender.connect(LOCALHOST_IP, 
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
             .authenticate(FROM, PASSWORD)
-            .sendMessage(FROM, "user2+" + TARGETED_MAILBOX + "@" + 
DEFAULT_DOMAIN);
+            .sendMessage(FROM,"recipient+" + targetMailbox + "@" + 
DEFAULT_DOMAIN);
     }
 
     private void awaitSubAddressedMail(String expectedMailbox) throws 
IOException {
diff --git 
a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SubAddressing.java
 
b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SubAddressing.java
index 41c4e1119f..f3d1c5c1c6 100644
--- 
a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SubAddressing.java
+++ 
b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/SubAddressing.java
@@ -21,6 +21,9 @@ package org.apache.james.transport.mailets;
 
 import static 
org.apache.james.mailbox.MessageManager.MailboxMetaData.RecentMode.IGNORE;
 
+import java.util.Comparator;
+import java.util.Optional;
+
 import jakarta.inject.Inject;
 import jakarta.inject.Named;
 import jakarta.mail.MessagingException;
@@ -36,7 +39,10 @@ import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.exception.UnsupportedRightException;
 import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxMetaData;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.search.ExactNameCaseInsensitive;
+import org.apache.james.mailbox.model.search.MailboxQuery;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
 import org.apache.mailet.Mail;
@@ -83,33 +89,56 @@ public class SubAddressing extends GenericMailet {
     public void service(Mail mail) throws MessagingException {
         mail.getRecipients().forEach(recipient ->
             
recipient.getLocalPartDetails(UsersRepository.LOCALPART_DETAIL_DELIMITER)
-                .ifPresent(Throwing.consumer(targetFolder -> 
postIfHasRight(mail, recipient, targetFolder))));
+                .ifPresent(Throwing.consumer(targetFolder -> postIfHasRight(
+                        mail,
+                        
recipient.stripDetails(UsersRepository.LOCALPART_DETAIL_DELIMITER),
+                        getPathWithCorrectCase(recipient, targetFolder)))));
+    }
+
+    private Optional<MailboxPath> getPathWithCorrectCase(MailAddress 
recipient, String targetFolder) throws UsersRepositoryException, 
MailboxException {
+        Username recipientUsername = usersRepository.getUsername(recipient);
+        MailboxSession session = 
mailboxManager.createSystemSession(recipientUsername);
+
+        Comparator<MailboxPath> exactMatchFirst = 
Comparator.comparing(mailboxPath -> mailboxPath.getName().equals(targetFolder) 
? 0 : 1);
+
+        return mailboxManager.search(
+                        
MailboxQuery.privateMailboxesBuilder(session).expression(new 
ExactNameCaseInsensitive(targetFolder)).build(),
+                        session)
+                .toStream()
+                .map(MailboxMetaData::getPath)
+                .sorted(exactMatchFirst)
+                .findFirst()
+                .or(() -> {
+                    LOG.info("{}'s subfolder `{}` was tried to be addressed 
but it does not exist", recipient, targetFolder);
+                    return Optional.empty();
+                });
     }
 
-    private void postIfHasRight(Mail mail, MailAddress recipient, String 
targetFolder) throws UsersRepositoryException, MailboxException {
-        if (hasPostRight(mail, recipient, targetFolder)) {
-            
StorageDirective.builder().targetFolders(ImmutableList.of(targetFolder)).build()
+    private void postIfHasRight(Mail mail, MailAddress recipient, 
Optional<MailboxPath> targetFolderPath) throws UsersRepositoryException, 
MailboxException {
+        if (hasPostRight(mail, recipient, targetFolderPath)) {
+            
StorageDirective.builder().targetFolders(ImmutableList.of(targetFolderPath.get().getName())).build()
                 .encodeAsAttributes(usersRepository.getUsername(recipient))
                 .forEach(mail::setAttribute);
         } else {
             LOG.info("{} tried to address {}'s subfolder `{}` but they did not 
have the right to",
-                mail.getMaybeSender().toString(), 
recipient.stripDetails(UsersRepository.LOCALPART_DETAIL_DELIMITER), 
targetFolder);
+                mail.getMaybeSender().toString(), recipient, targetFolderPath);
         }
     }
 
-    private Boolean hasPostRight(Mail mail, MailAddress recipient, String 
targetFolder) throws MailboxException, UsersRepositoryException {
+    private Boolean hasPostRight(Mail mail, MailAddress recipient, 
Optional<MailboxPath> targetFolderPath) throws MailboxException, 
UsersRepositoryException {
         try {
-            return resolvePostRight(retrieveMailboxACL(recipient, 
targetFolder), mail.getMaybeSender(), recipient);
+            return targetFolderPath.isPresent() && 
resolvePostRight(retrieveMailboxACL(recipient, targetFolderPath.get()), 
mail.getMaybeSender(), recipient);
         } catch (MailboxNotFoundException e) {
-            LOG.info("{}'s subfolder `{}` was tried to be addressed but it 
does not exist", 
recipient.stripDetails(UsersRepository.LOCALPART_DETAIL_DELIMITER), 
targetFolder);
+            LOG.info("{}'s subfolder `{}` was tried to be addressed but it 
does not exist", recipient, targetFolderPath);
             return false;
         }
     }
 
-    private MailboxACL retrieveMailboxACL(MailAddress recipient, String 
targetFolder) throws MailboxException, UsersRepositoryException {
+    private MailboxACL retrieveMailboxACL(MailAddress recipient, MailboxPath 
targetFolderPath) throws MailboxException, UsersRepositoryException {
         Username recipientUsername = usersRepository.getUsername(recipient);
         MailboxSession session = 
mailboxManager.createSystemSession(recipientUsername);
-        return 
mailboxManager.getMailbox(MailboxPath.forUser(recipientUsername, targetFolder), 
session)
+
+        return mailboxManager.getMailbox(targetFolderPath, session)
             .getMetaData(IGNORE, session, 
MessageManager.MailboxMetaData.FetchGroup.NO_COUNT)
             .getACL();
     }
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SubAddressingTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SubAddressingTest.java
index c7b160f480..3307083262 100644
--- 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SubAddressingTest.java
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/SubAddressingTest.java
@@ -52,15 +52,15 @@ import org.junit.jupiter.api.Test;
 public class SubAddressingTest {
 
     private static final String SENDER1 = "sender1@localhost";
-    private static final String SENDER2 = "sender2@localhost";
     private static final String RECIPIENT = "recipient@localhost";
     private static final String TARGET = "targetfolder";
-    private static final String UNEXISTING_TARGET = "unexistingfolder";
+    private static final String TARGET_UPPERCASE = "Targetfolder";
+    private static final String TARGET_UNEXISTING = "unexistingfolder";
 
     private MailboxManager mailboxManager;
     private SubAddressing testee;
     private UsersRepository usersRepository;
-    private Username sender1Username;
+    private Username senderUsername;
     private Username recipientUsername;
     private MailboxSession recipientSession;
     private MailboxId targetMailboxId;
@@ -71,7 +71,7 @@ public class SubAddressingTest {
         usersRepository = 
MemoryUsersRepository.withVirtualHosting(NO_DOMAIN_LIST);
 
         recipientUsername = usersRepository.getUsername(new 
MailAddress(RECIPIENT));
-        sender1Username = usersRepository.getUsername(new 
MailAddress(SENDER1));
+        senderUsername = usersRepository.getUsername(new MailAddress(SENDER1));
 
         recipientSession = 
mailboxManager.createSystemSession(recipientUsername);
         targetMailboxId = mailboxManager.createMailbox(
@@ -83,73 +83,78 @@ public class SubAddressingTest {
 
     @Test
     void shouldNotAddStorageDirectiveWhenTargetMailboxDoesNotExist() throws 
Exception {
-        Mail mail = mailBuilder(UNEXISTING_TARGET).sender(SENDER1).build();
+        Mail mail = mailBuilder(TARGET_UNEXISTING).sender(SENDER1).build();
         testee.service(mail);
 
         AttributeName recipient = 
AttributeName.of("DeliveryPaths_recipient@localhost");
         assertThat(mail.attributes().map(this::unbox))
-            .doesNotContain(Pair.of(recipient, UNEXISTING_TARGET));
+            .doesNotContain(Pair.of(recipient, TARGET_UNEXISTING));
     }
 
     @Test
-    void shouldNotAddStorageDirectiveWhenNobodyHasRight() throws Exception {
-        removePostRightForKey(MailboxACL.ANYONE_KEY);
+    void shouldAddStorageDirectiveWhenTargetMailboxExistsButLowerCase() throws 
Exception {
+        givePostRightForKey(MailboxACL.ANYONE_KEY);
 
-        Mail mail = mailBuilder(TARGET).sender(SENDER1).build();
+        Mail mail = mailBuilder(TARGET_UPPERCASE).sender(SENDER1).build();
         testee.service(mail);
 
         AttributeName recipient = 
AttributeName.of("DeliveryPaths_recipient@localhost");
         assertThat(mail.attributes().map(this::unbox))
-            .doesNotContain(Pair.of(recipient, TARGET));
+                .containsOnly(Pair.of(recipient, TARGET));
     }
 
-
     @Test
-    void shouldAddStorageDirectiveWhenAnyoneHasRight() throws Exception {
-        givePostRightForKey(MailboxACL.ANYONE_KEY);
+    void shouldAddStorageDirectiveWhenTargetMailboxExistsButUpperCase() throws 
Exception {
+        targetMailboxId = mailboxManager.createMailbox(
+                MailboxPath.forUser(recipientUsername, "Target"), 
recipientSession).get();
 
-        Mail mail = mailBuilder(TARGET).sender(SENDER1).build();
+        MailboxACL.ACLCommand command = MailboxACL.command()
+                .key(MailboxACL.ANYONE_KEY)
+                .rights(MailboxACL.Right.Post)
+                .asAddition();
+        mailboxManager.applyRightsCommand(targetMailboxId, command, 
recipientSession);
+
+        Mail mail = mailBuilder("target").sender(SENDER1).build();
         testee.service(mail);
 
         AttributeName recipient = 
AttributeName.of("DeliveryPaths_recipient@localhost");
         assertThat(mail.attributes().map(this::unbox))
-            .containsOnly(Pair.of(recipient, TARGET));
+                .containsOnly(Pair.of(recipient, "Target"));
     }
 
-    //@Disabled
     @Test
-    void shouldAddStorageDirectiveWhenSenderIsWhiteListed() throws Exception {
-        // whitelist sender 1 and send from sender 1
-        removePostRightForKey(MailboxACL.ANYONE_KEY);
-        
givePostRightForKey(MailboxACL.EntryKey.createUserEntryKey(sender1Username));
+    void shouldAddStorageDirectiveWhenTargetFolderIsASubFolder() throws 
Exception {
+        givePostRightForKey(MailboxACL.ANYONE_KEY);
 
-        Mail mail = mailBuilder(TARGET).sender(SENDER1).build();
+        MailboxPath newPath = MailboxPath.forUser(recipientUsername, "folder" 
+ recipientSession.getPathDelimiter() + TARGET);
+        mailboxManager.renameMailbox(targetMailboxId, newPath, 
recipientSession).getFirst().getMailboxId();
+
+        Mail mail = mailBuilder("folder" + recipientSession.getPathDelimiter() 
+ TARGET).sender(SENDER1).build();
         testee.service(mail);
 
         AttributeName recipient = 
AttributeName.of("DeliveryPaths_recipient@localhost");
         assertThat(mail.attributes().map(this::unbox))
-            .containsOnly(Pair.of(recipient, TARGET));
+                .containsOnly(Pair.of(recipient, "folder" + 
recipientSession.getPathDelimiter() + TARGET));
     }
 
     @Test
-    void shouldNotAddStorageDirectiveWhenSenderIsNotWhiteListed() throws 
Exception {
-        // whitelist sender 1 and send from sender 2
-        removePostRightForKey(MailboxACL.ANYONE_KEY);
-        
givePostRightForKey(MailboxACL.EntryKey.createUserEntryKey(sender1Username));
+    void 
shouldAddStorageDirectiveWhenTargetFolderIsASubFolderWithDifferentCase() throws 
Exception {
+        givePostRightForKey(MailboxACL.ANYONE_KEY);
+
+        MailboxPath newPath = MailboxPath.forUser(recipientUsername, "Folder" 
+ recipientSession.getPathDelimiter() + TARGET_UPPERCASE);
+        mailboxManager.renameMailbox(targetMailboxId, newPath, 
recipientSession).getFirst().getMailboxId();
 
-        Mail mail = mailBuilder(TARGET).sender(SENDER2).build();
+        Mail mail = mailBuilder("folder" + recipientSession.getPathDelimiter() 
+ TARGET).sender(SENDER1).build();
         testee.service(mail);
 
         AttributeName recipient = 
AttributeName.of("DeliveryPaths_recipient@localhost");
         assertThat(mail.attributes().map(this::unbox))
-            .doesNotContain(Pair.of(recipient, TARGET));
+                .containsOnly(Pair.of(recipient, "Folder" + 
recipientSession.getPathDelimiter() + TARGET_UPPERCASE));
     }
 
     @Test
-    void shouldNotAddStorageDirectiveWhenSenderIsBlackListed() throws 
Exception {
-        // blacklist sender 1 and send from sender 1
-        givePostRightForKey(MailboxACL.ANYONE_KEY);
-        
givePostRightForKey(MailboxACL.EntryKey.createNegativeUserEntryKey(sender1Username));
+    void shouldNotAddStorageDirectiveWhenNobodyHasRight() throws Exception {
+        removePostRightForKey(MailboxACL.ANYONE_KEY);
 
         Mail mail = mailBuilder(TARGET).sender(SENDER1).build();
         testee.service(mail);
@@ -159,13 +164,12 @@ public class SubAddressingTest {
             .doesNotContain(Pair.of(recipient, TARGET));
     }
 
+
     @Test
-    void shouldAddStorageDirectiveWhenSenderIsNotBlackListed() throws 
Exception {
-        // blacklist sender 1 and send from sender 2
+    void shouldAddStorageDirectiveWhenAnyoneHasRight() throws Exception {
         givePostRightForKey(MailboxACL.ANYONE_KEY);
-        
removePostRightForKey(MailboxACL.EntryKey.createUserEntryKey(sender1Username));
 
-        Mail mail = mailBuilder(TARGET).sender(SENDER2).build();
+        Mail mail = mailBuilder(TARGET).sender(SENDER1).build();
         testee.service(mail);
 
         AttributeName recipient = 
AttributeName.of("DeliveryPaths_recipient@localhost");


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


Reply via email to