summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-bucket-cors.go136
-rw-r--r--vendor/github.com/minio/minio-go/v7/api.go2
-rw-r--r--vendor/github.com/minio/minio-go/v7/core.go3
-rw-r--r--vendor/github.com/minio/minio-go/v7/functional_tests.go1102
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/cors/cors.go91
-rw-r--r--vendor/github.com/minio/minio-go/v7/s3-error.go1
-rw-r--r--vendor/modules.txt3
9 files changed, 1324 insertions, 20 deletions
diff --git a/go.mod b/go.mod
index 8bd82e9c9..95cb5f38e 100644
--- a/go.mod
+++ b/go.mod
@@ -43,7 +43,7 @@ require (
github.com/jackc/pgx/v5 v5.6.0
github.com/microcosm-cc/bluemonday v1.0.27
github.com/miekg/dns v1.1.61
- github.com/minio/minio-go/v7 v7.0.74
+ github.com/minio/minio-go/v7 v7.0.75
github.com/mitchellh/mapstructure v1.5.0
github.com/ncruces/go-sqlite3 v0.18.0
github.com/oklog/ulid v1.3.1
diff --git a/go.sum b/go.sum
index 9089c3cab..1e405a1fc 100644
--- a/go.sum
+++ b/go.sum
@@ -421,8 +421,8 @@ github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
-github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0=
-github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
+github.com/minio/minio-go/v7 v7.0.75 h1:0uLrB6u6teY2Jt+cJUVi9cTvDRuBKWSRzSAcznRkwlE=
+github.com/minio/minio-go/v7 v7.0.75/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
diff --git a/vendor/github.com/minio/minio-go/v7/api-bucket-cors.go b/vendor/github.com/minio/minio-go/v7/api-bucket-cors.go
new file mode 100644
index 000000000..8bf537f73
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-bucket-cors.go
@@ -0,0 +1,136 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2024 MinIO, Inc.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/cors"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// SetBucketCors sets the cors configuration for the bucket
+func (c *Client) SetBucketCors(ctx context.Context, bucketName string, corsConfig *cors.Config) error {
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ if corsConfig == nil {
+ return c.removeBucketCors(ctx, bucketName)
+ }
+
+ return c.putBucketCors(ctx, bucketName, corsConfig)
+}
+
+func (c *Client) putBucketCors(ctx context.Context, bucketName string, corsConfig *cors.Config) error {
+ urlValues := make(url.Values)
+ urlValues.Set("cors", "")
+
+ corsStr, err := corsConfig.ToXML()
+ if err != nil {
+ return err
+ }
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(corsStr),
+ contentLength: int64(len(corsStr)),
+ contentMD5Base64: sumMD5Base64([]byte(corsStr)),
+ }
+
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ return nil
+}
+
+func (c *Client) removeBucketCors(ctx context.Context, bucketName string) error {
+ urlValues := make(url.Values)
+ urlValues.Set("cors", "")
+
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+
+ return nil
+}
+
+// GetBucketCors returns the current cors
+func (c *Client) GetBucketCors(ctx context.Context, bucketName string) (*cors.Config, error) {
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, err
+ }
+ bucketCors, err := c.getBucketCors(ctx, bucketName)
+ if err != nil {
+ errResponse := ToErrorResponse(err)
+ if errResponse.Code == "NoSuchCORSConfiguration" {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return bucketCors, nil
+}
+
+func (c *Client) getBucketCors(ctx context.Context, bucketName string) (*cors.Config, error) {
+ urlValues := make(url.Values)
+ urlValues.Set("cors", "")
+
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex, // TODO: needed? copied over from other example, but not spec'd in API.
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+
+ corsConfig, err := cors.ParseBucketCorsConfig(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ return corsConfig, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api.go b/vendor/github.com/minio/minio-go/v7/api.go
index 937551403..13c493d0f 100644
--- a/vendor/github.com/minio/minio-go/v7/api.go
+++ b/vendor/github.com/minio/minio-go/v7/api.go
@@ -129,7 +129,7 @@ type Options struct {
// Global constants.
const (
libraryName = "minio-go"
- libraryVersion = "v7.0.74"
+ libraryVersion = "v7.0.75"
)
// User Agent should always following the below style.
diff --git a/vendor/github.com/minio/minio-go/v7/core.go b/vendor/github.com/minio/minio-go/v7/core.go
index 132ea702f..99b99db9b 100644
--- a/vendor/github.com/minio/minio-go/v7/core.go
+++ b/vendor/github.com/minio/minio-go/v7/core.go
@@ -91,6 +91,7 @@ type PutObjectPartOptions struct {
Md5Base64, Sha256Hex string
SSE encrypt.ServerSide
CustomHeader, Trailer http.Header
+ DisableContentSha256 bool
}
// PutObjectPart - Upload an object part.
@@ -107,7 +108,7 @@ func (c Core) PutObjectPart(ctx context.Context, bucket, object, uploadID string
sha256Hex: opts.Sha256Hex,
size: size,
sse: opts.SSE,
- streamSha256: true,
+ streamSha256: !opts.DisableContentSha256,
customHeader: opts.CustomHeader,
trailer: opts.Trailer,
}
diff --git a/vendor/github.com/minio/minio-go/v7/functional_tests.go b/vendor/github.com/minio/minio-go/v7/functional_tests.go
index e77bf9d4a..871034bc7 100644
--- a/vendor/github.com/minio/minio-go/v7/functional_tests.go
+++ b/vendor/github.com/minio/minio-go/v7/functional_tests.go
@@ -52,6 +52,7 @@ import (
"github.com/google/uuid"
"github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/cors"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/encrypt"
"github.com/minio/minio-go/v7/pkg/notification"
@@ -2972,7 +2973,6 @@ func testGetObjectAttributes() {
ContentType: v.ContentType,
SendContentMd5: v.SendContentMd5,
})
-
if err != nil {
logError(testName, function, args, startTime, "", "PutObject failed", err)
return
@@ -7212,7 +7212,6 @@ func testFunctional() {
"bucketName": bucketName,
}
exists, err = c.BucketExists(context.Background(), bucketName)
-
if err != nil {
logError(testName, function, args, startTime, "", "BucketExists failed", err)
return
@@ -7275,7 +7274,6 @@ func testFunctional() {
"bucketPolicy": writeOnlyPolicy,
}
err = c.SetBucketPolicy(context.Background(), bucketName, writeOnlyPolicy)
-
if err != nil {
logError(testName, function, args, startTime, "", "SetBucketPolicy failed", err)
return
@@ -7304,7 +7302,6 @@ func testFunctional() {
"bucketPolicy": readWritePolicy,
}
err = c.SetBucketPolicy(context.Background(), bucketName, readWritePolicy)
-
if err != nil {
logError(testName, function, args, startTime, "", "SetBucketPolicy failed", err)
return
@@ -7481,7 +7478,6 @@ func testFunctional() {
"fileName": fileName + "-f",
}
err = c.FGetObject(context.Background(), bucketName, objectName, fileName+"-f", minio.GetObjectOptions{})
-
if err != nil {
logError(testName, function, args, startTime, "", "FGetObject failed", err)
return
@@ -7613,7 +7609,6 @@ func testFunctional() {
"reqParams": reqParams,
}
presignedGetURL, err = c.PresignedGetObject(context.Background(), bucketName, objectName, 3600*time.Second, reqParams)
-
if err != nil {
logError(testName, function, args, startTime, "", "PresignedGetObject failed", err)
return
@@ -7770,14 +7765,12 @@ func testFunctional() {
"objectName": objectName,
}
err = c.RemoveObject(context.Background(), bucketName, objectName, minio.RemoveObjectOptions{})
-
if err != nil {
logError(testName, function, args, startTime, "", "RemoveObject failed", err)
return
}
args["objectName"] = objectName + "-f"
err = c.RemoveObject(context.Background(), bucketName, objectName+"-f", minio.RemoveObjectOptions{})
-
if err != nil {
logError(testName, function, args, startTime, "", "RemoveObject failed", err)
return
@@ -7785,7 +7778,6 @@ func testFunctional() {
args["objectName"] = objectName + "-nolength"
err = c.RemoveObject(context.Background(), bucketName, objectName+"-nolength", minio.RemoveObjectOptions{})
-
if err != nil {
logError(testName, function, args, startTime, "", "RemoveObject failed", err)
return
@@ -7793,7 +7785,6 @@ func testFunctional() {
args["objectName"] = objectName + "-presigned"
err = c.RemoveObject(context.Background(), bucketName, objectName+"-presigned", minio.RemoveObjectOptions{})
-
if err != nil {
logError(testName, function, args, startTime, "", "RemoveObject failed", err)
return
@@ -7801,7 +7792,6 @@ func testFunctional() {
args["objectName"] = objectName + "-presign-custom"
err = c.RemoveObject(context.Background(), bucketName, objectName+"-presign-custom", minio.RemoveObjectOptions{})
-
if err != nil {
logError(testName, function, args, startTime, "", "RemoveObject failed", err)
return
@@ -7813,7 +7803,6 @@ func testFunctional() {
"bucketName": bucketName,
}
err = c.RemoveBucket(context.Background(), bucketName)
-
if err != nil {
logError(testName, function, args, startTime, "", "RemoveBucket failed", err)
return
@@ -12281,7 +12270,6 @@ func testFunctionalV2() {
"bucketPolicy": readWritePolicy,
}
err = c.SetBucketPolicy(context.Background(), bucketName, readWritePolicy)
-
if err != nil {
logError(testName, function, args, startTime, "", "SetBucketPolicy failed", err)
return
@@ -13012,7 +13000,6 @@ func testGetObjectACLContext() {
ContentType: "binary/octet-stream",
UserMetadata: metaData,
})
-
if err != nil {
logError(testName, function, args, startTime, "", "PutObject failed", err)
return
@@ -13491,6 +13478,849 @@ func testListObjects() {
logSuccess(testName, function, args, startTime)
}
+// testCors is runnable against S3 itself.
+// Just provide the env var MINIO_GO_TEST_BUCKET_CORS with bucket that is public and WILL BE DELETED.
+// Recreate this manually each time. Minio-go SDK does not support calling
+// SetPublicBucket (put-public-access-block) on S3, otherwise we could script the whole thing.
+func testCors() {
+ ctx := context.Background()
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "SetBucketCors(bucketName, cors)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "cors": "",
+ }
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Transport: createHTTPTransport(),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", appVersion)
+
+ // Create or reuse a bucket that will get cors settings applied to it and deleted when done
+ bucketName := os.Getenv("MINIO_GO_TEST_BUCKET_CORS")
+ if bucketName == "" {
+ bucketName = randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ err = c.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ }
+ args["bucketName"] = bucketName
+ defer cleanupBucket(bucketName, c)
+
+ publicPolicy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:*"],"Resource":["arn:aws:s3:::` + bucketName + `", "arn:aws:s3:::` + bucketName + `/*"]}]}`
+ err = c.SetBucketPolicy(ctx, bucketName, publicPolicy)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "SetBucketPolicy failed", err)
+ return
+ }
+
+ // Upload an object for testing.
+ objectContents := `some-text-file-contents`
+ reader := strings.NewReader(objectContents)
+ bufSize := int64(len(objectContents))
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(ctx, bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+ bucketURL := c.EndpointURL().String() + "/" + bucketName + "/"
+ objectURL := bucketURL + objectName
+
+ transport, err := minio.DefaultTransport(mustParseBool(os.Getenv(enableHTTPS)))
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "DefaultTransport failed", err)
+ return
+ }
+ httpClient := &http.Client{
+ Timeout: 30 * time.Second,
+ Transport: transport,
+ }
+
+ errStrAccessForbidden := `<Error><Code>AccessForbidden</Code><Message>CORSResponse: This CORS request is not allowed. This is usually because the evalution of Origin, request method / Access-Control-Request-Method or Access-Control-Request-Headers are not whitelisted`
+ testCases := []struct {
+ name string
+
+ // Cors rules to apply
+ applyCorsRules []cors.Rule
+
+ // Outbound request info
+ method string
+ url string
+ headers map[string]string
+
+ // Wanted response
+ wantStatus int
+ wantHeaders map[string]string
+ wantBodyContains string
+ }{
+ {
+ name: "apply bucket rules",
+ applyCorsRules: []cors.Rule{
+ {
+ AllowedOrigin: []string{"https"}, // S3 documents 'https' origin, but it does not actually work, see test below.
+ AllowedMethod: []string{"PUT"},
+ AllowedHeader: []string{"*"},
+ },
+ {
+ AllowedOrigin: []string{"http://www.example1.com"},
+ AllowedMethod: []string{"PUT"},
+ AllowedHeader: []string{"*"},
+ ExposeHeader: []string{"x-amz-server-side-encryption", "x-amz-request-id"},
+ MaxAgeSeconds: 3600,
+ },
+ {
+ AllowedOrigin: []string{"http://www.example2.com"},
+ AllowedMethod: []string{"POST"},
+ AllowedHeader: []string{"X-My-Special-Header"},
+ ExposeHeader: []string{"X-AMZ-Request-ID"},
+ },
+ {
+ AllowedOrigin: []string{"http://www.example3.com"},
+ AllowedMethod: []string{"PUT"},
+ AllowedHeader: []string{"X-Example-3-Special-Header"},
+ MaxAgeSeconds: 10,
+ },
+ {
+ AllowedOrigin: []string{"*"},
+ AllowedMethod: []string{"GET"},
+ AllowedHeader: []string{"*"},
+ ExposeHeader: []string{"x-amz-request-id", "X-AMZ-server-side-encryption"},
+ MaxAgeSeconds: 3600,
+ },
+ {
+ AllowedOrigin: []string{"http://multiplemethodstest.com"},
+ AllowedMethod: []string{"POST", "PUT", "DELETE"},
+ AllowedHeader: []string{"x-abc-*", "x-def-*"},
+ },
+ {
+ AllowedOrigin: []string{"http://UPPERCASEEXAMPLE.com"},
+ AllowedMethod: []string{"DELETE"},
+ },
+ {
+ AllowedOrigin: []string{"https://*"},
+ AllowedMethod: []string{"DELETE"},
+ AllowedHeader: []string{"x-abc-*", "x-def-*"},
+ },
+ },
+ },
+ {
+ name: "preflight to object url matches example1 rule",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "Access-Control-Request-Method": "PUT",
+ "Access-Control-Request-Headers": "x-another-header,x-could-be-anything",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Origin": "http://www.example1.com",
+ "Access-Control-Allow-Methods": "PUT",
+ "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything",
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Max-Age": "3600",
+ "Content-Length": "0",
+ // S3 additionally sets the following headers here, MinIO follows fetch spec and does not:
+ // "Access-Control-Expose-Headers": "",
+ },
+ },
+ {
+ name: "preflight to bucket url matches example1 rule",
+ method: http.MethodOptions,
+ url: bucketURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "Access-Control-Request-Method": "PUT",
+ "Access-Control-Request-Headers": "x-another-header,x-could-be-anything",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Origin": "http://www.example1.com",
+ "Access-Control-Allow-Methods": "PUT",
+ "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything",
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Max-Age": "3600",
+ "Content-Length": "0",
+ },
+ },
+ {
+ name: "preflight matches example2 rule with header given",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example2.com",
+ "Access-Control-Request-Method": "POST",
+ "Access-Control-Request-Headers": "X-My-Special-Header",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Origin": "http://www.example2.com",
+ "Access-Control-Allow-Methods": "POST",
+ "Access-Control-Allow-Headers": "x-my-special-header",
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Max-Age": "",
+ "Content-Length": "0",
+ },
+ },
+ {
+ name: "preflight matches example2 rule with no header given",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example2.com",
+ "Access-Control-Request-Method": "POST",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Origin": "http://www.example2.com",
+ "Access-Control-Allow-Methods": "POST",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Max-Age": "",
+ "Content-Length": "0",
+ },
+ },
+ {
+ name: "preflight matches wildcard origin rule",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.couldbeanything.com",
+ "Access-Control-Request-Method": "GET",
+ "Access-Control-Request-Headers": "x-custom-header,x-other-custom-header",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET",
+ "Access-Control-Allow-Headers": "x-custom-header,x-other-custom-header",
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Max-Age": "3600",
+ "Content-Length": "0",
+ },
+ },
+ {
+ name: "preflight does not match any rule",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.couldbeanything.com",
+ "Access-Control-Request-Method": "DELETE",
+ },
+ wantStatus: http.StatusForbidden,
+ wantBodyContains: errStrAccessForbidden,
+ },
+ {
+ name: "preflight does not match example1 rule because of method",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "Access-Control-Request-Method": "POST",
+ },
+ wantStatus: http.StatusForbidden,
+ wantBodyContains: errStrAccessForbidden,
+ },
+ {
+ name: "s3 processes cors rules even when request is not preflight if cors headers present test get",
+ method: http.MethodGet,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "Access-Control-Request-Headers": "x-another-header,x-could-be-anything",
+ "Access-Control-Request-Method": "PUT",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Allow-Origin": "http://www.example1.com",
+ "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id",
+ // S3 additionally sets the following headers here, MinIO follows fetch spec and does not:
+ // "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything",
+ // "Access-Control-Allow-Methods": "PUT",
+ // "Access-Control-Max-Age": "3600",
+ },
+ },
+ {
+ name: "s3 processes cors rules even when request is not preflight if cors headers present test put",
+ method: http.MethodPut,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "Access-Control-Request-Method": "GET",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Expose-Headers": "x-amz-request-id,x-amz-server-side-encryption",
+ // S3 additionally sets the following headers here, MinIO follows fetch spec and does not:
+ // "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything",
+ // "Access-Control-Allow-Methods": "PUT",
+ // "Access-Control-Max-Age": "3600",
+ },
+ },
+ {
+ name: "s3 processes cors rules even when request is not preflight but there is no rule match",
+ method: http.MethodGet,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "Access-Control-Request-Headers": "x-another-header,x-could-be-anything",
+ "Access-Control-Request-Method": "DELETE",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Methods": "",
+ "Access-Control-Allow-Origin": "",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "get request matches wildcard origin rule and returns cors headers",
+ method: http.MethodGet,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "x-amz-request-id,X-AMZ-server-side-encryption",
+ // S3 returns the following headers, MinIO follows fetch spec and does not:
+ // "Access-Control-Max-Age": "3600",
+ // "Access-Control-Allow-Methods": "GET",
+ },
+ },
+ {
+ name: "head request does not match rule and returns no cors headers",
+ method: http.MethodHead,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.nomatchingdomainfound.com",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Allow-Methods": "",
+ "Access-Control-Allow-Origin": "",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "put request with origin does not match rule and returns no cors headers",
+ method: http.MethodPut,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.nomatchingdomainfound.com",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Allow-Methods": "",
+ "Access-Control-Allow-Origin": "",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "put request with no origin does not match rule and returns no cors headers",
+ method: http.MethodPut,
+ url: objectURL,
+ headers: map[string]string{},
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Allow-Methods": "",
+ "Access-Control-Allow-Origin": "",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "preflight for delete request with wildcard origin does not match",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.notsecureexample.com",
+ "Access-Control-Request-Method": "DELETE",
+ },
+ wantStatus: http.StatusForbidden,
+ wantBodyContains: errStrAccessForbidden,
+ },
+ {
+ name: "preflight for delete request with wildcard https origin matches secureexample",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "https://www.secureexample.com",
+ "Access-Control-Request-Method": "DELETE",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Allow-Methods": "DELETE",
+ "Access-Control-Allow-Origin": "https://www.secureexample.com",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "preflight for delete request matches secureexample with wildcard https origin and request headers",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "https://www.secureexample.com",
+ "Access-Control-Request-Method": "DELETE",
+ "Access-Control-Request-Headers": "x-abc-1,x-abc-second,x-def-1",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Allow-Methods": "DELETE",
+ "Access-Control-Allow-Origin": "https://www.secureexample.com",
+ "Access-Control-Allow-Headers": "x-abc-1,x-abc-second,x-def-1",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "preflight for delete request matches secureexample rejected because request header does not match",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "https://www.secureexample.com",
+ "Access-Control-Request-Method": "DELETE",
+ "Access-Control-Request-Headers": "x-abc-1,x-abc-second,x-def-1,x-does-not-match",
+ },
+ wantStatus: http.StatusForbidden,
+ wantBodyContains: errStrAccessForbidden,
+ },
+ {
+ name: "preflight with https origin is documented by s3 as matching but it does not match",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "https://www.securebutdoesnotmatch.com",
+ "Access-Control-Request-Method": "PUT",
+ },
+ wantStatus: http.StatusForbidden,
+ wantBodyContains: errStrAccessForbidden,
+ },
+ {
+ name: "put no origin no match returns no cors headers",
+ method: http.MethodPut,
+ url: objectURL,
+ headers: map[string]string{},
+ wantStatus: http.StatusOK,
+
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Allow-Methods": "",
+ "Access-Control-Allow-Origin": "",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "put with origin match example1 returns cors headers",
+ method: http.MethodPut,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ },
+ wantStatus: http.StatusOK,
+
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Allow-Origin": "http://www.example1.com",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id",
+ // S3 returns the following headers, MinIO follows fetch spec and does not:
+ // "Access-Control-Max-Age": "3600",
+ // "Access-Control-Allow-Methods": "PUT",
+ },
+ },
+ {
+ name: "put with origin and header match example1 returns cors headers",
+ method: http.MethodPut,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "x-could-be-anything": "myvalue",
+ },
+ wantStatus: http.StatusOK,
+
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Allow-Origin": "http://www.example1.com",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id",
+ // S3 returns the following headers, MinIO follows fetch spec and does not:
+ // "Access-Control-Max-Age": "3600",
+ // "Access-Control-Allow-Methods": "PUT",
+ },
+ },
+ {
+ name: "put no match found returns no cors headers",
+ method: http.MethodPut,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.unmatchingdomain.com",
+ },
+ wantStatus: http.StatusOK,
+
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "",
+ "Access-Control-Allow-Methods": "",
+ "Access-Control-Allow-Origin": "",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "put with origin match example3 returns cors headers",
+ method: http.MethodPut,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example3.com",
+ "X-My-Special-Header": "myvalue",
+ },
+ wantStatus: http.StatusOK,
+
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Allow-Origin": "http://www.example3.com",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "",
+ // S3 returns the following headers, MinIO follows fetch spec and does not:
+ // "Access-Control-Max-Age": "10",
+ // "Access-Control-Allow-Methods": "PUT",
+ },
+ },
+ {
+ name: "preflight matches example1 rule headers case is incorrect",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "Access-Control-Request-Method": "PUT",
+ // Fetch standard guarantees that these are sent lowercase, here we test what happens when they are not.
+ "Access-Control-Request-Headers": "X-Another-Header,X-Could-Be-Anything",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Origin": "http://www.example1.com",
+ "Access-Control-Allow-Methods": "PUT",
+ "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything",
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Max-Age": "3600",
+ "Content-Length": "0",
+ // S3 returns the following headers, MinIO follows fetch spec and does not:
+ // "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id",
+ },
+ },
+ {
+ name: "preflight matches example1 rule headers are not sorted",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.example1.com",
+ "Access-Control-Request-Method": "PUT",
+ // Fetch standard guarantees that these are sorted, test what happens when they are not.
+ "Access-Control-Request-Headers": "a-customer-header,b-should-be-last",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Origin": "http://www.example1.com",
+ "Access-Control-Allow-Methods": "PUT",
+ "Access-Control-Allow-Headers": "a-customer-header,b-should-be-last",
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Max-Age": "3600",
+ "Content-Length": "0",
+ // S3 returns the following headers, MinIO follows fetch spec and does not:
+ // "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id",
+ },
+ },
+ {
+ name: "preflight with case sensitivity in origin matches uppercase",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://UPPERCASEEXAMPLE.com",
+ "Access-Control-Request-Method": "DELETE",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Allow-Methods": "DELETE",
+ "Access-Control-Allow-Origin": "http://UPPERCASEEXAMPLE.com",
+ "Access-Control-Allow-Headers": "",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ },
+ },
+ {
+ name: "preflight with case sensitivity in origin does not match when lowercase",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://uppercaseexample.com",
+ "Access-Control-Request-Method": "DELETE",
+ },
+ wantStatus: http.StatusForbidden,
+ wantBodyContains: errStrAccessForbidden,
+ },
+ {
+ name: "preflight match upper case with unknown header but no header restrictions",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://UPPERCASEEXAMPLE.com",
+ "Access-Control-Request-Method": "DELETE",
+ "Access-Control-Request-Headers": "x-unknown-1",
+ },
+ wantStatus: http.StatusForbidden,
+ wantBodyContains: errStrAccessForbidden,
+ },
+ {
+ name: "preflight for delete request matches multiplemethodstest.com origin and request headers",
+ method: http.MethodOptions,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://multiplemethodstest.com",
+ "Access-Control-Request-Method": "DELETE",
+ "Access-Control-Request-Headers": "x-abc-1",
+ },
+ wantStatus: http.StatusOK,
+ wantHeaders: map[string]string{
+ "Access-Control-Allow-Credentials": "true",
+ "Access-Control-Allow-Origin": "http://multiplemethodstest.com",
+ "Access-Control-Allow-Headers": "x-abc-1",
+ "Access-Control-Expose-Headers": "",
+ "Access-Control-Max-Age": "",
+ // S3 returns POST, PUT, DELETE here, MinIO does not as spec does not require it.
+ // "Access-Control-Allow-Methods": "DELETE",
+ },
+ },
+ {
+ name: "delete request goes ahead because cors is only for browsers and does not block on the server side",
+ method: http.MethodDelete,
+ url: objectURL,
+ headers: map[string]string{
+ "Origin": "http://www.justrandom.com",
+ },
+ wantStatus: http.StatusNoContent,
+ },
+ }
+
+ for i, test := range testCases {
+ testName := fmt.Sprintf("%s_%d_%s", testName, i+1, strings.ReplaceAll(test.name, " ", "_"))
+
+ // Apply the CORS rules
+ if test.applyCorsRules != nil {
+ corsConfig := &cors.Config{
+ CORSRules: test.applyCorsRules,
+ }
+ err = c.SetBucketCors(ctx, bucketName, corsConfig)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "SetBucketCors failed to apply", err)
+ return
+ }
+ }
+
+ // Make request
+ if test.method != "" && test.url != "" {
+ req, err := http.NewRequestWithContext(ctx, test.method, test.url, nil)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "HTTP request creation failed", err)
+ return
+ }
+ req.Header.Set("User-Agent", "MinIO-go-FunctionalTest/"+appVersion)
+
+ for k, v := range test.headers {
+ req.Header.Set(k, v)
+ }
+ resp, err := httpClient.Do(req)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "HTTP request failed", err)
+ return
+ }
+ defer resp.Body.Close()
+
+ // Check returned status code
+ if resp.StatusCode != test.wantStatus {
+ errStr := fmt.Sprintf(" incorrect status code in response, want: %d, got: %d", test.wantStatus, resp.StatusCode)
+ logFailure(testName, function, args, startTime, "", errStr, nil)
+ return
+ }
+
+ // Check returned body
+ if test.wantBodyContains != "" {
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "Failed to read response body", err)
+ return
+ }
+ if !strings.Contains(string(body), test.wantBodyContains) {
+ errStr := fmt.Sprintf(" incorrect body in response, want: %s, in got: %s", test.wantBodyContains, string(body))
+ logFailure(testName, function, args, startTime, "", errStr, nil)
+ return
+ }
+ }
+
+ // Check returned response headers
+ for k, v := range test.wantHeaders {
+ gotVal := resp.Header.Get(k)
+ if k == "Access-Control-Expose-Headers" {
+ // MinIO returns this in canonical form, S3 does not.
+ gotVal = strings.ToLower(gotVal)
+ v = strings.ToLower(v)
+ }
+ // Remove all spaces, S3 adds spaces after CSV values in headers, MinIO does not.
+ gotVal = strings.ReplaceAll(gotVal, " ", "")
+ if gotVal != v {
+ errStr := fmt.Sprintf(" incorrect header in response, want: %s: '%s', got: '%s'", k, v, gotVal)
+ logFailure(testName, function, args, startTime, "", errStr, nil)
+ return
+ }
+ }
+ }
+ logSuccess(testName, function, args, startTime)
+ }
+ logSuccess(testName, function, args, startTime)
+}
+
+func testCorsSetGetDelete() {
+ ctx := context.Background()
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "SetBucketCors(bucketName, cors)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "cors": "",
+ }
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Transport: createHTTPTransport(),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", appVersion)
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, c)
+
+ // Set the CORS rules on the new bucket
+ corsRules := []cors.Rule{
+ {
+ AllowedOrigin: []string{"http://www.example1.com"},
+ AllowedMethod: []string{"PUT"},
+ AllowedHeader: []string{"*"},
+ },
+ {
+ AllowedOrigin: []string{"http://www.example2.com"},
+ AllowedMethod: []string{"POST"},
+ AllowedHeader: []string{"X-My-Special-Header"},
+ },
+ {
+ AllowedOrigin: []string{"*"},
+ AllowedMethod: []string{"GET"},
+ AllowedHeader: []string{"*"},
+ },
+ }
+ corsConfig := cors.NewConfig(corsRules)
+ err = c.SetBucketCors(ctx, bucketName, corsConfig)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "SetBucketCors failed to apply", err)
+ return
+ }
+
+ // Get the rules and check they match what we set
+ gotCorsConfig, err := c.GetBucketCors(ctx, bucketName)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "GetBucketCors failed", err)
+ return
+ }
+ if !reflect.DeepEqual(corsConfig, gotCorsConfig) {
+ msg := fmt.Sprintf("GetBucketCors returned unexpected rules, expected: %+v, got: %+v", corsConfig, gotCorsConfig)
+ logFailure(testName, function, args, startTime, "", msg, nil)
+ return
+ }
+
+ // Delete the rules
+ err = c.SetBucketCors(ctx, bucketName, nil)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "SetBucketCors failed to delete", err)
+ return
+ }
+
+ // Get the rules and check they are now empty
+ gotCorsConfig, err = c.GetBucketCors(ctx, bucketName)
+ if err != nil {
+ logFailure(testName, function, args, startTime, "", "GetBucketCors failed", err)
+ return
+ }
+ if gotCorsConfig != nil {
+ logFailure(testName, function, args, startTime, "", "GetBucketCors returned unexpected rules", nil)
+ return
+ }
+
+ logSuccess(testName, function, args, startTime)
+}
+
// Test deleting multiple objects with object retention set in Governance mode
func testRemoveObjects() {
// initialize logging params
@@ -13627,6 +14457,245 @@ func testRemoveObjects() {
logSuccess(testName, function, args, startTime)
}
+// Test get bucket tags
+func testGetBucketTagging() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetBucketTagging(bucketName)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Transport: createHTTPTransport(),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", appVersion)
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ _, err = c.GetBucketTagging(context.Background(), bucketName)
+ if minio.ToErrorResponse(err).Code != "NoSuchTagSet" {
+ logError(testName, function, args, startTime, "", "Invalid error from server failed", err)
+ return
+ }
+
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ logSuccess(testName, function, args, startTime)
+}
+
+// Test setting tags for bucket
+func testSetBucketTagging() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "SetBucketTagging(bucketName, tags)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "tags": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Transport: createHTTPTransport(),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", appVersion)
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ _, err = c.GetBucketTagging(context.Background(), bucketName)
+ if minio.ToErrorResponse(err).Code != "NoSuchTagSet" {
+ logError(testName, function, args, startTime, "", "Invalid error from server", err)
+ return
+ }
+
+ tag := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ expectedValue := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+
+ t, err := tags.MapToBucketTags(map[string]string{
+ tag: expectedValue,
+ })
+ args["tags"] = t.String()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "tags.MapToBucketTags failed", err)
+ return
+ }
+
+ err = c.SetBucketTagging(context.Background(), bucketName, t)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "SetBucketTagging failed", err)
+ return
+ }
+
+ tagging, err := c.GetBucketTagging(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetBucketTagging failed", err)
+ return
+ }
+
+ if tagging.ToMap()[tag] != expectedValue {
+ msg := fmt.Sprintf("Tag %s; got value %s; wanted %s", tag, tagging.ToMap()[tag], expectedValue)
+ logError(testName, function, args, startTime, "", msg, err)
+ return
+ }
+
+ // Delete all objects and buckets
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ logSuccess(testName, function, args, startTime)
+}
+
+// Test removing bucket tags
+func testRemoveBucketTagging() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "RemoveBucketTagging(bucketName)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Transport: createHTTPTransport(),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", appVersion)
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ _, err = c.GetBucketTagging(context.Background(), bucketName)
+ if minio.ToErrorResponse(err).Code != "NoSuchTagSet" {
+ logError(testName, function, args, startTime, "", "Invalid error from server", err)
+ return
+ }
+
+ tag := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ expectedValue := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+
+ t, err := tags.MapToBucketTags(map[string]string{
+ tag: expectedValue,
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "tags.MapToBucketTags failed", err)
+ return
+ }
+
+ err = c.SetBucketTagging(context.Background(), bucketName, t)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "SetBucketTagging failed", err)
+ return
+ }
+
+ tagging, err := c.GetBucketTagging(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetBucketTagging failed", err)
+ return
+ }
+
+ if tagging.ToMap()[tag] != expectedValue {
+ msg := fmt.Sprintf("Tag %s; got value %s; wanted %s", tag, tagging.ToMap()[tag], expectedValue)
+ logError(testName, function, args, startTime, "", msg, err)
+ return
+ }
+
+ err = c.RemoveBucketTagging(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "RemoveBucketTagging failed", err)
+ return
+ }
+
+ _, err = c.GetBucketTagging(context.Background(), bucketName)
+ if minio.ToErrorResponse(err).Code != "NoSuchTagSet" {
+ logError(testName, function, args, startTime, "", "Invalid error from server", err)
+ return
+ }
+
+ // Delete all objects and buckets
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ logSuccess(testName, function, args, startTime)
+}
+
// Convert string to bool and always return false if any error
func mustParseBool(str string) bool {
b, err := strconv.ParseBool(str)
@@ -13660,6 +14729,8 @@ func main() {
// execute tests
if isFullMode() {
+ testCorsSetGetDelete()
+ testCors()
testListMultipartUpload()
testGetObjectAttributes()
testGetObjectAttributesErrorCases()
@@ -13731,6 +14802,9 @@ func main() {
testObjectTaggingWithVersioning()
testTrailingChecksums()
testPutObjectWithAutomaticChecksums()
+ testGetBucketTagging()
+ testSetBucketTagging()
+ testRemoveBucketTagging()
// SSE-C tests will only work over TLS connection.
if tls {
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/cors/cors.go b/vendor/github.com/minio/minio-go/v7/pkg/cors/cors.go
new file mode 100644
index 000000000..e71864ee9
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/cors/cors.go
@@ -0,0 +1,91 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2024 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cors
+
+import (
+ "encoding/xml"
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/dustin/go-humanize"
+)
+
+const defaultXMLNS = "http://s3.amazonaws.com/doc/2006-03-01/"
+
+// 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"`
+}
+
+// NewConfig creates a new CORS configuration with the given rules.
+func NewConfig(rules []Rule) *Config {
+ return &Config{
+ XMLNS: defaultXMLNS,
+ XMLName: xml.Name{
+ Local: "CORSConfiguration",
+ Space: defaultXMLNS,
+ },
+ CORSRules: rules,
+ }
+}
+
+// ParseBucketCorsConfig parses a CORS configuration in XML from an io.Reader.
+func ParseBucketCorsConfig(reader io.Reader) (*Config, error) {
+ var c Config
+
+ // Max size of cors document is 64KiB according to https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketCors.html
+ // This limiter is just for safety so has a max of 128KiB
+ err := xml.NewDecoder(io.LimitReader(reader, 128*humanize.KiByte)).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/vendor/github.com/minio/minio-go/v7/s3-error.go b/vendor/github.com/minio/minio-go/v7/s3-error.go
index f365157ee..f7fad19f6 100644
--- a/vendor/github.com/minio/minio-go/v7/s3-error.go
+++ b/vendor/github.com/minio/minio-go/v7/s3-error.go
@@ -57,5 +57,6 @@ var s3ErrorResponseMap = map[string]string{
"BucketAlreadyOwnedByYou": "Your previous request to create the named bucket succeeded and you already own it.",
"InvalidDuration": "Duration provided in the request is invalid.",
"XAmzContentSHA256Mismatch": "The provided 'x-amz-content-sha256' header does not match what was computed.",
+ "NoSuchCORSConfiguration": "The specified bucket does not have a CORS configuration.",
// Add new API errors here.
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 1c475f83c..fbb31c404 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -495,9 +495,10 @@ github.com/miekg/dns
# github.com/minio/md5-simd v1.1.2
## explicit; go 1.14
github.com/minio/md5-simd
-# github.com/minio/minio-go/v7 v7.0.74
+# github.com/minio/minio-go/v7 v7.0.75
## explicit; go 1.21
github.com/minio/minio-go/v7
+github.com/minio/minio-go/v7/pkg/cors
github.com/minio/minio-go/v7/pkg/credentials
github.com/minio/minio-go/v7/pkg/encrypt
github.com/minio/minio-go/v7/pkg/lifecycle