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 }