Fix UploadPartCopy source handling (#127442)

The leading `/` on the `x-amz-copy-source` header is optional; SDKv1
includes it but SDKv2 does not. Today we handle its omission when
handling a `CopyObject` request but we must do the same for the
`UploadPartCopy` path.

Closes #127436
This commit is contained in:
David Turner 2025-04-28 01:03:10 +01:00 committed by GitHub
parent 3057168ede
commit 9772b5e005
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 22 additions and 12 deletions

View file

@ -447,9 +447,6 @@ tests:
- class: org.elasticsearch.xpack.esql.qa.single_node.PushQueriesIT
method: testPushCaseInsensitiveEqualityOnDefaults
issue: https://github.com/elastic/elasticsearch/issues/127431
- class: org.elasticsearch.repositories.blobstore.testkit.analyze.S3RepositoryAnalysisRestIT
method: testRepositoryAnalysis
issue: https://github.com/elastic/elasticsearch/issues/127436
# Examples:
#

View file

@ -158,9 +158,9 @@ public class S3HttpHandler implements HttpHandler {
exchange.sendResponseHeaders(RestStatus.NOT_FOUND.getStatus(), -1);
} else {
// CopyPart is UploadPart with an x-amz-copy-source header
final var sourceBlobName = exchange.getRequestHeaders().get("X-amz-copy-source");
if (sourceBlobName != null) {
var sourceBlob = blobs.get(sourceBlobName.getFirst());
final var copySource = copySourceName(exchange);
if (copySource != null) {
var sourceBlob = blobs.get(copySource);
if (sourceBlob == null) {
exchange.sendResponseHeaders(RestStatus.NOT_FOUND.getStatus(), -1);
} else {
@ -230,12 +230,10 @@ public class S3HttpHandler implements HttpHandler {
exchange.sendResponseHeaders((upload == null ? RestStatus.NOT_FOUND : RestStatus.NO_CONTENT).getStatus(), -1);
} else if (request.isPutObjectRequest()) {
// a copy request is a put request with a copy source header
final var copySources = exchange.getRequestHeaders().get("X-amz-copy-source");
if (copySources != null) {
final var copySource = copySources.getFirst();
final var prefix = copySource.length() > 0 && copySource.charAt(0) == '/' ? "" : "/";
var sourceBlob = blobs.get(prefix + copySource);
// a copy request is a put request with an X-amz-copy-source header
final var copySource = copySourceName(exchange);
if (copySource != null) {
var sourceBlob = blobs.get(copySource);
if (sourceBlob == null) {
exchange.sendResponseHeaders(RestStatus.NOT_FOUND.getStatus(), -1);
} else {
@ -516,6 +514,21 @@ public class S3HttpHandler implements HttpHandler {
}
}
@Nullable // if no X-amz-copy-source header present
private static String copySourceName(final HttpExchange exchange) {
final var copySources = exchange.getRequestHeaders().get("X-amz-copy-source");
if (copySources != null) {
if (copySources.size() != 1) {
throw new AssertionError("multiple X-amz-copy-source headers found: " + copySources);
}
final var copySource = copySources.get(0);
// SDKv1 uses format /bucket/path/blob whereas SDKv2 omits the leading / so we must add it back in
return copySource.length() > 0 && copySource.charAt(0) == '/' ? copySource : ("/" + copySource);
} else {
return null;
}
}
private static HttpHeaderParser.Range parsePartRange(final HttpExchange exchange) {
final var sourceRangeHeaders = exchange.getRequestHeaders().get("X-amz-copy-source-range");
if (sourceRangeHeaders == null) {