This is an automated email from the ASF dual-hosted git repository.

chibenwa 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 47c84fa30e [FIX] Return tagged bad upon utf7 errors
47c84fa30e is described below

commit 47c84fa30eea2932b72e05b12af2f4214fc27489
Author: Benoit TELLIER <[email protected]>
AuthorDate: Wed Sep 9 14:18:56 2026 +0200

    [FIX] Return tagged bad upon utf7 errors
---
 .../suite/MailboxNameWithInvalidUtf7.java          | 52 +++++++++++++++
 .../imap/scripts/MailboxNameWithInvalidUtf7.test   | 74 ++++++++++++++++++++++
 .../InMemoryMailboxNameWithInvalidUtf7Test.java    | 42 ++++++++++++
 ...difiedUtf7.java => MalformedUtf7Exception.java} | 42 ++----------
 .../james/imap/api/display/ModifiedUtf7.java       | 15 ++++-
 .../james/imap/decode/ImapRequestLineReader.java   |  8 ++-
 .../imap/decode/parser/ListCommandParser.java      | 23 ++++++-
 .../james/imap/api/display/ModifiedUtf7Test.java   | 66 +++++++++++++++++++
 8 files changed, 282 insertions(+), 40 deletions(-)

diff --git 
a/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/MailboxNameWithInvalidUtf7.java
 
b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/MailboxNameWithInvalidUtf7.java
new file mode 100644
index 0000000000..c38f8ec93a
--- /dev/null
+++ 
b/mpt/impl/imap-mailbox/core/src/main/java/org/apache/james/mpt/imapmailbox/suite/MailboxNameWithInvalidUtf7.java
@@ -0,0 +1,52 @@
+/****************************************************************
+ * 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.mpt.imapmailbox.suite;
+
+import java.util.Locale;
+
+import org.apache.james.mpt.api.ImapHostSystem;
+import org.apache.james.mpt.imapmailbox.ImapTestConstants;
+import org.apache.james.mpt.imapmailbox.suite.base.BasicImapCommands;
+import org.apache.james.mpt.script.SimpleScriptedTestProtocol;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public abstract class MailboxNameWithInvalidUtf7 implements ImapTestConstants {
+
+    protected abstract ImapHostSystem createImapHostSystem();
+
+    private SimpleScriptedTestProtocol simpleScriptedTestProtocol;
+
+    @BeforeEach
+    public void setUp() throws Exception {
+        simpleScriptedTestProtocol = new 
SimpleScriptedTestProtocol("/org/apache/james/imap/scripts/", 
createImapHostSystem())
+                .withUser(USER, PASSWORD)
+                .withLocale(Locale.US);
+        BasicImapCommands.welcome(simpleScriptedTestProtocol);
+        BasicImapCommands.authenticate(simpleScriptedTestProtocol);
+    }
+
+    @Test
+    public void testMailboxNameWithInvalidUtf7US() throws Exception {
+        simpleScriptedTestProtocol
+            .withLocale(Locale.US)
+            .run("MailboxNameWithInvalidUtf7");
+    }
+}
diff --git 
a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/MailboxNameWithInvalidUtf7.test
 
b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/MailboxNameWithInvalidUtf7.test
new file mode 100644
index 0000000000..1c08cf4977
--- /dev/null
+++ 
b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/MailboxNameWithInvalidUtf7.test
@@ -0,0 +1,74 @@
+################################################################
+# 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.                                           #
+################################################################
+# A mailbox name holding a badly encoded modified UTF-7 sequence must be
+# rejected with a BAD response, and must not tear down the connection.
+C: A1 CREATE &
+S: A1 BAD CREATE failed. Illegal arguments.
+
+# An unterminated shift sequence: the trailing '-' is missing
+C: A2 CREATE "&AOE"
+S: A2 BAD CREATE failed. Illegal arguments.
+
+# Non BASE64 content within a shift sequence
+C: A3 CREATE "&x-"
+S: A3 BAD CREATE failed. Illegal arguments.
+
+# Other commands taking a mailbox name are rejected the very same way
+C: A4 SELECT &
+S: A4 BAD SELECT failed. Illegal arguments.
+
+C: A5 DELETE "&AOE"
+S: A5 BAD DELETE failed. Illegal arguments.
+
+C: A6 STATUS & (MESSAGES)
+S: A6 BAD STATUS failed. Illegal arguments.
+
+C: A7 RENAME & other
+S: A7 BAD RENAME failed. Illegal arguments.
+
+C: A8 SUBSCRIBE &
+S: A8 BAD SUBSCRIBE failed. Illegal arguments.
+
+# LIST and LSUB carry their pattern modified UTF-7 encoded up to the processor
+C: A9 LIST "&" ""
+S: A9 BAD LIST failed. Illegal arguments.
+
+C: A10 LIST "" "&AOE"
+S: A10 BAD LIST failed. Illegal arguments.
+
+C: A11 LSUB "" "&AOE"
+S: A11 BAD LSUB failed. Illegal arguments.
+
+# '&-' is the modified UTF-7 encoding of a single '&': a valid reference name 
that
+# decodes to a value that is not valid modified UTF-7 itself
+C: A12 LIST "&-" ""
+S: \* LIST \(\\Noselect\) \"\.\" \"\"
+S: A12 OK LIST completed.
+
+# Valid modified UTF-7 keeps being accepted: &AOE- decodes to 'a' with an 
acute accent
+C: A13 CREATE "&AOE-"
+S: A13 OK \[MAILBOXID \(.+\)\] CREATE completed.
+
+C: A14 LIST "" "&AOE-"
+S: \* LIST \(\\HasNoChildren\) \"\.\" \"&AOE-\"
+S: A14 OK LIST completed.
+
+# The connection survived every single one of those errors
+C: A15 CREATE anothermailbox
+S: A15 OK \[MAILBOXID \(.+\)\] CREATE completed.
diff --git 
a/mpt/impl/imap-mailbox/inmemory/src/test/java/org/apache/james/mpt/imapmailbox/inmemory/InMemoryMailboxNameWithInvalidUtf7Test.java
 
b/mpt/impl/imap-mailbox/inmemory/src/test/java/org/apache/james/mpt/imapmailbox/inmemory/InMemoryMailboxNameWithInvalidUtf7Test.java
new file mode 100644
index 0000000000..002897cd6c
--- /dev/null
+++ 
b/mpt/impl/imap-mailbox/inmemory/src/test/java/org/apache/james/mpt/imapmailbox/inmemory/InMemoryMailboxNameWithInvalidUtf7Test.java
@@ -0,0 +1,42 @@
+/****************************************************************
+ * 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.mpt.imapmailbox.inmemory;
+
+import org.apache.james.mpt.api.ImapHostSystem;
+import org.apache.james.mpt.imapmailbox.inmemory.host.InMemoryHostSystem;
+import org.apache.james.mpt.imapmailbox.suite.MailboxNameWithInvalidUtf7;
+import org.junit.jupiter.api.BeforeEach;
+
+public class InMemoryMailboxNameWithInvalidUtf7Test extends 
MailboxNameWithInvalidUtf7 {
+    private ImapHostSystem system;
+
+    @Override
+    @BeforeEach
+    public void setUp() throws Exception {
+        system = new InMemoryHostSystem();
+        system.beforeTest();
+        super.setUp();
+    }
+
+    @Override
+    protected ImapHostSystem createImapHostSystem() {
+        return system;
+    }
+}
diff --git 
a/protocols/imap/src/main/java/org/apache/james/imap/api/display/ModifiedUtf7.java
 
b/protocols/imap/src/main/java/org/apache/james/imap/api/display/MalformedUtf7Exception.java
similarity index 50%
copy from 
protocols/imap/src/main/java/org/apache/james/imap/api/display/ModifiedUtf7.java
copy to 
protocols/imap/src/main/java/org/apache/james/imap/api/display/MalformedUtf7Exception.java
index e39ffd91e2..a1637ecee6 100644
--- 
a/protocols/imap/src/main/java/org/apache/james/imap/api/display/ModifiedUtf7.java
+++ 
b/protocols/imap/src/main/java/org/apache/james/imap/api/display/MalformedUtf7Exception.java
@@ -18,42 +18,14 @@
  ****************************************************************/
 package org.apache.james.imap.api.display;
 
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-
-import com.beetstra.jutf7.CharsetProvider;
-import com.google.common.base.CharMatcher;
-
 /**
- * This class has some methods included which helps to encode/decode modified 
UTF7
+ * Thrown when a value can not be decoded as modified UTF-7, as defined by 
RFC3501.
+ *
+ * This denotes a client side protocol error: such a value needs to be 
rejected rather than
+ * to be treated as an internal error.
  */
-public class ModifiedUtf7 {
-    private static final CharMatcher UNENCODED_CHAR_MATCHER = 
CharMatcher.isNot('&');
-
-    private static final Charset X_MODIFIED_UTF_7_CHARSET = new 
CharsetProvider().charsetForName("X-MODIFIED-UTF-7");
-
-    /**
-     * Decode the given UTF7 encoded <code>String</code>
-     * 
-     * @param input utf7-encoded value
-     * @return decoded value
-     */
-    public static String decodeModifiedUTF7(String input) {
-        if (UNENCODED_CHAR_MATCHER.matchesAllOf(input)) {
-            return input;
-        }
-        return 
X_MODIFIED_UTF_7_CHARSET.decode(ByteBuffer.wrap(input.getBytes())).toString();
-    }
-
-    /**
-     * Encode the given <code>String</code> to modified UTF7. 
-     * See RFC3501 for more details
-     * 
-     * @param input
-     * @return utf7-encoded value
-     */
-    public static String encodeModifiedUTF7(String input) {
-        ByteBuffer encode = X_MODIFIED_UTF_7_CHARSET.encode(input);
-        return new String(encode.array(), 0, encode.remaining());
+public class MalformedUtf7Exception extends RuntimeException {
+    public MalformedUtf7Exception(String input, Throwable cause) {
+        super("'" + input + "' is not a valid modified UTF-7 value", cause);
     }
 }
diff --git 
a/protocols/imap/src/main/java/org/apache/james/imap/api/display/ModifiedUtf7.java
 
b/protocols/imap/src/main/java/org/apache/james/imap/api/display/ModifiedUtf7.java
index e39ffd91e2..1353dc2e8b 100644
--- 
a/protocols/imap/src/main/java/org/apache/james/imap/api/display/ModifiedUtf7.java
+++ 
b/protocols/imap/src/main/java/org/apache/james/imap/api/display/ModifiedUtf7.java
@@ -19,7 +19,9 @@
 package org.apache.james.imap.api.display;
 
 import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
 import java.nio.charset.Charset;
+import java.nio.charset.CodingErrorAction;
 
 import com.beetstra.jutf7.CharsetProvider;
 import com.google.common.base.CharMatcher;
@@ -37,12 +39,23 @@ public class ModifiedUtf7 {
      * 
      * @param input utf7-encoded value
      * @return decoded value
+     * @throws MalformedUtf7Exception when the input holds an invalid modified 
UTF-7 sequence
      */
     public static String decodeModifiedUTF7(String input) {
         if (UNENCODED_CHAR_MATCHER.matchesAllOf(input)) {
             return input;
         }
-        return 
X_MODIFIED_UTF_7_CHARSET.decode(ByteBuffer.wrap(input.getBytes())).toString();
+        try {
+            // Charset::decode would report an unterminated shift sequence as 
a java.lang.Error, which
+            // callers can hardly handle: decode by hand in order to turn it 
into a regular exception.
+            return X_MODIFIED_UTF_7_CHARSET.newDecoder()
+                .onMalformedInput(CodingErrorAction.REPLACE)
+                .onUnmappableCharacter(CodingErrorAction.REPLACE)
+                .decode(ByteBuffer.wrap(input.getBytes()))
+                .toString();
+        } catch (CharacterCodingException e) {
+            throw new MalformedUtf7Exception(input, e);
+        }
     }
 
     /**
diff --git 
a/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestLineReader.java
 
b/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestLineReader.java
index 3aa5bbbe18..d30a26d433 100644
--- 
a/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestLineReader.java
+++ 
b/protocols/imap/src/main/java/org/apache/james/imap/decode/ImapRequestLineReader.java
@@ -43,6 +43,7 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.api.Tag;
 import org.apache.james.imap.api.display.HumanReadableText;
+import org.apache.james.imap.api.display.MalformedUtf7Exception;
 import org.apache.james.imap.api.display.ModifiedUtf7;
 import org.apache.james.imap.api.message.IdRange;
 import org.apache.james.imap.api.message.PartialRange;
@@ -489,7 +490,12 @@ public abstract class ImapRequestLineReader {
      * 
      */
     public String mailbox() throws DecodingException {
-       return ModifiedUtf7.decodeModifiedUTF7(mailboxUTF7());
+        String mailboxUTF7 = mailboxUTF7();
+        try {
+            return ModifiedUtf7.decodeModifiedUTF7(mailboxUTF7);
+        } catch (MalformedUtf7Exception e) {
+            throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, 
"Invalid mailbox name: not a valid modified UTF-7 value", e);
+        }
     }
 
     /**
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 e6d3e73ae4..8be5af4362 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
@@ -29,6 +29,8 @@ import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.api.ImapMessage;
 import org.apache.james.imap.api.Tag;
 import org.apache.james.imap.api.display.HumanReadableText;
+import org.apache.james.imap.api.display.MalformedUtf7Exception;
+import org.apache.james.imap.api.display.ModifiedUtf7;
 import org.apache.james.imap.api.message.StatusDataItems;
 import org.apache.james.imap.api.message.response.StatusResponseFactory;
 import org.apache.james.imap.api.process.ImapSession;
@@ -76,11 +78,26 @@ public class ListCommandParser extends 
AbstractUidCommandParser {
         char next = request.nextWordChar();
         switch (next) {
         case '"':
-            return request.consumeQuoted();
+            return assertDecodable(request.consumeQuoted());
         case '{':
-            return request.consumeLiteral(null);
+            return assertDecodable(request.consumeLiteral(null));
         default:
-            return request.consumeWord(ListCharValidator.INSTANCE);
+            return 
assertDecodable(request.consumeWord(ListCharValidator.INSTANCE));
+        }
+    }
+
+    /**
+     * The mailbox pattern is carried around modified UTF-7 encoded and is 
only decoded upon processing.
+     *
+     * Validate it here so that a malformed pattern is answered a BAD response 
rather than blowing up
+     * later on, outside of any decoding error handling.
+     */
+    private String assertDecodable(String mailboxPattern) throws 
DecodingException {
+        try {
+            ModifiedUtf7.decodeModifiedUTF7(mailboxPattern);
+            return mailboxPattern;
+        } catch (MalformedUtf7Exception e) {
+            throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, 
"Invalid mailbox pattern: not a valid modified UTF-7 value", e);
         }
     }
 
diff --git 
a/protocols/imap/src/test/java/org/apache/james/imap/api/display/ModifiedUtf7Test.java
 
b/protocols/imap/src/test/java/org/apache/james/imap/api/display/ModifiedUtf7Test.java
new file mode 100644
index 0000000000..0e0b09fb39
--- /dev/null
+++ 
b/protocols/imap/src/test/java/org/apache/james/imap/api/display/ModifiedUtf7Test.java
@@ -0,0 +1,66 @@
+/****************************************************************
+ * 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.imap.api.display;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.nio.charset.MalformedInputException;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class ModifiedUtf7Test {
+    @Test
+    void decodeShouldReturnUnencodedValueAsIs() {
+        
assertThat(ModifiedUtf7.decodeModifiedUTF7("INBOX.test")).isEqualTo("INBOX.test");
+    }
+
+    @Test
+    void decodeShouldSupportShiftSequences() {
+        assertThat(ModifiedUtf7.decodeModifiedUTF7("&AOE-")).isEqualTo("á");
+    }
+
+    @Test
+    void decodeShouldSupportEscapedAmpersand() {
+        assertThat(ModifiedUtf7.decodeModifiedUTF7("&-")).isEqualTo("&");
+    }
+
+    @Test
+    void encodeThenDecodeShouldRoundTrip() {
+        String value = "Dossier & éléments";
+
+        
assertThat(ModifiedUtf7.decodeModifiedUTF7(ModifiedUtf7.encodeModifiedUTF7(value))).isEqualTo(value);
+    }
+
+    /**
+     * Those used to be reported as a java.lang.Error, which would tear down 
the IMAP connection
+     * rather than being answered a BAD response.
+     *
+     * See https://github.com/linagora/tmail-backend/issues/2611
+     */
+    @ParameterizedTest
+    @ValueSource(strings = {"&", "&&", "&A", "&AO", "&AOE", "&x-", "test&", 
"a&b"})
+    void decodeShouldThrowMalformedUtf7ExceptionOnInvalidShiftSequence(String 
input) {
+        assertThatThrownBy(() -> ModifiedUtf7.decodeModifiedUTF7(input))
+            .isInstanceOf(MalformedUtf7Exception.class)
+            .hasCauseInstanceOf(MalformedInputException.class);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to