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]