fix: forward slash url encoded used as bucket/key separator

handle %2F ("/") url encoded separator for bucket/key

Fixes #2024

---------

Co-authored-by: Galie Alexandru MTANA <alexandru.galie@mt.com>
This commit is contained in:
alexgalie
2026-05-11 10:31:02 -07:00
committed by GitHub
co-authored by Galie Alexandru MTANA
parent e49bb1d76a
commit fe3cfbfce9
4 changed files with 185 additions and 17 deletions
+14 -15
View File
@@ -232,29 +232,28 @@ func ParseCopySource(copySourceHeader string) (string, string, string, error) {
copySourceHeader = copySourceHeader[1:]
}
var copySource, versionId string
// Split the raw header on the versionId query parameter before any
// URL-decoding so that the '?' delimiter is not percent-encoded.
var rawSource, versionId string
i := strings.LastIndex(copySourceHeader, "?versionId=")
if i == -1 {
copySource = copySourceHeader
rawSource = copySourceHeader
} else {
copySource = copySourceHeader[:i]
rawSource = copySourceHeader[:i]
versionId = copySourceHeader[i+11:]
}
srcBucket, srcObject, ok := strings.Cut(copySource, "/")
if !ok {
return "", "", "", s3err.GetAPIError(s3err.ErrInvalidCopySourceBucket)
// URL-decode the entire source path first so that clients that send the
// bucket/key separator as "%2F" (e.g. AWS .NET SDK v4) are handled
// correctly before we split on a literal '/'.
decoded, err := url.QueryUnescape(rawSource)
if err != nil {
return "", "", "", s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding)
}
var err error
// URL-decode the bucket and object names to handle special characters
srcBucket, err = url.QueryUnescape(srcBucket)
if err != nil {
return "", "", "", s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding)
}
srcObject, err = url.QueryUnescape(srcObject)
if err != nil {
return "", "", "", s3err.GetAPIError(s3err.ErrInvalidCopySourceEncoding)
srcBucket, srcObject, ok := strings.Cut(decoded, "/")
if !ok {
return "", "", "", s3err.GetAPIError(s3err.ErrInvalidCopySourceBucket)
}
return srcBucket, srcObject, versionId, nil
+32
View File
@@ -87,6 +87,38 @@ func TestParseCopySource(t *testing.T) {
wantVersionId: "xyz789",
wantErr: false,
},
{
name: "percent-encoded slash as bucket/key separator",
copySourceHeader: "my-namespace-test-container%2Ftest-blob",
wantBucket: "my-namespace-test-container",
wantObject: "test-blob",
wantVersionId: "",
wantErr: false,
},
{
name: "percent-encoded slash separator with leading slash",
copySourceHeader: "/my-namespace-test-container%2Ftest-blob",
wantBucket: "my-namespace-test-container",
wantObject: "test-blob",
wantVersionId: "",
wantErr: false,
},
{
name: "percent-encoded slash separator with versionId",
copySourceHeader: "my-bucket%2Fmy-object?versionId=abc123",
wantBucket: "my-bucket",
wantObject: "my-object",
wantVersionId: "abc123",
wantErr: false,
},
{
name: "percent-encoded slash separator with encoded object key",
copySourceHeader: "my-bucket%2Fmy%20folder%2Fmy%20object",
wantBucket: "my-bucket",
wantObject: "my folder/my object",
wantVersionId: "",
wantErr: false,
},
{
name: "invalid URL encoding - incomplete escape",
copySourceHeader: "mybucket/object%",