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 }