-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(s3): preserve question marks in copy source keys #1713
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -383,6 +383,62 @@ void copyObjectWithNonAsciiKeySucceeds() { | |
|
|
||
| @Test | ||
| @Order(21) | ||
| void copyObjectWithQuestionMarkInSourceKeySucceeds() { | ||
| String sourceBucket = "copy-question-source-bucket"; | ||
| String destBucket = "copy-question-dest-bucket"; | ||
| String srcKey = "folder/file with question ?.txt"; | ||
| String encodedSrcKey = "folder/file%20with%20question%20%3F.txt"; | ||
|
|
||
| given().put("/" + sourceBucket).then().statusCode(200); | ||
| given().put("/" + destBucket).then().statusCode(200); | ||
|
|
||
| given() | ||
| .contentType("text/plain") | ||
| .body("copy test") | ||
| .when() | ||
| .put("/" + sourceBucket + "/" + srcKey) | ||
| .then() | ||
| .statusCode(200); | ||
|
Comment on lines
+395
to
+401
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
REST Assured (backed by Apache HttpClient) treats |
||
|
|
||
| given() | ||
| .header("x-amz-copy-source", "/" + sourceBucket + "/" + encodedSrcKey) | ||
| .when() | ||
| .put("/" + destBucket + "/copied/encoded.txt") | ||
| .then() | ||
| .statusCode(200) | ||
| .body(containsString("CopyObjectResult")); | ||
|
|
||
| given() | ||
| .header("x-amz-copy-source", "/" + sourceBucket + "/" + srcKey) | ||
| .when() | ||
| .put("/" + destBucket + "/copied/raw.txt") | ||
| .then() | ||
| .statusCode(200) | ||
| .body(containsString("CopyObjectResult")); | ||
|
|
||
| given() | ||
| .when() | ||
| .get("/" + destBucket + "/copied/encoded.txt") | ||
| .then() | ||
| .statusCode(200) | ||
| .body(equalTo("copy test")); | ||
|
|
||
| given() | ||
| .when() | ||
| .get("/" + destBucket + "/copied/raw.txt") | ||
| .then() | ||
| .statusCode(200) | ||
| .body(equalTo("copy test")); | ||
|
|
||
| given().delete("/" + sourceBucket + "/" + srcKey); | ||
| given().delete("/" + destBucket + "/copied/encoded.txt"); | ||
| given().delete("/" + destBucket + "/copied/raw.txt"); | ||
| given().delete("/" + sourceBucket); | ||
| given().delete("/" + destBucket); | ||
| } | ||
|
Comment on lines
384
to
+438
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The PR description states the same parsing fix was applied to both Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
|
|
||
| @Test | ||
| @Order(22) | ||
| void copyObjectWithMalformedEncodedSourceReturns400() { | ||
| given() | ||
| .header("x-amz-copy-source", "/test-bucket/%ZZinvalid") | ||
|
|
@@ -394,7 +450,7 @@ void copyObjectWithMalformedEncodedSourceReturns400() { | |
| } | ||
|
|
||
| @Test | ||
| @Order(22) | ||
| @Order(23) | ||
| void copyObjectWithEmptyBucketReturns400() { | ||
| given() | ||
| .header("x-amz-copy-source", "/key-only-no-bucket") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?alongside?versionId=When a copy-source header contains both a raw (unencoded)
?in the key and a trailing?versionId=X— e.g./bucket/dir/file?.txt?versionId=abc—parseCopySourceObjectfinds the first?at the?inside the key. Thequerystring becomestxt?versionId=abc, which does containversionId=.extractVersionIdthen splits on&and gets the single tokentxt?versionId=abc; its name (everything before the first=) istxt?versionId≠"versionId", so it returnsnull. The fallback then decodes the entirepathAfterBucket(including?versionId=abc) as the key, silently discarding the version selector. The recommended AWS approach is to percent-encode?as%3Fin the key portion, which the new code handles correctly — worth documenting in the Javadoc onparseCopySourceObject.