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 913a41941a2f82e0a854361e4950f861432b124a Author: Benoit TELLIER <[email protected]> AuthorDate: Wed Nov 4 15:01:27 2026 +0100 [FIX] Message: drop mime4j's per-field limits MimeConfig.PERMISSIVE still caps each header field at 64 KB and the header at 4096 fields. RFC 5322 sets no limit on a folded field, and a To: listing a few thousand recipients is longer than 64 KB: such messages could be neither signed nor verified (PermFailException "Maximum header length limit (65536) exceeded"). Message now raises these two limits to the profile's limits on the whole header (16384 fields, 1 MB), which keep the memory the header takes bounded. --- docs/modules/ROOT/pages/usage.adoc | 9 +++-- .../java/org/apache/james/jdkim/impl/Message.java | 12 ++++-- .../apache/james/jdkim/impl/MessageLimitsTest.java | 47 ++++++++++++++++++++++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc index 2245155..9938d07 100644 --- a/docs/modules/ROOT/pages/usage.adoc +++ b/docs/modules/ROOT/pages/usage.adoc @@ -293,10 +293,11 @@ 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 +`MimeConfig.PERMISSIVE` profile, without its per-field limits: RFC 5322 sets no +limit on the length of a folded field, and a `To:` listing a few thousand +recipients is longer than 64 KB. What remains (16384 header fields and 1 MB for +the whole header, no line limit, 100 MB of content) is well above what real +mail uses. A message exceeding these limits is a permanent error (`PermFailException`), not an I/O error. == Applying several signatures 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 3015562..687e669 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 @@ -78,10 +78,14 @@ public class Message implements Headers { } private MessageServiceFactory newMessageBuilder() throws MimeException { - // 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; + // mime4j's permissive profile, without its per-field limits (4096 fields, 64 KB per field): RFC 5322 + // sets none, and a To: listing a few thousand recipients exceeds 64 KB. The memory the header takes + // stays bounded by the limits on the whole header (16384 fields, 1 MB), which a single field cannot + // exceed anyway. + MimeConfig mec = MimeConfig.copy(MimeConfig.PERMISSIVE) + .setMaxHeaderCount(MimeConfig.PERMISSIVE.getMaxTotalHeaderCount()) + .setMaxHeaderLen((int) Math.min(Integer.MAX_VALUE, MimeConfig.PERMISSIVE.getMaxTotalHeaderLen())) + .build(); 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 index bfc4439..b2945f0 100644 --- a/main/src/test/java/org/apache/james/jdkim/impl/MessageLimitsTest.java +++ b/main/src/test/java/org/apache/james/jdkim/impl/MessageLimitsTest.java @@ -20,12 +20,14 @@ package org.apache.james.jdkim.impl; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import org.apache.james.jdkim.DKIMSigner; import org.apache.james.jdkim.TestKeys; +import org.apache.james.mime4j.MimeException; import org.junit.Test; public class MessageLimitsTest { @@ -86,4 +88,49 @@ public class MessageLimitsTest { assertThat(field).startsWith("DKIM-Signature:"); } + + private static StringBuilder longTo(int recipients) { + StringBuilder sb = new StringBuilder("From: [email protected]\r\nTo: [email protected]"); + for (int i = 1; i < recipients; i++) { + sb.append(",\r\n r").append(i).append("@example.com"); + } + return sb.append("\r\n\r\nbody\r\n"); + } + + @Test + public void shouldParseFoldedFieldLongerThan64K() throws Exception { + // about 90 KB unfolded + Message message = new Message(stream(longTo(5000))); + + assertThat(message.getFields("To")).hasSize(1); + } + + @Test + public void signerShouldSignMessageWithFoldedFieldLongerThan64K() throws Exception { + String field = new DKIMSigner("v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=from:to; s=selector2;", TestKeys.privateKey) + .sign(stream(longTo(5000))); + + assertThat(field).startsWith("DKIM-Signature:"); + } + + @Test + public void shouldParseMoreThan4096HeaderFields() throws Exception { + StringBuilder sb = new StringBuilder("From: [email protected]\r\n"); + for (int i = 0; i < 6000; i++) { + sb.append("Received: hop ").append(i).append("\r\n"); + } + sb.append("\r\nbody\r\n"); + + Message message = new Message(stream(sb)); + + assertThat(message.getFields("Received")).hasSize(6000); + } + + @Test + public void headerOverOneMegabyteShouldBeRejected() { + // about 1.6 MB unfolded + assertThatThrownBy(() -> new Message(stream(longTo(100000)))) + .isInstanceOf(MimeException.class) + .hasMessageContaining("limit"); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
