pax_global_header00006660000000000000000000000064141300535350014511gustar00rootroot0000000000000052 comment=4c9fc61b493b7aa0a3d347e9190aa78c5bec09cf match-1.1.1/000077500000000000000000000000001413005353500126055ustar00rootroot00000000000000match-1.1.1/.github/000077500000000000000000000000001413005353500141455ustar00rootroot00000000000000match-1.1.1/.github/workflows/000077500000000000000000000000001413005353500162025ustar00rootroot00000000000000match-1.1.1/.github/workflows/go.yml000066400000000000000000000012221413005353500173270ustar00rootroot00000000000000name: Go on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ^1.13 - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: | go get -v -t -d ./... if [ -f Gopkg.toml ]; then curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure fi - name: Build run: go build -v . - name: Test run: go test -v . match-1.1.1/LICENSE000066400000000000000000000020651413005353500136150ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Josh Baker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. match-1.1.1/README.md000066400000000000000000000010551413005353500140650ustar00rootroot00000000000000# Match [![GoDoc](https://godoc.org/github.com/tidwall/match?status.svg)](https://godoc.org/github.com/tidwall/match) Match is a very simple pattern matcher where '*' matches on any number characters and '?' matches on any one character. ## Installing ``` go get -u github.com/tidwall/match ``` ## Example ```go match.Match("hello", "*llo") match.Match("jello", "?ello") match.Match("hello", "h*o") ``` ## Contact Josh Baker [@tidwall](http://twitter.com/tidwall) ## License Redcon source code is available under the MIT [License](/LICENSE). match-1.1.1/go.mod000066400000000000000000000000511413005353500137070ustar00rootroot00000000000000module github.com/tidwall/match go 1.15 match-1.1.1/match.go000066400000000000000000000125741413005353500142410ustar00rootroot00000000000000// Package match provides a simple pattern matcher with unicode support. package match import ( "unicode/utf8" ) // Match returns true if str matches pattern. This is a very // simple wildcard match where '*' matches on any number characters // and '?' matches on any one character. // // pattern: // { term } // term: // '*' matches any sequence of non-Separator characters // '?' matches any single non-Separator character // c matches character c (c != '*', '?', '\\') // '\\' c matches character c // func Match(str, pattern string) bool { if pattern == "*" { return true } return match(str, pattern, 0, nil, -1) == rMatch } // MatchLimit is the same as Match but will limit the complexity of the match // operation. This is to avoid long running matches, specifically to avoid ReDos // attacks from arbritary inputs. // // How it works: // The underlying match routine is recursive and may call itself when it // encounters a sandwiched wildcard pattern, such as: `user:*:name`. // Everytime it calls itself a counter is incremented. // The operation is stopped when counter > maxcomp*len(str). func MatchLimit(str, pattern string, maxcomp int) (matched, stopped bool) { if pattern == "*" { return true, false } counter := 0 r := match(str, pattern, len(str), &counter, maxcomp) if r == rStop { return false, true } return r == rMatch, false } type result int const ( rNoMatch result = iota rMatch rStop ) func match(str, pat string, slen int, counter *int, maxcomp int) result { // check complexity limit if maxcomp > -1 { if *counter > slen*maxcomp { return rStop } *counter++ } for len(pat) > 0 { var wild bool pc, ps := rune(pat[0]), 1 if pc > 0x7f { pc, ps = utf8.DecodeRuneInString(pat) } var sc rune var ss int if len(str) > 0 { sc, ss = rune(str[0]), 1 if sc > 0x7f { sc, ss = utf8.DecodeRuneInString(str) } } switch pc { case '?': if ss == 0 { return rNoMatch } case '*': // Ignore repeating stars. for len(pat) > 1 && pat[1] == '*' { pat = pat[1:] } // If this star is the last character then it must be a match. if len(pat) == 1 { return rMatch } // Match and trim any non-wildcard suffix characters. var ok bool str, pat, ok = matchTrimSuffix(str, pat) if !ok { return rNoMatch } // Check for single star again. if len(pat) == 1 { return rMatch } // Perform recursive wildcard search. r := match(str, pat[1:], slen, counter, maxcomp) if r != rNoMatch { return r } if len(str) == 0 { return rNoMatch } wild = true default: if ss == 0 { return rNoMatch } if pc == '\\' { pat = pat[ps:] pc, ps = utf8.DecodeRuneInString(pat) if ps == 0 { return rNoMatch } } if sc != pc { return rNoMatch } } str = str[ss:] if !wild { pat = pat[ps:] } } if len(str) == 0 { return rMatch } return rNoMatch } // matchTrimSuffix matches and trims any non-wildcard suffix characters. // Returns the trimed string and pattern. // // This is called because the pattern contains extra data after the wildcard // star. Here we compare any suffix characters in the pattern to the suffix of // the target string. Basically a reverse match that stops when a wildcard // character is reached. This is a little trickier than a forward match because // we need to evaluate an escaped character in reverse. // // Any matched characters will be trimmed from both the target // string and the pattern. func matchTrimSuffix(str, pat string) (string, string, bool) { // It's expected that the pattern has at least two bytes and the first byte // is a wildcard star '*' match := true for len(str) > 0 && len(pat) > 1 { pc, ps := utf8.DecodeLastRuneInString(pat) var esc bool for i := 0; ; i++ { if pat[len(pat)-ps-i-1] != '\\' { if i&1 == 1 { esc = true ps++ } break } } if pc == '*' && !esc { match = true break } sc, ss := utf8.DecodeLastRuneInString(str) if !((pc == '?' && !esc) || pc == sc) { match = false break } str = str[:len(str)-ss] pat = pat[:len(pat)-ps] } return str, pat, match } var maxRuneBytes = [...]byte{244, 143, 191, 191} // Allowable parses the pattern and determines the minimum and maximum allowable // values that the pattern can represent. // When the max cannot be determined, 'true' will be returned // for infinite. func Allowable(pattern string) (min, max string) { if pattern == "" || pattern[0] == '*' { return "", "" } minb := make([]byte, 0, len(pattern)) maxb := make([]byte, 0, len(pattern)) var wild bool for i := 0; i < len(pattern); i++ { if pattern[i] == '*' { wild = true break } if pattern[i] == '?' { minb = append(minb, 0) maxb = append(maxb, maxRuneBytes[:]...) } else { minb = append(minb, pattern[i]) maxb = append(maxb, pattern[i]) } } if wild { r, n := utf8.DecodeLastRune(maxb) if r != utf8.RuneError { if r < utf8.MaxRune { r++ if r > 0x7f { b := make([]byte, 4) nn := utf8.EncodeRune(b, r) maxb = append(maxb[:len(maxb)-n], b[:nn]...) } else { maxb = append(maxb[:len(maxb)-n], byte(r)) } } } } return string(minb), string(maxb) } // IsPattern returns true if the string is a pattern. func IsPattern(str string) bool { for i := 0; i < len(str); i++ { if str[i] == '*' || str[i] == '?' { return true } } return false } match-1.1.1/match_test.go000066400000000000000000000265541413005353500153030ustar00rootroot00000000000000package match import ( "fmt" "math/rand" "strings" "testing" "time" "unicode/utf8" ) func TestMatch(t *testing.T) { if !Match("hello world", "hello world") { t.Fatal("fail") } if Match("hello world", "jello world") { t.Fatal("fail") } if !Match("hello world", "hello*") { t.Fatal("fail") } if Match("hello world", "jello*") { t.Fatal("fail") } if !Match("hello world", "hello?world") { t.Fatal("fail") } if Match("hello world", "jello?world") { t.Fatal("fail") } if !Match("hello world", "he*o?world") { t.Fatal("fail") } if !Match("hello world", "he*o?wor*") { t.Fatal("fail") } if !Match("hello world", "he*o?*r*") { t.Fatal("fail") } if !Match("hello*world", `hello\*world`) { t.Fatal("fail") } if !Match("he解lo*world", `he解lo\*world`) { t.Fatal("fail") } if !Match("的情况下解析一个", "*") { t.Fatal("fail") } if !Match("的情况下解析一个", "*况下*") { t.Fatal("fail") } if !Match("的情况下解析一个", "*况?*") { t.Fatal("fail") } if !Match("的情况下解析一个", "的情况?解析一个") { t.Fatal("fail") } if Match("hello world\\", "hello world\\") { t.Fatal("fail") } } // TestWildcardMatch - Tests validate the logic of wild card matching. // `WildcardMatch` supports '*' and '?' wildcards. // Sample usage: In resource matching for folder policy validation. func TestWildcardMatch(t *testing.T) { testCases := []struct { pattern string text string matched bool }{ // Test case - 1. // Test case with pattern containing key name with a prefix. Should accept the same text without a "*". { pattern: "my-folder/oo*", text: "my-folder/oo", matched: true, }, // Test case - 2. // Test case with "*" at the end of the pattern. { pattern: "my-folder/In*", text: "my-folder/India/Karnataka/", matched: true, }, // Test case - 3. // Test case with prefixes shuffled. // This should fail. { pattern: "my-folder/In*", text: "my-folder/Karnataka/India/", matched: false, }, // Test case - 4. // Test case with text expanded to the wildcards in the pattern. { pattern: "my-folder/In*/Ka*/Ban", text: "my-folder/India/Karnataka/Ban", matched: true, }, // Test case - 5. // Test case with the keyname part is repeated as prefix several times. // This is valid. { pattern: "my-folder/In*/Ka*/Ban", text: "my-folder/India/Karnataka/Ban/Ban/Ban/Ban/Ban", matched: true, }, // Test case - 6. // Test case to validate that `*` can be expanded into multiple prefixes. { pattern: "my-folder/In*/Ka*/Ban", text: "my-folder/India/Karnataka/Area1/Area2/Area3/Ban", matched: true, }, // Test case - 7. // Test case to validate that `*` can be expanded into multiple prefixes. { pattern: "my-folder/In*/Ka*/Ban", text: "my-folder/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban", matched: true, }, // Test case - 8. // Test case where the keyname part of the pattern is expanded in the text. { pattern: "my-folder/In*/Ka*/Ban", text: "my-folder/India/Karnataka/Bangalore", matched: false, }, // Test case - 9. // Test case with prefixes and wildcard expanded for all "*". { pattern: "my-folder/In*/Ka*/Ban*", text: "my-folder/India/Karnataka/Bangalore", matched: true, }, // Test case - 10. // Test case with keyname part being a wildcard in the pattern. {pattern: "my-folder/*", text: "my-folder/India", matched: true, }, // Test case - 11. { pattern: "my-folder/oo*", text: "my-folder/odo", matched: false, }, // Test case with pattern containing wildcard '?'. // Test case - 12. // "my-folder?/" matches "my-folder1/", "my-folder2/", "my-folder3" etc... // doesn't match "myfolder/". { pattern: "my-folder?/abc*", text: "myfolder/abc", matched: false, }, // Test case - 13. { pattern: "my-folder?/abc*", text: "my-folder1/abc", matched: true, }, // Test case - 14. { pattern: "my-?-folder/abc*", text: "my--folder/abc", matched: false, }, // Test case - 15. { pattern: "my-?-folder/abc*", text: "my-1-folder/abc", matched: true, }, // Test case - 16. { pattern: "my-?-folder/abc*", text: "my-k-folder/abc", matched: true, }, // Test case - 17. { pattern: "my??folder/abc*", text: "myfolder/abc", matched: false, }, // Test case - 18. { pattern: "my??folder/abc*", text: "my4afolder/abc", matched: true, }, // Test case - 19. { pattern: "my-folder?abc*", text: "my-folder/abc", matched: true, }, // Test case 20-21. // '?' matches '/' too. (works with s3). // This is because the namespace is considered flat. // "abc?efg" matches both "abcdefg" and "abc/efg". { pattern: "my-folder/abc?efg", text: "my-folder/abcdefg", matched: true, }, { pattern: "my-folder/abc?efg", text: "my-folder/abc/efg", matched: true, }, // Test case - 22. { pattern: "my-folder/abc????", text: "my-folder/abc", matched: false, }, // Test case - 23. { pattern: "my-folder/abc????", text: "my-folder/abcde", matched: false, }, // Test case - 24. { pattern: "my-folder/abc????", text: "my-folder/abcdefg", matched: true, }, // Test case 25-26. // test case with no '*'. { pattern: "my-folder/abc?", text: "my-folder/abc", matched: false, }, { pattern: "my-folder/abc?", text: "my-folder/abcd", matched: true, }, { pattern: "my-folder/abc?", text: "my-folder/abcde", matched: false, }, // Test case 27. { pattern: "my-folder/mnop*?", text: "my-folder/mnop", matched: false, }, // Test case 28. { pattern: "my-folder/mnop*?", text: "my-folder/mnopqrst/mnopqr", matched: true, }, // Test case 29. { pattern: "my-folder/mnop*?", text: "my-folder/mnopqrst/mnopqrs", matched: true, }, // Test case 30. { pattern: "my-folder/mnop*?", text: "my-folder/mnop", matched: false, }, // Test case 31. { pattern: "my-folder/mnop*?", text: "my-folder/mnopq", matched: true, }, // Test case 32. { pattern: "my-folder/mnop*?", text: "my-folder/mnopqr", matched: true, }, // Test case 33. { pattern: "my-folder/mnop*?and", text: "my-folder/mnopqand", matched: true, }, // Test case 34. { pattern: "my-folder/mnop*?and", text: "my-folder/mnopand", matched: false, }, // Test case 35. { pattern: "my-folder/mnop*?and", text: "my-folder/mnopqand", matched: true, }, // Test case 36. { pattern: "my-folder/mnop*?", text: "my-folder/mn", matched: false, }, // Test case 37. { pattern: "my-folder/mnop*?", text: "my-folder/mnopqrst/mnopqrs", matched: true, }, // Test case 38. { pattern: "my-folder/mnop*??", text: "my-folder/mnopqrst", matched: true, }, // Test case 39. { pattern: "my-folder/mnop*qrst", text: "my-folder/mnopabcdegqrst", matched: true, }, // Test case 40. { pattern: "my-folder/mnop*?and", text: "my-folder/mnopqand", matched: true, }, // Test case 41. { pattern: "my-folder/mnop*?and", text: "my-folder/mnopand", matched: false, }, // Test case 42. { pattern: "my-folder/mnop*?and?", text: "my-folder/mnopqanda", matched: true, }, // Test case 43. { pattern: "my-folder/mnop*?and", text: "my-folder/mnopqanda", matched: false, }, // Test case 44. { pattern: "my-?-folder/abc*", text: "my-folder/mnopqanda", matched: false, }, } // Iterating over the test cases, call the function under test and asert the output. for i, testCase := range testCases { // println("=====", i+1, "=====") actualResult := Match(testCase.text, testCase.pattern) if testCase.matched != actualResult { t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.matched, actualResult) } } } func TestRandomInput(t *testing.T) { rand.Seed(time.Now().UnixNano()) b1 := make([]byte, 100) b2 := make([]byte, 100) for i := 0; i < 1000000; i++ { if _, err := rand.Read(b1); err != nil { t.Fatal(err) } if _, err := rand.Read(b2); err != nil { t.Fatal(err) } Match(string(b1), string(b2)) } } func testAllowable(pattern, exmin, exmax string) error { min, max := Allowable(pattern) if min != exmin || max != exmax { return fmt.Errorf("expected '%v'/'%v', got '%v'/'%v'", exmin, exmax, min, max) } return nil } func TestAllowable(t *testing.T) { if err := testAllowable("*", "", ""); err != nil { t.Fatal(err) } if err := testAllowable("hell*", "hell", "helm"); err != nil { t.Fatal(err) } if err := testAllowable("hell?", "hell"+string(rune(0)), "hell"+string(utf8.MaxRune)); err != nil { t.Fatal(err) } if err := testAllowable("h解析ell*", "h解析ell", "h解析elm"); err != nil { t.Fatal(err) } if err := testAllowable("h解*ell*", "h解", "h觤"); err != nil { t.Fatal(err) } } func TestIsPattern(t *testing.T) { patterns := []string{ "*", "hello*", "hello*world", "*world", "?", "hello?", "hello?world", "?world", } nonPatterns := []string{ "", "hello", } for _, pattern := range patterns { if !IsPattern(pattern) { t.Fatalf("expected true") } } for _, s := range nonPatterns { if IsPattern(s) { t.Fatalf("expected false") } } } func BenchmarkAscii(t *testing.B) { for i := 0; i < t.N; i++ { if !Match("hello", "hello") { t.Fatal("fail") } } } func BenchmarkUnicode(t *testing.B) { for i := 0; i < t.N; i++ { if !Match("h情llo", "h情llo") { t.Fatal("fail") } } } func TestLotsaStars(t *testing.T) { // This tests that a pattern with lots of stars will complete quickly. var str, pat string str = `,**,,**,**,**,**,**,**,` pat = `,**********************************************{**",**,,**,**,` + `**,**,"",**,**,**,**,**,**,**,**,**,**]` Match(pat, str) str = strings.Replace(str, ",", "情", -1) pat = strings.Replace(pat, ",", "情", -1) Match(pat, str) str = strings.Repeat("hello", 100) pat = `*?*?*?*?*?*?*""` Match(str, pat) str = `*?**?**?**?**?**?***?**?**?**?**?*""` pat = `*?*?*?*?*?*?**?**?**?**?**?**?**?*""` Match(str, pat) } func TestLimit(t *testing.T) { var str, pat string str = `,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,` pat = `*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*"*,*` _, stopped := MatchLimit(str, pat, 100) if !stopped { t.Fatal("expected true") } match, _ := MatchLimit(str, "*", 100) if !match { t.Fatal("expected true") } match, _ = MatchLimit(str, "*,*", 100) if !match { t.Fatal("expected true") } } func TestSuffix(t *testing.T) { sufmatch := func(t *testing.T, str, pat string, exstr, expat string, exok bool) { t.Helper() rstr, rpat, rok := matchTrimSuffix(str, pat) if rstr != exstr || rpat != expat || rok != exok { t.Fatalf( "for '%s' '%s', expected '%s' '%s' '%t', got '%s' '%s' '%t'", str, pat, exstr, expat, exok, rstr, rpat, rok) } } sufmatch(t, "hello", "*hello", "", "*", true) sufmatch(t, "jello", "*hello", "j", "*h", false) sufmatch(t, "jello", "*?ello", "", "*", true) sufmatch(t, "jello", "*\\?ello", "j", "*\\?", false) sufmatch(t, "?ello", "*\\?ello", "", "*", true) sufmatch(t, "?ello", "*\\?ello", "", "*", true) sufmatch(t, "f?ello", "*\\?ello", "f", "*", true) sufmatch(t, "f?ello", "**\\?ello", "f", "**", true) sufmatch(t, "f?el*o", "**\\?el\\*o", "f", "**", true) }