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  }