[
https://issues.apache.org/jira/browse/HDDS-17630?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Ritesh Shukla updated HDDS-17630:
---------------------------------
Description:
h3. Problem
An S3 {{PUT}} on an object key means one of two things:
* {{PUT /bucket/key?partNumber=N&uploadId=<id>}} uploads one part of a
multipart upload (UploadPart). Nothing under the key changes until the upload
is completed.
* {{PUT /bucket/key}} writes the object (PutObject).
When {{uploadId}} is empty, or {{partNumber}} comes with no {{uploadId}} at
all, the S3 Gateway handles the request as a PutObject, or as a CopyObject when
{{x-amz-copy-source}} is set. The body (or the copy source) replaces the
committed object, and the client gets {{200 OK}}, just as for a part upload:
{code}
PUT /bucket/key body "original object" -> 200, key
holds "original object"
PUT /bucket/key?partNumber=1&uploadId= body "part 1" -> 200, key
now holds "part 1"
{code}
AWS S3 refuses each of these requests and leaves the object alone (tested
against AWS S3 on 2026-10-31). The Ozone column was run on master (ef63bacef7)
against a mini cluster with the AWS SDK for Java v2, and in the S3 Gateway
unit-test harness:
|| Request || AWS S3 || S3 Gateway on master ||
| {{PUT ?partNumber=1&uploadId=}} | 400 InvalidArgument, object kept | 200,
object replaced by the body |
| {{PUT ?partNumber=1&uploadId}} (no {{=}}) | 400 InvalidArgument, object kept
| 200, object replaced by the body: the gateway reads a bare {{uploadId}} as
the same empty value |
| {{PUT ?partNumber=1}} (no {{uploadId}}) | 400 InvalidArgument, object kept |
200, object replaced by the body |
| {{PUT ?partNumber=1&uploadId=}} with {{x-amz-copy-source}} (UploadPartCopy) |
400 InvalidArgument, object kept | 200, object replaced by a full copy of the
source; {{x-amz-copy-source-range}} is ignored |
| {{PUT ?uploadId=}} (no {{partNumber}}) | 405 MethodNotAllowed, object kept |
200, object replaced by the body |
| {{PUT ?partNumber=}} or {{?partNumber=0}} (no {{uploadId}}), or
{{?partNumber=&uploadId=}} | 400 InvalidArgument, object kept
({{?partNumber=abc}} too) | 200, object replaced by the body |
| {{PUT ?partNumber=1&uploadId=}} while an upload is in progress on the key |
400 InvalidArgument, object kept, no part added | 200, object replaced by the
body |
| {{PUT ?uploadId=<non-empty ID>}} (no {{partNumber}}), unknown or real upload
| 405 MethodNotAllowed, object kept, no part added | 400 InvalidArgument "Part
number must be an integer between 1 and 10000, inclusive", object kept, no part
added: the gateway reads the missing part number as 0, and Ozone's client
library in the gateway refuses it before calling OM |
S3's InvalidArgument message is "This operation does not accept partNumber
without uploadId", for any value of {{partNumber}}. With an empty body, the
gateway truncates the object to 0 bytes (run).
On master, the server-side encryption check added by HDDS-17498 runs before the
gateway picks the operation. So a request that also asks for encryption the
gateway cannot honour (SSE-C in any bucket, or SSE-S3 or SSE-KMS on an
unencrypted bucket) is refused first with 501 NotImplemented and keeps the
object (run for SSE-C and SSE-S3 on an unencrypted bucket, the other cases read
from code). Without an encryption header, or with one an encrypted bucket can
honour, the object is replaced as above (run).
Other handlers that read {{uploadId}} (gateway answers run against a mini
cluster):
* {{POST ?uploadId=}} (CompleteMultipartUpload) and {{GET ?uploadId=}}
(ListParts): S3 and the gateway both answer 404 NoSuchUpload.
* {{DELETE ?uploadId=}}: S3 answers 404 NoSuchUpload; the gateway deletes the
object. That is HDDS-17599.
* {{HEAD ?uploadId=}} and {{GET ?partNumber=1&uploadId=}}: S3 answers 405
MethodNotAllowed; the gateway answers 200 (HeadObject) and 404 NoSuchUpload.
Both are read-only and are left out of this fix.
h3. Impact
* Only a caller who may already write the key can do this. The operation that
actually runs is authorized as itself: PutObject, or GetObject on the source
and PutObject on the destination for the copy form. No permission boundary is
crossed (read from code).
* What is lost: the object's data, metadata and tags (run). The response is a
success, so the client cannot tell. A client that uploads several parts under
the same empty ID ends up with only the last part as the object (run).
* How a client gets here: an application bug that passes an empty upload ID,
for example after losing the CreateMultipartUpload response. Stock clients send
it as-is. The AWS SDK for Java v2 (2.55.6) and v1 (1.12.788), and the AWS CLI
v2 (2.35.10), all put {{uploadId=}} on the wire for an empty ID, including for
UploadPartCopy; both SDKs refuse a null ID before sending (run against a local
capture server).
h3. Cause
{{ObjectEndpoint#handlePutRequest}} treats a request as a part upload only when
{{uploadID != null && !uploadID.equals("")}} (ObjectEndpoint.java:216 at
ef63bacef7). Every other request becomes a PutObject, or a CopyObject when
{{x-amz-copy-source}} is set (ObjectEndpoint.java:238-245). That path never
reads {{partNumber}}. The only checks that run before this condition are the
bucket lookup and the server-side encryption check
(ObjectEndpoint.java:210-212).
The empty-string test dates from the first UploadPart code (HDDS-902). The
parameter was then declared with {{@DefaultValue("")}}, so a missing
{{uploadId}} arrived as an empty string, and comparing with {{""}} was how
"absent" was written. HDDS-14209 switched to reading query parameters directly,
where a missing {{uploadId}} is {{null}}, but kept the comparison. So a present
but empty {{uploadId}} still counts as absent.
h3. Proposed fix
Answer as S3 does (tested against AWS S3 on 2026-10-31), before any OM call:
* {{partNumber}} present and {{uploadId}} missing or empty: 400
{{InvalidArgument}} with S3's message, "This operation does not accept
partNumber without uploadId". This also covers UploadPartCopy.
* {{uploadId}} present, empty or not, and no {{partNumber}}: 405
{{MethodNotAllowed}}, as S3 answers both. {{S3ErrorTable}} gains a
{{METHOD_NOT_ALLOWED}} entry with S3's code and message.
* Everything else is unchanged: a non-empty {{uploadId}} with a {{partNumber}}
is still a part upload, and a PUT with neither parameter is still a PutObject.
Both checks go at the top of {{handlePutRequest}}, before the bucket lookup and
the server-side encryption check, so a refused request makes no OM call and the
key is never opened. It is audited and counted as a failed PutObject. The
part-upload branch then only needs {{uploadID != null}}.
The obvious one-line fix, dropping {{!uploadID.equals("")}} so the empty ID
reaches OM, is worse. Run against a mini cluster, it answers the empty ID with
OM's 404 NoSuchUpload, not S3's 400, and {{PUT ?partNumber=1}} with no
{{uploadId}} still overwrites the object. UploadPartCopy would also read the
source key before failing (read from code). HDDS-17599 answers DELETE with
NoSuchUpload because that is what S3 does for DELETE; S3 answers PUT
differently, so this fix does too.
h3. Testing
* Unit, {{TestPartUpload}}: a PUT with {{partNumber}} and no non-empty
{{uploadId}} ({{?partNumber=1&uploadId=}}, {{?partNumber=1}}, {{?partNumber=}},
{{?partNumber=0}}, {{?partNumber=abc}}, {{?partNumber=&uploadId=}}) expects
S3's 400; a PUT with {{uploadId}} and no {{partNumber}} ({{?uploadId=}},
{{?uploadId=<unknown>}}) expects S3's 405; and with an upload in progress on
the key, {{?partNumber=1&uploadId=}} (400) and {{?uploadId=<that upload>}}
(405) must add no part. Each test puts a key first and checks that its content
is unchanged. They run with and without data stream. On master all 20 runs
fail: 16 replace the key, 2 answer 404 instead of 405, and 2 are accepted by
the unit tests' client stub, which does not check part numbers.
* Unit, {{TestMultipartUploadWithCopy}}: UploadPartCopy with
{{?partNumber=1&uploadId=}} and with {{?partNumber=1}}. On master both copy the
source over the destination.
* {{EndpointTestUtils#put}} stops adding {{partNumber=0}} to plain PUTs and
clears a left-over {{uploadId}}. A real PutObject carries neither, and the new
checks would refuse it: with the fix but without this change, 275 existing
unit-test runs in 17 classes fail.
* Integration, {{AbstractS3SDKV2Tests}}: {{uploadPart}} and {{uploadPartCopy}}
with {{uploadId("")}}, which the SDK sends as {{uploadId=}}. Each expects 400
InvalidArgument with S3's message and the object's content unchanged. On master
the object is replaced.
* Run on ef63bacef7 with the change: all 1,943 s3gateway unit-test runs pass,
and so do the 255 tests of the SDK v1 and v2 integration suite ({{TestS3SDK}}).
Without the change, only the new tests fail.
----
🤖 Generated with [Claude Code|https://claude.com/claude-code]
was:
h3. Problem
An S3 {{PUT}} on an object key means one of two things:
* {{PUT /bucket/key?partNumber=N&uploadId=<id>}} uploads one part of a
multipart upload (UploadPart). Nothing under the key changes until the upload
is completed.
* {{PUT /bucket/key}} writes the object (PutObject).
When {{uploadId}} is empty, or {{partNumber}} comes with no {{uploadId}} at
all, the S3 Gateway handles the request as a PutObject, or as a CopyObject when
{{x-amz-copy-source}} is set. The body (or the copy source) replaces the
committed object, and the client gets {{200 OK}}, just as for a part upload:
{code}
PUT /bucket/key body "original object" -> 200, key
holds "original object"
PUT /bucket/key?partNumber=1&uploadId= body "part 1" -> 200, key
now holds "part 1"
{code}
AWS S3 refuses each of these requests and leaves the object alone (tested
against AWS S3 on 2026-10-31). The Ozone column was run in the S3 Gateway
unit-test harness on master:
|| Request || AWS S3 || S3 Gateway on master ||
| {{PUT ?partNumber=1&uploadId=}} | 400 InvalidArgument, object kept | 200,
object replaced by the body |
| {{PUT ?partNumber=1&uploadId}} (no {{=}}) | 400 InvalidArgument, object kept
| same as above: the gateway's JAX-RS layer decodes a bare {{uploadId}} to the
same empty value (checked with Jersey's query decoder) |
| {{PUT ?partNumber=1}} (no {{uploadId}}) | 400 InvalidArgument, object kept |
200, object replaced by the body |
| {{PUT ?partNumber=1&uploadId=}} with {{x-amz-copy-source}} (UploadPartCopy) |
400 InvalidArgument, object kept | 200, object replaced by a full copy of the
source; {{x-amz-copy-source-range}} is ignored |
| {{PUT ?uploadId=}} (no {{partNumber}}) | 405 MethodNotAllowed, object kept |
200, object replaced by the body |
| {{PUT ?partNumber=}} or {{?partNumber=0}} (no {{uploadId}}), or
{{?partNumber=&uploadId=}} | 400 InvalidArgument, object kept
({{?partNumber=abc}} too) | 200, object replaced by the body |
| {{PUT ?partNumber=1&uploadId=}} while an upload is in progress on the key |
400 InvalidArgument, object kept, no part added | 200, object replaced by the
body |
| {{PUT ?uploadId=<non-empty ID>}} (no {{partNumber}}), unknown or real upload
| 405 MethodNotAllowed, object kept, no part added | unknown ID: 404
NoSuchUpload; real upload: OM refuses part number 0 with 400 (read from code) |
S3's InvalidArgument message is "This operation does not accept partNumber
without uploadId", for any value of {{partNumber}}. With an empty body, the
gateway truncates the object to 0 bytes.
Other handlers that read {{uploadId}}:
* {{POST ?uploadId=}} (CompleteMultipartUpload) and {{GET ?uploadId=}}
(ListParts): S3 and the gateway both answer 404 NoSuchUpload. On the gateway
side this was run against the test client; that OM refuses an empty upload ID
was read from code.
* {{DELETE ?uploadId=}}: S3 answers 404 NoSuchUpload; the gateway deletes the
object. That is HDDS-17599.
* {{HEAD ?uploadId=}} and {{GET ?partNumber=1&uploadId=}}: S3 answers 405
MethodNotAllowed; the gateway answers 200 (HeadObject) and 404 NoSuchUpload.
Both are read-only and are left out of this fix.
h3. Impact
* Only a caller who may already write the key can do this. The operation that
actually runs is authorized as itself: PutObject, or GetObject on the source
and PutObject on the destination for the copy form. No permission boundary is
crossed (read from code).
* What is lost: the object's data (run), and with it the object's metadata and
tags (read from code). The response is a success, so the client cannot tell. A
client that uploads several parts under the same empty ID ends up with only the
last part as the object (inferred).
* How a client gets here: an application bug that passes an empty upload ID,
for example after losing the CreateMultipartUpload response. Stock clients send
it as-is. The AWS SDK for Java v2 (2.55.6) and v1 (1.12.788), and the AWS CLI
v2 (2.35.10), all put {{uploadId=}} on the wire for an empty ID, including for
UploadPartCopy; both SDKs refuse a null ID before sending (run against a local
capture server).
h3. Cause
{{ObjectEndpoint#handlePutRequest}} treats a request as a part upload only when
{{uploadID != null && !uploadID.equals("")}} (ObjectEndpoint.java:214 on
master). Every other request becomes a PutObject, or a CopyObject when
{{x-amz-copy-source}} is set (ObjectEndpoint.java:236-243). That path never
reads {{partNumber}}.
The empty-string test dates from the first UploadPart code (HDDS-902). The
parameter was then declared with {{@DefaultValue("")}}, so a missing
{{uploadId}} arrived as an empty string, and comparing with {{""}} was how
"absent" was written. HDDS-14209 switched to reading query parameters directly,
where a missing {{uploadId}} is {{null}}, but kept the comparison. So a present
but empty {{uploadId}} still counts as absent.
h3. Proposed fix
Answer as S3 does (tested against AWS S3 on 2026-10-31), before any OM call:
* {{partNumber}} present and {{uploadId}} missing or empty: 400
{{InvalidArgument}} with S3's message, "This operation does not accept
partNumber without uploadId". This also covers UploadPartCopy.
* {{uploadId}} present, empty or not, and no {{partNumber}}: 405
{{MethodNotAllowed}}, as S3 answers both. {{S3ErrorTable}} gains a
{{METHOD_NOT_ALLOWED}} entry with S3's code and message.
* Everything else is unchanged: a non-empty {{uploadId}} with a {{partNumber}}
is still a part upload, and a PUT with neither parameter is still a PutObject.
Both checks go at the top of {{handlePutRequest}}. A refused request is audited
and counted as a failed PutObject, and the key is never opened. The part-upload
branch then only needs {{uploadID != null}}.
The obvious one-line fix, dropping {{!uploadID.equals("")}} so the empty ID
reaches OM, is worse. OM would answer 404 NoSuchUpload, not S3's 400. {{PUT
?partNumber=1}} with no {{uploadId}} would still overwrite the object. And
UploadPartCopy would read the source key before failing. HDDS-17599 answers
DELETE with NoSuchUpload because that is what S3 does for DELETE; S3 answers
PUT differently, so this fix does too.
h3. Testing
* Unit, {{TestPartUpload}}: one parameterized case for each of
{{?partNumber=1&uploadId=}}, {{?partNumber=1}} and {{?uploadId=}}. Each puts a
key, sends the PUT, checks S3's error code and message, and checks that the
key's content and size are unchanged. It runs with and without data stream. On
master all six runs fail: the PUT succeeds and the key is replaced. To add with
the fix: {{?partNumber=}}, {{?partNumber=0}} and {{?partNumber=abc}} without an
upload ID (400), and {{?uploadId=<non-empty>}} without a part number (405).
* Unit, {{TestMultipartUploadWithCopy}}: the same for UploadPartCopy with
{{?partNumber=1&uploadId=}}. On master the destination is replaced by the
source.
* {{EndpointTestUtils#put}} stops adding {{partNumber=0}} to plain PUTs. A real
PutObject carries no {{partNumber}}, and the new check would refuse it.
* All s3gateway unit tests pass with the change.
* Integration (to add with the fix): a case in {{AbstractS3SDKV2Tests}} calls
{{uploadPart}} and {{uploadPartCopy}} with {{uploadId("")}}, which the SDK
sends as {{uploadId=}}. It expects 400 InvalidArgument and the same ETag from
{{headObject}} afterwards.
----
🤖 Generated with [Claude Code|https://claude.com/claude-code]
> S3G: a part upload with an empty or missing uploadId overwrites the object
> instead of failing
> ---------------------------------------------------------------------------------------------
>
> Key: HDDS-17630
> URL: https://issues.apache.org/jira/browse/HDDS-17630
> Project: Apache Ozone
> Issue Type: Bug
> Components: S3
> Reporter: Ritesh Shukla
> Assignee: Ritesh Shukla
> Priority: Major
>
> h3. Problem
> An S3 {{PUT}} on an object key means one of two things:
> * {{PUT /bucket/key?partNumber=N&uploadId=<id>}} uploads one part of a
> multipart upload (UploadPart). Nothing under the key changes until the upload
> is completed.
> * {{PUT /bucket/key}} writes the object (PutObject).
> When {{uploadId}} is empty, or {{partNumber}} comes with no {{uploadId}} at
> all, the S3 Gateway handles the request as a PutObject, or as a CopyObject
> when {{x-amz-copy-source}} is set. The body (or the copy source) replaces the
> committed object, and the client gets {{200 OK}}, just as for a part upload:
> {code}
> PUT /bucket/key body "original object" -> 200, key
> holds "original object"
> PUT /bucket/key?partNumber=1&uploadId= body "part 1" -> 200, key
> now holds "part 1"
> {code}
> AWS S3 refuses each of these requests and leaves the object alone (tested
> against AWS S3 on 2026-10-31). The Ozone column was run on master
> (ef63bacef7) against a mini cluster with the AWS SDK for Java v2, and in the
> S3 Gateway unit-test harness:
> || Request || AWS S3 || S3 Gateway on master ||
> | {{PUT ?partNumber=1&uploadId=}} | 400 InvalidArgument, object kept | 200,
> object replaced by the body |
> | {{PUT ?partNumber=1&uploadId}} (no {{=}}) | 400 InvalidArgument, object
> kept | 200, object replaced by the body: the gateway reads a bare
> {{uploadId}} as the same empty value |
> | {{PUT ?partNumber=1}} (no {{uploadId}}) | 400 InvalidArgument, object kept
> | 200, object replaced by the body |
> | {{PUT ?partNumber=1&uploadId=}} with {{x-amz-copy-source}} (UploadPartCopy)
> | 400 InvalidArgument, object kept | 200, object replaced by a full copy of
> the source; {{x-amz-copy-source-range}} is ignored |
> | {{PUT ?uploadId=}} (no {{partNumber}}) | 405 MethodNotAllowed, object kept
> | 200, object replaced by the body |
> | {{PUT ?partNumber=}} or {{?partNumber=0}} (no {{uploadId}}), or
> {{?partNumber=&uploadId=}} | 400 InvalidArgument, object kept
> ({{?partNumber=abc}} too) | 200, object replaced by the body |
> | {{PUT ?partNumber=1&uploadId=}} while an upload is in progress on the key |
> 400 InvalidArgument, object kept, no part added | 200, object replaced by the
> body |
> | {{PUT ?uploadId=<non-empty ID>}} (no {{partNumber}}), unknown or real
> upload | 405 MethodNotAllowed, object kept, no part added | 400
> InvalidArgument "Part number must be an integer between 1 and 10000,
> inclusive", object kept, no part added: the gateway reads the missing part
> number as 0, and Ozone's client library in the gateway refuses it before
> calling OM |
> S3's InvalidArgument message is "This operation does not accept partNumber
> without uploadId", for any value of {{partNumber}}. With an empty body, the
> gateway truncates the object to 0 bytes (run).
> On master, the server-side encryption check added by HDDS-17498 runs before
> the gateway picks the operation. So a request that also asks for encryption
> the gateway cannot honour (SSE-C in any bucket, or SSE-S3 or SSE-KMS on an
> unencrypted bucket) is refused first with 501 NotImplemented and keeps the
> object (run for SSE-C and SSE-S3 on an unencrypted bucket, the other cases
> read from code). Without an encryption header, or with one an encrypted
> bucket can honour, the object is replaced as above (run).
> Other handlers that read {{uploadId}} (gateway answers run against a mini
> cluster):
> * {{POST ?uploadId=}} (CompleteMultipartUpload) and {{GET ?uploadId=}}
> (ListParts): S3 and the gateway both answer 404 NoSuchUpload.
> * {{DELETE ?uploadId=}}: S3 answers 404 NoSuchUpload; the gateway deletes the
> object. That is HDDS-17599.
> * {{HEAD ?uploadId=}} and {{GET ?partNumber=1&uploadId=}}: S3 answers 405
> MethodNotAllowed; the gateway answers 200 (HeadObject) and 404 NoSuchUpload.
> Both are read-only and are left out of this fix.
> h3. Impact
> * Only a caller who may already write the key can do this. The operation that
> actually runs is authorized as itself: PutObject, or GetObject on the source
> and PutObject on the destination for the copy form. No permission boundary is
> crossed (read from code).
> * What is lost: the object's data, metadata and tags (run). The response is a
> success, so the client cannot tell. A client that uploads several parts under
> the same empty ID ends up with only the last part as the object (run).
> * How a client gets here: an application bug that passes an empty upload ID,
> for example after losing the CreateMultipartUpload response. Stock clients
> send it as-is. The AWS SDK for Java v2 (2.55.6) and v1 (1.12.788), and the
> AWS CLI v2 (2.35.10), all put {{uploadId=}} on the wire for an empty ID,
> including for UploadPartCopy; both SDKs refuse a null ID before sending (run
> against a local capture server).
> h3. Cause
> {{ObjectEndpoint#handlePutRequest}} treats a request as a part upload only
> when {{uploadID != null && !uploadID.equals("")}} (ObjectEndpoint.java:216 at
> ef63bacef7). Every other request becomes a PutObject, or a CopyObject when
> {{x-amz-copy-source}} is set (ObjectEndpoint.java:238-245). That path never
> reads {{partNumber}}. The only checks that run before this condition are the
> bucket lookup and the server-side encryption check
> (ObjectEndpoint.java:210-212).
> The empty-string test dates from the first UploadPart code (HDDS-902). The
> parameter was then declared with {{@DefaultValue("")}}, so a missing
> {{uploadId}} arrived as an empty string, and comparing with {{""}} was how
> "absent" was written. HDDS-14209 switched to reading query parameters
> directly, where a missing {{uploadId}} is {{null}}, but kept the comparison.
> So a present but empty {{uploadId}} still counts as absent.
> h3. Proposed fix
> Answer as S3 does (tested against AWS S3 on 2026-10-31), before any OM call:
> * {{partNumber}} present and {{uploadId}} missing or empty: 400
> {{InvalidArgument}} with S3's message, "This operation does not accept
> partNumber without uploadId". This also covers UploadPartCopy.
> * {{uploadId}} present, empty or not, and no {{partNumber}}: 405
> {{MethodNotAllowed}}, as S3 answers both. {{S3ErrorTable}} gains a
> {{METHOD_NOT_ALLOWED}} entry with S3's code and message.
> * Everything else is unchanged: a non-empty {{uploadId}} with a
> {{partNumber}} is still a part upload, and a PUT with neither parameter is
> still a PutObject.
> Both checks go at the top of {{handlePutRequest}}, before the bucket lookup
> and the server-side encryption check, so a refused request makes no OM call
> and the key is never opened. It is audited and counted as a failed PutObject.
> The part-upload branch then only needs {{uploadID != null}}.
> The obvious one-line fix, dropping {{!uploadID.equals("")}} so the empty ID
> reaches OM, is worse. Run against a mini cluster, it answers the empty ID
> with OM's 404 NoSuchUpload, not S3's 400, and {{PUT ?partNumber=1}} with no
> {{uploadId}} still overwrites the object. UploadPartCopy would also read the
> source key before failing (read from code). HDDS-17599 answers DELETE with
> NoSuchUpload because that is what S3 does for DELETE; S3 answers PUT
> differently, so this fix does too.
> h3. Testing
> * Unit, {{TestPartUpload}}: a PUT with {{partNumber}} and no non-empty
> {{uploadId}} ({{?partNumber=1&uploadId=}}, {{?partNumber=1}},
> {{?partNumber=}}, {{?partNumber=0}}, {{?partNumber=abc}},
> {{?partNumber=&uploadId=}}) expects S3's 400; a PUT with {{uploadId}} and no
> {{partNumber}} ({{?uploadId=}}, {{?uploadId=<unknown>}}) expects S3's 405;
> and with an upload in progress on the key, {{?partNumber=1&uploadId=}} (400)
> and {{?uploadId=<that upload>}} (405) must add no part. Each test puts a key
> first and checks that its content is unchanged. They run with and without
> data stream. On master all 20 runs fail: 16 replace the key, 2 answer 404
> instead of 405, and 2 are accepted by the unit tests' client stub, which does
> not check part numbers.
> * Unit, {{TestMultipartUploadWithCopy}}: UploadPartCopy with
> {{?partNumber=1&uploadId=}} and with {{?partNumber=1}}. On master both copy
> the source over the destination.
> * {{EndpointTestUtils#put}} stops adding {{partNumber=0}} to plain PUTs and
> clears a left-over {{uploadId}}. A real PutObject carries neither, and the
> new checks would refuse it: with the fix but without this change, 275
> existing unit-test runs in 17 classes fail.
> * Integration, {{AbstractS3SDKV2Tests}}: {{uploadPart}} and
> {{uploadPartCopy}} with {{uploadId("")}}, which the SDK sends as
> {{uploadId=}}. Each expects 400 InvalidArgument with S3's message and the
> object's content unchanged. On master the object is replaced.
> * Run on ef63bacef7 with the change: all 1,943 s3gateway unit-test runs pass,
> and so do the 255 tests of the SDK v1 and v2 integration suite
> ({{TestS3SDK}}). Without the change, only the new tests fail.
> ----
> 🤖 Generated with [Claude Code|https://claude.com/claude-code]
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]