This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 66a07e331869 CAMEL-25743: camel-aws2-s3 - upload a body of known
length with its own length, not a stale CamelAwsS3ContentLength (#28070)
66a07e331869 is described below
commit 66a07e3318695cc2d158f04d1acb68171ea0e1e1
Author: allthingssecurity <[email protected]>
AuthorDate: Mon Oct 19 19:59:07 2026 +0530
CAMEL-25743: camel-aws2-s3 - upload a body of known length with its own
length, not a stale CamelAwsS3ContentLength (#28070)
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../camel/component/aws2/s3/AWS2S3Producer.java | 24 +++++---
.../aws2/s3/AWS2S3ProducerPayloadLengthTest.java | 66 ++++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 7 +++
3 files changed, 89 insertions(+), 8 deletions(-)
diff --git
a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Producer.java
b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Producer.java
index 680832b5389a..6341a79a7249 100644
---
a/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Producer.java
+++
b/components/camel-aws/camel-aws2-s3/src/main/java/org/apache/camel/component/aws2/s3/AWS2S3Producer.java
@@ -191,11 +191,8 @@ public class AWS2S3Producer extends DefaultProducer {
contentLength = f.length();
} else {
// okay we use input stream
- if (contentLength <= 0) {
- // such as a java.nio.file.Path, byte[] or stream cache
- contentLength = PayloadHelper.getLength(obj);
- }
inputStream = exchange.getIn().getMandatoryBody(InputStream.class);
+ contentLength = bodyLength(obj, inputStream, contentLength);
if (contentLength <= 0) {
contentLength = PayloadHelper.getLength(inputStream);
if (contentLength == -1) {
@@ -369,11 +366,8 @@ public class AWS2S3Producer extends DefaultProducer {
contentLength = filePayload.length();
} else {
// okay we use input stream
- if (contentLength <= 0) {
- // such as a java.nio.file.Path, byte[] or stream cache
- contentLength = PayloadHelper.getLength(obj);
- }
inputStream =
exchange.getIn().getMandatoryBody(InputStream.class);
+ contentLength = bodyLength(obj, inputStream, contentLength);
if (contentLength <= 0) {
contentLength = PayloadHelper.getLength(inputStream);
if (contentLength == -1) {
@@ -397,6 +391,20 @@ public class AWS2S3Producer extends DefaultProducer {
}
}
+ /**
+ * The length of the body when it is known without reading it (such as a
java.nio.file.Path, byte[], String or
+ * stream cache), otherwise the given content-length header. The header is
not used for a body of known length, as
+ * it can be stale once the route changed the body (for example the length
of an object read by the consumer), and
+ * the SDK sends exactly that many bytes.
+ */
+ private static long bodyLength(Object body, InputStream inputStream, long
contentLengthHeader) {
+ long length = PayloadHelper.getLength(body);
+ if (length < 0) {
+ length = PayloadHelper.getLength(inputStream);
+ }
+ return length >= 0 ? length : contentLengthHeader;
+ }
+
private void doPutObject(
Exchange exchange, Map<String, String> objectMetadata,
File file, InputStream inputStream, long contentLength) {
diff --git
a/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/AWS2S3ProducerPayloadLengthTest.java
b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/AWS2S3ProducerPayloadLengthTest.java
index 581fc0bdb9a3..bc4720018cac 100644
---
a/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/AWS2S3ProducerPayloadLengthTest.java
+++
b/components/camel-aws/camel-aws2-s3/src/test/java/org/apache/camel/component/aws2/s3/AWS2S3ProducerPayloadLengthTest.java
@@ -149,4 +149,70 @@ public class AWS2S3ProducerPayloadLengthTest {
assertEquals(PAYLOAD_SIZE, uploadedLength);
assertArrayEquals(payload, uploadedContent);
}
+
+ @Test
+ public void bodyLongerThanAStaleContentLengthHeaderIsStoredWhole() throws
Exception {
+ // the consumer of another bucket set the header to the length of the
object it read, then the route
+ // changed the body: the SDK sends only contentLength bytes, so the
header must not cut the new body
+ String text = "a body made longer by the route";
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getIn().setHeader(AWS2S3Constants.KEY, "changed.txt");
+ exchange.getIn().setHeader(AWS2S3Constants.CONTENT_LENGTH, 10L);
+ exchange.getIn().setBody(text);
+
+ producer.process(exchange);
+
+ assertEquals(text.length(), uploadedLength);
+ assertArrayEquals(text.getBytes(StandardCharsets.UTF_8),
uploadedContent);
+ }
+
+ @Test
+ public void
bodyShorterThanAStaleContentLengthHeaderIsStoredWithItsLength() throws
Exception {
+ byte[] body = "short".getBytes(StandardCharsets.UTF_8);
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getIn().setHeader(AWS2S3Constants.KEY, "short.txt");
+ exchange.getIn().setHeader(AWS2S3Constants.CONTENT_LENGTH, 1000L);
+ exchange.getIn().setBody(body);
+
+ producer.process(exchange);
+
+ assertEquals(body.length, uploadedLength);
+ assertArrayEquals(body, uploadedContent);
+ }
+
+ @Test
+ public void multiPartUploadOfABodyLongerThanAStaleContentLengthHeader()
throws Exception {
+ when(configuration.isMultiPartUpload()).thenReturn(true);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getIn().setHeader(AWS2S3Constants.KEY, "big.bin");
+ exchange.getIn().setHeader(AWS2S3Constants.CONTENT_LENGTH, 10L);
+ exchange.getIn().setBody(payload);
+
+ producer.process(exchange);
+
+ assertEquals(PAYLOAD_SIZE, uploadedLength);
+ assertArrayEquals(payload, uploadedContent);
+ }
+
+ @Test
+ public void streamOfUnknownLengthUsesTheContentLengthHeader() throws
Exception {
+ // guards the case the header is for: a stream whose length is not
known is sent with the header length
+ InputStream stream = new FilterInputStream(new
ByteArrayInputStream(payload)) {
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+ };
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getIn().setHeader(AWS2S3Constants.KEY, "big.bin");
+ exchange.getIn().setHeader(AWS2S3Constants.CONTENT_LENGTH, (long)
PAYLOAD_SIZE);
+ exchange.getIn().setBody(stream);
+
+ producer.process(exchange);
+
+ assertEquals(PAYLOAD_SIZE, uploadedLength);
+ assertArrayEquals(payload, uploadedContent);
+ }
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 4d4f8a860fcc..32969ae2039e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -1297,6 +1297,13 @@ In both cases the objects are no longer kept in the
`inProgressRepository`; befo
the same key) was skipped by all later polls until the endpoint was restarted.
With the `fileName` option, a failed
get still fails the poll.
+=== camel-aws2-s3 - the producer stores the whole body when the content-length
header is stale
+
+When the length of the body is known without reading it (a `byte[]`, `String`,
file, `Path` or cached stream), the
+producer now uploads it with that length, and the `CamelAwsS3ContentLength`
header is only used for a stream whose
+length is not known. Previously the header took precedence: after a route
changed the body of an object read by the
+aws2-s3 consumer (which sets the header), a longer body was stored cut to the
old length and a shorter one failed.
+
=== camel-mina - the host name of the server is verified over SSL
When `sslContextParameters` is set (on the endpoint, or with
`useGlobalSslContextParameters` on the component), the