blob: af7e647b62a6f85ed95f3ebd4f3dfb95e5e71f7e [file] [log] [blame]
package doublestar
// SplitPattern is a utility function. Given a pattern, SplitPattern will
// return two strings: the first string is everything up to the last slash
// (`/`) that appears _before_ any unescaped "meta" characters (ie, `*?[{`).
// The second string is everything after that slash. For example, given the
// pattern:
//
// ../../path/to/meta*/**
// ^----------- split here
//
// SplitPattern returns "../../path/to" and "meta*/**". This is useful for
// initializing os.DirFS() to call Glob() because Glob() will silently fail if
// your pattern includes `/./` or `/../`. For example:
//
// base, pattern := SplitPattern("../../path/to/meta*/**")
// fsys := os.DirFS(base)
// matches, err := Glob(fsys, pattern)
//
// If SplitPattern cannot find somewhere to split the pattern (for example,
// `meta*/**`), it will return "." and the unaltered pattern (`meta*/**` in
// this example).
//
// Of course, it is your responsibility to decide if the returned base path is
// "safe" in the context of your application. Perhaps you could use Match() to
// validate against a list of approved base directories?
//
func SplitPattern(p string) (base, pattern string) {
base = "."
pattern = p
splitIdx := -1
for i := 0; i < len(p); i++ {
c := p[i]
if c == '\\' {
i++
} else if c == '/' {
splitIdx = i
} else if c == '*' || c == '?' || c == '[' || c == '{' {
break
}
}
if splitIdx >= 0 {
return p[:splitIdx], p[splitIdx+1:]
}
return
}
// Finds the next comma, but ignores any commas that appear inside nested `{}`.
// Assumes that each opening bracket has a corresponding closing bracket.
func indexNextAlt(s string, allowEscaping bool) int {
alts := 1
l := len(s)
for i := 0; i < l; i++ {
if allowEscaping && s[i] == '\\' {
// skip next byte
i++
} else if s[i] == '{' {
alts++
} else if s[i] == '}' {
alts--
} else if s[i] == ',' && alts == 1 {
return i
}
}
return -1
}