github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/bucket/replication/filter.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package replication
    19  
    20  import (
    21  	"encoding/xml"
    22  
    23  	"github.com/minio/minio-go/v7/pkg/tags"
    24  )
    25  
    26  var errInvalidFilter = Errorf("Filter must have exactly one of Prefix, Tag, or And specified")
    27  
    28  // Filter - a filter for a replication configuration Rule.
    29  type Filter struct {
    30  	XMLName xml.Name `xml:"Filter" json:"Filter"`
    31  	Prefix  string
    32  	And     And
    33  	Tag     Tag
    34  
    35  	// Caching tags, only once
    36  	cachedTags map[string]string
    37  }
    38  
    39  // IsEmpty returns true if filter is not set
    40  func (f Filter) IsEmpty() bool {
    41  	return f.And.isEmpty() && f.Tag.IsEmpty() && f.Prefix == ""
    42  }
    43  
    44  // MarshalXML - produces the xml representation of the Filter struct
    45  // only one of Prefix, And and Tag should be present in the output.
    46  func (f Filter) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    47  	if err := e.EncodeToken(start); err != nil {
    48  		return err
    49  	}
    50  
    51  	switch {
    52  	case !f.And.isEmpty():
    53  		if err := e.EncodeElement(f.And, xml.StartElement{Name: xml.Name{Local: "And"}}); err != nil {
    54  			return err
    55  		}
    56  	case !f.Tag.IsEmpty():
    57  		if err := e.EncodeElement(f.Tag, xml.StartElement{Name: xml.Name{Local: "Tag"}}); err != nil {
    58  			return err
    59  		}
    60  	default:
    61  		// Always print Prefix field when both And & Tag are empty
    62  		if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil {
    63  			return err
    64  		}
    65  	}
    66  
    67  	return e.EncodeToken(xml.EndElement{Name: start.Name})
    68  }
    69  
    70  // Validate - validates the filter element
    71  func (f Filter) Validate() error {
    72  	// A Filter must have exactly one of Prefix, Tag, or And specified.
    73  	if !f.And.isEmpty() {
    74  		if f.Prefix != "" {
    75  			return errInvalidFilter
    76  		}
    77  		if !f.Tag.IsEmpty() {
    78  			return errInvalidFilter
    79  		}
    80  		if err := f.And.Validate(); err != nil {
    81  			return err
    82  		}
    83  	}
    84  	if f.Prefix != "" {
    85  		if !f.Tag.IsEmpty() {
    86  			return errInvalidFilter
    87  		}
    88  	}
    89  	if !f.Tag.IsEmpty() {
    90  		if err := f.Tag.Validate(); err != nil {
    91  			return err
    92  		}
    93  	}
    94  	return nil
    95  }
    96  
    97  // TestTags tests if the object tags satisfy the Filter tags requirement,
    98  // it returns true if there is no tags in the underlying Filter.
    99  func (f *Filter) TestTags(userTags string) bool {
   100  	if f.cachedTags == nil {
   101  		cached := make(map[string]string)
   102  		for _, t := range append(f.And.Tags, f.Tag) {
   103  			if !t.IsEmpty() {
   104  				cached[t.Key] = t.Value
   105  			}
   106  		}
   107  		f.cachedTags = cached
   108  	}
   109  
   110  	// This filter does not have any tags, always return true
   111  	if len(f.cachedTags) == 0 {
   112  		return true
   113  	}
   114  
   115  	parsedTags, err := tags.ParseObjectTags(userTags)
   116  	if err != nil {
   117  		return false
   118  	}
   119  
   120  	tagsMap := parsedTags.ToMap()
   121  
   122  	// This filter has tags configured but this object
   123  	// does not have any tag, skip this object
   124  	if len(tagsMap) == 0 {
   125  		return false
   126  	}
   127  
   128  	// Both filter and object have tags, find a match,
   129  	// skip this object otherwise
   130  	for k, cv := range f.cachedTags {
   131  		v, ok := tagsMap[k]
   132  		if ok && v == cv {
   133  			return true
   134  		}
   135  	}
   136  
   137  	return false
   138  }