github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/secrets/matchers.go (about) 1 // (c) Copyright IBM Corp. 2021 2 // (c) Copyright Instana Inc. 2020 3 4 package secrets 5 6 import ( 7 "bytes" 8 "fmt" 9 "regexp" 10 "strings" 11 ) 12 13 // NoneMatcher does not match any string. It's used as a default value for (instana.Options).Secrets 14 type NoneMatcher struct{} 15 16 // Match returns false for any string 17 func (m NoneMatcher) Match(s string) bool { return false } 18 19 // EqualsMatcher matches a string that is contained in the terms list. This is the 20 // matcher for the 'equals' match type 21 type EqualsMatcher struct { 22 list []string 23 } 24 25 // NewEqualsMatcher returns an EqualsMatcher for a list of terms 26 func NewEqualsMatcher(terms ...string) EqualsMatcher { 27 return EqualsMatcher{terms} 28 } 29 30 // Match returns true if provided value present in matcher's terms list 31 func (m EqualsMatcher) Match(s string) bool { 32 for _, term := range m.list { 33 if term == s { 34 return true 35 } 36 } 37 38 return false 39 } 40 41 // EqualsIgnoreCaseMatcher is the case-insensitive version of EqualsMatcher. This 42 // is the matcher for the 'equals-ignore-case' match type 43 type EqualsIgnoreCaseMatcher struct { 44 m EqualsMatcher 45 } 46 47 // NewEqualsIgnoreCaseMatcher returns an EqualsIgnoreCaseMatcher for a list of terms 48 func NewEqualsIgnoreCaseMatcher(terms ...string) EqualsIgnoreCaseMatcher { 49 for i := range terms { 50 terms[i] = strings.ToLower(terms[i]) 51 } 52 53 return EqualsIgnoreCaseMatcher{ 54 m: NewEqualsMatcher(terms...), 55 } 56 } 57 58 // Match returns true if provided value present in matcher's terms list regardless of the case 59 func (m EqualsIgnoreCaseMatcher) Match(s string) bool { 60 return m.m.Match(strings.ToLower(s)) 61 } 62 63 // ContainsMatcher matches a string if it contains at any of the terms in the matcher's list. 64 // This is the matcher for the 'contains' match type 65 type ContainsMatcher struct { 66 list []string 67 } 68 69 // NewContainsMatcher returns a ContainsMatcher for a list of terms 70 func NewContainsMatcher(terms ...string) ContainsMatcher { 71 return ContainsMatcher{terms} 72 } 73 74 // Match returns true if a string contains any of matcher's terms 75 func (m ContainsMatcher) Match(s string) bool { 76 for _, term := range m.list { 77 if strings.Contains(s, term) { 78 return true 79 } 80 } 81 82 return false 83 } 84 85 // ContainsIgnoreCaseMatcher is the case-insensitive version of ContainsMatcher. This 86 // is the matcher for the 'contains-ignore-case' match type 87 type ContainsIgnoreCaseMatcher struct { 88 m ContainsMatcher 89 } 90 91 // NewContainsIgnoreCaseMatcher returns a ContainsIgnoreCaseMatcher for a list of terms 92 func NewContainsIgnoreCaseMatcher(terms ...string) ContainsIgnoreCaseMatcher { 93 for i := range terms { 94 terms[i] = strings.ToLower(terms[i]) 95 } 96 97 return ContainsIgnoreCaseMatcher{ 98 m: NewContainsMatcher(terms...), 99 } 100 } 101 102 // Match returns true if a string contains any of matcher's terms regardless of the case 103 func (m ContainsIgnoreCaseMatcher) Match(s string) bool { 104 return m.m.Match(strings.ToLower(s)) 105 } 106 107 var matchNothingRegexpMatcher = RegexpMatcher{regexp.MustCompile(".^")} 108 109 // RegexpMatcher matches a string using a set of regular expressions. This is the matcher 110 // for the 'regex' match type 111 type RegexpMatcher struct { 112 re *regexp.Regexp 113 } 114 115 // NewRegexpMatcher returns a RegexpMatcher for a list of regular expressions 116 func NewRegexpMatcher(terms ...*regexp.Regexp) (RegexpMatcher, error) { 117 if len(terms) == 0 { 118 return matchNothingRegexpMatcher, nil 119 } 120 121 // combine expressions into one using OR, i.e. 122 // [RE1, RE2, ..., REn] -> (RE1)|(RE2)|...|(REn) 123 buf := bytes.NewBuffer([]byte(`(\A`)) 124 sep := []byte(`\z)|(\A`) 125 126 for _, term := range terms { 127 reBytes := []byte(term.String()) 128 // strip leading beginning-of-line matchers, as they are already included into the combined expression 129 reBytes = bytes.TrimPrefix(bytes.TrimLeft(reBytes, "^"), []byte(`\A`)) 130 // strip trailing end-of-line matchers, as they are already included into the combined expression 131 reBytes = bytes.TrimSuffix(bytes.TrimRight(reBytes, "$"), []byte(`\z`)) 132 133 buf.Write(reBytes) 134 buf.Write(sep) 135 } 136 buf.Truncate(buf.Len() - len(sep)) // trim trailing separator 137 buf.WriteString(`\z)`) 138 139 combined := buf.String() 140 141 re, err := regexp.Compile(combined) 142 if err != nil { 143 return matchNothingRegexpMatcher, fmt.Errorf("malformed regexp %q: %s", combined, err) 144 } 145 146 return RegexpMatcher{re}, nil 147 } 148 149 // Match returns true if a string fully matches any of matcher's regular expessions. If an expression matches only 150 // a part of string, this method returns false: 151 // 152 // m := NewRegexpMatcher(regexp.MustCompile(`aaa`), regexp.MustCompile(`bbb`)) 153 // m.Match("aaa") // returns true 154 // m.Match("bbb") // returns true 155 // m.Match("aaabbb") // returns false, as both regular expressions match only a part of the string 156 func (m RegexpMatcher) Match(s string) bool { 157 return m.re.MatchString(s) 158 }