github.com/DataDog/datadog-agent/pkg/security/secl@v0.55.0-devel.0.20240517055856-10c4965fea94/compiler/eval/strings.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the Apache License Version 2.0.
     3  // This product includes software developed at Datadog (https://www.datadoghq.com/).
     4  // Copyright 2016-present Datadog, Inc.
     5  
     6  // Package eval holds eval related files
     7  package eval
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"regexp"
    13  	"slices"
    14  	"strings"
    15  )
    16  
    17  // StringCmpOpts defines options to apply during string comparison
    18  type StringCmpOpts struct {
    19  	CaseInsensitive        bool
    20  	PathSeparatorNormalize bool
    21  }
    22  
    23  // DefaultStringCmpOpts defines the default comparison options
    24  var DefaultStringCmpOpts = StringCmpOpts{}
    25  
    26  // StringValues describes a set of string values, either regex or scalar
    27  type StringValues struct {
    28  	scalars        []string
    29  	stringMatchers []StringMatcher
    30  
    31  	// caches
    32  	scalarCache []string
    33  	fieldValues []FieldValue
    34  }
    35  
    36  // GetFieldValues return the list of FieldValue stored in the StringValues
    37  func (s *StringValues) GetFieldValues() []FieldValue {
    38  	return s.fieldValues
    39  }
    40  
    41  // AppendFieldValue append a FieldValue
    42  func (s *StringValues) AppendFieldValue(value FieldValue) {
    43  	if slices.Contains(s.fieldValues, value) {
    44  		return
    45  	}
    46  
    47  	if value.Type == ScalarValueType {
    48  		s.scalars = append(s.scalars, value.Value.(string))
    49  	}
    50  
    51  	s.fieldValues = append(s.fieldValues, value)
    52  }
    53  
    54  // Compile all the values
    55  func (s *StringValues) Compile(opts StringCmpOpts) error {
    56  	for _, value := range s.fieldValues {
    57  		// fast path for scalar value without specific comparison behavior
    58  		if opts == DefaultStringCmpOpts && value.Type == ScalarValueType {
    59  			str := value.Value.(string)
    60  			s.scalars = append(s.scalars, str)
    61  			s.scalarCache = append(s.scalarCache, str)
    62  		} else {
    63  			str, ok := value.Value.(string)
    64  			if !ok {
    65  				return fmt.Errorf("invalid field value `%v`", value.Value)
    66  			}
    67  
    68  			matcher, err := NewStringMatcher(value.Type, str, opts)
    69  			if err != nil {
    70  				return err
    71  			}
    72  			s.stringMatchers = append(s.stringMatchers, matcher)
    73  		}
    74  	}
    75  
    76  	return nil
    77  }
    78  
    79  // GetScalarValues return the scalar values
    80  func (s *StringValues) GetScalarValues() []string {
    81  	return s.scalars
    82  }
    83  
    84  // GetStringMatchers return the pattern matchers
    85  func (s *StringValues) GetStringMatchers() []StringMatcher {
    86  	return s.stringMatchers
    87  }
    88  
    89  // SetFieldValues apply field values
    90  func (s *StringValues) SetFieldValues(values ...FieldValue) error {
    91  	// reset internal caches
    92  	s.stringMatchers = s.stringMatchers[:0]
    93  	s.scalarCache = nil
    94  
    95  	for _, value := range values {
    96  		s.AppendFieldValue(value)
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // AppendScalarValue append a scalar string value
   103  func (s *StringValues) AppendScalarValue(value string) {
   104  	s.AppendFieldValue(FieldValue{Value: value, Type: ScalarValueType})
   105  }
   106  
   107  // Matches returns whether the value matches the string values
   108  func (s *StringValues) Matches(value string) bool {
   109  	if slices.Contains(s.scalarCache, value) {
   110  		return true
   111  	}
   112  	for _, pm := range s.stringMatchers {
   113  		if pm.Matches(value) {
   114  			return true
   115  		}
   116  	}
   117  
   118  	return false
   119  }
   120  
   121  // StringMatcher defines a pattern matcher
   122  type StringMatcher interface {
   123  	Matches(value string) bool
   124  }
   125  
   126  // RegexpStringMatcher defines a regular expression pattern matcher
   127  type RegexpStringMatcher struct {
   128  	stringOptionsOpt []string
   129  
   130  	re *regexp.Regexp
   131  }
   132  
   133  var stringBigOrRe = regexp.MustCompile(`^\.\*\(([a-zA-Z_|]+)\)\.\*$`)
   134  
   135  // Compile a regular expression based pattern
   136  func (r *RegexpStringMatcher) Compile(pattern string, caseInsensitive bool) error {
   137  	if !caseInsensitive {
   138  		if groups := stringBigOrRe.FindStringSubmatch(pattern); groups != nil {
   139  			r.stringOptionsOpt = strings.Split(groups[1], "|")
   140  			r.re = nil
   141  			return nil
   142  		}
   143  	}
   144  
   145  	if caseInsensitive {
   146  		pattern = "(?i)" + pattern
   147  	}
   148  
   149  	re, err := regexp.Compile(pattern)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	r.stringOptionsOpt = nil
   154  	r.re = re
   155  
   156  	return nil
   157  }
   158  
   159  // Matches returns whether the value matches
   160  func (r *RegexpStringMatcher) Matches(value string) bool {
   161  	if r.stringOptionsOpt != nil {
   162  		for _, search := range r.stringOptionsOpt {
   163  			if strings.Contains(value, search) {
   164  				return true
   165  			}
   166  		}
   167  		return false
   168  	}
   169  
   170  	return r.re.MatchString(value)
   171  }
   172  
   173  // GlobStringMatcher defines a glob pattern matcher
   174  type GlobStringMatcher struct {
   175  	glob *Glob
   176  }
   177  
   178  // Compile a simple pattern
   179  func (g *GlobStringMatcher) Compile(pattern string, caseInsensitive bool, normalizePath bool) error {
   180  	if g.glob != nil {
   181  		return nil
   182  	}
   183  
   184  	glob, err := NewGlob(pattern, caseInsensitive, normalizePath)
   185  	if err != nil {
   186  		return err
   187  	}
   188  	g.glob = glob
   189  
   190  	return nil
   191  }
   192  
   193  // Matches returns whether the value matches
   194  func (g *GlobStringMatcher) Matches(value string) bool {
   195  	return g.glob.Matches(value)
   196  }
   197  
   198  // Contains returns whether the pattern contains the value
   199  func (g *GlobStringMatcher) Contains(value string) bool {
   200  	return g.glob.Contains(value)
   201  }
   202  
   203  // PatternStringMatcher defines a pattern matcher
   204  type PatternStringMatcher struct {
   205  	pattern         patternElement
   206  	caseInsensitive bool
   207  }
   208  
   209  // Compile a simple pattern
   210  func (p *PatternStringMatcher) Compile(pattern string, caseInsensitive bool) error {
   211  	// ** are not allowed in normal patterns
   212  	if strings.Contains(pattern, "**") {
   213  		return fmt.Errorf("`**` is not allowed in patterns")
   214  	}
   215  
   216  	p.pattern = newPatternElement(pattern)
   217  	p.caseInsensitive = caseInsensitive
   218  	return nil
   219  }
   220  
   221  // Matches returns whether the value matches
   222  func (p *PatternStringMatcher) Matches(value string) bool {
   223  	return PatternMatchesWithSegments(p.pattern, value, p.caseInsensitive)
   224  }
   225  
   226  // ScalarStringMatcher defines a scalar matcher
   227  type ScalarStringMatcher struct {
   228  	value           string
   229  	caseInsensitive bool
   230  }
   231  
   232  // Compile a simple pattern
   233  func (s *ScalarStringMatcher) Compile(pattern string, caseInsensitive bool) error {
   234  	s.value = pattern
   235  	s.caseInsensitive = caseInsensitive
   236  	return nil
   237  }
   238  
   239  // Matches returns whether the value matches
   240  func (s *ScalarStringMatcher) Matches(value string) bool {
   241  	if s.caseInsensitive {
   242  		return strings.EqualFold(s.value, value)
   243  	}
   244  	return s.value == value
   245  }
   246  
   247  // NewStringMatcher returns a new string matcher
   248  func NewStringMatcher(kind FieldValueType, pattern string, opts StringCmpOpts) (StringMatcher, error) {
   249  	switch kind {
   250  	case PatternValueType:
   251  		var matcher PatternStringMatcher
   252  		if err := matcher.Compile(pattern, opts.CaseInsensitive); err != nil {
   253  			return nil, fmt.Errorf("invalid pattern `%s`: %s", pattern, err)
   254  		}
   255  		return &matcher, nil
   256  	case GlobValueType:
   257  		var matcher GlobStringMatcher
   258  		if err := matcher.Compile(pattern, opts.CaseInsensitive, opts.PathSeparatorNormalize); err != nil {
   259  			return nil, fmt.Errorf("invalid glob `%s`: %s", pattern, err)
   260  		}
   261  		return &matcher, nil
   262  	case RegexpValueType:
   263  		var matcher RegexpStringMatcher
   264  		if err := matcher.Compile(pattern, opts.CaseInsensitive); err != nil {
   265  			return nil, fmt.Errorf("invalid regexp `%s`: %s", pattern, err)
   266  		}
   267  		return &matcher, nil
   268  	case ScalarValueType:
   269  		var matcher ScalarStringMatcher
   270  		if err := matcher.Compile(pattern, opts.CaseInsensitive); err != nil {
   271  			return nil, fmt.Errorf("invalid regexp `%s`: %s", pattern, err)
   272  		}
   273  		return &matcher, nil
   274  	}
   275  
   276  	return nil, errors.New("unknown type")
   277  }