diff --git a/cors/cors.go b/cors/cors.go
new file mode 100644
index 0000000..f2f45b9
--- /dev/null
+++ b/cors/cors.go
@@ -0,0 +1,179 @@
+// Copyright (c) 2015-2024 MinIO, Inc.
+//
+// # This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cors
+
+import (
+ "encoding/xml"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+
+ "github.com/minio/pkg/v3/wildcard"
+)
+
+const defaultXMLNS = "http://s3.amazonaws.com/doc/2006-03-01/"
+
+var allowedCORSRuleMethods = map[string]bool{
+ http.MethodGet: true,
+ http.MethodPut: true,
+ http.MethodPost: true,
+ http.MethodDelete: true,
+ http.MethodHead: true,
+}
+
+// Config is the container for a CORS configuration for a bucket.
+type Config struct {
+ XMLNS string `xml:"xmlns,attr,omitempty"`
+ XMLName xml.Name `xml:"CORSConfiguration"`
+ CORSRules []Rule `xml:"CORSRule"`
+}
+
+// Rule is a single rule in a CORS configuration.
+type Rule struct {
+ AllowedHeader []string `xml:"AllowedHeader,omitempty"`
+ AllowedMethod []string `xml:"AllowedMethod,omitempty"`
+ AllowedOrigin []string `xml:"AllowedOrigin,omitempty"`
+ ExposeHeader []string `xml:"ExposeHeader,omitempty"`
+ ID string `xml:"ID,omitempty"`
+ MaxAgeSeconds int `xml:"MaxAgeSeconds,omitempty"`
+}
+
+// Validate checks the CORS configuration is valid. This has been implemented to return errors that can be transformed
+// to match the S3 API externally, while being slightly more informative internally using wrapping.
+// Validate copies S3 behavior, and validates one rule at a time, erroring on the first invalid one found.
+func (c *Config) Validate() error {
+ if len(c.CORSRules) == 0 {
+ return fmt.Errorf("no CORS rules found, %w", ErrMalformedXML{})
+ }
+ if len(c.CORSRules) > 100 {
+ return fmt.Errorf("too many CORS rules, max 100 allowed, got: %d, %w", len(c.CORSRules), ErrTooManyRules{})
+ }
+ for _, rule := range c.CORSRules {
+ // Origin validation
+ if len(rule.AllowedOrigin) == 0 {
+ return fmt.Errorf("no AllowedOrigin found in CORS rule, id: %s, %w", rule.ID, ErrMalformedXML{})
+ }
+ for _, origin := range rule.AllowedOrigin {
+ if strings.Count(origin, "*") > 1 {
+ return fmt.Errorf("origin %s in CORS rule, id: %s, %w", origin, rule.ID, ErrAllowedOriginWildcards{Origin: origin})
+ }
+ }
+
+ // Methods validation
+ if len(rule.AllowedMethod) == 0 {
+ return fmt.Errorf("no AllowedMethod found in CORS rule, id: %s, %w", rule.ID, ErrMalformedXML{})
+ }
+ for _, method := range rule.AllowedMethod {
+ if !allowedCORSRuleMethods[method] {
+ return fmt.Errorf("method %s in CORS rule, id: %s, %w", method, rule.ID, ErrInvalidMethod{Method: method})
+ }
+ }
+
+ // Headers validation
+ for _, header := range rule.AllowedHeader {
+ if strings.Count(header, "*") > 1 {
+ return fmt.Errorf("header %s in CORS rule, id: %s, %w", header, rule.ID, ErrAllowedHeaderWildcards{Header: header})
+ }
+ }
+ }
+
+ return nil
+}
+
+// HasAllowedOrigin returns true if the given origin is allowed by the CORS rule
+func (c *Rule) HasAllowedOrigin(origin string) bool {
+ // See "AllowedOrigin element" in https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html
+ for _, allowedOrigin := range c.AllowedOrigin {
+ if wildcard.Match(allowedOrigin, origin) {
+ // Only one wildcard character (*) is allowed by S3 spec, but Match does
+ // not enforce that, it's done by Validate() function.
+ // Origins are case sensitive
+ return true
+ }
+ }
+ return false
+}
+
+// HasAllowedMethod returns true if the given method is contained in the CORS rule.
+func (c *Rule) HasAllowedMethod(method string) bool {
+ // See "AllowedMethod element" in https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html
+ for _, allowedMethod := range c.AllowedMethod {
+ if allowedMethod == method {
+ // Methods are always uppercase, enforced by Validate() function.
+ return true
+ }
+ }
+ return false
+}
+
+// FilterAllowedHeaders returns the headers that are allowed by the rule, and a boolean indicating if all headers are allowed.
+func (c *Rule) FilterAllowedHeaders(headers []string) ([]string, bool) {
+ // See "AllowedHeader element" in https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html
+ // It's inefficient to store the CORS config verbatim and run ToLower here, but S3 essentially
+ // behaves this way, and will return the XML config verbatim when you GET it.
+ filtered := []string{}
+ for _, header := range headers {
+ header = strings.ToLower(header)
+ found := false
+ for _, allowedHeader := range c.AllowedHeader {
+ // Case insensitive comparison for headers
+ if wildcard.Match(strings.ToLower(allowedHeader), header) {
+ // Only one wildcard character (*) is allowed by S3 spec, but Match does
+ // not enforce that, it's done by rule.Validate() function.
+ filtered = append(filtered, header)
+ found = true
+ break
+ }
+ }
+ if !found {
+ return nil, false
+ }
+ }
+ return filtered, true
+}
+
+// ParseBucketCorsConfig parses a CORS configuration in XML from an io.Reader.
+func ParseBucketCorsConfig(reader io.Reader) (*Config, error) {
+ var c Config
+ err := xml.NewDecoder(reader).Decode(&c)
+ if err != nil {
+ return nil, fmt.Errorf("decoding xml: %w", err)
+ }
+ if c.XMLNS == "" {
+ c.XMLNS = defaultXMLNS
+ }
+ for i, rule := range c.CORSRules {
+ for j, method := range rule.AllowedMethod {
+ c.CORSRules[i].AllowedMethod[j] = strings.ToUpper(method)
+ }
+ }
+ return &c, nil
+}
+
+// ToXML marshals the CORS configuration to XML.
+func (c Config) ToXML() ([]byte, error) {
+ if c.XMLNS == "" {
+ c.XMLNS = defaultXMLNS
+ }
+ data, err := xml.Marshal(&c)
+ if err != nil {
+ return nil, fmt.Errorf("marshaling xml: %w", err)
+ }
+ return append([]byte(xml.Header), data...), nil
+}
diff --git a/cors/cors_test.go b/cors/cors_test.go
new file mode 100644
index 0000000..b4757c8
--- /dev/null
+++ b/cors/cors_test.go
@@ -0,0 +1,290 @@
+// Copyright (c) 2015-2024 MinIO, Inc.
+//
+// # This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cors
+
+import (
+ "bytes"
+ "encoding/xml"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+var defaultXMLName = xml.Name{Space: "http://s3.amazonaws.com/doc/2006-03-01/", Local: "CORSConfiguration"}
+
+func TestCORSFilterHeaders(t *testing.T) {
+ tests := []struct {
+ name string
+ rule Rule
+ headers []string
+
+ wantOk bool
+ wantHeaders []string
+ }{
+ {
+ name: "plain single header",
+ rule: Rule{AllowedHeader: []string{"x-custom-header"}},
+ headers: []string{"x-custom-header"},
+ wantOk: true,
+ wantHeaders: []string{"x-custom-header"},
+ },
+ {
+ name: "single header case insensitive",
+ rule: Rule{AllowedHeader: []string{"x-CUSTOM-header"}},
+ headers: []string{"x-custom-HEADER"},
+ wantOk: true,
+ wantHeaders: []string{"x-custom-header"},
+ },
+ {
+ name: "plain multiple headers in order",
+ rule: Rule{AllowedHeader: []string{"x-custom-header-1", "x-custom-header-2"}},
+ headers: []string{"x-custom-header-1", "x-custom-header-2"},
+ wantOk: true,
+ wantHeaders: []string{"x-custom-header-1", "x-custom-header-2"},
+ },
+ {
+ name: "plain multiple headers out of order",
+ rule: Rule{AllowedHeader: []string{"x-custom-header-2", "x-custom-header-1"}},
+ headers: []string{"x-custom-header-1", "x-custom-header-2"},
+ wantOk: true,
+ wantHeaders: []string{"x-custom-header-1", "x-custom-header-2"},
+ },
+ {
+ name: "plain multiple headers with unknown header",
+ rule: Rule{AllowedHeader: []string{"x-custom-header-1", "x-custom-header-2"}},
+ headers: []string{"x-custom-header-1", "x-custom-header-2", "x-custom-header-3"},
+ wantOk: false,
+ wantHeaders: nil,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ config := &Config{
+ CORSRules: []Rule{test.rule},
+ }
+ for _, rule := range config.CORSRules {
+ headers, ok := rule.FilterAllowedHeaders(test.headers)
+ if ok != test.wantOk {
+ t.Errorf("got: %v, want: %v", ok, test.wantOk)
+ }
+ if !reflect.DeepEqual(headers, test.wantHeaders) {
+ t.Errorf("got: %v, want: %v", headers, test.wantHeaders)
+ }
+ }
+ })
+ }
+}
+
+func TestCORSInvalid(t *testing.T) {
+ tests := []struct {
+ name string
+ config *Config
+ wantErrContains string
+ }{
+ {
+ name: "no CORS rules",
+ config: &Config{
+ CORSRules: []Rule{},
+ },
+ wantErrContains: "no CORS rules found",
+ },
+ {
+ name: "too many CORS rules",
+ config: &Config{
+ CORSRules: make([]Rule, 101),
+ },
+ wantErrContains: "too many CORS rules",
+ },
+ {
+ name: "no AllowedOrigin",
+ config: &Config{
+ CORSRules: []Rule{
+ {
+ ID: "1",
+ AllowedOrigin: []string{},
+ AllowedMethod: []string{"GET"},
+ },
+ },
+ },
+ wantErrContains: "no AllowedOrigin found in CORS rule, id: 1",
+ },
+ {
+ name: "invalid origin multiple wildcards",
+ config: &Config{
+ CORSRules: []Rule{
+ {
+ AllowedOrigin: []string{"https", "http://*.example.*"},
+ AllowedMethod: []string{"GET"},
+ },
+ },
+ },
+ wantErrContains: "can not have more than one wildcard",
+ },
+ {
+ name: "no AllowedMethod",
+ config: &Config{
+ CORSRules: []Rule{
+ {
+ AllowedOrigin: []string{"*"},
+ AllowedMethod: []string{},
+ },
+ },
+ },
+ wantErrContains: "no AllowedMethod found in CORS rule",
+ },
+ {
+ name: "invalid method",
+ config: &Config{
+ CORSRules: []Rule{
+ {
+ AllowedOrigin: []string{"*"},
+ AllowedMethod: []string{"GET", "POST", "PATCH"},
+ },
+ },
+ },
+ wantErrContains: "Unsupported method is PATCH",
+ },
+ {
+ name: "invalid method lowercase",
+ config: &Config{
+ CORSRules: []Rule{
+ {
+ AllowedOrigin: []string{"*"},
+ AllowedMethod: []string{"get"},
+ },
+ },
+ },
+ wantErrContains: "Unsupported method is get",
+ },
+ {
+ name: "invalid header multiple wildcards",
+ config: &Config{
+ CORSRules: []Rule{
+ {
+ AllowedOrigin: []string{"*"},
+ AllowedMethod: []string{"GET"},
+ AllowedHeader: []string{"X-*-Header-*"},
+ },
+ },
+ },
+ wantErrContains: "not have more than one wildcard",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.config.Validate()
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if !strings.Contains(err.Error(), test.wantErrContains) {
+ t.Errorf("got: %v, want contains: %v", err, test.wantErrContains)
+ }
+ })
+ }
+}
+
+func TestCORSXMLValid(t *testing.T) {
+ tests := []struct {
+ name string
+ filename string
+ wantCORSConfig *Config
+ }{
+ {
+ name: "example1 cors config",
+ filename: "example1.xml",
+ wantCORSConfig: &Config{
+ XMLName: defaultXMLName,
+ XMLNS: defaultXMLNS,
+ CORSRules: []Rule{
+ {
+ AllowedOrigin: []string{"http://www.example1.com"},
+ AllowedMethod: []string{"PUT", "POST", "DELETE"},
+ AllowedHeader: []string{"*"},
+ },
+ {
+ AllowedOrigin: []string{"http://www.example2.com"},
+ AllowedMethod: []string{"PUT", "POST", "DELETE"},
+ AllowedHeader: []string{"*"},
+ },
+ {
+ AllowedOrigin: []string{"*"},
+ AllowedMethod: []string{"GET"},
+ },
+ },
+ },
+ },
+ {
+ name: "example2 cors config",
+ filename: "example2.xml",
+ wantCORSConfig: &Config{
+ XMLName: defaultXMLName,
+ XMLNS: defaultXMLNS,
+ CORSRules: []Rule{
+ {
+ AllowedOrigin: []string{"http://www.example.com"},
+ AllowedMethod: []string{"PUT", "POST", "DELETE"},
+ AllowedHeader: []string{"*"},
+ MaxAgeSeconds: 3000,
+ ExposeHeader: []string{"x-amz-server-side-encryption", "x-amz-request-id", "x-amz-id-2"},
+ },
+ },
+ },
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ fileContents, err := os.ReadFile("testdata/" + test.filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ c, err := ParseBucketCorsConfig(bytes.NewReader(fileContents))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(c, test.wantCORSConfig) {
+ t.Errorf("got: %v, want: %v", c, test.wantCORSConfig)
+ }
+ err = c.Validate()
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ })
+ }
+}
+
+func TestCORSXMLMarshal(t *testing.T) {
+ fileContents, err := os.ReadFile("testdata/example3.xml")
+ if err != nil {
+ t.Fatal(err)
+ }
+ c, err := ParseBucketCorsConfig(bytes.NewReader(fileContents))
+ if err != nil {
+ t.Fatal(err)
+ }
+ remarshalled, err := c.ToXML()
+ if err != nil {
+ t.Fatal(err)
+ }
+ trimmedFileContents := bytes.TrimSpace(fileContents)
+ if !bytes.Equal(trimmedFileContents, remarshalled) {
+ t.Errorf("got: %s, want: %s", string(remarshalled), string(trimmedFileContents))
+ }
+}
diff --git a/cors/errors.go b/cors/errors.go
new file mode 100644
index 0000000..45c7044
--- /dev/null
+++ b/cors/errors.go
@@ -0,0 +1,65 @@
+// Copyright (c) 2015-2024 MinIO, Inc.
+//
+// # This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cors
+
+import "fmt"
+
+// ErrTooManyRules is returned when the number of CORS rules exceeds the allowed limit.
+type ErrTooManyRules struct{}
+
+func (e ErrTooManyRules) Error() string {
+ return "The number of CORS rules should not exceed allowed limit of 100 rules."
+}
+
+// ErrMalformedXML is returned when the XML provided is not well-formed
+type ErrMalformedXML struct{}
+
+func (e ErrMalformedXML) Error() string {
+ return "The XML you provided was not well-formed or did not validate against our published schema"
+}
+
+// ErrAllowedOriginWildcards is returned when more than one wildcard is found in an AllowedOrigin.
+type ErrAllowedOriginWildcards struct {
+ Origin string
+}
+
+func (e ErrAllowedOriginWildcards) Error() string {
+ // S3 quotes the origin, e.g. "http://*.*.example.com", in the error message, but these quotes are currently
+ // escaped by Go xml encoder. We could fix this with a `,innerxml` tag on the struct, but that has
+ // other implications. Easier to not add quotes in our error for now, revisit if this is an issue.
+ return fmt.Sprintf(`AllowedOrigin %s can not have more than one wildcard.`, e.Origin)
+}
+
+// ErrInvalidMethod is returned when an unsupported HTTP method is found in a CORS config.
+type ErrInvalidMethod struct {
+ Method string
+}
+
+func (e ErrInvalidMethod) Error() string {
+ return fmt.Sprintf("Found unsupported HTTP method in CORS config. Unsupported method is %s", e.Method)
+}
+
+// ErrAllowedHeaderWildcards is returned when more than one wildcard is found in an AllowedHeader.
+type ErrAllowedHeaderWildcards struct {
+ Header string
+}
+
+func (e ErrAllowedHeaderWildcards) Error() string {
+ // S3 quotes the header, e.g. "*-amz-*", in the error message, similar situation to ErrAllowedOriginWildcards above.
+ return fmt.Sprintf(`AllowedHeader %s can not have more than one wildcard.`, e.Header)
+}
diff --git a/cors/testdata/example1.xml b/cors/testdata/example1.xml
new file mode 100644
index 0000000..aa78aee
--- /dev/null
+++ b/cors/testdata/example1.xml
@@ -0,0 +1,21 @@
+
+
+
+ http://www.example1.com
+ PUT
+ POST
+ DELETE
+ *
+
+
+ http://www.example2.com
+ PUT
+ POST
+ DELETE
+ *
+
+
+ *
+ GET
+
+
diff --git a/cors/testdata/example2.xml b/cors/testdata/example2.xml
new file mode 100644
index 0000000..f7b8aca
--- /dev/null
+++ b/cors/testdata/example2.xml
@@ -0,0 +1,14 @@
+
+
+
+ http://www.example.com
+ PUT
+ POST
+ DELETE
+ *
+ 3000
+ x-amz-server-side-encryption
+ x-amz-request-id
+ x-amz-id-2
+
+
diff --git a/cors/testdata/example3.xml b/cors/testdata/example3.xml
new file mode 100644
index 0000000..fb2f33a
--- /dev/null
+++ b/cors/testdata/example3.xml
@@ -0,0 +1,2 @@
+
+*PUTPOSTDELETEhttp://www.example1.com*PUTPOSTDELETEhttp://www.example2.*GET*x-amz-id-26000POSThttps://www.example3.com
diff --git a/go.mod b/go.mod
index ada24d1..fd678b2 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,6 @@ module github.com/minio/pkg/v3
go 1.21
require (
- github.com/cespare/xxhash/v2 v2.2.0
github.com/cheggaaa/pb v1.0.29
github.com/fatih/color v1.16.0
github.com/fatih/structs v1.1.0
@@ -16,7 +15,6 @@ require (
github.com/minio/mux v1.8.2
github.com/montanaflynn/stats v0.7.1
github.com/rjeczalik/notify v0.9.3
- github.com/secure-io/sio-go v0.3.1
github.com/tinylib/msgp v1.2.0
go.etcd.io/etcd/client/v3 v3.5.13
golang.org/x/crypto v0.24.0
@@ -58,6 +56,7 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
+ github.com/secure-io/sio-go v0.3.1 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
diff --git a/go.sum b/go.sum
index 58711fc..f552c39 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,6 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
-github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
-github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=
github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
diff --git a/policy/action.go b/policy/action.go
index 40d885c..5f236f2 100644
--- a/policy/action.go
+++ b/policy/action.go
@@ -44,6 +44,9 @@ const (
// DeleteBucketPolicyAction - DeleteBucketPolicy Rest API action.
DeleteBucketPolicyAction = "s3:DeleteBucketPolicy"
+ // DeleteBucketCorsAction - DeleteBucketCors Rest API action.
+ DeleteBucketCorsAction = "s3:DeleteBucketCors"
+
// DeleteObjectAction - DeleteObject Rest API action.
DeleteObjectAction = "s3:DeleteObject"
@@ -56,6 +59,9 @@ const (
// GetBucketPolicyAction - GetBucketPolicy Rest API action.
GetBucketPolicyAction = "s3:GetBucketPolicy"
+ // GetBucketCorsAction - GetBucketCors Rest API action.
+ GetBucketCorsAction = "s3:GetBucketCors"
+
// GetObjectAction - GetObject Rest API action.
GetObjectAction = "s3:GetObject"
@@ -103,6 +109,9 @@ const (
// PutBucketPolicyAction - PutBucketPolicy Rest API action.
PutBucketPolicyAction = "s3:PutBucketPolicy"
+ // PutBucketCorsAction - PutBucketCors Rest API action.
+ PutBucketCorsAction = "s3:PutBucketCors"
+
// PutObjectAction - PutObject Rest API action.
PutObjectAction = "s3:PutObject"