Skip to content

Commit

Permalink
Fix *tags.Tags XML encoding
Browse files Browse the repository at this point in the history
Tags would not be marshaled correctly due to xml library fork.

Add small package to handle this.
  • Loading branch information
klauspost committed Mar 30, 2023
1 parent e01a4a2 commit 0c90342
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 5 deletions.
10 changes: 5 additions & 5 deletions cmd/api-response.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
119 changes: 119 additions & 0 deletions internal/tagging/tags.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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
}
56 changes: 56 additions & 0 deletions internal/tagging/tags_test.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
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 := "<Sample><Tagging><TagSet><Tag><Key>key</Key><Value>value</Value></Tag><Tag><Key>key2</Key><Value></Value></Tag><Tag><Key>key3</Key><Value>else</Value></Tag></TagSet></Tagging></Sample>"
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)
}
}

0 comments on commit 0c90342

Please sign in to comment.