github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/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  }