diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9efa417..d72a7c641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ This document outlines major changes between releases. ## [Unreleased] +### Fixed +- Number of bucket tags increased to 50 (#613) + ## [0.32.13] - 2025-03-10 ### Fixed diff --git a/api/handler/copy.go b/api/handler/copy.go index 7b2f2c59f..4d6e8afec 100644 --- a/api/handler/copy.go +++ b/api/handler/copy.go @@ -153,7 +153,7 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { } if args.TaggingDirective == replaceDirective { - tagSet, err = parseTaggingHeader(r.Header) + tagSet, err = parseObjectTaggingHeader(r.Header) if err != nil { h.logAndSendError(ctx, w, "could not parse tagging header", reqInfo, err) return diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go index 727d49fa2..97fdcaf91 100644 --- a/api/handler/multipart_upload.go +++ b/api/handler/multipart_upload.go @@ -134,7 +134,7 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re } if len(r.Header.Get(api.AmzTagging)) > 0 { - p.Data.TagSet, err = parseTaggingHeader(r.Header) + p.Data.TagSet, err = parseObjectTaggingHeader(r.Header) if err != nil { h.logAndSendError(ctx, w, "could not parse tagging", reqInfo, err, additional...) return diff --git a/api/handler/put.go b/api/handler/put.go index 4d50b2175..b4cf9af46 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -222,7 +222,7 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { return } - tagSet, err := parseTaggingHeader(r.Header) + tagSet, err := parseObjectTaggingHeader(r.Header) if err != nil { h.logAndSendError(ctx, w, "could not parse tagging header", reqInfo, err) return @@ -511,7 +511,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) { fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error())) return } - tagSet, err = h.readTagSet(tags) + tagSet, err = readObjectTagSet(tags) if err != nil { h.logAndSendError(ctx, w, "could not read tag set", reqInfo, err) return @@ -742,14 +742,14 @@ func aclHeadersStatus(r *http.Request) aclStatus { return aclStatusNo } -func parseTaggingHeader(header http.Header) (map[string]string, error) { +func parseObjectTaggingHeader(header http.Header) (map[string]string, error) { var tagSet map[string]string if tagging := header.Get(api.AmzTagging); len(tagging) > 0 { queries, err := url.ParseQuery(tagging) if err != nil { return nil, apierr.GetAPIError(apierr.ErrInvalidArgument) } - if len(queries) > maxTags { + if len(queries) > maxObjectTags { return nil, apierr.GetAPIError(apierr.ErrInvalidTagsSizeExceed) } tagSet = make(map[string]string, len(queries)) diff --git a/api/handler/tagging.go b/api/handler/tagging.go index 76008d560..2f55fccb6 100644 --- a/api/handler/tagging.go +++ b/api/handler/tagging.go @@ -16,7 +16,8 @@ import ( const ( allowedTagChars = "+-=._:/@" - maxTags = 10 + maxObjectTags = 10 + maxBucketTags = 50 keyTagMaxLength = 128 valueTagMaxLength = 256 ) @@ -27,7 +28,7 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request reqInfo := middleware.GetReqInfo(ctx) - tagSet, err := h.readTagSet(reqInfo.Tagging) + tagSet, err := readObjectTagSet(reqInfo.Tagging) if err != nil { h.logAndSendError(ctx, w, "could not read tag set", reqInfo, err) return @@ -128,7 +129,7 @@ func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request reqInfo := middleware.GetReqInfo(ctx) - tagSet, err := h.readTagSet(reqInfo.Tagging) + tagSet, err := readBucketTagSet(reqInfo.Tagging) if err != nil { h.logAndSendError(ctx, w, "could not read tag set", reqInfo, err) return @@ -194,8 +195,16 @@ func (h *handler) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusNoContent) } -func (h *handler) readTagSet(tagging *data.Tagging) (map[string]string, error) { - if err := checkTagSet(tagging.TagSet); err != nil { +func readObjectTagSet(tagging *data.Tagging) (map[string]string, error) { + return readTagSetBase(tagging, maxObjectTags) +} + +func readBucketTagSet(tagging *data.Tagging) (map[string]string, error) { + return readTagSetBase(tagging, maxBucketTags) +} + +func readTagSetBase(tagging *data.Tagging, maxTags int) (map[string]string, error) { + if err := checkTagSet(tagging.TagSet, maxTags); err != nil { return nil, err } @@ -222,7 +231,7 @@ func encodeTagging(tagSet map[string]string) *data.Tagging { return tagging } -func checkTagSet(tagSet []data.Tag) error { +func checkTagSet(tagSet []data.Tag, maxTags int) error { if len(tagSet) > maxTags { return errors.GetAPIError(errors.ErrInvalidTagsSizeExceed) } diff --git a/api/handler/tagging_test.go b/api/handler/tagging_test.go index 0df81b41e..29a662568 100644 --- a/api/handler/tagging_test.go +++ b/api/handler/tagging_test.go @@ -3,6 +3,7 @@ package handler import ( "net/http" "net/http/httptest" + "strconv" "strings" "testing" @@ -131,7 +132,40 @@ func TestGetBucketTagging(t *testing.T) { getBucketTaggingErr(hc, bktName, apierr.GetAPIError(apierr.ErrNoSuchTagSet)) } +func TestPutBucketTaggingLimit(t *testing.T) { + hc := prepareHandlerContext(t) + bktName := "bucket" + createBucket(hc, bktName) + + tagsLen := 51 + tags := make(map[string]string, tagsLen) + for i := 0; i < tagsLen; i++ { + tags["key"+strconv.Itoa(i)] = "val" + } + + putBucketTaggingErr(hc, bktName, tags, apierr.ErrInvalidTagsSizeExceed) + delete(tags, "key0") + + putBucketTagging(hc, bktName, tags) + + tagSet := getBucketTagging(hc, bktName) + require.Len(t, tagSet.TagSet, 50) + + putBucketTagging(hc, bktName, nil) + getBucketTaggingErr(hc, bktName, apierr.GetAPIError(apierr.ErrNoSuchTagSet)) +} + func putBucketTagging(hc *handlerContext, bktName string, tags map[string]string) { + w := putBucketTaggingBase(hc, bktName, tags) + assertStatus(hc.t, w, http.StatusOK) +} + +func putBucketTaggingErr(hc *handlerContext, bktName string, tags map[string]string, errCode apierr.ErrorCode) { + w := putBucketTaggingBase(hc, bktName, tags) + assertS3Error(hc.t, w, apierr.GetAPIError(errCode)) +} + +func putBucketTaggingBase(hc *handlerContext, bktName string, tags map[string]string) *httptest.ResponseRecorder { body := &data.Tagging{ TagSet: make([]data.Tag, 0, len(tags)), } @@ -146,7 +180,7 @@ func putBucketTagging(hc *handlerContext, bktName string, tags map[string]string w, r := prepareTestRequest(hc, bktName, "", body) middleware.GetReqInfo(r.Context()).Tagging = body hc.Handler().PutBucketTaggingHandler(w, r) - assertStatus(hc.t, w, http.StatusOK) + return w } func getBucketTagging(hc *handlerContext, bktName string) *data.Tagging {