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 }