diff --git a/cmd/bucket-metadata-sys.go b/cmd/bucket-metadata-sys.go index 20be4ffd3..65aed178e 100644 --- a/cmd/bucket-metadata-sys.go +++ b/cmd/bucket-metadata-sys.go @@ -29,6 +29,7 @@ import ( "github.com/minio/madmin-go/v3" "github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/tags" + "github.com/minio/minio/internal/bucket/cors" bucketsse "github.com/minio/minio/internal/bucket/encryption" "github.com/minio/minio/internal/bucket/lifecycle" objectlock "github.com/minio/minio/internal/bucket/object/lock" @@ -134,6 +135,9 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string, case bucketTaggingConfig: meta.TaggingConfigXML = configData meta.TaggingConfigUpdatedAt = updatedAt + case bucketCorsConfig: + meta.CorsConfigXML = configData + meta.CorsConfigUpdatedAt = updatedAt case bucketQuotaConfigFile: meta.QuotaConfigJSON = configData meta.QuotaConfigUpdatedAt = updatedAt @@ -359,6 +363,19 @@ func (sys *BucketMetadataSys) GetSSEConfig(bucket string) (*bucketsse.BucketSSEC return meta.sseConfig, meta.EncryptionConfigUpdatedAt, nil } +// GetCorsConfig returns the CORS configuration for the given bucket. +// The returned object must not be modified. +func (sys *BucketMetadataSys) GetCorsConfig(bucket string) (*cors.Config, time.Time, error) { + meta, _, err := sys.GetConfig(GlobalContext, bucket) + if err != nil { + return nil, time.Time{}, err + } + if meta.corsConfig == nil { + return nil, time.Time{}, errConfigNotFound + } + return meta.corsConfig, meta.CorsConfigUpdatedAt, nil +} + // CreatedAt returns the time of creation of bucket func (sys *BucketMetadataSys) CreatedAt(bucket string) (time.Time, error) { meta, _, err := sys.GetConfig(GlobalContext, bucket) diff --git a/cmd/bucket-metadata.go b/cmd/bucket-metadata.go index e78118175..556510b61 100644 --- a/cmd/bucket-metadata.go +++ b/cmd/bucket-metadata.go @@ -31,6 +31,7 @@ import ( "github.com/minio/madmin-go/v3" "github.com/minio/minio-go/v7/pkg/tags" + "github.com/minio/minio/internal/bucket/cors" bucketsse "github.com/minio/minio/internal/bucket/encryption" "github.com/minio/minio/internal/bucket/lifecycle" objectlock "github.com/minio/minio/internal/bucket/object/lock" @@ -58,6 +59,9 @@ var ( enabledBucketVersioningConfig = []byte(`Enabled`) ) +// Bucket CORS configuration file. +const bucketCorsConfig = "cors.xml" + //go:generate msgp -file $GOFILE // BucketMetadata contains bucket metadata. @@ -80,6 +84,7 @@ type BucketMetadata struct { ReplicationConfigXML []byte BucketTargetsConfigJSON []byte BucketTargetsConfigMetaJSON []byte + CorsConfigXML []byte PolicyConfigUpdatedAt time.Time ObjectLockConfigUpdatedAt time.Time @@ -92,6 +97,7 @@ type BucketMetadata struct { NotificationConfigUpdatedAt time.Time BucketTargetsConfigUpdatedAt time.Time BucketTargetsConfigMetaUpdatedAt time.Time + CorsConfigUpdatedAt time.Time // Add a new UpdatedAt field and update lastUpdate function // Unexported fields. Must be updated atomically. @@ -106,6 +112,7 @@ type BucketMetadata struct { replicationConfig *replication.Config bucketTargetConfig *madmin.BucketTargets bucketTargetConfigMeta map[string]string + corsConfig *cors.Config } // newBucketMetadata creates BucketMetadata with the supplied name and Created to Now. @@ -160,6 +167,9 @@ func (b BucketMetadata) lastUpdate() (t time.Time) { if b.BucketTargetsConfigMetaUpdatedAt.After(t) { t = b.BucketTargetsConfigMetaUpdatedAt } + if b.CorsConfigUpdatedAt.After(t) { + t = b.CorsConfigUpdatedAt + } return t } @@ -310,6 +320,15 @@ func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLa b.taggingConfig = nil } + if len(b.CorsConfigXML) != 0 { + b.corsConfig, err = cors.ParseBucketCorsConfig(bytes.NewReader(b.CorsConfigXML)) + if err != nil { + return err + } + } else { + b.corsConfig = nil + } + if bytes.Equal(b.ObjectLockConfigXML, enabledBucketObjectLockConfig) { b.VersioningConfigXML = enabledBucketVersioningConfig } diff --git a/cmd/bucket-metadata_gen.go b/cmd/bucket-metadata_gen.go index 0407b66ea..b074b3e13 100644 --- a/cmd/bucket-metadata_gen.go +++ b/cmd/bucket-metadata_gen.go @@ -108,6 +108,12 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) { err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON") return } + case "CorsConfigXML": + z.CorsConfigXML, err = dc.ReadBytes(z.CorsConfigXML) + if err != nil { + err = msgp.WrapError(err, "CorsConfigXML") + return + } case "PolicyConfigUpdatedAt": z.PolicyConfigUpdatedAt, err = dc.ReadTime() if err != nil { @@ -174,6 +180,12 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) { err = msgp.WrapError(err, "BucketTargetsConfigMetaUpdatedAt") return } + case "CorsConfigUpdatedAt": + z.CorsConfigUpdatedAt, err = dc.ReadTime() + if err != nil { + err = msgp.WrapError(err, "CorsConfigUpdatedAt") + return + } default: err = dc.Skip() if err != nil { @@ -187,9 +199,9 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) { // EncodeMsg implements msgp.Encodable func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) { - // map header, size 25 + // map header, size 27 // write "Name" - err = en.Append(0xde, 0x0, 0x19, 0xa4, 0x4e, 0x61, 0x6d, 0x65) + err = en.Append(0xde, 0x0, 0x1b, 0xa4, 0x4e, 0x61, 0x6d, 0x65) if err != nil { return } @@ -328,6 +340,16 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) { err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON") return } + // write "CorsConfigXML" + err = en.Append(0xad, 0x43, 0x6f, 0x72, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x58, 0x4d, 0x4c) + if err != nil { + return + } + err = en.WriteBytes(z.CorsConfigXML) + if err != nil { + err = msgp.WrapError(err, "CorsConfigXML") + return + } // write "PolicyConfigUpdatedAt" err = en.Append(0xb5, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74) if err != nil { @@ -438,15 +460,25 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) { err = msgp.WrapError(err, "BucketTargetsConfigMetaUpdatedAt") return } + // write "CorsConfigUpdatedAt" + err = en.Append(0xb3, 0x43, 0x6f, 0x72, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74) + if err != nil { + return + } + err = en.WriteTime(z.CorsConfigUpdatedAt) + if err != nil { + err = msgp.WrapError(err, "CorsConfigUpdatedAt") + return + } return } // MarshalMsg implements msgp.Marshaler func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) - // map header, size 25 + // map header, size 27 // string "Name" - o = append(o, 0xde, 0x0, 0x19, 0xa4, 0x4e, 0x61, 0x6d, 0x65) + o = append(o, 0xde, 0x0, 0x1b, 0xa4, 0x4e, 0x61, 0x6d, 0x65) o = msgp.AppendString(o, z.Name) // string "Created" o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64) @@ -487,6 +519,9 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) { // string "BucketTargetsConfigMetaJSON" o = append(o, 0xbb, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x4a, 0x53, 0x4f, 0x4e) o = msgp.AppendBytes(o, z.BucketTargetsConfigMetaJSON) + // string "CorsConfigXML" + o = append(o, 0xad, 0x43, 0x6f, 0x72, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x58, 0x4d, 0x4c) + o = msgp.AppendBytes(o, z.CorsConfigXML) // string "PolicyConfigUpdatedAt" o = append(o, 0xb5, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74) o = msgp.AppendTime(o, z.PolicyConfigUpdatedAt) @@ -520,6 +555,9 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) { // string "BucketTargetsConfigMetaUpdatedAt" o = append(o, 0xd9, 0x20, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74) o = msgp.AppendTime(o, z.BucketTargetsConfigMetaUpdatedAt) + // string "CorsConfigUpdatedAt" + o = append(o, 0xb3, 0x43, 0x6f, 0x72, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74) + o = msgp.AppendTime(o, z.CorsConfigUpdatedAt) return } @@ -625,6 +663,12 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "BucketTargetsConfigMetaJSON") return } + case "CorsConfigXML": + z.CorsConfigXML, bts, err = msgp.ReadBytesBytes(bts, z.CorsConfigXML) + if err != nil { + err = msgp.WrapError(err, "CorsConfigXML") + return + } case "PolicyConfigUpdatedAt": z.PolicyConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts) if err != nil { @@ -691,6 +735,12 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "BucketTargetsConfigMetaUpdatedAt") return } + case "CorsConfigUpdatedAt": + z.CorsConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts) + if err != nil { + err = msgp.WrapError(err, "CorsConfigUpdatedAt") + return + } default: bts, err = msgp.Skip(bts) if err != nil { @@ -705,6 +755,6 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *BucketMetadata) Msgsize() (s int) { - s = 3 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON) + 22 + msgp.TimeSize + 26 + msgp.TimeSize + 26 + msgp.TimeSize + 23 + msgp.TimeSize + 21 + msgp.TimeSize + 27 + msgp.TimeSize + 26 + msgp.TimeSize + 25 + msgp.TimeSize + 28 + msgp.TimeSize + 29 + msgp.TimeSize + 34 + msgp.TimeSize + s = 3 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON) + 14 + msgp.BytesPrefixSize + len(z.CorsConfigXML) + 22 + msgp.TimeSize + 26 + msgp.TimeSize + 26 + msgp.TimeSize + 23 + msgp.TimeSize + 21 + msgp.TimeSize + 27 + msgp.TimeSize + 26 + msgp.TimeSize + 25 + msgp.TimeSize + 28 + msgp.TimeSize + 29 + msgp.TimeSize + 34 + msgp.TimeSize + 20 + msgp.TimeSize return } diff --git a/cmd/bucket-metadata_test.go b/cmd/bucket-metadata_test.go new file mode 100644 index 000000000..70447bcb2 --- /dev/null +++ b/cmd/bucket-metadata_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015-2026 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 cmd + +import "testing" + +func TestBucketMetadataCorsRoundTrip(t *testing.T) { + meta := newBucketMetadata("test-cors") + meta.CorsConfigXML = []byte(`*GET`) + meta.CorsConfigUpdatedAt = UTCNow() + + buf, err := meta.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + var got BucketMetadata + if _, err := got.UnmarshalMsg(buf); err != nil { + t.Fatal(err) + } + if string(got.CorsConfigXML) != string(meta.CorsConfigXML) { + t.Fatalf("CorsConfigXML not preserved: %q", string(got.CorsConfigXML)) + } + if !got.CorsConfigUpdatedAt.Equal(meta.CorsConfigUpdatedAt) { + t.Fatalf("CorsConfigUpdatedAt not preserved") + } +}