Skip to content

Commit

Permalink
xds: Make regex matchers match on full string, not just partial match (
Browse files Browse the repository at this point in the history
…grpc#4875)

* xds: Make regex matchers match on full string, not just partial match
  • Loading branch information
zasweq committed Oct 15, 2021
1 parent d590071 commit 4757d02
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 3 deletions.
28 changes: 28 additions & 0 deletions internal/grpcutil/regex.go
@@ -0,0 +1,28 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package grpcutil

import "regexp"

// FullMatchWithRegex returns whether the full string matches the regex provided.
func FullMatchWithRegex(re *regexp.Regexp, string string) bool {
re.Longest()
rem := re.FindString(string)
return len(rem) == len(string)
}
60 changes: 60 additions & 0 deletions internal/grpcutil/regex_test.go
@@ -0,0 +1,60 @@
/*
*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package grpcutil

import (
"regexp"
"testing"
)

func TestFullMatchWithRegex(t *testing.T) {
tests := []struct {
name string
regexStr string
string string
want bool
}{
{
name: "not match because only partial",
regexStr: "^a+$",
string: "ab",
want: false,
},
{
name: "match because fully match",
regexStr: "^a+$",
string: "aa",
want: true,
},
{
name: "longest",
regexStr: "a(|b)",
string: "ab",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hrm := regexp.MustCompile(tt.regexStr)
if got := FullMatchWithRegex(hrm, tt.string); got != tt.want {
t.Errorf("match() = %v, want %v", got, tt.want)
}
})
}
}
3 changes: 2 additions & 1 deletion internal/xds/matcher/matcher_header.go
Expand Up @@ -24,6 +24,7 @@ import (
"strconv"
"strings"

"google.golang.org/grpc/internal/grpcutil"
"google.golang.org/grpc/metadata"
)

Expand Down Expand Up @@ -91,7 +92,7 @@ func (hrm *HeaderRegexMatcher) Match(md metadata.MD) bool {
if !ok {
return false
}
return hrm.re.MatchString(v)
return grpcutil.FullMatchWithRegex(hrm.re, v)
}

func (hrm *HeaderRegexMatcher) String() string {
Expand Down
14 changes: 14 additions & 0 deletions internal/xds/matcher/matcher_header_test.go
Expand Up @@ -107,6 +107,20 @@ func TestHeaderRegexMatcherMatch(t *testing.T) {
md: metadata.Pairs("th", "abc"),
want: false,
},
{
name: "no match because only part of value matches with regex",
key: "header",
regexStr: "^a+$",
md: metadata.Pairs("header", "ab"),
want: false,
},
{
name: "match because full value matches with regex",
key: "header",
regexStr: "^a+$",
md: metadata.Pairs("header", "aa"),
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion internal/xds/matcher/string_matcher.go
Expand Up @@ -27,6 +27,7 @@ import (
"strings"

v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"google.golang.org/grpc/internal/grpcutil"
)

// StringMatcher contains match criteria for matching a string, and is an
Expand Down Expand Up @@ -58,7 +59,7 @@ func (sm StringMatcher) Match(input string) bool {
case sm.suffixMatch != nil:
return strings.HasSuffix(input, *sm.suffixMatch)
case sm.regexMatch != nil:
return sm.regexMatch.MatchString(input)
return grpcutil.FullMatchWithRegex(sm.regexMatch, input)
case sm.containsMatch != nil:
return strings.Contains(input, *sm.containsMatch)
}
Expand Down
6 changes: 6 additions & 0 deletions internal/xds/matcher/string_matcher_test.go
Expand Up @@ -266,6 +266,12 @@ func TestMatch(t *testing.T) {
input: "goodregex",
wantMatch: true,
},
{
desc: "regex match failure because only part match",
matcher: regexMatcher,
input: "goodregexa",
wantMatch: false,
},
{
desc: "regex match failure",
matcher: regexMatcher,
Expand Down
4 changes: 3 additions & 1 deletion xds/internal/xdsclient/matcher_path.go
Expand Up @@ -21,6 +21,8 @@ package xdsclient
import (
"regexp"
"strings"

"google.golang.org/grpc/internal/grpcutil"
)

type pathMatcher interface {
Expand Down Expand Up @@ -93,7 +95,7 @@ func newPathRegexMatcher(re *regexp.Regexp) *pathRegexMatcher {
}

func (prm *pathRegexMatcher) match(path string) bool {
return prm.re.MatchString(path)
return grpcutil.FullMatchWithRegex(prm.re, path)
}

func (prm *pathRegexMatcher) String() string {
Expand Down
2 changes: 2 additions & 0 deletions xds/internal/xdsclient/matcher_path_test.go
Expand Up @@ -80,6 +80,8 @@ func TestPathRegexMatcherMatch(t *testing.T) {
}{
{name: "match", regexPath: "^/s+/m.*$", path: "/sss/me", want: true},
{name: "not match", regexPath: "^/s+/m*$", path: "/sss/b", want: false},
{name: "no match because only part of path matches with regex", regexPath: "^a+$", path: "ab", want: false},
{name: "match because full path matches with regex", regexPath: "^a+$", path: "aa", want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down

0 comments on commit 4757d02

Please sign in to comment.