diff --git a/cmd/api-response.go b/cmd/api-response.go
index 290e8f79ab03d9..64de4fd7e0c3b4 100644
--- a/cmd/api-response.go
+++ b/cmd/api-response.go
@@ -29,13 +29,13 @@ import (
"strings"
"time"
- "github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/amztime"
"github.com/minio/minio/internal/crypto"
"github.com/minio/minio/internal/handlers"
"github.com/minio/minio/internal/hash"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
+ "github.com/minio/minio/internal/tagging"
xxml "github.com/minio/xxml"
)
@@ -362,7 +362,7 @@ type Object struct {
UserMetadata *Metadata `xml:"UserMetadata,omitempty"`
// x-amz-tagging values in their k/v values.
- UserTags *tags.Tags `json:"userTags,omitempty" xml:"Tagging,omitempty"`
+ UserTags *tagging.Tagging `json:"userTags,omitempty" xml:"TagSet,omitempty"`
}
// CopyObjectResponse container returns ETag and LastModified of the successfully copied object
@@ -630,9 +630,9 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
}
content.Owner = owner
if metadata {
- userTags, err := tags.ParseObjectTags(object.UserTags)
- if err == nil {
- content.UserTags = userTags
+ if len(object.UserTags) > 0 {
+ t := tagging.Tagging(object.UserTags)
+ content.UserTags = &t
}
content.UserMetadata = &Metadata{}
switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
diff --git a/internal/tagging/tags.go b/internal/tagging/tags.go
new file mode 100644
index 00000000000000..aea5be80c483ad
--- /dev/null
+++ b/internal/tagging/tags.go
@@ -0,0 +1,119 @@
+// Copyright (c) 2015-2023 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 tagging
+
+import (
+ "encoding/json"
+ "net/url"
+ "strings"
+
+ xxml "github.com/minio/xxml"
+)
+
+// Tags encoded
+type Tagging string
+
+// stringsCut slices s around the first instance of sep,
+// returning the text before and after sep.
+// The found result reports whether sep appears in s.
+// If sep does not appear in s, cut returns s, "", false.
+func stringsCut(s, sep string) (before, after string, found bool) {
+ if i := strings.Index(s, sep); i >= 0 {
+ return s[:i], s[i+len(sep):], true
+ }
+ return s, "", false
+}
+
+// MarshalXML encodes tags to XML data.
+// Format corresponds to https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectTagging.html
+func (tags Tagging) MarshalXML(e *xxml.Encoder, start xxml.StartElement) (err error) {
+ // Tag denotes key and value.
+ type TagSet struct {
+ Key string `xml:"Key"`
+ Value string `xml:"Value"`
+ }
+
+ tagName := xxml.Name{Space: "", Local: "Tag"}
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ tgs := string(tags)
+ for tgs != "" {
+ var key string
+ key, tgs, _ = stringsCut(tgs, "&")
+ if key == "" {
+ continue
+ }
+ key, value, _ := stringsCut(key, "=")
+ key, err := url.QueryUnescape(key)
+ if err != nil {
+ return err
+ }
+
+ value, err = url.QueryUnescape(value)
+ if err != nil {
+ return err
+ }
+
+ // tagList.Tags = append(tagList.Tags, tag{key, value})
+ err = e.EncodeElement(TagSet{key, value}, xxml.StartElement{Name: tagName})
+ if err != nil {
+ return err
+ }
+ }
+
+ return e.EncodeToken(start.End())
+}
+
+// MarshalJSON returns a JSON representation of the tags.
+func (tags Tagging) MarshalJSON() ([]byte, error) {
+ m, err := tags.Map()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(m)
+}
+
+// Map returns a map representation of the tags.
+func (tags Tagging) Map() (map[string]string, error) {
+ if len(tags) == 0 {
+ return map[string]string{}, nil
+ }
+ tgs := string(tags)
+ guess := strings.Count(tgs, "=")
+ res := make(map[string]string, guess)
+ for tgs != "" {
+ var key string
+ key, tgs, _ = stringsCut(tgs, "&")
+ if key == "" {
+ continue
+ }
+ key, value, _ := stringsCut(key, "=")
+ key, err := url.QueryUnescape(key)
+ if err != nil {
+ return nil, err
+ }
+
+ value, err = url.QueryUnescape(value)
+ if err != nil {
+ return nil, err
+ }
+ res[key] = value
+ }
+ return res, nil
+}
diff --git a/internal/tagging/tags_test.go b/internal/tagging/tags_test.go
new file mode 100644
index 00000000000000..271fd18b96e7ea
--- /dev/null
+++ b/internal/tagging/tags_test.go
@@ -0,0 +1,56 @@
+// Copyright (c) 2015-2023 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 tagging
+
+import (
+ "encoding/json"
+ "testing"
+
+ xxml "github.com/minio/xxml"
+)
+
+func TestTags_MarshalXML(t *testing.T) {
+ type Sample struct {
+ Tagging *Tagging
+ }
+ tagString := Tagging("key=value&key2=&key3=else")
+ sample := Sample{Tagging: &tagString}
+ got, err := xxml.Marshal(sample)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := "keyvaluekey2key3else"
+ if want != string(got) {
+ t.Errorf("want %s\ngot %s", want, got)
+ }
+}
+
+func TestTagging_MarshalJSON(t *testing.T) {
+ type Sample struct {
+ Tagging *Tagging
+ }
+ tagString := Tagging("key=value&key2=&key3=else")
+ sample := Sample{Tagging: &tagString}
+ got, err := json.Marshal(sample)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := `{"Tagging":{"key":"value","key2":"","key3":"else"}}`
+ if want != string(got) {
+ t.Errorf("want %s\ngot %s", want, got)
+ }
+}