This is an automated email from the ASF dual-hosted git repository. jono pushed a commit to branch camel-4.4.x in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.4.x by this push: new abaeeea2cc8 Camel-18017: mdn content corruption (#13719) abaeeea2cc8 is described below commit abaeeea2cc82c43dc22a64e97789735a868d19ed Author: Jono Morris <j...@apache.org> AuthorDate: Tue Apr 9 21:52:40 2024 +1200 Camel-18017: mdn content corruption (#13719) --- .../AS2MessageDispositionNotificationEntity.java | 15 +++- .../util/DispositionNotificationContentUtils.java | 10 ++- .../component/as2/api/entity/EntityParserTest.java | 85 +++++++++++++++++++--- 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java index 24591a849f6..f35260b4714 100644 --- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java +++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java @@ -67,6 +67,7 @@ public class AS2MessageDispositionNotificationEntity extends MimeEntity { private String[] warningFields; private Map<String, String> extensionFields = new HashMap<>(); private ReceivedContentMic receivedContentMic; + private String parsedBodyPartFields; public AS2MessageDispositionNotificationEntity(HttpEntityEnclosingRequest request, HttpResponse response, @@ -120,7 +121,8 @@ public class AS2MessageDispositionNotificationEntity extends MimeEntity { String[] errorFields, String[] warningFields, Map<String, String> extensionFields, - ReceivedContentMic receivedContentMic) { + ReceivedContentMic receivedContentMic, + String parsedBodyPartFields) { this.reportingUA = reportingUA; this.mtnName = mtnName; this.finalRecipient = finalRecipient; @@ -133,6 +135,7 @@ public class AS2MessageDispositionNotificationEntity extends MimeEntity { this.warningFields = warningFields; this.extensionFields = extensionFields; this.receivedContentMic = receivedContentMic; + this.parsedBodyPartFields = parsedBodyPartFields; } public String getReportingUA() { @@ -201,6 +204,16 @@ public class AS2MessageDispositionNotificationEntity extends MimeEntity { // 5.1.1 } + if (parsedBodyPartFields != null) { + // The 'writeTo' method is used when verifying the signature of the received MDN, and any alteration + // to the body part fields would mean that the signature would fail verification. Therefor return + // the fields parsed from the MDN entity if available so that the specific field + // ordering/formatting is maintained otherwise fall back to recreating each header, e.g. 'Reporting-UA', + // in the order prescribed in this method. + canonicalOutstream.writeln(parsedBodyPartFields); + return; + } + if (reportingUA != null) { Header reportingUAField = new BasicHeader(REPORTING_UA, reportingUA); canonicalOutstream.writeln(reportingUAField.toString()); diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtils.java index 7350d0af7fc..6fc4613a38e 100644 --- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtils.java +++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtils.java @@ -131,6 +131,7 @@ public final class DispositionNotificationContentUtils { private static final char PARAM_DELIMITER = ','; private static final char ELEM_DELIMITER = ';'; + private static final int DEFAULT_BUFFER_SIZE = 8 * 1024; private static final BitSet TOKEN_DELIMS = TokenParser.INIT_BITSET(PARAM_DELIMITER, ELEM_DELIMITER); @@ -152,9 +153,15 @@ public final class DispositionNotificationContentUtils { List<String> warnings = new ArrayList<>(); Map<String, String> extensionFields = new HashMap<>(); ReceivedContentMic receivedContentMic = null; + CharArrayBuffer bodyPartFields = new CharArrayBuffer(DEFAULT_BUFFER_SIZE); for (int i = 0; i < dispositionNotificationFields.size(); i++) { final CharArrayBuffer fieldLine = dispositionNotificationFields.get(i); + bodyPartFields.append(fieldLine); + if (i < dispositionNotificationFields.size() - 1) { + bodyPartFields.append('\r'); + bodyPartFields.append('\n'); + } final Field field = parseDispositionField(fieldLine); switch (field.getName().toLowerCase()) { case REPORTING_UA: { @@ -250,7 +257,8 @@ public final class DispositionNotificationContentUtils { errors.toArray(new String[0]), warnings.toArray(new String[0]), extensionFields, - receivedContentMic); + receivedContentMic, + bodyPartFields.toString()); } public static Field parseDispositionField(CharArrayBuffer fieldLine) { diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/EntityParserTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/EntityParserTest.java index 1033e8cb8e2..33b5bab27ec 100644 --- a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/EntityParserTest.java +++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/EntityParserTest.java @@ -17,6 +17,7 @@ package org.apache.camel.component.as2.api.entity; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; @@ -43,6 +44,7 @@ import org.apache.http.HttpVersion; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.impl.EnglishReasonPhraseCatalog; import org.apache.http.impl.io.HttpTransportMetricsImpl; +import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpResponse; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; @@ -108,6 +110,39 @@ public class EntityParserTest { + "\r\n" + "------=_Part_56_1672293592.1028122454656--\r\n"; + // version of the MDN report without any folded body parts that would be unfolded when the entity is parsed + // modifying the report + public static final String DISPOSITION_NOTIFICATION_REPORT_CONTENT_UNFOLDED = "\r\n" + + "------=_Part_56_1672293592.1028122454656\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Transfer-Encoding: 7bit\r\n" + + "\r\n" + + "MDN for -\r\n" + + " Message ID: <200207310834482A70BF63@\\\"~~foo~~\\\">\r\n" + + " From: \"\\\" as2Name \\\"\"\r\n" + + " To: \"0123456780000\"" + + " Received on: 2002-07-31 at 09:34:14 (EDT)\r\n" + + " Status: processed\r\n" + + " Comment: This is not a guarantee that the message has\r\n" + + " been completely processed or &understood by the receiving\r\n" + + " translator\r\n" + "\r\n" + + "------=_Part_56_1672293592.1028122454656\r\n" + + "Content-Type: message/disposition-notification\r\n" + + "Content-Transfer-Encoding: 7bit\r\n" + + "\r\n" + + "Reporting-UA: AS2 Server\r\n" + + "MDN-Gateway: dns; example.com\r\n" + + "Original-Recipient: rfc822; 0123456780000\r\n" + + "Final-Recipient: rfc822; 0123456780000\r\n" + + "Original-Message-ID: <200207310834482A70BF63@\\\"~~foo~~\\\">\r\n" + + "Disposition: automatic-action/MDN-sent-automatically; rocessed/warning: you're awesome\r\n" + + "Failure: oops-a-failure\r\n" + + "Error: oops-an-error\r\n" + + "Warning: oops-a-warning\r\n" + + "Received-content-MIC: 7v7F++fQaNB1sVLFtMRp+dF+eG4=, sha1\r\n" + + "\r\n" + + "------=_Part_56_1672293592.1028122454656--\r\n"; + public static final String DISPOSITION_NOTIFICATION_REPORT_CONTENT_BOUNDARY = "----=_Part_56_1672293592.1028122454656"; public static final String DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME = "US-ASCII"; @@ -214,16 +249,8 @@ public class EntityParserTest { @Test public void parseMessageDispositionNotificationReportBodyTest() throws Exception { - InputStream is = new ByteArrayInputStream( - DISPOSITION_NOTIFICATION_REPORT_CONTENT.getBytes(DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME)); - AS2SessionInputBuffer inbuffer - = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), DEFAULT_BUFFER_SIZE, DEFAULT_BUFFER_SIZE, null); - inbuffer.bind(is); - - DispositionNotificationMultipartReportEntity dispositionNotificationMultipartReportEntity = EntityParser - .parseMultipartReportEntityBody(inbuffer, DISPOSITION_NOTIFICATION_REPORT_CONTENT_BOUNDARY, - DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME, - DISPOSITION_NOTIFICATION_REPORT_CONTENT_TRANSFER_ENCODING); + DispositionNotificationMultipartReportEntity dispositionNotificationMultipartReportEntity + = createMdnEntity(DISPOSITION_NOTIFICATION_REPORT_CONTENT, DISPOSITION_NOTIFICATION_REPORT_CONTENT_BOUNDARY); assertNotNull(dispositionNotificationMultipartReportEntity, "Unexpected Null disposition notification multipart entity"); @@ -235,6 +262,25 @@ public class EntityParserTest { "Unexpected type for second body part"); } + // verify that parsing the MDN has made no alteration to the entity's body part fields + @Test + public void messageDispositionNotificationReportBodyContentTest() throws Exception { + + DispositionNotificationMultipartReportEntity dispositionNotificationMultipartReportEntity + = createMdnEntity(DISPOSITION_NOTIFICATION_REPORT_CONTENT_UNFOLDED, + DISPOSITION_NOTIFICATION_REPORT_CONTENT_BOUNDARY); + + String expectedContent = String.format("%s\r\n%s\r\n%s", + new BasicHeader(AS2Header.CONTENT_TYPE, REPORT_CONTENT_TYPE_VALUE), + new BasicHeader(AS2Header.CONTENT_TRANSFER_ENCODING, DISPOSITION_NOTIFICATION_REPORT_CONTENT_TRANSFER_ENCODING), + DISPOSITION_NOTIFICATION_REPORT_CONTENT_UNFOLDED); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + dispositionNotificationMultipartReportEntity.writeTo(out); + + assertEquals(expectedContent, out.toString(DISPOSITION_NOTIFICATION_CONTENT_CHARSET_NAME)); + } + @Test public void parseTextPlainBodyTest() throws Exception { @@ -380,6 +426,25 @@ public class EntityParserTest { return utils.createAuthorityKeyIdentifier(info); } + private DispositionNotificationMultipartReportEntity createMdnEntity(String reportContent, String boundary) + throws Exception { + InputStream is = new ByteArrayInputStream( + reportContent.getBytes(DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME)); + AS2SessionInputBuffer inbuffer + = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), DEFAULT_BUFFER_SIZE); + inbuffer.bind(is); + + DispositionNotificationMultipartReportEntity dispositionNotificationMultipartReportEntity = EntityParser + .parseMultipartReportEntityBody(inbuffer, boundary, + DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME, + DISPOSITION_NOTIFICATION_REPORT_CONTENT_TRANSFER_ENCODING); + + assertNotNull(dispositionNotificationMultipartReportEntity, + "Unexpected Null disposition notification multipart entity"); + + return dispositionNotificationMultipartReportEntity; + } + static SubjectKeyIdentifier createSubjectKeyId(PublicKey pub) throws IOException { SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pub.getEncoded());