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
118 changes: 77 additions & 41 deletions pkg/util/wildcard/wildcard.go
Original file line number Diff line number Diff line change
@@ -1,53 +1,89 @@
/*
* MinIO Cloud Storage, (C) 2015, 2016 MinIO, Inc.
* Copyright 2019-present by Nedim Sabic
* http://rabbitstack.github.io
* All Rights Reserved.
*
* 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
* 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.
* http://www.apache.org/licenses/LICENSE-2.0
*/

package wildcard

// 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 Match(pattern, name string) (matched bool) {
if pattern == "" {
return name == pattern
}
if pattern == "*" {
return true
}
// Does extended wildcard '*' and '?' match?
return deepMatchRune(name, pattern, false)
}
import "unicode/utf8"

func deepMatchRune(s, pattern string, simple bool) bool {
for len(pattern) > 0 {
switch pattern[0] {
default:
if len(s) == 0 || s[0] != pattern[0] {
return false
}
case '?':
if len(s) == 0 && !simple {
return false
// Match performs ASCII-first, iterative wildcard matching with UTF-8 fallback.
// It supports '*' and '?' wildcards in the pattern string.
func Match(pattern, str string) bool {
slen := len(str)
plen := len(pattern)

var p, s int
wildcardIdx, matchIdx := -1, 0

for s < slen {
if p < plen {
pb := pattern[p]

switch pb {
case '?':
// match exactly one character
if str[s] < utf8.RuneSelf && pb < utf8.RuneSelf {
p++
s++
} else {
_, psize := utf8.DecodeRuneInString(pattern[p:])
_, ssize := utf8.DecodeRuneInString(str[s:])
p += psize
s += ssize
}
continue

case '*':
// record wildcard position
wildcardIdx = p
matchIdx = s
p++
continue

default:
// literal match
if pb < utf8.RuneSelf && str[s] < utf8.RuneSelf {
if pb == str[s] {
p++
s++
continue
}
} else {
pr, psize := utf8.DecodeRuneInString(pattern[p:])
sr, ssize := utf8.DecodeRuneInString(str[s:])
if pr == sr {
p += psize
s += ssize
continue
}
}
}
case '*':
return deepMatchRune(s, pattern[1:], simple) ||
(len(s) > 0 && deepMatchRune(s[1:], pattern, simple))
}
s = s[1:]
pattern = pattern[1:]

// backtrack if there was a previous '*'
if wildcardIdx != -1 {
p = wildcardIdx + 1
matchIdx++
s = matchIdx
continue
}

// previous '*', and mismatch
return false
}

// Skip remaining stars in pattern
for p < plen && pattern[p] == '*' {
p++
}
return len(s) == 0 && len(pattern) == 0

return p == plen
}
32 changes: 24 additions & 8 deletions pkg/util/wildcard/wildcard_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 by Nedim Sabic
* Copyright 2019-present by Nedim Sabic
* http://rabbitstack.github.io
* All Rights Reserved.
*
Expand All @@ -13,15 +13,31 @@
package wildcard

import (
"github.com/stretchr/testify/assert"
"testing"

"github.com/stretchr/testify/assert"
)

func TestMatch(t *testing.T) {
assert.True(t, Match("C:\\*\\lsass?.dmp", "C:\\Windows\\System32\\lsass2.dmp"))
assert.True(t, Match("C:\\*\\ActionList.x?l", "C:\\Windows\\Setup\\LatentAcquisition\\ActionList.xml"))
assert.True(t, Match("C:\\ProgramData\\*.dll", "C:\\ProgramData\\Directory\\OneMoreDirectory\\mal.dll"))
assert.True(t, Match("C:\\ProgramData\\*.dll", "C:\\ProgramData\\Directory\\OneMoreDirectory\\mal.dll"))
assert.True(t, Match("HKEY_USERS\\*\\Environment\\windir", "HKEY_USERS\\S-1-5-21-2271034452-2606270099-984871569-1001\\Environment\\windir"))
assert.True(t, Match("C:\\Windows\\SoftwareDistribution\\*", "C:\\Windows\\SoftwareDistribution\\SLS\\7971F918-A847-4430-9279-4A52D1EFE18D\\sls.rar"))
var tests = []struct {
p string
s string
match bool
}{
{"C:\\*\\lsass?.dmp", "C:\\Windows\\System32\\lsass2.dmp", true},
{"?:\\*\\lsass?.dmp", "C:\\Windows\\System32\\lsass2.dmp", true},
{"?:\\*\\lsass?.dmp", "C:\\Windows\\System32\\cmd.exe", false},
{"C:\\*\\ActionList.x?l", "C:\\Windows\\Setup\\LatentAcquisition\\ActionList.xml", true},
{"C:\\ProgramData\\*.dll", "C:\\ProgramData\\Directory\\OneMoreDirectory\\mal.dll", true},
{"HKEY_USERS\\*\\Environment\\windir", "HKEY_USERS\\S-1-5-21-2271034452-2606270099-984871569-1001\\Environment\\windir", true},
{"C:\\Windows\\SoftwareDistribution\\*", "C:\\Windows\\SoftwareDistribution\\SLS\\7971F918-A847-4430-9279-4A52D1EFE18D\\sls.rar", true},
{"HKEY_USERS\\S-1-5-21-*_CLASSES\\MS-SETTINGS\\CURVER", "HKEY_USERS\\S-1-5-21-2271034452-1207270099-244871569-1021_CLASSES\\MS-SETTINGS\\CURVER", true},
{"ntdll.dll|KernelBase.dll|advapi32.dll|*", "ntdll.dll|KernelBase.dll|advapi32.dll|pe386.dll|com.dll|clr.dll|mmc.exe", true},
}

for _, tt := range tests {
t.Run(tt.p, func(t *testing.T) {
assert.Equal(t, tt.match, Match(tt.p, tt.s))
})
}
}