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  }