github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/bucket/lifecycle/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 lifecycle
    19  
    20  import (
    21  	"encoding/xml"
    22  	"io"
    23  
    24  	"github.com/minio/minio-go/v7/pkg/tags"
    25  )
    26  
    27  var errInvalidFilter = Errorf("Filter must have exactly one of Prefix, Tag, or And specified")
    28  
    29  // Filter - a filter for a lifecycle configuration Rule.
    30  type Filter struct {
    31  	XMLName xml.Name `xml:"Filter"`
    32  	set     bool
    33  
    34  	Prefix Prefix
    35  
    36  	ObjectSizeGreaterThan int64 `xml:"ObjectSizeGreaterThan,omitempty"`
    37  	ObjectSizeLessThan    int64 `xml:"ObjectSizeLessThan,omitempty"`
    38  
    39  	And    And
    40  	andSet bool
    41  
    42  	Tag    Tag
    43  	tagSet bool
    44  
    45  	// Caching tags, only once
    46  	cachedTags map[string]string
    47  }
    48  
    49  // MarshalXML - produces the xml representation of the Filter struct
    50  // only one of Prefix, And and Tag should be present in the output.
    51  func (f Filter) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    52  	if err := e.EncodeToken(start); err != nil {
    53  		return err
    54  	}
    55  
    56  	switch {
    57  	case !f.And.isEmpty():
    58  		if err := e.EncodeElement(f.And, xml.StartElement{Name: xml.Name{Local: "And"}}); err != nil {
    59  			return err
    60  		}
    61  	case !f.Tag.IsEmpty():
    62  		if err := e.EncodeElement(f.Tag, xml.StartElement{Name: xml.Name{Local: "Tag"}}); err != nil {
    63  			return err
    64  		}
    65  	default:
    66  		// Always print Prefix field when both And & Tag are empty
    67  		if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil {
    68  			return err
    69  		}
    70  
    71  		if f.ObjectSizeLessThan > 0 {
    72  			if err := e.EncodeElement(f.ObjectSizeLessThan, xml.StartElement{Name: xml.Name{Local: "ObjectSizeLessThan"}}); err != nil {
    73  				return err
    74  			}
    75  		}
    76  		if f.ObjectSizeGreaterThan > 0 {
    77  			if err := e.EncodeElement(f.ObjectSizeGreaterThan, xml.StartElement{Name: xml.Name{Local: "ObjectSizeGreaterThan"}}); err != nil {
    78  				return err
    79  			}
    80  		}
    81  	}
    82  
    83  	return e.EncodeToken(xml.EndElement{Name: start.Name})
    84  }
    85  
    86  // UnmarshalXML - decodes XML data.
    87  func (f *Filter) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
    88  	f.set = true
    89  	for {
    90  		// Read tokens from the XML document in a stream.
    91  		t, err := d.Token()
    92  		if err != nil {
    93  			if err == io.EOF {
    94  				break
    95  			}
    96  			return err
    97  		}
    98  
    99  		if se, ok := t.(xml.StartElement); ok {
   100  			switch se.Name.Local {
   101  			case "Prefix":
   102  				var p Prefix
   103  				if err = d.DecodeElement(&p, &se); err != nil {
   104  					return err
   105  				}
   106  				f.Prefix = p
   107  			case "And":
   108  				var and And
   109  				if err = d.DecodeElement(&and, &se); err != nil {
   110  					return err
   111  				}
   112  				f.And = and
   113  				f.andSet = true
   114  			case "Tag":
   115  				var tag Tag
   116  				if err = d.DecodeElement(&tag, &se); err != nil {
   117  					return err
   118  				}
   119  				f.Tag = tag
   120  				f.tagSet = true
   121  			case "ObjectSizeLessThan":
   122  				var sz int64
   123  				if err = d.DecodeElement(&sz, &se); err != nil {
   124  					return err
   125  				}
   126  				f.ObjectSizeLessThan = sz
   127  			case "ObjectSizeGreaterThan":
   128  				var sz int64
   129  				if err = d.DecodeElement(&sz, &se); err != nil {
   130  					return err
   131  				}
   132  				f.ObjectSizeGreaterThan = sz
   133  			default:
   134  				return errUnknownXMLTag
   135  			}
   136  		}
   137  	}
   138  	return nil
   139  }
   140  
   141  // IsEmpty returns true if Filter is not specified in the XML
   142  func (f Filter) IsEmpty() bool {
   143  	return !f.set
   144  }
   145  
   146  // Validate - validates the filter element
   147  func (f Filter) Validate() error {
   148  	if f.IsEmpty() {
   149  		return errXMLNotWellFormed
   150  	}
   151  	// A Filter must have exactly one of Prefix, Tag,
   152  	// ObjectSize{LessThan,GreaterThan} or And specified.
   153  	type predType uint8
   154  	const (
   155  		nonePred predType = iota
   156  		prefixPred
   157  		andPred
   158  		tagPred
   159  		sizeLtPred
   160  		sizeGtPred
   161  	)
   162  	var predCount int
   163  	var pType predType
   164  	if !f.And.isEmpty() {
   165  		pType = andPred
   166  		predCount++
   167  	}
   168  	if f.Prefix.set {
   169  		pType = prefixPred
   170  		predCount++
   171  	}
   172  	if !f.Tag.IsEmpty() {
   173  		pType = tagPred
   174  		predCount++
   175  	}
   176  	if f.ObjectSizeGreaterThan != 0 {
   177  		pType = sizeGtPred
   178  		predCount++
   179  	}
   180  	if f.ObjectSizeLessThan != 0 {
   181  		pType = sizeLtPred
   182  		predCount++
   183  	}
   184  	// Note: S3 supports empty <Filter></Filter>, so predCount == 0 is
   185  	// valid.
   186  	if predCount > 1 {
   187  		return errInvalidFilter
   188  	}
   189  
   190  	var err error
   191  	switch pType {
   192  	case nonePred:
   193  	// S3 supports empty <Filter></Filter>
   194  	case prefixPred:
   195  	case andPred:
   196  		err = f.And.Validate()
   197  	case tagPred:
   198  		err = f.Tag.Validate()
   199  	case sizeLtPred:
   200  		if f.ObjectSizeLessThan < 0 {
   201  			err = errXMLNotWellFormed
   202  		}
   203  	case sizeGtPred:
   204  		if f.ObjectSizeGreaterThan < 0 {
   205  			err = errXMLNotWellFormed
   206  		}
   207  	}
   208  	return err
   209  }
   210  
   211  // TestTags tests if the object tags satisfy the Filter tags requirement,
   212  // it returns true if there is no tags in the underlying Filter.
   213  func (f Filter) TestTags(userTags string) bool {
   214  	if f.cachedTags == nil {
   215  		cache := make(map[string]string)
   216  		for _, t := range append(f.And.Tags, f.Tag) {
   217  			if !t.IsEmpty() {
   218  				cache[t.Key] = t.Value
   219  			}
   220  		}
   221  		f.cachedTags = cache
   222  	}
   223  
   224  	// This filter does not have any tags, always return true
   225  	if len(f.cachedTags) == 0 {
   226  		return true
   227  	}
   228  
   229  	parsedTags, err := tags.ParseObjectTags(userTags)
   230  	if err != nil {
   231  		return false
   232  	}
   233  	tagsMap := parsedTags.ToMap()
   234  
   235  	// Not enough tags on object to satisfy the rule filter's tags
   236  	if len(tagsMap) < len(f.cachedTags) {
   237  		return false
   238  	}
   239  
   240  	var mismatch bool
   241  	for k, cv := range f.cachedTags {
   242  		v, ok := tagsMap[k]
   243  		if !ok || v != cv {
   244  			mismatch = true
   245  			break
   246  		}
   247  	}
   248  	return !mismatch
   249  }
   250  
   251  // BySize returns true if sz satisfies one of ObjectSizeGreaterThan,
   252  // ObjectSizeLessThan predicates or a combination of them via And.
   253  func (f Filter) BySize(sz int64) bool {
   254  	if f.ObjectSizeGreaterThan > 0 &&
   255  		sz <= f.ObjectSizeGreaterThan {
   256  		return false
   257  	}
   258  	if f.ObjectSizeLessThan > 0 &&
   259  		sz >= f.ObjectSizeLessThan {
   260  		return false
   261  	}
   262  	if !f.And.isEmpty() {
   263  		return f.And.BySize(sz)
   264  	}
   265  	return true
   266  }