diff --git a/internal/bucket/lifecycle/filter.go b/internal/bucket/lifecycle/filter.go index f881e99b1..aa8ef455b 100644 --- a/internal/bucket/lifecycle/filter.go +++ b/internal/bucket/lifecycle/filter.go @@ -20,6 +20,9 @@ package lifecycle import ( "encoding/xml" "io" + "log" + + "github.com/minio/minio-go/v7/pkg/tags" ) var errInvalidFilter = Errorf("Filter must have exactly one of Prefix, Tag, or And specified") @@ -36,8 +39,9 @@ type Filter struct { Tag Tag tagSet bool + // Caching tags, only once - cachedTags []string + cachedTags map[string]string } // MarshalXML - produces the xml representation of the Filter struct @@ -150,27 +154,42 @@ func (f Filter) Validate() error { // TestTags tests if the object tags satisfy the Filter tags requirement, // it returns true if there is no tags in the underlying Filter. -func (f Filter) TestTags(tags []string) bool { +func (f Filter) TestTags(userTags string) bool { if f.cachedTags == nil { - tags := make([]string, 0) + cache := make(map[string]string) for _, t := range append(f.And.Tags, f.Tag) { if !t.IsEmpty() { - tags = append(tags, t.String()) + cache[t.Key] = t.Value } } - f.cachedTags = tags + f.cachedTags = cache } - for _, ct := range f.cachedTags { - foundTag := false - for _, t := range tags { - if ct == t { - foundTag = true - break - } - } - if !foundTag { - return false + + // This filter does not have any tags, always return true + if len(f.cachedTags) == 0 { + return true + } + + parsedTags, err := tags.ParseObjectTags(userTags) + if err != nil { + log.Printf("Unexpected object tag found: `%s`\n", userTags) + return false + } + tagsMap := parsedTags.ToMap() + + // This filter has tags configured but this object + // does not have any tag, skip this object + if len(tagsMap) == 0 { + return false + } + + // Both filter and object have tags, find a match, + // skip this object otherwise + for k, cv := range f.cachedTags { + v, ok := tagsMap[k] + if ok && v == cv { + return true } } - return true + return false } diff --git a/internal/bucket/lifecycle/lifecycle.go b/internal/bucket/lifecycle/lifecycle.go index 594726415..9e70c2ea1 100644 --- a/internal/bucket/lifecycle/lifecycle.go +++ b/internal/bucket/lifecycle/lifecycle.go @@ -259,7 +259,7 @@ func (lc Lifecycle) FilterActionableRules(obj ObjectOpts) []Rule { continue } - if rule.Filter.TestTags(strings.Split(obj.UserTags, "&")) { + if rule.Filter.TestTags(obj.UserTags) { rules = append(rules, rule) } if !rule.Transition.IsNull() { diff --git a/internal/bucket/lifecycle/lifecycle_test.go b/internal/bucket/lifecycle/lifecycle_test.go index f5fd3d8a7..14b0166e9 100644 --- a/internal/bucket/lifecycle/lifecycle_test.go +++ b/internal/bucket/lifecycle/lifecycle_test.go @@ -325,6 +325,14 @@ func TestComputeActions(t *testing.T) { objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago expectedAction: DeleteAction, }, + // Should remove (Tags with encoded chars) + { + inputConfig: `factorytruestore foreverfalseEnabled` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + ``, + objectName: "fooobject", + objectTags: "store+forever=false&factory=true", + objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago + expectedAction: DeleteAction, + }, // Should not remove (Tags don't match) {