You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by gn...@apache.org on 2026/03/09 10:05:19 UTC
(camel) 01/08: CAMEL-23064: Support non-standard content types in AS2 EDI payload
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 43d5068b57c2f31ac2c0f6177a824ed87b3849a0
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Mon Mar 9 11:04:16 2026 +0100
CAMEL-23064: Support non-standard content types in AS2 EDI payload
Add GenericApplicationEntity to handle EDI payloads with content types
not explicitly mapped (e.g. text/plain, application/octet-stream).
Previously, unknown content types threw a CamelException. Also fix
missing return statement in HttpMessageUtils.getEntity() for response
branch.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../component/as2/api/entity/EntityParser.java | 3 +
.../as2/api/entity/GenericApplicationEntity.java | 39 ++++++++
.../camel/component/as2/api/util/EntityUtils.java | 5 +-
.../component/as2/api/util/HttpMessageUtils.java | 59 +++++++++---
.../api/entity/GenericApplicationEntityTest.java | 100 +++++++++++++++++++++
5 files changed, 192 insertions(+), 14 deletions(-)
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java
index d6b4f84efeaa..7d2b25b6de84 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java
@@ -523,6 +523,9 @@ public final class EntityParser {
contentTransferEncoding);
break;
default:
+ // Accept non-standard content types (e.g., text/plain, application/octet-stream)
+ // that real-world AS2 partners may use to wrap EDI payloads
+ parseApplicationEDIEntity(message, entity.getContent(), inBuffer, contentType, contentTransferEncoding);
break;
}
}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/GenericApplicationEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/GenericApplicationEntity.java
new file mode 100644
index 000000000000..6bc3d2e7a49f
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/GenericApplicationEntity.java
@@ -0,0 +1,39 @@
+/*
+ * 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.entity;
+
+import java.io.IOException;
+
+import org.apache.hc.core5.http.ContentType;
+
+/**
+ * A generic application entity that wraps EDI payloads with non-standard content types (e.g., text/plain,
+ * application/octet-stream). Real-world AS2 partners frequently send EDI payloads wrapped in content types other than
+ * the standard application/edifact, application/edi-x12, or application/edi-consent types.
+ */
+public class GenericApplicationEntity extends ApplicationEntity {
+
+ public GenericApplicationEntity(byte[] content, ContentType contentType, String contentTransferEncoding,
+ boolean isMainBody, String filename) {
+ super(content, contentType, contentTransferEncoding, isMainBody, filename);
+ }
+
+ @Override
+ public void close() throws IOException {
+ // do nothing
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/EntityUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/EntityUtils.java
index 3de97bda41dc..3bd4404ff917 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/EntityUtils.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/EntityUtils.java
@@ -33,6 +33,7 @@ import org.apache.camel.component.as2.api.entity.ApplicationEDIFACTEntity;
import org.apache.camel.component.as2.api.entity.ApplicationEDIX12Entity;
import org.apache.camel.component.as2.api.entity.ApplicationEntity;
import org.apache.camel.component.as2.api.entity.ApplicationXMLEntity;
+import org.apache.camel.component.as2.api.entity.GenericApplicationEntity;
import org.apache.camel.component.as2.api.entity.MimeEntity;
import org.apache.camel.util.ObjectHelper;
import org.apache.commons.codec.DecoderException;
@@ -203,7 +204,9 @@ public final class EntityUtils {
case AS2MediaType.APPLICATION_XML:
return new ApplicationXMLEntity(ediMessage, charset, contentTransferEncoding, isMainBody, filename);
default:
- throw new CamelException("Invalid EDI entity mime type: " + ediMessageContentType.getMimeType());
+ return new GenericApplicationEntity(
+ ediMessage, ediMessageContentType, contentTransferEncoding,
+ isMainBody, filename);
}
}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
index d6a03c17dec9..7acc58adee41 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
@@ -75,7 +75,7 @@ public final class HttpMessageUtils {
} else if (message instanceof BasicClassicHttpResponse httpResponse) {
HttpEntity entity = httpResponse.getEntity();
if (entity != null && type.isInstance(entity)) {
- type.cast(entity);
+ return type.cast(entity);
}
}
return null;
@@ -152,10 +152,23 @@ public final class HttpMessageUtils {
}
break;
}
- default:
- throw new HttpException(
- "Failed to extract EDI message: invalid content type '" + contentType.getMimeType()
- + "' for AS2 request message");
+ default: {
+ // Accept non-standard content types (e.g., text/plain, application/octet-stream)
+ // that real-world AS2 partners may use to wrap EDI payloads
+ if (decrpytingAndSigningInfo.getValidateSigningCertificateChain() != null) {
+ throw new AS2InsufficientSecurityException("Failed to validate the signature");
+ }
+ if (decrpytingAndSigningInfo.getDecryptingPrivateKey() != null) {
+ throw new AS2InsufficientSecurityException("Expected to be encrypted");
+ }
+ ediEntity = getEntity(message, ApplicationEntity.class);
+ if (ediEntity == null) {
+ throw new HttpException(
+ "Failed to extract EDI message: could not parse entity with content type '"
+ + contentType.getMimeType() + "'");
+ }
+ break;
+ }
}
return ediEntity;
@@ -274,10 +287,20 @@ public final class HttpMessageUtils {
ediEntity = extractEdiPayloadFromCompressedEntity(compressedDataEntity, decrpytingAndSigningInfo, false);
break;
}
- default:
- throw new HttpException(
- "Failed to extract EDI payload: invalid content type '" + contentType.getMimeType()
- + "' for AS2 enveloped entity");
+ default: {
+ // Accept non-standard content types within enveloped entities
+ if (decrpytingAndSigningInfo.getValidateSigningCertificateChain() != null) {
+ throw new AS2InsufficientSecurityException("Failed to validate the signature");
+ }
+ if (entity instanceof ApplicationEntity) {
+ ediEntity = (ApplicationEntity) entity;
+ } else {
+ throw new HttpException(
+ "Failed to extract EDI payload: invalid content type '" + contentType.getMimeType()
+ + "' for AS2 enveloped entity");
+ }
+ break;
+ }
}
return ediEntity;
@@ -325,10 +348,20 @@ public final class HttpMessageUtils {
}
break;
}
- default:
- throw new HttpException(
- "Failed to extract EDI payload: invalid content type '" + contentType.getMimeType()
- + "' for AS2 compressed entity");
+ default: {
+ // Accept non-standard content types within compressed entities
+ if (!hasValidSignature && decrpytingAndSigningInfo.getValidateSigningCertificateChain() != null) {
+ throw new AS2InsufficientSecurityException("Failed to validate the signature");
+ }
+ if (entity instanceof ApplicationEntity) {
+ ediEntity = (ApplicationEntity) entity;
+ } else {
+ throw new HttpException(
+ "Failed to extract EDI payload: invalid content type '" + contentType.getMimeType()
+ + "' for AS2 compressed entity");
+ }
+ break;
+ }
}
return ediEntity;
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/GenericApplicationEntityTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/GenericApplicationEntityTest.java
new file mode 100644
index 000000000000..ab00bfbe705b
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/GenericApplicationEntityTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.entity;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.Security;
+
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2TransferEncoding;
+import org.apache.camel.component.as2.api.util.EntityUtils;
+import org.apache.camel.component.as2.api.util.HttpMessageUtils;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class GenericApplicationEntityTest {
+
+ private static final String EDI_MESSAGE = "UNB+UNOA:1+005435656:1+006415160:1+060515:1434+00000000000778'";
+
+ @BeforeAll
+ static void setUp() {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ @Test
+ void genericApplicationEntityPreservesContent() throws Exception {
+ byte[] content = EDI_MESSAGE.getBytes(StandardCharsets.US_ASCII);
+ ContentType contentType = ContentType.create("text/plain", StandardCharsets.US_ASCII);
+ GenericApplicationEntity entity = new GenericApplicationEntity(content, contentType, null, true, null);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ entity.writeTo(out);
+ assertArrayEquals(content, out.toByteArray());
+ }
+
+ @Test
+ void genericApplicationEntityGetEdiMessage() {
+ byte[] content = EDI_MESSAGE.getBytes(StandardCharsets.US_ASCII);
+ ContentType contentType = ContentType.create("text/plain", StandardCharsets.US_ASCII);
+ GenericApplicationEntity entity = new GenericApplicationEntity(content, contentType, null, true, null);
+
+ Object ediMessage = entity.getEdiMessage();
+ assertInstanceOf(String.class, ediMessage);
+ assertEquals(EDI_MESSAGE, ediMessage);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "text/plain", "application/octet-stream" })
+ void extractEdiPayloadAcceptsNonStandardContentTypes(String mimeType) throws Exception {
+ byte[] content = EDI_MESSAGE.getBytes(StandardCharsets.US_ASCII);
+ ContentType contentType = ContentType.create(mimeType, StandardCharsets.US_ASCII);
+
+ BasicClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
+ request.addHeader(AS2Header.CONTENT_TYPE, contentType.toString());
+ InputStream is = new ByteArrayInputStream(content);
+ request.setEntity(new BasicHttpEntity(is, content.length, contentType));
+
+ ApplicationEntity ediEntity = HttpMessageUtils.extractEdiPayload(request,
+ new HttpMessageUtils.DecrpytingAndSigningInfo(null, null));
+
+ assertNotNull(ediEntity, "EDI entity should not be null for content type: " + mimeType);
+ assertInstanceOf(GenericApplicationEntity.class, ediEntity);
+ assertEquals(EDI_MESSAGE, ediEntity.getEdiMessage().toString());
+ }
+
+ @Test
+ void createEDIEntityReturnsGenericForUnknownType() throws Exception {
+ byte[] content = EDI_MESSAGE.getBytes(StandardCharsets.US_ASCII);
+ ContentType contentType = ContentType.create("text/plain", StandardCharsets.US_ASCII);
+
+ ApplicationEntity entity = EntityUtils.createEDIEntity(content, contentType,
+ AS2TransferEncoding.NONE, false, "test.txt");
+
+ assertInstanceOf(GenericApplicationEntity.class, entity);
+ }
+}