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 }