You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2022/12/23 11:57:14 UTC
[camel] 01/02: [CAMEL-15111] Fixes bug in camel-as2 via not forcing ascii charset when not specified (#8944)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-3.20.x
in repository https://gitbox.apache.org/repos/asf/camel.git
commit fb8eb8d9a1e8711ba2f0f78557ad95db6d763567
Author: Yasser Zamani <ya...@apache.org>
AuthorDate: Fri Dec 23 14:53:00 2022 +0330
[CAMEL-15111] Fixes bug in camel-as2 via not forcing ascii charset when not specified (#8944)
* add CAMEL-15111 reproducer test case
* fix CAMEL-15111 via not forcing ascii charset when not specified
otherwise you will get java.nio.charset.MalformedInputException: Input length = 1 when content transfer encoding is binary
* fix maven checkstyle errors
* maven formatter:format
* do not use a static version inside the component pom
this should be in a property placeholder in parent pom
---
components/camel-as2/camel-as2-api/pom.xml | 7 ++
.../component/as2/api/entity/EntityParser.java | 87 +++++++++++-----------
.../as2/api/io/AS2SessionInputBuffer.java | 9 +++
.../camel/component/as2/api/AS2MessageTest.java | 77 +++++++++++++++++++
components/camel-as2/pom.xml | 4 +
5 files changed, 141 insertions(+), 43 deletions(-)
diff --git a/components/camel-as2/camel-as2-api/pom.xml b/components/camel-as2/camel-as2-api/pom.xml
index 712fd8c6c09..28e2c325029 100644
--- a/components/camel-as2/camel-as2-api/pom.xml
+++ b/components/camel-as2/camel-as2-api/pom.xml
@@ -96,6 +96,13 @@
<artifactId>camel-test-junit5</artifactId>
<scope>test</scope>
</dependency>
+ <!-- added for binary content transfer encoding test because Camel AS2 client doesn't support binary currently -->
+ <dependency>
+ <groupId>com.helger.as2</groupId>
+ <artifactId>as2-lib</artifactId>
+ <version>${as2-lib-version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
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 85bede10230..01b4c7398cc 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
@@ -29,6 +29,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import org.apache.camel.CamelException;
import org.apache.camel.component.as2.api.AS2Header;
import org.apache.camel.component.as2.api.AS2MimeType;
import org.apache.camel.component.as2.api.io.AS2SessionInputBuffer;
@@ -37,6 +38,7 @@ import org.apache.camel.component.as2.api.util.ContentTypeUtils;
import org.apache.camel.component.as2.api.util.DispositionNotificationContentUtils;
import org.apache.camel.component.as2.api.util.EntityUtils;
import org.apache.camel.component.as2.api.util.HttpMessageUtils;
+import org.apache.commons.codec.DecoderException;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
@@ -50,6 +52,7 @@ import org.apache.http.message.BasicLineParser;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.message.LineParser;
import org.apache.http.message.ParserCursor;
+import org.apache.http.protocol.HTTP;
import org.apache.http.util.Args;
import org.apache.http.util.CharArrayBuffer;
import org.bouncycastle.cms.CMSCompressedData;
@@ -907,22 +910,14 @@ public final class EntityParser {
CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
try {
+ byte[] signature = parseBodyPartBytes(inbuffer, boundary, contentType, contentTransferEncoding);
+
Charset charset = contentType.getCharset();
if (charset == null) {
charset = StandardCharsets.US_ASCII;
}
- CharsetDecoder charsetDecoder = charset.newDecoder();
-
- inbuffer.setCharsetDecoder(charsetDecoder);
-
- String pkcs7SignatureBodyContent = parseBodyPartText(inbuffer, boundary);
-
- byte[] signature = EntityUtils.decode(pkcs7SignatureBodyContent.getBytes(charset), contentTransferEncoding);
-
String charsetName = charset.toString();
- ApplicationPkcs7SignatureEntity applicationPkcs7SignatureEntity = new ApplicationPkcs7SignatureEntity(
- signature, charsetName, contentTransferEncoding, false);
- return applicationPkcs7SignatureEntity;
+ return new ApplicationPkcs7SignatureEntity(signature, charsetName, contentTransferEncoding, false);
} catch (Exception e) {
ParseException parseException = new ParseException("failed to parse PKCS7 Signature entity");
parseException.initCause(e);
@@ -942,21 +937,8 @@ public final class EntityParser {
CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
try {
- Charset charset = contentType.getCharset();
- if (charset == null) {
- charset = StandardCharsets.US_ASCII;
- }
- CharsetDecoder charsetDecoder = charset.newDecoder();
-
- inbuffer.setCharsetDecoder(charsetDecoder);
-
- String pkcs7EncryptedBodyContent = parseBodyPartText(inbuffer, boundary);
-
- byte[] encryptedContent = EntityUtils.decode(pkcs7EncryptedBodyContent.getBytes(charset), contentTransferEncoding);
-
- ApplicationPkcs7MimeEnvelopedDataEntity applicationPkcs7MimeEntity = new ApplicationPkcs7MimeEnvelopedDataEntity(
- encryptedContent, contentTransferEncoding, false);
- return applicationPkcs7MimeEntity;
+ byte[] encryptedContent = parseBodyPartBytes(inbuffer, boundary, contentType, contentTransferEncoding);
+ return new ApplicationPkcs7MimeEnvelopedDataEntity(encryptedContent, contentTransferEncoding, false);
} catch (Exception e) {
ParseException parseException = new ParseException("failed to parse PKCS7 Mime entity");
parseException.initCause(e);
@@ -976,22 +958,8 @@ public final class EntityParser {
CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
try {
- Charset charset = contentType.getCharset();
- if (charset == null) {
- charset = StandardCharsets.US_ASCII;
- }
- CharsetDecoder charsetDecoder = charset.newDecoder();
-
- inbuffer.setCharsetDecoder(charsetDecoder);
-
- String pkcs7CompressedBodyContent = parseBodyPartText(inbuffer, boundary);
-
- byte[] compressedContent = EntityUtils.decode(pkcs7CompressedBodyContent.getBytes(charset),
- contentTransferEncoding);
-
- ApplicationPkcs7MimeCompressedDataEntity applicationPkcs7MimeEntity = new ApplicationPkcs7MimeCompressedDataEntity(
- compressedContent, contentTransferEncoding, false);
- return applicationPkcs7MimeEntity;
+ byte[] compressedContent = parseBodyPartBytes(inbuffer, boundary, contentType, contentTransferEncoding);
+ return new ApplicationPkcs7MimeCompressedDataEntity(compressedContent, contentTransferEncoding, false);
} catch (Exception e) {
ParseException parseException = new ParseException("failed to parse PKCS7 Mime entity");
parseException.initCause(e);
@@ -1001,6 +969,36 @@ public final class EntityParser {
}
}
+ public static byte[] parseBodyPartBytes(
+ final AS2SessionInputBuffer inbuffer,
+ final String boundary,
+ ContentType contentType,
+ String contentTransferEncoding)
+ throws IOException, CamelException, DecoderException {
+
+ Charset charset = contentType.getCharset();
+ if (charset != null) {
+ CharsetDecoder charsetDecoder = charset.newDecoder();
+ inbuffer.setCharsetDecoder(charsetDecoder);
+ } else {
+ inbuffer.setCharsetDecoder(null);
+ }
+
+ String bodyContent = parseBodyPartText(inbuffer, boundary);
+
+ byte[] bodyContentBytes;
+ if (charset != null) {
+ bodyContentBytes = bodyContent.getBytes(charset);
+ } else {
+ bodyContentBytes = new byte[bodyContent.length()];
+ for (int i = 0; i < bodyContent.length(); i++) {
+ bodyContentBytes[i] = (byte) bodyContent.charAt(i);
+ }
+ }
+
+ return EntityUtils.decode(bodyContentBytes, contentTransferEncoding);
+ }
+
public static String parseBodyPartText(
final AS2SessionInputBuffer inbuffer,
final String boundary)
@@ -1021,8 +1019,11 @@ public final class EntityParser {
}
buffer.append(line);
+ if (inbuffer.isLastLineReadEnrichedByCarriageReturn()) {
+ buffer.append((char) HTTP.CR);
+ }
if (inbuffer.isLastLineReadTerminatedByLineFeed()) {
- buffer.append("\r\n");
+ buffer.append((char) HTTP.LF);
}
line.clear();
}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2SessionInputBuffer.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2SessionInputBuffer.java
index d4fad28fdf5..2936cf0b158 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2SessionInputBuffer.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2SessionInputBuffer.java
@@ -52,6 +52,7 @@ public class AS2SessionInputBuffer implements SessionInputBuffer, BufferInfo {
private int bufferlen;
private CharBuffer cbuf;
+ private boolean lastLineReadEnrichedByCarriageReturn;
private boolean lastLineReadTerminatedByLineFeed;
public AS2SessionInputBuffer(final HttpTransportMetricsImpl metrics,
@@ -190,6 +191,7 @@ public class AS2SessionInputBuffer implements SessionInputBuffer, BufferInfo {
final int maxLineLen = this.constraints.getMaxLineLength();
int noRead = 0;
boolean retry = true;
+ this.lastLineReadEnrichedByCarriageReturn = false;
this.lastLineReadTerminatedByLineFeed = false;
while (retry) {
// attempt to find end of line (LF)
@@ -198,6 +200,9 @@ public class AS2SessionInputBuffer implements SessionInputBuffer, BufferInfo {
if (this.buffer[i] == HTTP.LF) {
pos = i;
this.lastLineReadTerminatedByLineFeed = true;
+ if (i > 0 && this.buffer[i - 1] == HTTP.CR) {
+ this.lastLineReadEnrichedByCarriageReturn = true;
+ }
break;
}
}
@@ -252,6 +257,10 @@ public class AS2SessionInputBuffer implements SessionInputBuffer, BufferInfo {
return lastLineReadTerminatedByLineFeed;
}
+ public boolean isLastLineReadEnrichedByCarriageReturn() {
+ return lastLineReadEnrichedByCarriageReturn;
+ }
+
@Override
public boolean isDataAvailable(int timeout) throws IOException {
return hasBufferedData();
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
index 696c151d28f..022c9f04b1e 100644
--- a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
@@ -16,16 +16,28 @@
*/
package org.apache.camel.component.as2.api;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
+import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;
+import com.helger.as2lib.client.AS2Client;
+import com.helger.as2lib.client.AS2ClientRequest;
+import com.helger.as2lib.client.AS2ClientResponse;
+import com.helger.as2lib.client.AS2ClientSettings;
+import com.helger.as2lib.crypto.ECompressionType;
+import com.helger.as2lib.crypto.ECryptoAlgorithmCrypt;
+import com.helger.as2lib.crypto.ECryptoAlgorithmSign;
+import com.helger.mail.cte.EContentTransferEncoding;
+import com.helger.security.keystore.EKeyStoreType;
import org.apache.camel.component.as2.api.entity.AS2DispositionModifier;
import org.apache.camel.component.as2.api.entity.AS2DispositionType;
import org.apache.camel.component.as2.api.entity.AS2MessageDispositionNotificationEntity;
@@ -76,6 +88,8 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,6 +98,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
public class AS2MessageTest {
@@ -145,6 +160,10 @@ public class AS2MessageTest {
private static X509Certificate signingCert;
private static List<X509Certificate> certList;
+ private static File keystoreFile;
+
+ private static ApplicationEDIEntity ediEntity;
+
private AS2SignedDataGenerator gen;
@BeforeAll
@@ -169,6 +188,17 @@ public class AS2MessageTest {
signingKP = kpg.generateKeyPair();
signingCert = Utils.makeCertificate(signingKP, signingDN, issueKP, issueDN);
+ //
+ // initialize as2-lib keystore file
+ //
+ KeyStore ks = KeyStore.getInstance(EKeyStoreType.PKCS12.getID());
+ ks.load(null, "test".toCharArray());
+ ks.setKeyEntry("openas2a_alias", issueKP.getPrivate(), "test".toCharArray(), new X509Certificate[] { issueCert });
+ ks.setKeyEntry("openas2b_alias", signingKP.getPrivate(), "test".toCharArray(), new X509Certificate[] { signingCert });
+ keystoreFile = File.createTempFile("camel-as2", "keystore-p12");
+ keystoreFile.deleteOnExit();
+ ks.store(new FileOutputStream(keystoreFile), "test".toCharArray());
+
certList = new ArrayList<>();
certList.add(signingCert);
@@ -188,6 +218,7 @@ public class AS2MessageTest {
context.setAttribute(AS2ServerManager.SUBJECT, SUBJECT);
context.setAttribute(AS2ServerManager.FROM, AS2_NAME);
LOG.debug(AS2Utils.printMessage(request));
+ ediEntity = HttpMessageUtils.extractEdiPayload(request, testServer.getDecryptingPrivateKey());
} catch (Exception e) {
throw new HttpException("Failed to parse AS2 Message Entity", e);
}
@@ -282,6 +313,52 @@ public class AS2MessageTest {
assertTrue(ediEntity.isMainBody(), "Entity not set as main body of request");
}
+ @ParameterizedTest
+ @CsvSource({
+ "false,false,false", "false,false,true", "false,true,false", "false,true,true", "true,false,false",
+ "true,false,true", "true,true,false", "true,true,true" })
+ void binaryContentTransferEncodingTest(boolean encrypt, boolean sign, boolean compress) {
+ // test with as2-lib because Camel AS2 client doesn't support binary content transfer encoding at the moment
+ // inspired from https://github.com/phax/as2-lib/wiki/Submodule-as2%E2%80%90lib#as2-client
+
+ // Start client configuration
+ final AS2ClientSettings aSettings = new AS2ClientSettings();
+ aSettings.setKeyStore(EKeyStoreType.PKCS12, keystoreFile, "test");
+
+ // Fixed sender
+ aSettings.setSenderData(AS2_NAME, FROM, "openas2a_alias");
+
+ // Fixed receiver
+ aSettings.setReceiverData(AS2_NAME, "openas2b_alias", "http://" + TARGET_HOST + ":" + TARGET_PORT + "/");
+ aSettings.setReceiverCertificate(issueCert);
+
+ // AS2 stuff
+ aSettings.setPartnershipName(aSettings.getSenderAS2ID() + "_" + aSettings.getReceiverAS2ID());
+
+ // Build client request
+ final AS2ClientRequest aRequest = new AS2ClientRequest("AS2 test message from as2-lib");
+ aRequest.setData(EDI_MESSAGE, StandardCharsets.US_ASCII);
+ aRequest.setContentType(AS2MediaType.APPLICATION_EDIFACT);
+
+ // reproduce https://issues.apache.org/jira/projects/CAMEL/issues/CAMEL-15111
+ aSettings.setEncryptAndSign(encrypt ? ECryptoAlgorithmCrypt.CRYPT_AES128_GCM : null,
+ sign ? ECryptoAlgorithmSign.DIGEST_SHA_512 : null);
+ if (compress) {
+ aSettings.setCompress(ECompressionType.ZLIB, false);
+ }
+ aRequest.setContentTransferEncoding(EContentTransferEncoding.BINARY);
+
+ // Send message
+ ediEntity = null;
+ final AS2ClientResponse aResponse = new AS2Client().sendSynchronous(aSettings, aRequest);
+
+ // Assertions
+ if (aResponse.hasException()) {
+ fail(aResponse.getException());
+ }
+ assertEquals(EDI_MESSAGE, ediEntity.getEdiMessage().replaceAll("\r", ""));
+ }
+
@Test
public void multipartSignedMessageTest() throws Exception {
AS2ClientConnection clientConnection = new AS2ClientConnection(
diff --git a/components/camel-as2/pom.xml b/components/camel-as2/pom.xml
index 5bc35d5345b..9d6a1770b22 100644
--- a/components/camel-as2/pom.xml
+++ b/components/camel-as2/pom.xml
@@ -29,6 +29,10 @@
<artifactId>camel-as2-parent</artifactId>
<packaging>pom</packaging>
+ <properties>
+ <as2-lib-version>4.11.0</as2-lib-version>
+ </properties>
+
<name>Camel :: AS2 :: Parent</name>
<description>Camel AS2 parent</description>