storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/event/config.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package event 18 19 import ( 20 "encoding/xml" 21 "errors" 22 "io" 23 "reflect" 24 "strings" 25 "unicode/utf8" 26 27 "github.com/minio/minio-go/v7/pkg/set" 28 ) 29 30 // ValidateFilterRuleValue - checks if given value is filter rule value or not. 31 func ValidateFilterRuleValue(value string) error { 32 for _, segment := range strings.Split(value, "/") { 33 if segment == "." || segment == ".." { 34 return &ErrInvalidFilterValue{value} 35 } 36 } 37 38 if len(value) <= 1024 && utf8.ValidString(value) && !strings.Contains(value, `\`) { 39 return nil 40 } 41 42 return &ErrInvalidFilterValue{value} 43 } 44 45 // FilterRule - represents elements inside <FilterRule>...</FilterRule> 46 type FilterRule struct { 47 Name string `xml:"Name"` 48 Value string `xml:"Value"` 49 } 50 51 func (filter FilterRule) isEmpty() bool { 52 return filter.Name == "" && filter.Value == "" 53 } 54 55 // MarshalXML implements a custom marshaller to support `omitempty` feature. 56 func (filter FilterRule) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 57 if filter.isEmpty() { 58 return nil 59 } 60 type filterRuleWrapper FilterRule 61 return e.EncodeElement(filterRuleWrapper(filter), start) 62 } 63 64 // UnmarshalXML - decodes XML data. 65 func (filter *FilterRule) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 66 // Make subtype to avoid recursive UnmarshalXML(). 67 type filterRule FilterRule 68 rule := filterRule{} 69 if err := d.DecodeElement(&rule, &start); err != nil { 70 return err 71 } 72 73 if rule.Name != "prefix" && rule.Name != "suffix" { 74 return &ErrInvalidFilterName{rule.Name} 75 } 76 77 if err := ValidateFilterRuleValue(filter.Value); err != nil { 78 return err 79 } 80 81 *filter = FilterRule(rule) 82 83 return nil 84 } 85 86 // FilterRuleList - represents multiple <FilterRule>...</FilterRule> 87 type FilterRuleList struct { 88 Rules []FilterRule `xml:"FilterRule,omitempty"` 89 } 90 91 // UnmarshalXML - decodes XML data. 92 func (ruleList *FilterRuleList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 93 // Make subtype to avoid recursive UnmarshalXML(). 94 type filterRuleList FilterRuleList 95 rules := filterRuleList{} 96 if err := d.DecodeElement(&rules, &start); err != nil { 97 return err 98 } 99 100 // FilterRuleList must have only one prefix and/or suffix. 101 nameSet := set.NewStringSet() 102 for _, rule := range rules.Rules { 103 if nameSet.Contains(rule.Name) { 104 if rule.Name == "prefix" { 105 return &ErrFilterNamePrefix{} 106 } 107 108 return &ErrFilterNameSuffix{} 109 } 110 111 nameSet.Add(rule.Name) 112 } 113 114 *ruleList = FilterRuleList(rules) 115 return nil 116 } 117 118 func (ruleList FilterRuleList) isEmpty() bool { 119 return len(ruleList.Rules) == 0 120 } 121 122 // Pattern - returns pattern using prefix and suffix values. 123 func (ruleList FilterRuleList) Pattern() string { 124 var prefix string 125 var suffix string 126 127 for _, rule := range ruleList.Rules { 128 switch rule.Name { 129 case "prefix": 130 prefix = rule.Value 131 case "suffix": 132 suffix = rule.Value 133 } 134 } 135 136 return NewPattern(prefix, suffix) 137 } 138 139 // S3Key - represents elements inside <S3Key>...</S3Key> 140 type S3Key struct { 141 RuleList FilterRuleList `xml:"S3Key,omitempty" json:"S3Key,omitempty"` 142 } 143 144 // MarshalXML implements a custom marshaller to support `omitempty` feature. 145 func (s3Key S3Key) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 146 if s3Key.RuleList.isEmpty() { 147 return nil 148 } 149 type s3KeyWrapper S3Key 150 return e.EncodeElement(s3KeyWrapper(s3Key), start) 151 } 152 153 // common - represents common elements inside <QueueConfiguration>, <CloudFunctionConfiguration> 154 // and <TopicConfiguration> 155 type common struct { 156 ID string `xml:"Id" json:"Id"` 157 Filter S3Key `xml:"Filter" json:"Filter"` 158 Events []Name `xml:"Event" json:"Event"` 159 } 160 161 // Queue - represents elements inside <QueueConfiguration> 162 type Queue struct { 163 common 164 ARN ARN `xml:"Queue"` 165 } 166 167 // UnmarshalXML - decodes XML data. 168 func (q *Queue) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 169 // Make subtype to avoid recursive UnmarshalXML(). 170 type queue Queue 171 parsedQueue := queue{} 172 if err := d.DecodeElement(&parsedQueue, &start); err != nil { 173 return err 174 } 175 176 if len(parsedQueue.Events) == 0 { 177 return errors.New("missing event name(s)") 178 } 179 180 eventStringSet := set.NewStringSet() 181 for _, eventName := range parsedQueue.Events { 182 if eventStringSet.Contains(eventName.String()) { 183 return &ErrDuplicateEventName{eventName} 184 } 185 186 eventStringSet.Add(eventName.String()) 187 } 188 189 *q = Queue(parsedQueue) 190 191 return nil 192 } 193 194 // Validate - checks whether queue has valid values or not. 195 func (q Queue) Validate(region string, targetList *TargetList) error { 196 if q.ARN.region == "" { 197 if !targetList.Exists(q.ARN.TargetID) { 198 return &ErrARNNotFound{q.ARN} 199 } 200 return nil 201 } 202 203 if region != "" && q.ARN.region != region { 204 return &ErrUnknownRegion{q.ARN.region} 205 } 206 207 if !targetList.Exists(q.ARN.TargetID) { 208 return &ErrARNNotFound{q.ARN} 209 } 210 211 return nil 212 } 213 214 // SetRegion - sets region value to queue's ARN. 215 func (q *Queue) SetRegion(region string) { 216 q.ARN.region = region 217 } 218 219 // ToRulesMap - converts Queue to RulesMap 220 func (q Queue) ToRulesMap() RulesMap { 221 pattern := q.Filter.RuleList.Pattern() 222 return NewRulesMap(q.Events, pattern, q.ARN.TargetID) 223 } 224 225 // Unused. Available for completion. 226 type lambda struct { 227 ARN string `xml:"CloudFunction"` 228 } 229 230 // Unused. Available for completion. 231 type topic struct { 232 ARN string `xml:"Topic" json:"Topic"` 233 } 234 235 // Config - notification configuration described in 236 // http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html 237 type Config struct { 238 XMLNS string `xml:"xmlns,attr,omitempty"` 239 XMLName xml.Name `xml:"NotificationConfiguration"` 240 QueueList []Queue `xml:"QueueConfiguration,omitempty"` 241 LambdaList []lambda `xml:"CloudFunctionConfiguration,omitempty"` 242 TopicList []topic `xml:"TopicConfiguration,omitempty"` 243 } 244 245 // UnmarshalXML - decodes XML data. 246 func (conf *Config) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 247 // Make subtype to avoid recursive UnmarshalXML(). 248 type config Config 249 parsedConfig := config{} 250 if err := d.DecodeElement(&parsedConfig, &start); err != nil { 251 return err 252 } 253 254 // Empty queue list means user wants to delete the notification configuration. 255 if len(parsedConfig.QueueList) > 0 { 256 for i, q1 := range parsedConfig.QueueList[:len(parsedConfig.QueueList)-1] { 257 for _, q2 := range parsedConfig.QueueList[i+1:] { 258 // Removes the region from ARN if server region is not set 259 if q2.ARN.region != "" && q1.ARN.region == "" { 260 q2.ARN.region = "" 261 } 262 if reflect.DeepEqual(q1, q2) { 263 return &ErrDuplicateQueueConfiguration{q1} 264 } 265 } 266 } 267 } 268 269 if len(parsedConfig.LambdaList) > 0 || len(parsedConfig.TopicList) > 0 { 270 return &ErrUnsupportedConfiguration{} 271 } 272 273 *conf = Config(parsedConfig) 274 275 return nil 276 } 277 278 // Validate - checks whether config has valid values or not. 279 func (conf Config) Validate(region string, targetList *TargetList) error { 280 for _, queue := range conf.QueueList { 281 if err := queue.Validate(region, targetList); err != nil { 282 return err 283 } 284 } 285 286 return nil 287 } 288 289 // SetRegion - sets region to all queue configuration. 290 func (conf *Config) SetRegion(region string) { 291 for i := range conf.QueueList { 292 conf.QueueList[i].SetRegion(region) 293 } 294 } 295 296 // ToRulesMap - converts all queue configuration to RulesMap. 297 func (conf *Config) ToRulesMap() RulesMap { 298 rulesMap := make(RulesMap) 299 300 for _, queue := range conf.QueueList { 301 rulesMap.Add(queue.ToRulesMap()) 302 } 303 304 return rulesMap 305 } 306 307 // ParseConfig - parses data in reader to notification configuration. 308 func ParseConfig(reader io.Reader, region string, targetList *TargetList) (*Config, error) { 309 var config Config 310 311 if err := xml.NewDecoder(reader).Decode(&config); err != nil { 312 return nil, err 313 } 314 315 if err := config.Validate(region, targetList); err != nil { 316 return nil, err 317 } 318 319 config.SetRegion(region) 320 //If xml namespace is empty, set a default value before returning. 321 if config.XMLNS == "" { 322 config.XMLNS = "http://s3.amazonaws.com/doc/2006-03-01/" 323 } 324 return &config, nil 325 }