github.com/waldiirawan/apm-agent-go/v2@v2.2.2/internal/wildcard/matcher.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package wildcard
    19  
    20  import (
    21  	"strings"
    22  	"unicode"
    23  	"unicode/utf8"
    24  )
    25  
    26  // CaseSensitivity controls the case sensitivity of matching.
    27  type CaseSensitivity bool
    28  
    29  // CaseSensitivity values.
    30  const (
    31  	CaseSensitive   CaseSensitivity = true
    32  	CaseInsensitive CaseSensitivity = false
    33  )
    34  
    35  // NewMatcher constructs a new wildcard matcher for the given pattern.
    36  //
    37  // If p is the empty string, it will match only the empty string.
    38  // If p is not a valid UTF-8 string, matching behaviour is undefined.
    39  func NewMatcher(p string, caseSensitive CaseSensitivity) *Matcher {
    40  	parts := strings.Split(p, "*")
    41  	m := &Matcher{
    42  		wildcardBegin: strings.HasPrefix(p, "*"),
    43  		wildcardEnd:   strings.HasSuffix(p, "*"),
    44  		caseSensitive: caseSensitive,
    45  	}
    46  	for _, part := range parts {
    47  		if part == "" {
    48  			continue
    49  		}
    50  		if !m.caseSensitive {
    51  			part = strings.ToLower(part)
    52  		}
    53  		m.parts = append(m.parts, part)
    54  	}
    55  	return m
    56  }
    57  
    58  // Matcher matches strings against a wildcard pattern with configurable case sensitivity.
    59  type Matcher struct {
    60  	parts         []string
    61  	wildcardBegin bool
    62  	wildcardEnd   bool
    63  	caseSensitive CaseSensitivity
    64  }
    65  
    66  // Match reports whether s matches m's wildcard pattern.
    67  func (m *Matcher) Match(s string) bool {
    68  	if len(m.parts) == 0 && !m.wildcardBegin && !m.wildcardEnd {
    69  		return s == ""
    70  	}
    71  	if len(m.parts) == 1 && !m.wildcardBegin && !m.wildcardEnd {
    72  		if m.caseSensitive {
    73  			return s == m.parts[0]
    74  		}
    75  		return len(s) == len(m.parts[0]) && hasPrefixLower(s, m.parts[0]) == 0
    76  	}
    77  	parts := m.parts
    78  	if !m.wildcardEnd && len(parts) > 0 {
    79  		part := parts[len(parts)-1]
    80  		if m.caseSensitive {
    81  			if !strings.HasSuffix(s, part) {
    82  				return false
    83  			}
    84  		} else {
    85  			if len(s) < len(part) {
    86  				return false
    87  			}
    88  			if hasPrefixLower(s[len(s)-len(part):], part) != 0 {
    89  				return false
    90  			}
    91  		}
    92  		parts = parts[:len(parts)-1]
    93  	}
    94  	for i, part := range parts {
    95  		j := -1
    96  		if m.caseSensitive {
    97  			if i > 0 || m.wildcardBegin {
    98  				j = strings.Index(s, part)
    99  			} else {
   100  				if !strings.HasPrefix(s, part) {
   101  					return false
   102  				}
   103  				j = 0
   104  			}
   105  		} else {
   106  			off := 0
   107  			for j == -1 && len(s)-off >= len(part) {
   108  				skip := hasPrefixLower(s[off:], part)
   109  				if skip == 0 {
   110  					j = off
   111  				} else {
   112  					if i == 0 && !m.wildcardBegin {
   113  						return false
   114  					}
   115  					off += skip
   116  				}
   117  			}
   118  		}
   119  		if j == -1 {
   120  			return false
   121  		}
   122  		s = s[j+len(part):]
   123  	}
   124  	return true
   125  }
   126  
   127  // hasPrefixLower reports whether or not s begins with prefixLower,
   128  // returning 0 if it does, and the number of bytes representing the
   129  // first rune in s otherwise.
   130  func hasPrefixLower(s, prefixLower string) (skip int) {
   131  	var firstSize int
   132  	for i, r := range prefixLower {
   133  		r2, size := utf8.DecodeRuneInString(s[i:])
   134  		if firstSize == 0 {
   135  			firstSize = size
   136  		}
   137  		if r2 != r && r2 != unicode.ToUpper(r) {
   138  			return firstSize
   139  		}
   140  	}
   141  	return 0
   142  }