fix(s3api): correct wildcard matching (#8052)

* fix(s3api): correct wildcard matching

* chore(tests): add multi-slash test case

in ref. to cases provided here https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html\#reference_policies_elements_resource_wildcards

* fix: gemini suggestions
This commit is contained in:
SoSweetHam
2026-01-19 04:24:03 +05:30
committed by GitHub
parent 753e1db096
commit 2662420194
3 changed files with 158 additions and 59 deletions

View File

@@ -125,21 +125,12 @@ func (c *WildcardMatcherCache) GetCacheStats() (size int, maxSize int) {
}
// NewWildcardMatcher creates a new wildcard matcher for the given pattern
// The matcher uses an efficient string-based algorithm that handles both * and ? wildcards
// without requiring regex compilation.
func NewWildcardMatcher(pattern string) (*WildcardMatcher, error) {
matcher := &WildcardMatcher{
pattern: pattern,
}
// Determine if we need regex (contains ? wildcards)
if strings.Contains(pattern, "?") {
matcher.useRegex = true
regex, err := compileWildcardPattern(pattern)
if err != nil {
return nil, err
}
matcher.regex = regex
} else {
matcher.useRegex = false
pattern: pattern,
useRegex: false, // String-based matching now handles both * and ?
}
return matcher, nil
@@ -155,19 +146,12 @@ func (m *WildcardMatcher) Match(str string) bool {
// MatchesWildcard provides a simple function interface for wildcard matching
// This function consolidates the logic from the previous separate implementations
//
// Rules:
// - '*' matches any sequence of characters (including empty string)
// - '?' matches exactly one character (any character)
func MatchesWildcard(pattern, str string) bool {
// Handle simple cases first
if pattern == "*" {
return true
}
if pattern == str {
return true
}
// Use regex for patterns with ? wildcards, string manipulation for * only
if strings.Contains(pattern, "?") {
return matchWildcardRegex(pattern, str)
}
// matchWildcardString now handles both * and ? efficiently without regex
return matchWildcardString(pattern, str)
}
@@ -177,7 +161,13 @@ func CompileWildcardPattern(pattern string) (*regexp.Regexp, error) {
return compileWildcardPattern(pattern)
}
// matchWildcardString uses string manipulation for * wildcards only (more efficient)
// matchWildcardString uses efficient string manipulation for * and ? wildcards
// This implementation uses a backtracking algorithm that handles both wildcard types
// without requiring regex compilation.
//
// Rules:
// - '*' matches any sequence of characters (including empty string)
// - '?' matches exactly one character (any character)
func matchWildcardString(pattern, str string) bool {
// Handle simple cases
if pattern == "*" {
@@ -187,43 +177,52 @@ func matchWildcardString(pattern, str string) bool {
return true
}
// Split pattern by wildcards
parts := strings.Split(pattern, "*")
if len(parts) == 1 {
// No wildcards, exact match
return pattern == str
}
targetIndex := 0
patternIndex := 0
// Check if string starts with first part
if len(parts[0]) > 0 && !strings.HasPrefix(str, parts[0]) {
return false
}
// Index of the most recent '*' in the pattern (-1 if none)
lastStarIndex := -1
// Check if string ends with last part
if len(parts[len(parts)-1]) > 0 && !strings.HasSuffix(str, parts[len(parts)-1]) {
return false
}
// Index in target where the last '*' started matching
lastStarMatchIndex := 0
// Check middle parts
searchStr := str
if len(parts[0]) > 0 {
searchStr = searchStr[len(parts[0]):]
}
if len(parts[len(parts)-1]) > 0 {
searchStr = searchStr[:len(searchStr)-len(parts[len(parts)-1])]
}
for targetIndex < len(str) {
switch {
// Case 1: Current characters match directly or '?' matches any single character
case patternIndex < len(pattern) &&
(pattern[patternIndex] == '?' || pattern[patternIndex] == str[targetIndex]):
for i := 1; i < len(parts)-1; i++ {
if len(parts[i]) > 0 {
index := strings.Index(searchStr, parts[i])
if index == -1 {
return false
}
searchStr = searchStr[index+len(parts[i]):]
targetIndex++
patternIndex++
// Case 2: Wildcard '*' found in pattern
case patternIndex < len(pattern) &&
pattern[patternIndex] == '*':
lastStarIndex = patternIndex
lastStarMatchIndex = targetIndex
patternIndex++
// Case 3: Previous '*' can absorb one more character
case lastStarIndex != -1:
patternIndex = lastStarIndex + 1
lastStarMatchIndex++
targetIndex = lastStarMatchIndex
// Case 4: No match possible
default:
return false
}
}
return true
// Consume any trailing '*' in the pattern
for patternIndex < len(pattern) && pattern[patternIndex] == '*' {
patternIndex++
}
// Match is valid only if the entire pattern is consumed
return patternIndex == len(pattern)
}
// matchWildcardRegex uses WildcardMatcher for patterns with ? wildcards