github.com/rajeev159/opa@v0.45.0/topdown/regex.go (about) 1 // Copyright 2016 The OPA Authors. All rights reserved. 2 // Use of this source code is governed by an Apache2 3 // license that can be found in the LICENSE file. 4 5 package topdown 6 7 import ( 8 "fmt" 9 "regexp" 10 "sync" 11 12 gintersect "github.com/yashtewari/glob-intersection" 13 14 "github.com/open-policy-agent/opa/ast" 15 "github.com/open-policy-agent/opa/topdown/builtins" 16 ) 17 18 var regexpCacheLock = sync.Mutex{} 19 var regexpCache map[string]*regexp.Regexp 20 21 func builtinRegexIsValid(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { 22 23 s, err := builtins.StringOperand(operands[0].Value, 1) 24 if err != nil { 25 return iter(ast.BooleanTerm(false)) 26 } 27 28 _, err = regexp.Compile(string(s)) 29 if err != nil { 30 return iter(ast.BooleanTerm(false)) 31 } 32 33 return iter(ast.BooleanTerm(true)) 34 } 35 36 func builtinRegexMatch(a, b ast.Value) (ast.Value, error) { 37 s1, err := builtins.StringOperand(a, 1) 38 if err != nil { 39 return nil, err 40 } 41 s2, err := builtins.StringOperand(b, 2) 42 if err != nil { 43 return nil, err 44 } 45 re, err := getRegexp(string(s1)) 46 if err != nil { 47 return nil, err 48 } 49 return ast.Boolean(re.Match([]byte(s2))), nil 50 } 51 52 func builtinRegexMatchTemplate(a, b, c, d ast.Value) (ast.Value, error) { 53 pattern, err := builtins.StringOperand(a, 1) 54 if err != nil { 55 return nil, err 56 } 57 match, err := builtins.StringOperand(b, 2) 58 if err != nil { 59 return nil, err 60 } 61 start, err := builtins.StringOperand(c, 3) 62 if err != nil { 63 return nil, err 64 } 65 end, err := builtins.StringOperand(d, 4) 66 if err != nil { 67 return nil, err 68 } 69 if len(start) != 1 { 70 return nil, fmt.Errorf("start delimiter has to be exactly one character long but is %d long", len(start)) 71 } 72 if len(end) != 1 { 73 return nil, fmt.Errorf("end delimiter has to be exactly one character long but is %d long", len(start)) 74 } 75 re, err := getRegexpTemplate(string(pattern), string(start)[0], string(end)[0]) 76 if err != nil { 77 return nil, err 78 } 79 return ast.Boolean(re.MatchString(string(match))), nil 80 } 81 82 func builtinRegexSplit(a, b ast.Value) (ast.Value, error) { 83 s1, err := builtins.StringOperand(a, 1) 84 if err != nil { 85 return nil, err 86 } 87 s2, err := builtins.StringOperand(b, 2) 88 if err != nil { 89 return nil, err 90 } 91 re, err := getRegexp(string(s1)) 92 if err != nil { 93 return nil, err 94 } 95 96 elems := re.Split(string(s2), -1) 97 arr := make([]*ast.Term, len(elems)) 98 for i := range elems { 99 arr[i] = ast.StringTerm(elems[i]) 100 } 101 return ast.NewArray(arr...), nil 102 } 103 104 func getRegexp(pat string) (*regexp.Regexp, error) { 105 regexpCacheLock.Lock() 106 defer regexpCacheLock.Unlock() 107 re, ok := regexpCache[pat] 108 if !ok { 109 var err error 110 re, err = regexp.Compile(string(pat)) 111 if err != nil { 112 return nil, err 113 } 114 regexpCache[pat] = re 115 } 116 return re, nil 117 } 118 119 func getRegexpTemplate(pat string, delimStart, delimEnd byte) (*regexp.Regexp, error) { 120 regexpCacheLock.Lock() 121 defer regexpCacheLock.Unlock() 122 re, ok := regexpCache[pat] 123 if !ok { 124 var err error 125 re, err = compileRegexTemplate(string(pat), delimStart, delimEnd) 126 if err != nil { 127 return nil, err 128 } 129 regexpCache[pat] = re 130 } 131 return re, nil 132 } 133 134 func builtinGlobsMatch(a, b ast.Value) (ast.Value, error) { 135 s1, err := builtins.StringOperand(a, 1) 136 if err != nil { 137 return nil, err 138 } 139 s2, err := builtins.StringOperand(b, 2) 140 if err != nil { 141 return nil, err 142 } 143 ne, err := gintersect.NonEmpty(string(s1), string(s2)) 144 if err != nil { 145 return nil, err 146 } 147 return ast.Boolean(ne), nil 148 } 149 150 func builtinRegexFind(a, b, c ast.Value) (ast.Value, error) { 151 s1, err := builtins.StringOperand(a, 1) 152 if err != nil { 153 return nil, err 154 } 155 s2, err := builtins.StringOperand(b, 2) 156 if err != nil { 157 return nil, err 158 } 159 n, err := builtins.IntOperand(c, 3) 160 if err != nil { 161 return nil, err 162 } 163 re, err := getRegexp(string(s1)) 164 if err != nil { 165 return nil, err 166 } 167 168 elems := re.FindAllString(string(s2), n) 169 arr := make([]*ast.Term, len(elems)) 170 for i := range elems { 171 arr[i] = ast.StringTerm(elems[i]) 172 } 173 return ast.NewArray(arr...), nil 174 } 175 176 func builtinRegexFindAllStringSubmatch(a, b, c ast.Value) (ast.Value, error) { 177 s1, err := builtins.StringOperand(a, 1) 178 if err != nil { 179 return nil, err 180 } 181 s2, err := builtins.StringOperand(b, 2) 182 if err != nil { 183 return nil, err 184 } 185 n, err := builtins.IntOperand(c, 3) 186 if err != nil { 187 return nil, err 188 } 189 190 re, err := getRegexp(string(s1)) 191 if err != nil { 192 return nil, err 193 } 194 matches := re.FindAllStringSubmatch(string(s2), n) 195 196 outer := make([]*ast.Term, len(matches)) 197 for i := range matches { 198 inner := make([]*ast.Term, len(matches[i])) 199 for j := range matches[i] { 200 inner[j] = ast.StringTerm(matches[i][j]) 201 } 202 outer[i] = ast.NewTerm(ast.NewArray(inner...)) 203 } 204 205 return ast.NewArray(outer...), nil 206 } 207 208 func init() { 209 regexpCache = map[string]*regexp.Regexp{} 210 RegisterBuiltinFunc(ast.RegexIsValid.Name, builtinRegexIsValid) 211 RegisterFunctionalBuiltin2(ast.RegexMatch.Name, builtinRegexMatch) 212 RegisterFunctionalBuiltin2(ast.RegexMatchDeprecated.Name, builtinRegexMatch) 213 RegisterFunctionalBuiltin2(ast.RegexSplit.Name, builtinRegexSplit) 214 RegisterFunctionalBuiltin2(ast.GlobsMatch.Name, builtinGlobsMatch) 215 RegisterFunctionalBuiltin4(ast.RegexTemplateMatch.Name, builtinRegexMatchTemplate) 216 RegisterFunctionalBuiltin3(ast.RegexFind.Name, builtinRegexFind) 217 RegisterFunctionalBuiltin3(ast.RegexFindAllStringSubmatch.Name, builtinRegexFindAllStringSubmatch) 218 }