Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions app/allowlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,27 +129,34 @@ func matchPath(pattern, p string) bool {
}

func matchSegments(pattern, path []string) bool {
if len(pattern) == 0 {
return len(path) == 0
}
if pattern[0] == "**" {
if matchSegments(pattern[1:], path) {
// "**" matches zero segments
return true
pi, si := 0, 0
lastDoubleStar := -1
doubleStarMatch := 0

for si < len(path) {
if pi < len(pattern) && (pattern[pi] == "*" || pattern[pi] == path[si]) {
pi++
si++
continue
}
if len(path) > 0 && matchSegments(pattern, path[1:]) {
// consume one segment and try again
return true
if pi < len(pattern) && pattern[pi] == "**" {
lastDoubleStar = pi
doubleStarMatch = si
pi++
continue
}
return false
}
if len(path) == 0 {
return false
if lastDoubleStar == -1 {
return false
}
doubleStarMatch++
si = doubleStarMatch
pi = lastDoubleStar + 1
}
if pattern[0] == "*" || pattern[0] == path[0] {
return matchSegments(pattern[1:], path[1:])

for pi < len(pattern) && pattern[pi] == "**" {
pi++
}
return false
return pi == len(pattern)
}

// validateRequest checks headers and body according to the request constraint.
Expand Down
70 changes: 70 additions & 0 deletions app/allowlist_match_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ func TestMatchSegmentsEdgeCases(t *testing.T) {
{[]string{"**", "b"}, []string{"a", "b"}, true},
{[]string{"*", "b"}, []string{"a", "b"}, true},
{[]string{"a", "b"}, []string{"a"}, false},
{[]string{"**", "**", "b"}, []string{"a", "b"}, true},
{[]string{"a", "**", "b", "**", "c"}, []string{"a", "x", "b", "y", "z", "c"}, true},
{[]string{"**", "a", "**", "b"}, []string{"x", "a", "y", "c"}, false},
}
for i, tt := range cases {
if got := matchSegments(tt.pattern, tt.path); got != tt.ok {
Expand All @@ -171,6 +174,73 @@ func TestMatchSegmentsEdgeCases(t *testing.T) {
}
}

func TestMatchSegmentsDoubleStarPathologicalFailure(t *testing.T) {
pattern := make([]string, 0, 65)
for i := 0; i < 32; i++ {
pattern = append(pattern, "**", "a")
}
pattern = append(pattern, "c")

path := make([]string, 33)
for i := range path {
path[i] = "a"
}
path[len(path)-1] = "b"

if matchSegments(pattern, path) {
t.Fatal("expected pathological double-star pattern to fail")
}
}

func TestMatchSegmentsMatchesRecursiveSemantics(t *testing.T) {
patterns := segmentPermutations([]string{"a", "b", "*", "**"}, 5)
paths := segmentPermutations([]string{"a", "b"}, 4)

for _, pattern := range patterns {
for _, path := range paths {
got := matchSegments(pattern, path)
want := recursiveMatchSegments(pattern, path)
if got != want {
t.Fatalf("matchSegments(%v, %v)=%v want %v", pattern, path, got, want)
}
}
}
}

func segmentPermutations(tokens []string, maxLen int) [][]string {
var out [][]string
var build func([]string, int)
build = func(prefix []string, remaining int) {
cp := append([]string(nil), prefix...)
out = append(out, cp)
if remaining == 0 {
return
}
for _, token := range tokens {
build(append(prefix, token), remaining-1)
}
}
build(nil, maxLen)
return out
}

func recursiveMatchSegments(pattern, path []string) bool {
if len(pattern) == 0 {
return len(path) == 0
}
if pattern[0] == "**" {
return recursiveMatchSegments(pattern[1:], path) ||
(len(path) > 0 && recursiveMatchSegments(pattern, path[1:]))
}
if len(path) == 0 {
return false
}
if pattern[0] == "*" || pattern[0] == path[0] {
return recursiveMatchSegments(pattern[1:], path[1:])
}
return false
}

func TestToFloatVariousTypes(t *testing.T) {
cases := []struct {
val interface{}
Expand Down
Loading