package urls import ( "strings" ) const DefaultPathSeparator = "/" type AntPathMatcher struct { PathSeparator string } func NewAntPathMatcher() *AntPathMatcher { return &AntPathMatcher{PathSeparator: DefaultPathSeparator} } func (matcher *AntPathMatcher) Match(pattern string, path string) bool { return matcher.doMatch(pattern, path, true) } func (matcher *AntPathMatcher) IsPattern(path string) bool { return strings.Index(path, "*") != -1 || strings.Index(path, "?") != -1 } func (matcher *AntPathMatcher) doMatch(pattern string, path string, fullMatch bool) bool { if strings.HasPrefix(path, matcher.PathSeparator) != strings.HasPrefix(pattern, matcher.PathSeparator) { return false } pattDirs := tokenizeToStringArray(pattern, matcher.PathSeparator) pathDirs := tokenizeToStringArray(path, matcher.PathSeparator) var pattIdxStart = 0 var pattIdxEnd = len(pattDirs) - 1 var pathIdxStart = 0 var pathIdxEnd = len(pathDirs) - 1 // Match all elements up to the first ** for pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd { patDir := pattDirs[pattIdxStart] if "**" == patDir { break } if !matchStrings(patDir, pathDirs[pathIdxStart]) { return false } pattIdxStart++ pathIdxStart++ } if pathIdxStart > pathIdxEnd { // Path is exhausted, only match if rest of pattern is * or **'s if pattIdxStart > pattIdxEnd { if strings.HasSuffix(pattern, matcher.PathSeparator) { return strings.HasSuffix(path, matcher.PathSeparator) } else { return !strings.HasSuffix(path, matcher.PathSeparator) } } if !fullMatch { return true } if (pattIdxStart == pattIdxEnd) && ("*" == pattDirs[pattIdxStart]) && strings.HasSuffix(path, matcher.PathSeparator) { return true } for i := pattIdxStart; i <= pattIdxEnd; i++ { if "**" == pattDirs[i] { return false } } return true } else if pattIdxStart > pattIdxEnd { // string not exhausted, but pattern is. Failure. return false } else if !fullMatch && ("**" == pattDirs[pattIdxStart]) { // Path start definitely matches due to "**" in pattern. return true } // up to last '**' for pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd { patDir := pattDirs[pattIdxEnd] if "**" == patDir { break } if !matchStrings(patDir, pathDirs[pathIdxEnd]) { return false } pattIdxEnd-- pathIdxEnd-- } if pathIdxStart > pathIdxEnd { // string is exhausted for i := pattIdxStart; i < pattIdxEnd; i++ { if !(pattDirs[i] == "**") { return false } } return true } for pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd { patIdxTmp := -1 for i := pattIdxStart + 1; i <= pattIdxEnd; i++ { if "*" == pattDirs[i] { patIdxTmp = i break } } if patIdxTmp == pattIdxStart+1 { // '**/**' situation, so skip one pattIdxStart++ continue } // Find the pattern between padIdxStart & padIdxTmp in str between // strIdxStart & strIdxEnd patLength := patIdxTmp - pattIdxStart - 1 strLength := pathIdxEnd - pathIdxStart - 1 foundIdx := -1 strLoop: for i := 0; i <= strLength-patLength; i++ { for j := 0; j < patLength; j++ { subPat := pattDirs[pattIdxStart+j+i] subStr := pathDirs[pattIdxStart+i+j] if !matchStrings(subPat, subStr) { continue strLoop } } foundIdx = pathIdxStart + i break } if foundIdx == -1 { return false } pattIdxStart = patIdxTmp pathIdxStart = foundIdx + patLength } for i := pattIdxStart; i <= pattIdxEnd; i++ { if !("**" == pattDirs[i]) { return false } } return true } func tokenizeToStringArray(str string, delimiters string) []string { tokens := strings.Split(str, delimiters) for i, token := range tokens { tokens[i] = strings.TrimSpace(token) } return tokens } func matchStrings(pattern string, str string) bool { patArr := []byte(pattern) strArr := []byte(str) patIdxStart := 0 patIdxEnd := len(patArr) - 1 strIdxStart := 0 strIdxEnd := len(strArr) - 1 var b byte containsStar := false for _, aPatArr := range patArr { if aPatArr == '*' { containsStar = true break } } if !containsStar { // No '*'s, so we make a shortcut if patIdxEnd != strIdxEnd { // Pattern and string do not have the same size return false } for i := 0; i <= patIdxEnd; i++ { b := patArr[i] if b != '?' { if b != strArr[i] { // Character mismatch return false } } } return true } if patIdxEnd == 0 { // Pattern contains only '*', which matches anything return true } // Process characters before first star b = patArr[patIdxStart] for (b != '*') && strIdxStart <= strIdxEnd { if b != '?' { if b != strArr[strIdxStart] { return false } } patIdxStart++ strIdxStart++ b = patArr[patIdxStart] } if strIdxStart > strIdxEnd { // All characters in the string are used, check if only '*'s are // left in the pattern. If so, we succeeded.Otherwise failure for i := patIdxStart; i < patIdxEnd; i++ { if patArr[i] != '*' { return false } } return true } // Process characters after last star b = patArr[patIdxEnd] for (b != '*') && strIdxStart <= strIdxEnd { if b != '?' { if b != strArr[strIdxEnd] { return false } } patIdxEnd-- strIdxEnd-- b = patArr[patIdxEnd] } if strIdxStart > strIdxEnd { // All characters in the string are used, check if only '*'s are // left in the pattern. If so, we succeeded.Otherwise failure for i := patIdxStart; i < patIdxEnd; i++ { if patArr[i] != '*' { return false } } return true } for patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd { patIdxTmp := -1 for i := patIdxStart; i <= patIdxEnd; i++ { if patArr[i] == '*' { patIdxTmp = i break } } if patIdxTmp == patIdxStart+1 { // Two stars next to each other, skip the first one patIdxStart++ continue } // Find the pattern between padIdxStart & padIdxTmp in str between // strIdxStart & strIdxEnd patLength := patIdxTmp - patIdxStart - 1 strLength := strIdxEnd - strIdxStart - 1 foundIdx := -1 strLoop: for i := 0; i <= strLength-patLength; i++ { for j := 0; j < patLength; j++ { b = patArr[patIdxStart+j+1] if b != '?' { if b != strArr[strIdxStart+i+j] { continue strLoop } } } foundIdx = strIdxStart + i break } if foundIdx == -1 { return false } patIdxStart = patIdxTmp strIdxStart = foundIdx + patLength } // All characters in the string are used. Check if only '*'s are left // in the pattern. If so, we succeeded. Otherwise failure for i := patIdxStart; i <= patIdxEnd; i++ { if patArr[i] != '*' { return false } } return true }