shashank created CAMEL-25743:
--------------------------------

             Summary: camel-aws2-s3 - the producer uploads a body of known 
length with the length from a stale CamelAwsS3ContentLength header, so a body 
the route made longer is stored cut to the old length
                 Key: CAMEL-25743
                 URL: https://issues.apache.org/jira/browse/CAMEL-25743
             Project: Camel
          Issue Type: Bug
          Components: camel-aws2-s3
            Reporter: shashank
            Assignee: shashank


The aws2-s3 producer takes the content length from the 
{{CamelAwsS3ContentLength}} header whenever it is set, and only looks at the 
body when it is not ({{AWS2S3Producer.processSingleOp}}, main 55ed20f14e90, 
lines 354 and 371-375; {{processMultiPart}}, lines 178 and 193-197):

{code:java}
long contentLength = exchange.getIn().getHeader(AWS2S3Constants.CONTENT_LENGTH, 
-1, Long.class);
...
if (contentLength <= 0) {
    // such as a java.nio.file.Path, byte[] or stream cache
    contentLength = PayloadHelper.getLength(obj);
}
{code}

The length is then given to the SDK ({{RequestBody.fromInputStream(inputStream, 
contentLength)}} and {{PutObjectRequest.contentLength}}, lines 461 and 510), 
which sends exactly that many bytes: {{MakeHttpRequestStage}} (sdk-core) wraps 
the content in {{software.amazon.awssdk.utils.io.LengthAwareInputStream}}, 
which returns end of stream after {{contentLength}} bytes and throws "The 
request content has fewer bytes than the specified content-length" for a 
shorter body (SDK 2.55.15, the version on main). Checked end to end with the 
real {{S3Client}} against a local HTTP server: a 31-byte String body with the 
header 10 arrives as the 10 bytes {{a body mad}}, and the exchange completes 
without an error.

The aws2-s3 consumer sets {{CamelAwsS3ContentLength}} to the length of the 
object it read ({{AWS2S3Consumer}} line 584; {{getObject}} does the same, 
producer line 1553), and the header stays on the message when the route changes 
the body. So a route like

{code:java}
from("aws2-s3://incoming?...")
    .convertBodyTo(String.class)
    .transform(simple("${body}\n# processed"))
    .to("aws2-s3://processed?...");
{code}

(or an enrich, marshal or template step between the two) stores the new body 
cut to the length of the original object, and the exchange completes normally. 
A body made shorter fails in the SDK. The truncation is silent and the object 
in the target bucket is corrupt.

The header exists for a stream whose length is not known: CAMEL-4126 added it 
because "With InputStreams, Amazon's Java libraries aren't able to generate the 
content length", and CAMEL-13433 stopped copying a big stream into memory when 
the header was set. CAMEL-25167 says "Existing length headers 
({{CamelAwsS3ContentLength}}, {{CamelAzureStorageBlobUploadSize}}, ...) keep 
precedence"; its change kept the order that was there and did not discuss a 
header that no longer matches the body. This issue proposes to narrow that 
precedence to bodies whose length is not known; if the precedence is meant to 
hold for every body, the alternative is a documentation note (remove the 
{{CamelAwsS3*}} headers before writing a changed body). For a {{byte[]}}, 
{{String}}, file, {{Path}} or cached stream the length is known without reading 
the body, and CAMEL-25167's own length helper ({{PayloadHelper.getBodyLength}}) 
does not use {{CamelFileLength}} "as it can be stale once the route changed the 
body".

h3. Reproduction

New tests in {{AWS2S3ProducerPayloadLengthTest}} (mocked {{S3Client}}):
* {{bodyLongerThanAStaleContentLengthHeaderIsStoredWhole}}: a 31-character 
String body with the header 10 is uploaded with length 10 ({{expected: <31> but 
was: <10>}}).
* {{bodyShorterThanAStaleContentLengthHeaderIsStoredWithItsLength}}: a 5-byte 
body with the header 1000 ({{expected: <5> but was: <1000>}}).
* {{multiPartUploadOfABodyLongerThanAStaleContentLengthHeader}}: 
{{multiPartUpload=true}}, a 1 MB {{byte[]}} with the header 10 ({{expected: 
<1048576> but was: <10>}}).
All three fail on main, 3 of 3 runs. A fourth test, 
{{streamOfUnknownLengthUsesTheContentLengthHeader}}, passes on main and with 
the fix; it guards the case the header is for. The end-to-end probe above (real 
SDK client, local HTTP server; not part of the change) shows the same with the 
fix: the 31 bytes arrive.

h3. Proposed fix

When the length of the body is known without reading it 
({{PayloadHelper.getLength}} of the body or of its input stream), that length 
is used; the header is only used for a stream whose length is not known, as 
before. Same change in the single and the multi-part upload. Upgrade note in 
the 4.24 guide.

With the fix the camel-aws2-s3 tests pass: 108 run, 0 failures, 0 errors, 0 
skipped.

Not changed: a stale header on a stream whose length is not known (for example 
{{includeBody=false}} and a route that wraps the stream) is still used, as 
there is nothing else to measure without copying the stream. Same pattern, not 
changed here: camel-minio ({{MinioProducer}}), camel-ibm-cos and 
camel-google-storage give their own content-length header precedence too 
(possible follow-ups); camel-azure-files with {{CamelFileLength}} is a separate 
issue.

Affected: main (tested); camel-4.23.x, camel-4.22.x and camel-4.18.x give the 
header the same precedence (read, not run).

Duplicate check (2026-10-17): JIRA text "CamelAwsS3ContentLength", "aws2-s3 
content-length header stale": CAMEL-4126, CAMEL-12121 (Not A Problem, a 
constant header in a route), CAMEL-13433, CAMEL-25167; none about a stale 
header from the consumer. GitHub pull requests "AWS2S3Producer content length": 
#27132 (CAMEL-25167), #21122.

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to