@@ -158,9 +158,9 @@ public void handle(final HttpExchange exchange) throws IOException {
158158 exchange .sendResponseHeaders (RestStatus .NOT_FOUND .getStatus (), -1 );
159159 } else {
160160 // CopyPart is UploadPart with an x-amz-copy-source header
161- final var sourceBlobName = exchange . getRequestHeaders (). get ( "X-amz-copy-source" );
162- if (sourceBlobName != null ) {
163- var sourceBlob = blobs .get (sourceBlobName . getFirst () );
161+ final var copySource = copySourceName ( exchange );
162+ if (copySource != null ) {
163+ var sourceBlob = blobs .get (copySource );
164164 if (sourceBlob == null ) {
165165 exchange .sendResponseHeaders (RestStatus .NOT_FOUND .getStatus (), -1 );
166166 } else {
@@ -230,12 +230,10 @@ public void handle(final HttpExchange exchange) throws IOException {
230230 exchange .sendResponseHeaders ((upload == null ? RestStatus .NOT_FOUND : RestStatus .NO_CONTENT ).getStatus (), -1 );
231231
232232 } else if (request .isPutObjectRequest ()) {
233- // a copy request is a put request with a copy source header
234- final var copySources = exchange .getRequestHeaders ().get ("X-amz-copy-source" );
235- if (copySources != null ) {
236- final var copySource = copySources .getFirst ();
237- final var prefix = copySource .length () > 0 && copySource .charAt (0 ) == '/' ? "" : "/" ;
238- var sourceBlob = blobs .get (prefix + copySource );
233+ // a copy request is a put request with an X-amz-copy-source header
234+ final var copySource = copySourceName (exchange );
235+ if (copySource != null ) {
236+ var sourceBlob = blobs .get (copySource );
239237 if (sourceBlob == null ) {
240238 exchange .sendResponseHeaders (RestStatus .NOT_FOUND .getStatus (), -1 );
241239 } else {
@@ -516,6 +514,21 @@ static List<String> extractPartEtags(BytesReference completeMultipartUploadBody)
516514 }
517515 }
518516
517+ @ Nullable // if no X-amz-copy-source header present
518+ private static String copySourceName (final HttpExchange exchange ) {
519+ final var copySources = exchange .getRequestHeaders ().get ("X-amz-copy-source" );
520+ if (copySources != null ) {
521+ if (copySources .size () != 1 ) {
522+ throw new AssertionError ("multiple X-amz-copy-source headers found: " + copySources );
523+ }
524+ final var copySource = copySources .get (0 );
525+ // SDKv1 uses format /bucket/path/blob whereas SDKv2 omits the leading / so we must add it back in
526+ return copySource .length () > 0 && copySource .charAt (0 ) == '/' ? copySource : ("/" + copySource );
527+ } else {
528+ return null ;
529+ }
530+ }
531+
519532 private static HttpHeaderParser .Range parsePartRange (final HttpExchange exchange ) {
520533 final var sourceRangeHeaders = exchange .getRequestHeaders ().get ("X-amz-copy-source-range" );
521534 if (sourceRangeHeaders == null ) {
0 commit comments