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-jdkim.git

commit e2c458cbbcfa5182ff0443f1ef500bff450ea9fe
Author: Benoit TELLIER <[email protected]>
AuthorDate: Sun Nov 1 19:59:16 2026 +0000

    [FIX] Message: parse with mime4j's permissive limits
    
    Message parsed with mime4j's default limit of 1000 header fields and
    hard-coded limits of 30000 octets per field and 10000 per line, so
    RFC 5322-valid mail with long Received: traces or a To: of a few hundred
    recipients could be neither signed nor verified. The MimeIOException
    was rethrown as an IOException: the signer sent the mail unsigned and
    the verifier gave no result at all.
    
    mime4j is upgraded to 0.8.15 and Message now uses its
    MimeConfig.PERMISSIVE profile (4096 fields, 64 KB per field, no line
    limit, 100 MB of content), well above what real mail uses; the next
    mime4j release makes these limits configurable with the
    james.mime4j.permissive.* system properties. Message rethrows a parsing
    failure as a MimeException, which sign and verify already turn into a
    PermFailException.
---
 docs/modules/ROOT/pages/usage.adoc                 |  9 +++
 .../java/org/apache/james/jdkim/impl/Message.java  | 17 +++--
 .../apache/james/jdkim/impl/MessageLimitsTest.java | 89 ++++++++++++++++++++++
 pom.xml                                            |  2 +-
 4 files changed, 110 insertions(+), 7 deletions(-)

diff --git a/docs/modules/ROOT/pages/usage.adoc 
b/docs/modules/ROOT/pages/usage.adoc
index ca2ef34..2ef4adf 100644
--- a/docs/modules/ROOT/pages/usage.adoc
+++ b/docs/modules/ROOT/pages/usage.adoc
@@ -286,6 +286,15 @@ slightly malformed signatures or keys. Start the JVM with
 with a semantic effect -- a key for another service (`s=chat`), an expired
 signature, an unsupported algorithm -- are always applied.
 
+=== Message parsing limits
+
+`sign(InputStream)` and `verify(InputStream)` parse the message with mime4j's
+`MimeConfig.PERMISSIVE` profile, whose limits (4096 header fields, 64 KB per
+header field, no line limit, 100 MB of content) are well above what real mail
+uses. The next mime4j release makes them configurable with the
+`james.mime4j.permissive.*` system properties. A message exceeding them is a 
permanent error
+(`PermFailException`), not an I/O error.
+
 == Applying several signatures
 
 Signing with more than one key lets you offer several algorithms on one
diff --git a/main/src/main/java/org/apache/james/jdkim/impl/Message.java 
b/main/src/main/java/org/apache/james/jdkim/impl/Message.java
index 8bb580d..3015562 100644
--- a/main/src/main/java/org/apache/james/jdkim/impl/Message.java
+++ b/main/src/main/java/org/apache/james/jdkim/impl/Message.java
@@ -52,7 +52,7 @@ public class Message implements Headers {
      *
      * @param is the stream to read the header from.
      * @throws IOException     on I/O errors.
-     * @throws MimeIOException on MIME protocol violations.
+     * @throws MimeException  on MIME protocol violations, or when a parsing 
limit is exceeded.
      */
     public Message(InputStream is) throws IOException, MimeException {
         MessageBuilder mb = newMessageBuilder().newMessageBuilder();
@@ -60,7 +60,12 @@ public class Message implements Headers {
         // mime4j drops the fields whose name it cannot parse and normalizes 
the
         // others: the fields are taken from the raw header bytes instead.
         HeaderCapturingInputStream header = new HeaderCapturingInputStream(new 
EOLConvertingInputStream(is));
-        this.message = mb.parseMessage(header);
+        try {
+            this.message = mb.parseMessage(header);
+        } catch (MimeIOException e) {
+            // a parsing limit or a MIME violation, not an I/O error: callers 
report it as a permanent error
+            throw e.getCause() != null ? e.getCause() : new MimeException(e);
+        }
 
         List<String> all = splitFields(new String(header.getHeader(), 
StandardCharsets.ISO_8859_1));
         Map<String, List<String>> byName = new HashMap<>();
@@ -73,10 +78,10 @@ public class Message implements Headers {
     }
 
     private MessageServiceFactory newMessageBuilder() throws MimeException {
-        MimeConfig mec = MimeConfig.custom()
-                .setMaxLineLen(10000)
-                .setMaxHeaderLen(30000)
-                .build();
+        // the limits of mime4j's permissive profile (4096 fields, 64 KB per 
field, no line limit) are well
+        // above what real mail uses; the next mime4j release makes them 
configurable with the
+        // james.mime4j.permissive.* system properties
+        MimeConfig mec = MimeConfig.PERMISSIVE;
 
         MessageServiceFactory mbf = MessageServiceFactory.newInstance();
         mbf.setAttribute("MimeEntityConfig", mec);
diff --git 
a/main/src/test/java/org/apache/james/jdkim/impl/MessageLimitsTest.java 
b/main/src/test/java/org/apache/james/jdkim/impl/MessageLimitsTest.java
new file mode 100644
index 0000000..bfc4439
--- /dev/null
+++ b/main/src/test/java/org/apache/james/jdkim/impl/MessageLimitsTest.java
@@ -0,0 +1,89 @@
+/******************************************************************************
+ * 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.jdkim.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.james.jdkim.DKIMSigner;
+import org.apache.james.jdkim.TestKeys;
+import org.junit.Test;
+
+public class MessageLimitsTest {
+
+    private static ByteArrayInputStream stream(StringBuilder sb) {
+        return new 
ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.US_ASCII));
+    }
+
+    @Test
+    public void shouldParseMoreThanThousandHeaderFields() throws Exception {
+        StringBuilder sb = new StringBuilder("From: [email protected]\r\n");
+        for (int i = 0; i < 2500; i++) {
+            sb.append("X-Trace: hop ").append(i).append("\r\n");
+        }
+        sb.append("\r\nbody\r\n");
+
+        Message message = new Message(stream(sb));
+
+        assertThat(message.getFields("X-Trace")).hasSize(2500);
+    }
+
+    @Test
+    public void shouldParseLongFoldedField() throws Exception {
+        StringBuilder sb = new StringBuilder("From: [email protected]\r\nTo: 
[email protected]");
+        for (int i = 1; i < 2000; i++) {
+            sb.append(",\r\n r").append(i).append("@example.com");
+        }
+        sb.append("\r\n\r\nbody\r\n");
+
+        Message message = new Message(stream(sb));
+
+        assertThat(message.getFields("To")).hasSize(1);
+    }
+
+    @Test
+    public void shouldParseLongLine() throws Exception {
+        StringBuilder sb = new StringBuilder("From: 
[email protected]\r\nX-Long: ");
+        for (int i = 0; i < 20000; i++) {
+            sb.append('x');
+        }
+        sb.append("\r\n\r\nbody\r\n");
+
+        Message message = new Message(stream(sb));
+
+        assertThat(message.getFields("X-Long")).hasSize(1);
+    }
+
+    @Test
+    public void signerShouldSignMessagesWithManyHeaderFields() throws 
Exception {
+        StringBuilder sb = new StringBuilder("From: [email protected]\r\n");
+        for (int i = 0; i < 2500; i++) {
+            sb.append("Received: hop ").append(i).append("\r\n");
+        }
+        sb.append("\r\nbody\r\n");
+
+        String field = new DKIMSigner("v=1; a=rsa-sha256; c=simple; 
d=messiah.edu; h=from; s=selector2;", TestKeys.privateKey)
+                .sign(stream(sb));
+
+        assertThat(field).startsWith("DKIM-Signature:");
+    }
+}
diff --git a/pom.xml b/pom.xml
index 29fe43d..6a34050 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,7 +61,7 @@
     </distributionManagement>
 
     <properties>
-        <apache-mime4j.version>0.8.12</apache-mime4j.version>
+        <apache-mime4j.version>0.8.15</apache-mime4j.version>
         <dnsjava.version>3.6.3</dnsjava.version>
         <james-skin.version>1.8</james-skin.version>
         <junit.version>4.13.2</junit.version>


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

Reply via email to