diff --git a/cmd/bucket-policy-handlers.go b/cmd/bucket-policy-handlers.go index cafb6879c..5cb206115 100644 --- a/cmd/bucket-policy-handlers.go +++ b/cmd/bucket-policy-handlers.go @@ -70,12 +70,12 @@ func bucketPolicyActionMatch(action string, statement policyStatement) bool { // Match function matches wild cards in 'pattern' for resource. func resourceMatch(pattern, resource string) bool { - return wildcard.MatchExtended(pattern, resource) + return wildcard.Match(pattern, resource) } // Match function matches wild cards in 'pattern' for action. func actionMatch(pattern, action string) bool { - return wildcard.Match(pattern, action) + return wildcard.MatchSimple(pattern, action) } // Verify if given resource matches with policy statement. diff --git a/cmd/notifiers.go b/cmd/notifiers.go index 3da1533e7..f5a2528d5 100644 --- a/cmd/notifiers.go +++ b/cmd/notifiers.go @@ -114,7 +114,7 @@ func isElasticQueue(sqsArn arnSQS) bool { // Match function matches wild cards in 'pattern' for events. func eventMatch(eventType string, events []string) (ok bool) { for _, event := range events { - ok = wildcard.Match(event, eventType) + ok = wildcard.MatchSimple(event, eventType) if ok { break } diff --git a/pkg/wildcard/match.go b/pkg/wildcard/match.go index e1119eeb6..12a36b1dd 100644 --- a/pkg/wildcard/match.go +++ b/pkg/wildcard/match.go @@ -16,123 +16,68 @@ package wildcard -import ( - "strings" - "unicode/utf8" -) - -// Match - finds whether the text matches/satisfies the pattern string. +// MatchSimple - finds whether the text matches/satisfies the pattern string. // supports only '*' wildcard in the pattern. // considers a file system path as a flat name space. -func Match(pattern, text string) bool { +func MatchSimple(pattern, name string) bool { if pattern == "" { - return text == pattern + return name == pattern } if pattern == "*" { return true } - parts := strings.Split(pattern, "*") - if len(parts) == 1 { - return text == pattern - } - tGlob := strings.HasSuffix(pattern, "*") - end := len(parts) - 1 - if !strings.HasPrefix(text, parts[0]) { - return false + rname := make([]rune, 0, len(name)) + rpattern := make([]rune, 0, len(pattern)) + for _, r := range name { + rname = append(rname, r) } - for i := 1; i < end; i++ { - if !strings.Contains(text, parts[i]) { - return false - } - idx := strings.Index(text, parts[i]) + len(parts[i]) - text = text[idx:] + for _, r := range pattern { + rpattern = append(rpattern, r) } - return tGlob || strings.HasSuffix(text, parts[end]) + simple := true // Does only wildcard '*' match. + return deepMatchRune(rname, rpattern, simple) } -// MatchExtended - finds whether the text matches/satisfies the pattern string. +// Match - finds whether the text matches/satisfies the pattern string. // supports '*' and '?' wildcards in the pattern string. // unlike path.Match(), considers a path as a flat name space while matching the pattern. // The difference is illustrated in the example here https://play.golang.org/p/Ega9qgD4Qz . -func MatchExtended(pattern, name string) (matched bool) { -Pattern: - for len(pattern) > 0 { - var star bool - var chunk string - star, chunk, pattern = scanChunk(pattern) - if star && chunk == "" { - // Trailing * matches rest of string. - return true - } - // Look for match at current position. - t, ok := matchChunk(chunk, name) - // if we're the last chunk, make sure we've exhausted the name - // otherwise we'll give a false result even if we could still match - // using the star - if ok && (len(t) == 0 || len(pattern) > 0) { - name = t - continue - } - if star { - // Look for match skipping i+1 bytes. - for i := 0; i < len(name); i++ { - t, ok := matchChunk(chunk, name[i+1:]) - if ok { - // if we're the last chunk, make sure we exhausted the name - if len(pattern) == 0 && len(t) > 0 { - continue - } - name = t - continue Pattern - } - } - } - return false +func Match(pattern, name string) (matched bool) { + if pattern == "" { + return name == pattern } - return len(name) == 0 -} - -// scanChunk gets the next segment of pattern, which is a non-star string -// possibly preceded by a star. -func scanChunk(pattern string) (star bool, chunk, rest string) { - for len(pattern) > 0 && pattern[0] == '*' { - pattern = pattern[1:] - star = true + if pattern == "*" { + return true } - inrange := false - var i int -Scan: - for i = 0; i < len(pattern); i++ { - switch pattern[i] { - case '*': - if !inrange { - break Scan - } - } + rname := make([]rune, 0, len(name)) + rpattern := make([]rune, 0, len(pattern)) + for _, r := range name { + rname = append(rname, r) + } + for _, r := range pattern { + rpattern = append(rpattern, r) } - return star, pattern[0:i], pattern[i:] + simple := false // Does extended wildcard '*' and '?' match. + return deepMatchRune(rname, rpattern, simple) } -// matchChunk checks whether chunk matches the beginning of s. -// If so, it returns the remainder of s (after the match). -// Chunk is all single-character operators: literals, char classes, and ?. -func matchChunk(chunk, s string) (rest string, ok bool) { - for len(chunk) > 0 { - if len(s) == 0 { - return - } - switch chunk[0] { - case '?': - _, n := utf8.DecodeRuneInString(s) - s = s[n:] - chunk = chunk[1:] +func deepMatchRune(str, pattern []rune, simple bool) bool { + for len(pattern) > 0 { + switch pattern[0] { default: - if chunk[0] != s[0] { - return + if len(str) == 0 || str[0] != pattern[0] { + return false + } + case '?': + if len(str) == 0 && !simple { + return false } - s = s[1:] - chunk = chunk[1:] + case '*': + return deepMatchRune(str, pattern[1:], simple) || + (len(str) > 0 && deepMatchRune(str[1:], pattern, simple)) } + str = str[1:] + pattern = pattern[1:] } - return s, true + return len(str) == 0 && len(pattern) == 0 } diff --git a/pkg/wildcard/match_test.go b/pkg/wildcard/match_test.go index d796b5208..0f6ffdb5d 100644 --- a/pkg/wildcard/match_test.go +++ b/pkg/wildcard/match_test.go @@ -14,16 +14,18 @@ * limitations under the License. */ -package wildcard +package wildcard_test import ( "testing" + + "github.com/minio/minio/pkg/wildcard" ) -// TestMatchExtended - Tests validate the logic of wild card matching. -// `MatchExtended` supports '*' and '?' wildcards. +// TestMatch - Tests validate the logic of wild card matching. +// `Match` supports '*' and '?' wildcards. // Sample usage: In resource matching for bucket policy validation. -func TestMatchExtended(t *testing.T) { +func TestMatch(t *testing.T) { testCases := []struct { pattern string text string @@ -131,7 +133,8 @@ func TestMatchExtended(t *testing.T) { }, // Test case - 15. // Test case where the keyname part of the pattern is expanded in the text. - {pattern: "my-bucket/In*/Ka*/Ban", + { + pattern: "my-bucket/In*/Ka*/Ban", text: "my-bucket/India/Karnataka/Bangalore", matched: false, }, @@ -144,7 +147,8 @@ func TestMatchExtended(t *testing.T) { }, // Test case - 17. // Test case with keyname part being a wildcard in the pattern. - {pattern: "my-bucket/*", + { + pattern: "my-bucket/*", text: "my-bucket/India", matched: true, }, @@ -367,16 +371,16 @@ func TestMatchExtended(t *testing.T) { } // Iterating over the test cases, call the function under test and asert the output. for i, testCase := range testCases { - actualResult := MatchExtended(testCase.pattern, testCase.text) + actualResult := wildcard.Match(testCase.pattern, testCase.text) 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) } } } -// TestMatch - Tests validate the logic of wild card matching. -// `Match` supports matching for only '*' in the pattern string. -func TestMatch(t *testing.T) { +// TestMatchSimple - Tests validate the logic of wild card matching. +// `MatchSimple` supports matching for only '*' in the pattern string. +func TestMatchSimple(t *testing.T) { testCases := []struct { pattern string text string @@ -484,7 +488,8 @@ func TestMatch(t *testing.T) { }, // Test case - 15. // Test case where the keyname part of the pattern is expanded in the text. - {pattern: "my-bucket/In*/Ka*/Ban", + { + pattern: "my-bucket/In*/Ka*/Ban", text: "my-bucket/India/Karnataka/Bangalore", matched: false, }, @@ -497,7 +502,8 @@ func TestMatch(t *testing.T) { }, // Test case - 17. // Test case with keyname part being a wildcard in the pattern. - {pattern: "my-bucket/*", + { + pattern: "my-bucket/*", text: "my-bucket/India", matched: true, }, @@ -507,10 +513,28 @@ func TestMatch(t *testing.T) { text: "my-bucket/odo", matched: false, }, + // Test case - 11. + { + pattern: "my-bucket/oo?*", + text: "my-bucket/oo???", + matched: true, + }, + // Test case - 12: + { + pattern: "my-bucket/oo??*", + text: "my-bucket/odo", + matched: false, + }, + // Test case - 13: + { + pattern: "?h?*", + text: "?h?hello", + matched: true, + }, } // Iterating over the test cases, call the function under test and asert the output. for i, testCase := range testCases { - actualResult := Match(testCase.pattern, testCase.text) + actualResult := wildcard.MatchSimple(testCase.pattern, testCase.text) 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) }