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 e6c7c062a8dbaf95aac63b56d4e1dbea83c67569 Author: Benoit TELLIER <[email protected]> AuthorDate: Fri Oct 30 08:52:37 2026 +0100 [FIX] Relaxed body canonicalization: an empty body stays empty RelaxedBodyCanonicalizer delegates to SimpleBodyCanonicalizer, which always ends its output with CRLF. That is right for simple, where an empty body becomes CRLF, but RFC 6376 section 3.4.4 only adds the CRLF to a non-empty relaxed body: an empty or blank-lines-only body hashed "\r\n" instead of the empty string, so such signatures from other implementations failed in jDKIM and jDKIM's failed elsewhere. Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../jdkim/canon/RelaxedBodyCanonicalizer.java | 2 +- .../james/jdkim/canon/SimpleBodyCanonicalizer.java | 23 ++++++++++++++++++++-- .../jdkim/canon/RelaxedBodyCanonicalizerTest.java | 18 +++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java b/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java index 2716028..cbecbb9 100644 --- a/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java +++ b/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java @@ -32,7 +32,7 @@ public class RelaxedBodyCanonicalizer extends FilterOutputStream { private boolean pendingSpaces; public RelaxedBodyCanonicalizer(OutputStream out) { - super(new SimpleBodyCanonicalizer(out)); + super(new SimpleBodyCanonicalizer(out, true)); pendingSpaces = false; } diff --git a/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java b/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java index 90b14bf..359cbfe 100644 --- a/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java +++ b/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java @@ -31,11 +31,23 @@ public class SimpleBodyCanonicalizer extends FilterOutputStream { private static final boolean DEEP_DEBUG = false; + private final boolean emptyBodyIsEmpty; private boolean lastWasCR; private int countCRLF; + private boolean hasContent; public SimpleBodyCanonicalizer(OutputStream arg0) { + this(arg0, false); + } + + /** + * @param emptyBodyIsEmpty true to output nothing for a body made only of + * CRLFs, as relaxed canonicalization requires (RFC + * 6376 section 3.4.4); simple outputs a single CRLF. + */ + SimpleBodyCanonicalizer(OutputStream arg0, boolean emptyBodyIsEmpty) { super(arg0); + this.emptyBodyIsEmpty = emptyBodyIsEmpty; } public void write(byte[] b, int off, int len) throws IOException { @@ -54,6 +66,7 @@ public class SimpleBodyCanonicalizer extends FilterOutputStream { // TODO output the lone \r ? (this condition should never happen // as we expect only CRLF in a compliant 7bit email. out.write('\r'); + hasContent = true; lastWasCR = false; } } @@ -70,6 +83,7 @@ public class SimpleBodyCanonicalizer extends FilterOutputStream { if (len > 0) { dumpCRLF(); out.write(b, off, len); + hasContent = true; } countCRLF += newCountCRLF; lastWasCR = newLastWasCR; @@ -86,10 +100,12 @@ public class SimpleBodyCanonicalizer extends FilterOutputStream { lastWasCR = true; } else { dumpCRLF(); - if ('\r' == b) + if ('\r' == b) { lastWasCR = true; - else + } else { out.write(b); + hasContent = true; + } } } } @@ -107,6 +123,9 @@ public class SimpleBodyCanonicalizer extends FilterOutputStream { // to output the missing \n lastWasCR = false; } + if (emptyBodyIsEmpty && !hasContent) { + return; + } countCRLF = 1; dumpCRLF(); } diff --git a/main/src/test/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizerTest.java b/main/src/test/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizerTest.java index e5ad724..d571747 100644 --- a/main/src/test/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizerTest.java +++ b/main/src/test/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizerTest.java @@ -50,4 +50,22 @@ public class RelaxedBodyCanonicalizerTest extends AbstractOutputStreamTestCase { chunker(testData, os); assertArrayEquals(expectedData, bos.toByteArray()); } + + public void testEmptyAndBlankBodiesCanonicalizeToNothing() throws IOException { + for (String body : new String[] {"", " \r\n", "\r\n\r\n", "\t \r\n \r\n"}) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + RelaxedBodyCanonicalizer os = new RelaxedBodyCanonicalizer(bos); + os.write(body.getBytes()); + os.close(); + assertArrayEquals("[" + body + "]", new byte[0], bos.toByteArray()); + } + } + + public void testNonEmptyBodyGetsAFinalCRLF() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + RelaxedBodyCanonicalizer os = new RelaxedBodyCanonicalizer(bos); + os.write("\r\nx".getBytes()); + os.close(); + assertArrayEquals("\r\nx\r\n".getBytes(), bos.toByteArray()); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
