diff --git a/.changes/next-release/service.s3-bugfix-1615508971377612000.json b/.changes/next-release/service.s3-bugfix-1615508971377612000.json new file mode 100644 index 00000000000..8dcee98c53b --- /dev/null +++ b/.changes/next-release/service.s3-bugfix-1615508971377612000.json @@ -0,0 +1,9 @@ +{ + "ID": "service.s3-bugfix-1615508971377612000", + "SchemaVersion": 1, + "Module": "service/s3", + "Type": "bugfix", + "Description": "Fixed key encoding when addressing S3 Access Points", + "MinVersion": "", + "AffectedModules": null +} \ No newline at end of file diff --git a/service/s3/internal/customizations/update_endpoint.go b/service/s3/internal/customizations/update_endpoint.go index bb0569224c7..1e10c19ec82 100644 --- a/service/s3/internal/customizations/update_endpoint.go +++ b/service/s3/internal/customizations/update_endpoint.go @@ -3,6 +3,7 @@ package customizations import ( "context" "fmt" + "github.com/aws/smithy-go/encoding/httpbinding" "log" "net/url" "strings" @@ -229,14 +230,18 @@ func moveBucketNameToHost(u *url.URL, bucket string) { // remove bucket from url func removeBucketFromPath(u *url.URL, bucket string) { - // modify url path - u.Path = strings.Replace(u.Path, "/"+bucket, "", -1) + if strings.HasPrefix(u.Path, "/"+bucket) { + // modify url path + u.Path = strings.Replace(u.Path, "/"+bucket, "", 1) + + // modify url raw path + u.RawPath = strings.Replace(u.RawPath, "/"+httpbinding.EscapePath(bucket, true), "", 1) + } + if u.Path == "" { u.Path = "/" } - // modify url raw path - u.RawPath = strings.Replace(u.RawPath, "/"+bucket, "", -1) if u.RawPath == "" { u.RawPath = "/" } diff --git a/service/s3/internal/customizations/update_endpoint_internal_test.go b/service/s3/internal/customizations/update_endpoint_internal_test.go new file mode 100644 index 00000000000..3bba49c3510 --- /dev/null +++ b/service/s3/internal/customizations/update_endpoint_internal_test.go @@ -0,0 +1,76 @@ +package customizations + +import ( + "net/url" + "strconv" + "testing" +) + +func TestRemoveBucketFromPath(t *testing.T) { + cases := []struct { + url url.URL + bucket string + expected string + }{ + { + url: url.URL{ + Scheme: "https", + Host: "amazonaws.com", + Path: "/bucket-name/key/path", + RawPath: "/bucket-name/key/path", + }, + bucket: "bucket-name", + expected: "https://amazonaws.com/key/path", + }, + { + url: url.URL{ + Scheme: "https", + Host: "amazonaws.com", + Path: "/bucket-name/key/path/with/bucket-name", + RawPath: "/bucket-name/key/path/with/bucket-name", + }, + bucket: "bucket-name", + expected: "https://amazonaws.com/key/path/with/bucket-name", + }, + { + url: url.URL{ + Scheme: "https", + Host: "amazonaws.com", + Path: "/arn:aws:s3:us-east-1:012345678901:accesspoint:myap/key/path?isEscaped=true", + RawPath: "/arn%3Aaws%3As3%3Aus-east-1%3A012345678901%3Aaccesspoint%3Amyap/key/path%3FisEscaped%3Dtrue", + }, + bucket: "arn:aws:s3:us-east-1:012345678901:accesspoint:myap", + expected: "https://amazonaws.com/key/path%3FisEscaped%3Dtrue", + }, + { + url: url.URL{ + Scheme: "https", + Host: "amazonaws.com", + Path: "/path/to/key", + RawPath: "/path/to/key", + }, + bucket: "not-a-match", + expected: "https://amazonaws.com/path/to/key", + }, + { + url: url.URL{ + Scheme: "https", + Host: "amazonaws.com", + Path: "", + RawPath: "", + }, + bucket: "not-a-match", + expected: "https://amazonaws.com/", + }, + } + + for i, tt := range cases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + removeBucketFromPath(&tt.url, tt.bucket) + + if e, a := tt.expected, tt.url.String(); e != a { + t.Errorf("expect %v, got %v", e, a) + } + }) + } +}