This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch fix/as2-issues in repository https://gitbox.apache.org/repos/asf/camel.git
commit b8432cd0ef169b947c4f857ad3b77cbe3e1888f5 Author: Guillaume Nodet <[email protected]> AuthorDate: Mon Mar 9 11:04:39 2026 +0100 CAMEL-23068: Fix CRLF line endings in MDN message template Change DEFAULT_MDN_MESSAGE_TEMPLATE from Java text block (LF line endings) to string concatenation with explicit CRLF. This ensures consistent digest computation for signed MDNs, where headers are written through CanonicalOutputStream (CRLF) but body content was written directly with LF-only line endings. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../component/as2/api/protocol/ResponseMDN.java | 22 +++--- .../as2/api/protocol/ResponseMDNTest.java | 79 ++++++++++++++++++++++ 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java index 852ff8e3c0d9..59ad21bf618e 100644 --- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java +++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java @@ -73,16 +73,18 @@ public class ResponseMDN implements HttpResponseInterceptor { public static final String DISPOSITION_MODIFIER = "Disposition-Modifier"; - private static final String DEFAULT_MDN_MESSAGE_TEMPLATE = """ - MDN for - - Message ID: $requestHeaders["Message-Id"] - Subject: $requestHeaders["Subject"] - Date: $requestHeaders["Date"] - From: $requestHeaders["AS2-From"] - To: $requestHeaders["AS2-To"] - Received on: $responseHeaders["Date"] - Status: $dispositionType - """; + // Use explicit CRLF line endings to ensure consistent digest computation for signed MDNs. + // Java text blocks use LF, which causes CRLF/LF mismatch when headers are written through + // CanonicalOutputStream (CRLF) but body content is written directly (LF). + private static final String DEFAULT_MDN_MESSAGE_TEMPLATE + = "MDN for -\r\n" + + " Message ID: $requestHeaders[\"Message-Id\"]\r\n" + + " Subject: $requestHeaders[\"Subject\"]\r\n" + + " Date: $requestHeaders[\"Date\"]\r\n" + + " From: $requestHeaders[\"AS2-From\"]\r\n" + + " To: $requestHeaders[\"AS2-To\"]\r\n" + + " Received on: $responseHeaders[\"Date\"]\r\n" + + " Status: $dispositionType\r\n"; private static final Logger LOG = LoggerFactory.getLogger(ResponseMDN.class); diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/protocol/ResponseMDNTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/protocol/ResponseMDNTest.java new file mode 100644 index 000000000000..2267ab687dd7 --- /dev/null +++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/protocol/ResponseMDNTest.java @@ -0,0 +1,79 @@ +/* + * 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.camel.component.as2.api.protocol; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.camel.component.as2.api.entity.TextPlainEntity; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ResponseMDNTest { + + @Test + void mdnTextPlainEntityWithCrlfProducesConsistentDigest() throws IOException { + // Simulate MDN text with explicit CRLF (as in the fixed DEFAULT_MDN_MESSAGE_TEMPLATE) + String mdnText = "MDN for -\r\n" + + " Message ID: test-id\r\n" + + " Status: processed\r\n"; + + TextPlainEntity entity = new TextPlainEntity(mdnText, StandardCharsets.US_ASCII.name(), "7bit", false); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + entity.writeTo(out); + byte[] output = out.toByteArray(); + + // Verify that the body content preserves CRLF + String outputStr = new String(output, StandardCharsets.US_ASCII); + // The body part (after headers + blank line) should contain CRLF + String body = outputStr.substring(outputStr.indexOf("\r\n\r\n") + 4); + assertTrue(body.contains("\r\n"), "MDN body should contain CRLF line endings"); + assertFalse(body.contains("\n") && !body.contains("\r\n"), + "MDN body should not contain bare LF (without CR)"); + } + + @Test + void mdnTextPlainEntityWithLfProducesMismatch() throws IOException { + // Simulate MDN text with LF only (the old broken DEFAULT_MDN_MESSAGE_TEMPLATE behavior) + String mdnText = "MDN for -\n" + + " Message ID: test-id\n" + + " Status: processed\n"; + + TextPlainEntity entity = new TextPlainEntity(mdnText, StandardCharsets.US_ASCII.name(), "7bit", false); + + ByteArrayOutputStream out1 = new ByteArrayOutputStream(); + entity.writeTo(out1); + + // Now create with CRLF + String mdnTextCrlf = "MDN for -\r\n" + + " Message ID: test-id\r\n" + + " Status: processed\r\n"; + + TextPlainEntity entityCrlf = new TextPlainEntity(mdnTextCrlf, StandardCharsets.US_ASCII.name(), "7bit", false); + + ByteArrayOutputStream out2 = new ByteArrayOutputStream(); + entityCrlf.writeTo(out2); + + // Headers should be identical (both go through CanonicalOutputStream) + // but body content will differ in line endings + assertNotEquals(out1.size(), out2.size(), + "LF and CRLF bodies should produce different byte lengths"); + } +}
