dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/utils/matcher/string_matcher.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. 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, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * 20 * Copyright 2020 gRPC authors. 21 * 22 */ 23 24 // Package matcher contains types that need to be shared between code under 25 // google.golang.org/grpc/xds/... and the rest of gRPC. 26 package matcher 27 28 import ( 29 "errors" 30 "fmt" 31 "regexp" 32 "strings" 33 ) 34 35 import ( 36 v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" 37 ) 38 39 import ( 40 "dubbo.apache.org/dubbo-go/v3/xds/utils/grpcutil" 41 ) 42 43 // StringMatcher contains match criteria for matching a string, and is an 44 // internal representation of the `StringMatcher` proto defined at 45 // https://github.com/envoyproxy/envoy/blob/main/api/envoy/type/matcher/v3/string.proto. 46 type StringMatcher struct { 47 // Since these match fields are part of a `oneof` in the corresponding xDS 48 // proto, only one of them is expected to be set. 49 exactMatch *string 50 prefixMatch *string 51 suffixMatch *string 52 regexMatch *regexp.Regexp 53 containsMatch *string 54 // If true, indicates the exact/prefix/suffix/contains matching should be 55 // case insensitive. This has no effect on the regex match. 56 ignoreCase bool 57 } 58 59 // Match returns true if input matches the criteria in the given StringMatcher. 60 func (sm StringMatcher) Match(input string) bool { 61 if sm.ignoreCase { 62 input = strings.ToLower(input) 63 } 64 switch { 65 case sm.exactMatch != nil: 66 return input == *sm.exactMatch 67 case sm.prefixMatch != nil: 68 return strings.HasPrefix(input, *sm.prefixMatch) 69 case sm.suffixMatch != nil: 70 return strings.HasSuffix(input, *sm.suffixMatch) 71 case sm.regexMatch != nil: 72 return grpcutil.FullMatchWithRegex(sm.regexMatch, input) 73 case sm.containsMatch != nil: 74 return strings.Contains(input, *sm.containsMatch) 75 } 76 return false 77 } 78 79 // StringMatcherFromProto is a helper function to create a StringMatcher from 80 // the corresponding StringMatcher proto. 81 // 82 // Returns a non-nil error if matcherProto is invalid. 83 func StringMatcherFromProto(matcherProto *v3matcherpb.StringMatcher) (StringMatcher, error) { 84 if matcherProto == nil { 85 return StringMatcher{}, errors.New("input StringMatcher proto is nil") 86 } 87 88 matcher := StringMatcher{ignoreCase: matcherProto.GetIgnoreCase()} 89 switch mt := matcherProto.GetMatchPattern().(type) { 90 case *v3matcherpb.StringMatcher_Exact: 91 matcher.exactMatch = &mt.Exact 92 if matcher.ignoreCase { 93 *matcher.exactMatch = strings.ToLower(*matcher.exactMatch) 94 } 95 case *v3matcherpb.StringMatcher_Prefix: 96 if matcherProto.GetPrefix() == "" { 97 return StringMatcher{}, errors.New("empty prefix is not allowed in StringMatcher") 98 } 99 matcher.prefixMatch = &mt.Prefix 100 if matcher.ignoreCase { 101 *matcher.prefixMatch = strings.ToLower(*matcher.prefixMatch) 102 } 103 case *v3matcherpb.StringMatcher_Suffix: 104 if matcherProto.GetSuffix() == "" { 105 return StringMatcher{}, errors.New("empty suffix is not allowed in StringMatcher") 106 } 107 matcher.suffixMatch = &mt.Suffix 108 if matcher.ignoreCase { 109 *matcher.suffixMatch = strings.ToLower(*matcher.suffixMatch) 110 } 111 case *v3matcherpb.StringMatcher_SafeRegex: 112 regex := matcherProto.GetSafeRegex().GetRegex() 113 re, err := regexp.Compile(regex) 114 if err != nil { 115 return StringMatcher{}, fmt.Errorf("safe_regex matcher %q is invalid", regex) 116 } 117 matcher.regexMatch = re 118 case *v3matcherpb.StringMatcher_Contains: 119 if matcherProto.GetContains() == "" { 120 return StringMatcher{}, errors.New("empty contains is not allowed in StringMatcher") 121 } 122 matcher.containsMatch = &mt.Contains 123 if matcher.ignoreCase { 124 *matcher.containsMatch = strings.ToLower(*matcher.containsMatch) 125 } 126 default: 127 return StringMatcher{}, fmt.Errorf("unrecognized string matcher: %+v", matcherProto) 128 } 129 return matcher, nil 130 } 131 132 // StringMatcherForTesting is a helper function to create a StringMatcher based 133 // on the given arguments. Intended only for testing purposes. 134 func StringMatcherForTesting(exact, prefix, suffix, contains *string, regex *regexp.Regexp, ignoreCase bool) StringMatcher { 135 sm := StringMatcher{ 136 exactMatch: exact, 137 prefixMatch: prefix, 138 suffixMatch: suffix, 139 regexMatch: regex, 140 containsMatch: contains, 141 ignoreCase: ignoreCase, 142 } 143 if ignoreCase { 144 switch { 145 case sm.exactMatch != nil: 146 *sm.exactMatch = strings.ToLower(*exact) 147 case sm.prefixMatch != nil: 148 *sm.prefixMatch = strings.ToLower(*prefix) 149 case sm.suffixMatch != nil: 150 *sm.suffixMatch = strings.ToLower(*suffix) 151 case sm.containsMatch != nil: 152 *sm.containsMatch = strings.ToLower(*contains) 153 } 154 } 155 return sm 156 } 157 158 // ExactMatch returns the value of the configured exact match or an empty string 159 // if exact match criteria was not specified. 160 func (sm StringMatcher) ExactMatch() string { 161 if sm.exactMatch != nil { 162 return *sm.exactMatch 163 } 164 return "" 165 } 166 167 // Equal returns true if other and sm are equivalent to each other. 168 func (sm StringMatcher) Equal(other StringMatcher) bool { 169 if sm.ignoreCase != other.ignoreCase { 170 return false 171 } 172 173 if (sm.exactMatch != nil) != (other.exactMatch != nil) || 174 (sm.prefixMatch != nil) != (other.prefixMatch != nil) || 175 (sm.suffixMatch != nil) != (other.suffixMatch != nil) || 176 (sm.regexMatch != nil) != (other.regexMatch != nil) || 177 (sm.containsMatch != nil) != (other.containsMatch != nil) { 178 return false 179 } 180 181 switch { 182 case sm.exactMatch != nil: 183 return *sm.exactMatch == *other.exactMatch 184 case sm.prefixMatch != nil: 185 return *sm.prefixMatch == *other.prefixMatch 186 case sm.suffixMatch != nil: 187 return *sm.suffixMatch == *other.suffixMatch 188 case sm.regexMatch != nil: 189 return sm.regexMatch.String() == other.regexMatch.String() 190 case sm.containsMatch != nil: 191 return *sm.containsMatch == *other.containsMatch 192 } 193 return true 194 }