mirror of
				https://github.com/minio/minio.git
				synced 2025-10-31 08:11:19 +01:00 
			
		
		
		
	- remove some duplicated code - reported a bug, separately fixed in #13664 - using strings.ReplaceAll() when needed - using filepath.ToSlash() use when needed - remove all non-Go style comments from the codebase Co-authored-by: Aditya Manthramurthy <donatello@users.noreply.github.com>
		
			
				
	
	
		
			327 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) 2015-2021 MinIO, Inc.
 | |
| //
 | |
| // This file is part of MinIO Object Storage stack
 | |
| //
 | |
| // This program is free software: you can redistribute it and/or modify
 | |
| // it under the terms of the GNU Affero General Public License as published by
 | |
| // the Free Software Foundation, either version 3 of the License, or
 | |
| // (at your option) any later version.
 | |
| //
 | |
| // This program is distributed in the hope that it will be useful
 | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| // GNU Affero General Public License for more details.
 | |
| //
 | |
| // You should have received a copy of the GNU Affero General Public License
 | |
| // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| package event
 | |
| 
 | |
| import (
 | |
| 	"encoding/xml"
 | |
| 	"errors"
 | |
| 	"io"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 	"unicode/utf8"
 | |
| 
 | |
| 	"github.com/minio/minio-go/v7/pkg/set"
 | |
| )
 | |
| 
 | |
| // ValidateFilterRuleValue - checks if given value is filter rule value or not.
 | |
| func ValidateFilterRuleValue(value string) error {
 | |
| 	for _, segment := range strings.Split(value, "/") {
 | |
| 		if segment == "." || segment == ".." {
 | |
| 			return &ErrInvalidFilterValue{value}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(value) <= 1024 && utf8.ValidString(value) && !strings.Contains(value, `\`) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return &ErrInvalidFilterValue{value}
 | |
| }
 | |
| 
 | |
| // FilterRule - represents elements inside <FilterRule>...</FilterRule>
 | |
| type FilterRule struct {
 | |
| 	Name  string `xml:"Name"`
 | |
| 	Value string `xml:"Value"`
 | |
| }
 | |
| 
 | |
| func (filter FilterRule) isEmpty() bool {
 | |
| 	return filter.Name == "" && filter.Value == ""
 | |
| }
 | |
| 
 | |
| // MarshalXML implements a custom marshaller to support `omitempty` feature.
 | |
| func (filter FilterRule) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 | |
| 	if filter.isEmpty() {
 | |
| 		return nil
 | |
| 	}
 | |
| 	type filterRuleWrapper FilterRule
 | |
| 	return e.EncodeElement(filterRuleWrapper(filter), start)
 | |
| }
 | |
| 
 | |
| // UnmarshalXML - decodes XML data.
 | |
| func (filter *FilterRule) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
 | |
| 	// Make subtype to avoid recursive UnmarshalXML().
 | |
| 	type filterRule FilterRule
 | |
| 	rule := filterRule{}
 | |
| 	if err := d.DecodeElement(&rule, &start); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if rule.Name != "prefix" && rule.Name != "suffix" {
 | |
| 		return &ErrInvalidFilterName{rule.Name}
 | |
| 	}
 | |
| 
 | |
| 	if err := ValidateFilterRuleValue(filter.Value); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	*filter = FilterRule(rule)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // FilterRuleList - represents multiple <FilterRule>...</FilterRule>
 | |
| type FilterRuleList struct {
 | |
| 	Rules []FilterRule `xml:"FilterRule,omitempty"`
 | |
| }
 | |
| 
 | |
| // UnmarshalXML - decodes XML data.
 | |
| func (ruleList *FilterRuleList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
 | |
| 	// Make subtype to avoid recursive UnmarshalXML().
 | |
| 	type filterRuleList FilterRuleList
 | |
| 	rules := filterRuleList{}
 | |
| 	if err := d.DecodeElement(&rules, &start); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// FilterRuleList must have only one prefix and/or suffix.
 | |
| 	nameSet := set.NewStringSet()
 | |
| 	for _, rule := range rules.Rules {
 | |
| 		if nameSet.Contains(rule.Name) {
 | |
| 			if rule.Name == "prefix" {
 | |
| 				return &ErrFilterNamePrefix{}
 | |
| 			}
 | |
| 
 | |
| 			return &ErrFilterNameSuffix{}
 | |
| 		}
 | |
| 
 | |
| 		nameSet.Add(rule.Name)
 | |
| 	}
 | |
| 
 | |
| 	*ruleList = FilterRuleList(rules)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (ruleList FilterRuleList) isEmpty() bool {
 | |
| 	return len(ruleList.Rules) == 0
 | |
| }
 | |
| 
 | |
| // Pattern - returns pattern using prefix and suffix values.
 | |
| func (ruleList FilterRuleList) Pattern() string {
 | |
| 	var prefix string
 | |
| 	var suffix string
 | |
| 
 | |
| 	for _, rule := range ruleList.Rules {
 | |
| 		switch rule.Name {
 | |
| 		case "prefix":
 | |
| 			prefix = rule.Value
 | |
| 		case "suffix":
 | |
| 			suffix = rule.Value
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return NewPattern(prefix, suffix)
 | |
| }
 | |
| 
 | |
| // S3Key - represents elements inside <S3Key>...</S3Key>
 | |
| type S3Key struct {
 | |
| 	RuleList FilterRuleList `xml:"S3Key,omitempty" json:"S3Key,omitempty"`
 | |
| }
 | |
| 
 | |
| // MarshalXML implements a custom marshaller to support `omitempty` feature.
 | |
| func (s3Key S3Key) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 | |
| 	if s3Key.RuleList.isEmpty() {
 | |
| 		return nil
 | |
| 	}
 | |
| 	type s3KeyWrapper S3Key
 | |
| 	return e.EncodeElement(s3KeyWrapper(s3Key), start)
 | |
| }
 | |
| 
 | |
| // common - represents common elements inside <QueueConfiguration>, <CloudFunctionConfiguration>
 | |
| // and <TopicConfiguration>
 | |
| type common struct {
 | |
| 	ID     string `xml:"Id" json:"Id"`
 | |
| 	Filter S3Key  `xml:"Filter" json:"Filter"`
 | |
| 	Events []Name `xml:"Event" json:"Event"`
 | |
| }
 | |
| 
 | |
| // Queue - represents elements inside <QueueConfiguration>
 | |
| type Queue struct {
 | |
| 	common
 | |
| 	ARN ARN `xml:"Queue"`
 | |
| }
 | |
| 
 | |
| // UnmarshalXML - decodes XML data.
 | |
| func (q *Queue) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
 | |
| 	// Make subtype to avoid recursive UnmarshalXML().
 | |
| 	type queue Queue
 | |
| 	parsedQueue := queue{}
 | |
| 	if err := d.DecodeElement(&parsedQueue, &start); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if len(parsedQueue.Events) == 0 {
 | |
| 		return errors.New("missing event name(s)")
 | |
| 	}
 | |
| 
 | |
| 	eventStringSet := set.NewStringSet()
 | |
| 	for _, eventName := range parsedQueue.Events {
 | |
| 		if eventStringSet.Contains(eventName.String()) {
 | |
| 			return &ErrDuplicateEventName{eventName}
 | |
| 		}
 | |
| 
 | |
| 		eventStringSet.Add(eventName.String())
 | |
| 	}
 | |
| 
 | |
| 	*q = Queue(parsedQueue)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Validate - checks whether queue has valid values or not.
 | |
| func (q Queue) Validate(region string, targetList *TargetList) error {
 | |
| 	if q.ARN.region == "" {
 | |
| 		if !targetList.Exists(q.ARN.TargetID) {
 | |
| 			return &ErrARNNotFound{q.ARN}
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if region != "" && q.ARN.region != region {
 | |
| 		return &ErrUnknownRegion{q.ARN.region}
 | |
| 	}
 | |
| 
 | |
| 	if !targetList.Exists(q.ARN.TargetID) {
 | |
| 		return &ErrARNNotFound{q.ARN}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SetRegion - sets region value to queue's ARN.
 | |
| func (q *Queue) SetRegion(region string) {
 | |
| 	q.ARN.region = region
 | |
| }
 | |
| 
 | |
| // ToRulesMap - converts Queue to RulesMap
 | |
| func (q Queue) ToRulesMap() RulesMap {
 | |
| 	pattern := q.Filter.RuleList.Pattern()
 | |
| 	return NewRulesMap(q.Events, pattern, q.ARN.TargetID)
 | |
| }
 | |
| 
 | |
| // Unused.  Available for completion.
 | |
| type lambda struct {
 | |
| 	ARN string `xml:"CloudFunction"`
 | |
| }
 | |
| 
 | |
| // Unused. Available for completion.
 | |
| type topic struct {
 | |
| 	ARN string `xml:"Topic" json:"Topic"`
 | |
| }
 | |
| 
 | |
| // Config - notification configuration described in
 | |
| // http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html
 | |
| type Config struct {
 | |
| 	XMLNS      string   `xml:"xmlns,attr,omitempty"`
 | |
| 	XMLName    xml.Name `xml:"NotificationConfiguration"`
 | |
| 	QueueList  []Queue  `xml:"QueueConfiguration,omitempty"`
 | |
| 	LambdaList []lambda `xml:"CloudFunctionConfiguration,omitempty"`
 | |
| 	TopicList  []topic  `xml:"TopicConfiguration,omitempty"`
 | |
| }
 | |
| 
 | |
| // UnmarshalXML - decodes XML data.
 | |
| func (conf *Config) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
 | |
| 	// Make subtype to avoid recursive UnmarshalXML().
 | |
| 	type config Config
 | |
| 	parsedConfig := config{}
 | |
| 	if err := d.DecodeElement(&parsedConfig, &start); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Empty queue list means user wants to delete the notification configuration.
 | |
| 	if len(parsedConfig.QueueList) > 0 {
 | |
| 		for i, q1 := range parsedConfig.QueueList[:len(parsedConfig.QueueList)-1] {
 | |
| 			for _, q2 := range parsedConfig.QueueList[i+1:] {
 | |
| 				// Removes the region from ARN if server region is not set
 | |
| 				if q2.ARN.region != "" && q1.ARN.region == "" {
 | |
| 					q2.ARN.region = ""
 | |
| 				}
 | |
| 				if reflect.DeepEqual(q1, q2) {
 | |
| 					return &ErrDuplicateQueueConfiguration{q1}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(parsedConfig.LambdaList) > 0 || len(parsedConfig.TopicList) > 0 {
 | |
| 		return &ErrUnsupportedConfiguration{}
 | |
| 	}
 | |
| 
 | |
| 	*conf = Config(parsedConfig)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Validate - checks whether config has valid values or not.
 | |
| func (conf Config) Validate(region string, targetList *TargetList) error {
 | |
| 	for _, queue := range conf.QueueList {
 | |
| 		if err := queue.Validate(region, targetList); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SetRegion - sets region to all queue configuration.
 | |
| func (conf *Config) SetRegion(region string) {
 | |
| 	for i := range conf.QueueList {
 | |
| 		conf.QueueList[i].SetRegion(region)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ToRulesMap - converts all queue configuration to RulesMap.
 | |
| func (conf *Config) ToRulesMap() RulesMap {
 | |
| 	rulesMap := make(RulesMap)
 | |
| 
 | |
| 	for _, queue := range conf.QueueList {
 | |
| 		rulesMap.Add(queue.ToRulesMap())
 | |
| 	}
 | |
| 
 | |
| 	return rulesMap
 | |
| }
 | |
| 
 | |
| // ParseConfig - parses data in reader to notification configuration.
 | |
| func ParseConfig(reader io.Reader, region string, targetList *TargetList) (*Config, error) {
 | |
| 	var config Config
 | |
| 
 | |
| 	if err := xml.NewDecoder(reader).Decode(&config); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if err := config.Validate(region, targetList); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	config.SetRegion(region)
 | |
| 	// If xml namespace is empty, set a default value before returning.
 | |
| 	if config.XMLNS == "" {
 | |
| 		config.XMLNS = "http://s3.amazonaws.com/doc/2006-03-01/"
 | |
| 	}
 | |
| 	return &config, nil
 | |
| }
 |