github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/auth/wildcard/match.go (about) 1 package wildcard 2 3 import ( 4 "unicode/utf8" 5 ) 6 7 // Match reports whether name matches the shell pattern. 8 // This is a strip down version of Go's `path.Match` https://pkg.go.dev/path#Match 9 // Call a "fixword" a maximal portion of the pattern consisting only of regular characters and ?s. 10 // So a fixword has to begin after * or at the beginning of the string, and it has to end before * or at the end of the string. 11 // Each fixword matches a fixed length of string. Now a pattern is a list of fixwords separated by *s. 12 // Consider a fixword that is not preceded by a *; that's an easy match to find because it can only be at one place. 13 // Consider a fixword that is preceded by a *; if it matches at multiple places then it is always safe to match it at 14 // the first possible location: either the pattern ends after that fixword in which case there's only one possible location, 15 // or the pattern continues with *, in which case that * can "expand" to pick up all characters and the next match of the fixword. 16 func Match(pattern, name string) bool { 17 Pattern: 18 for len(pattern) > 0 { 19 var ( 20 star bool 21 chunk string 22 ) 23 star, chunk, pattern = scanChunk(pattern) 24 if star && chunk == "" { 25 // Trailing * matches rest of string 26 return true 27 } 28 // Look for match at current position. 29 t, ok := matchChunk(chunk, name) 30 // if we're the last chunk, make sure we've exhausted the name 31 // otherwise we'll give a false result even if we could still match 32 // using the star 33 if ok && (len(t) == 0 || len(pattern) > 0) { 34 name = t 35 continue 36 } 37 if star { 38 // Look for match skipping i+1 bytes. 39 for i := 0; i < len(name); i++ { 40 t, ok := matchChunk(chunk, name[i+1:]) 41 if ok { 42 // if we're the last chunk, make sure we exhausted the name 43 if len(pattern) == 0 && len(t) > 0 { 44 continue 45 } 46 name = t 47 continue Pattern 48 } 49 } 50 } 51 return false 52 } 53 return len(name) == 0 54 } 55 56 // scanChunk gets the next segment of pattern, which is a non-star string 57 // possibly preceded by a star. 58 func scanChunk(pattern string) (star bool, chunk, rest string) { 59 for len(pattern) > 0 && pattern[0] == '*' { 60 pattern = pattern[1:] 61 star = true 62 } 63 for i := 1; i < len(pattern); i++ { 64 if pattern[i] == '*' { 65 return star, pattern[0:i], pattern[i:] 66 } 67 } 68 return star, pattern, "" 69 } 70 71 // matchChunk checks whether chunk matches the beginning of s. 72 // If so, it returns the remainder of s (after the match). 73 // Chunk is all single-character operators: literals, char classes, and ?. 74 func matchChunk(chunk, s string) (rest string, ok bool) { 75 for len(chunk) > 0 { 76 if len(s) == 0 { 77 return "", false 78 } 79 n := 1 80 if chunk[0] == '?' { 81 _, n = utf8.DecodeRuneInString(s) 82 } else if chunk[0] != s[0] { 83 return "", false 84 } 85 s = s[n:] 86 chunk = chunk[1:] 87 } 88 return s, true 89 }