github.com/cilium/cilium@v1.16.2/pkg/fqdn/matchpattern/matchpattern.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package matchpattern
     5  
     6  import (
     7  	"errors"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/cilium/cilium/pkg/fqdn/dns"
    12  	"github.com/cilium/cilium/pkg/fqdn/re"
    13  )
    14  
    15  const allowedDNSCharsREGroup = "[-a-zA-Z0-9_]"
    16  
    17  // MatchAllAnchoredPattern is the simplest pattern that match all inputs. This resulting
    18  // parsed regular expression is the same as an empty string regex (""), but this
    19  // value is easier to reason about when serializing to and from json.
    20  const MatchAllAnchoredPattern = "(?:)"
    21  
    22  // MatchAllUnAnchoredPattern is the same as MatchAllAnchoredPattern, except that
    23  // it can be or-ed (joined with "|") with other rules, and still match all rules.
    24  const MatchAllUnAnchoredPattern = ".*"
    25  
    26  // Validate ensures that pattern is a parseable matchPattern. It returns the
    27  // regexp generated when validating.
    28  func Validate(pattern string) (matcher *regexp.Regexp, err error) {
    29  	if err := prevalidate(pattern); err != nil {
    30  		return nil, err
    31  	}
    32  	return re.CompileRegex(ToAnchoredRegexp(pattern))
    33  }
    34  
    35  // ValidateWithoutCache is the same as Validate() but doesn't consult the regex
    36  // LRU.
    37  func ValidateWithoutCache(pattern string) (matcher *regexp.Regexp, err error) {
    38  	if err := prevalidate(pattern); err != nil {
    39  		return nil, err
    40  	}
    41  	return regexp.Compile(ToAnchoredRegexp(pattern))
    42  }
    43  
    44  func prevalidate(pattern string) error {
    45  	pattern = strings.TrimSpace(pattern)
    46  	pattern = strings.ToLower(pattern)
    47  
    48  	// error check
    49  	if strings.ContainsAny(pattern, "[]+{},") {
    50  		return errors.New(`Only alphanumeric ASCII characters, the hyphen "-", underscore "_", "." and "*" are allowed in a matchPattern`)
    51  	}
    52  
    53  	return nil
    54  }
    55  
    56  // Sanitize canonicalized the pattern for use by ToAnchoredRegexp
    57  func Sanitize(pattern string) string {
    58  	if pattern == "*" {
    59  		return pattern
    60  	}
    61  
    62  	return dns.FQDN(pattern)
    63  }
    64  
    65  // ToAnchoredRegexp converts a MatchPattern field into a regexp string. It does not
    66  // validate the pattern. It also adds anchors to ensure it match the whole string.
    67  // It supports:
    68  // * to select 0 or more DNS valid characters
    69  func ToAnchoredRegexp(pattern string) string {
    70  	pattern = strings.TrimSpace(pattern)
    71  	pattern = strings.ToLower(pattern)
    72  
    73  	// handle the * match-all case. This will filter down to the end.
    74  	if pattern == "*" {
    75  		return "(^(" + allowedDNSCharsREGroup + "+[.])+$)|(^[.]$)"
    76  	}
    77  
    78  	pattern = escapeRegexpCharacters(pattern)
    79  
    80  	// Anchor the match to require the whole string to match this expression
    81  	return "^" + pattern + "$"
    82  }
    83  
    84  // ToUnAnchoredRegexp converts a MatchPattern field into a regexp string. It does not
    85  // validate the pattern. It does not add regexp anchors.
    86  // It supports:
    87  // * to select 0 or more DNS valid characters
    88  func ToUnAnchoredRegexp(pattern string) string {
    89  	pattern = strings.TrimSpace(pattern)
    90  	pattern = strings.ToLower(pattern)
    91  	// handle the * match-all case. This will filter down to the end.
    92  	if pattern == "*" {
    93  		return MatchAllUnAnchoredPattern
    94  	}
    95  	pattern = escapeRegexpCharacters(pattern)
    96  	return pattern
    97  }
    98  
    99  func escapeRegexpCharacters(pattern string) string {
   100  	// base case. "." becomes a literal .
   101  	pattern = strings.Replace(pattern, ".", "[.]", -1)
   102  
   103  	// base case. * becomes .*, but only for DNS valid characters
   104  	// NOTE: this only works because the case above does not leave the *
   105  	pattern = strings.Replace(pattern, "*", allowedDNSCharsREGroup+"*", -1)
   106  	return pattern
   107  }