2015-07-13 20:05:21 +00:00
|
|
|
package filter
|
|
|
|
|
|
|
|
import (
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2016-08-21 15:46:23 +00:00
|
|
|
|
2017-07-23 12:21:03 +00:00
|
|
|
"github.com/restic/restic/internal/errors"
|
2015-07-13 20:05:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ErrBadString is returned when Match is called with the empty string as the
|
|
|
|
// second argument.
|
|
|
|
var ErrBadString = errors.New("filter.Match: string is empty")
|
|
|
|
|
2020-10-07 12:39:51 +00:00
|
|
|
// Pattern represents a preparsed filter pattern
|
|
|
|
type Pattern []string
|
2020-10-07 12:27:59 +00:00
|
|
|
|
|
|
|
func prepareStr(str string) ([]string, error) {
|
|
|
|
if str == "" {
|
|
|
|
return nil, ErrBadString
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert file path separator to '/'
|
|
|
|
if filepath.Separator != '/' {
|
|
|
|
str = strings.Replace(str, string(filepath.Separator), "/", -1)
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Split(str, "/"), nil
|
|
|
|
}
|
|
|
|
|
2020-10-07 12:39:51 +00:00
|
|
|
func preparePattern(pattern string) Pattern {
|
2020-10-07 12:27:59 +00:00
|
|
|
pattern = filepath.Clean(pattern)
|
|
|
|
|
|
|
|
// convert file path separator to '/'
|
|
|
|
if filepath.Separator != '/' {
|
|
|
|
pattern = strings.Replace(pattern, string(filepath.Separator), "/", -1)
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Split(pattern, "/")
|
|
|
|
}
|
|
|
|
|
2015-07-13 20:05:21 +00:00
|
|
|
// Match returns true if str matches the pattern. When the pattern is
|
|
|
|
// malformed, filepath.ErrBadPattern is returned. The empty pattern matches
|
|
|
|
// everything, when str is the empty string ErrBadString is returned.
|
|
|
|
//
|
|
|
|
// Pattern can be a combination of patterns suitable for filepath.Match, joined
|
|
|
|
// by filepath.Separator.
|
2017-09-04 21:38:48 +00:00
|
|
|
//
|
|
|
|
// In addition patterns suitable for filepath.Match, pattern accepts a
|
|
|
|
// recursive wildcard '**', which greedily matches an arbitrary number of
|
|
|
|
// intermediate directories.
|
2015-07-13 20:05:21 +00:00
|
|
|
func Match(pattern, str string) (matched bool, err error) {
|
|
|
|
if pattern == "" {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2020-10-07 12:27:59 +00:00
|
|
|
patterns := preparePattern(pattern)
|
|
|
|
strs, err := prepareStr(str)
|
2016-04-17 19:54:12 +00:00
|
|
|
|
2020-10-07 12:27:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
2015-08-17 09:48:24 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 20:05:21 +00:00
|
|
|
return match(patterns, strs)
|
|
|
|
}
|
|
|
|
|
2017-06-16 14:46:16 +00:00
|
|
|
// ChildMatch returns true if children of str can match the pattern. When the pattern is
|
|
|
|
// malformed, filepath.ErrBadPattern is returned. The empty pattern matches
|
|
|
|
// everything, when str is the empty string ErrBadString is returned.
|
|
|
|
//
|
|
|
|
// Pattern can be a combination of patterns suitable for filepath.Match, joined
|
|
|
|
// by filepath.Separator.
|
2017-09-04 21:38:48 +00:00
|
|
|
//
|
|
|
|
// In addition patterns suitable for filepath.Match, pattern accepts a
|
|
|
|
// recursive wildcard '**', which greedily matches an arbitrary number of
|
|
|
|
// intermediate directories.
|
2017-06-16 14:46:16 +00:00
|
|
|
func ChildMatch(pattern, str string) (matched bool, err error) {
|
|
|
|
if pattern == "" {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2020-10-07 12:27:59 +00:00
|
|
|
patterns := preparePattern(pattern)
|
|
|
|
strs, err := prepareStr(str)
|
2017-06-16 14:46:16 +00:00
|
|
|
|
2020-10-07 12:27:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
2017-06-16 14:46:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return childMatch(patterns, strs)
|
|
|
|
}
|
|
|
|
|
2020-10-07 12:39:51 +00:00
|
|
|
func childMatch(patterns Pattern, strs []string) (matched bool, err error) {
|
2017-06-16 14:46:16 +00:00
|
|
|
if patterns[0] != "" {
|
|
|
|
// relative pattern can always be nested down
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2018-06-09 21:12:51 +00:00
|
|
|
ok, pos := hasDoubleWildcard(patterns)
|
|
|
|
if ok && len(strs) >= pos {
|
|
|
|
// cut off at the double wildcard
|
|
|
|
strs = strs[:pos]
|
|
|
|
}
|
|
|
|
|
2017-06-16 14:46:16 +00:00
|
|
|
// match path against absolute pattern prefix
|
|
|
|
l := 0
|
|
|
|
if len(strs) > len(patterns) {
|
|
|
|
l = len(patterns)
|
|
|
|
} else {
|
|
|
|
l = len(strs)
|
|
|
|
}
|
|
|
|
return match(patterns[0:l], strs)
|
|
|
|
}
|
|
|
|
|
2020-10-07 12:39:51 +00:00
|
|
|
func hasDoubleWildcard(list Pattern) (ok bool, pos int) {
|
2015-07-13 20:51:35 +00:00
|
|
|
for i, item := range list {
|
|
|
|
if item == "**" {
|
|
|
|
return true, i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, 0
|
|
|
|
}
|
|
|
|
|
2020-10-07 12:39:51 +00:00
|
|
|
func match(patterns Pattern, strs []string) (matched bool, err error) {
|
2015-07-13 20:51:35 +00:00
|
|
|
if ok, pos := hasDoubleWildcard(patterns); ok {
|
|
|
|
// gradually expand '**' into separate wildcards
|
|
|
|
for i := 0; i <= len(strs)-len(patterns)+1; i++ {
|
2020-10-07 12:39:51 +00:00
|
|
|
newPat := make(Pattern, pos)
|
2015-07-13 21:15:57 +00:00
|
|
|
copy(newPat, patterns[:pos])
|
2015-07-13 20:51:35 +00:00
|
|
|
for k := 0; k < i; k++ {
|
|
|
|
newPat = append(newPat, "*")
|
|
|
|
}
|
|
|
|
newPat = append(newPat, patterns[pos+1:]...)
|
|
|
|
|
|
|
|
matched, err := match(newPat, strs)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if matched {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2015-07-13 20:05:21 +00:00
|
|
|
if len(patterns) == 0 && len(strs) == 0 {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(patterns) <= len(strs) {
|
2019-07-06 20:12:24 +00:00
|
|
|
maxOffset := len(strs) - len(patterns)
|
|
|
|
// special case absolute patterns
|
|
|
|
if patterns[0] == "" {
|
|
|
|
maxOffset = 0
|
|
|
|
}
|
2015-07-13 20:05:21 +00:00
|
|
|
outer:
|
2019-07-06 20:12:24 +00:00
|
|
|
for offset := maxOffset; offset >= 0; offset-- {
|
2015-07-13 20:05:21 +00:00
|
|
|
|
|
|
|
for i := len(patterns) - 1; i >= 0; i-- {
|
|
|
|
ok, err := filepath.Match(patterns[i], strs[offset+i])
|
|
|
|
if err != nil {
|
2016-08-29 20:16:58 +00:00
|
|
|
return false, errors.Wrap(err, "Match")
|
2015-07-13 20:05:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
continue outer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2020-10-07 12:39:51 +00:00
|
|
|
// ParsePatterns prepares a list of patterns for use with List.
|
|
|
|
func ParsePatterns(patterns []string) []Pattern {
|
|
|
|
patpat := make([]Pattern, 0)
|
|
|
|
for _, pat := range patterns {
|
|
|
|
if pat == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
pats := preparePattern(pat)
|
|
|
|
patpat = append(patpat, pats)
|
|
|
|
}
|
|
|
|
return patpat
|
|
|
|
}
|
|
|
|
|
|
|
|
// List returns true if str matches one of the patterns. Empty patterns are ignored.
|
|
|
|
func List(patterns []Pattern, str string) (matched bool, childMayMatch bool, err error) {
|
2020-10-07 12:27:59 +00:00
|
|
|
if len(patterns) == 0 {
|
|
|
|
return false, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
strs, err := prepareStr(str)
|
|
|
|
if err != nil {
|
|
|
|
return false, false, err
|
|
|
|
}
|
2015-07-13 20:05:21 +00:00
|
|
|
for _, pat := range patterns {
|
2020-10-07 12:39:51 +00:00
|
|
|
m, err := match(pat, strs)
|
2015-07-13 20:05:21 +00:00
|
|
|
if err != nil {
|
2017-06-16 14:46:16 +00:00
|
|
|
return false, false, err
|
2015-07-13 20:05:21 +00:00
|
|
|
}
|
|
|
|
|
2020-10-07 12:39:51 +00:00
|
|
|
c, err := childMatch(pat, strs)
|
2017-06-16 14:46:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
matched = matched || m
|
|
|
|
childMayMatch = childMayMatch || c
|
|
|
|
|
|
|
|
if matched && childMayMatch {
|
|
|
|
return true, true, nil
|
2015-07-13 20:05:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-16 14:46:16 +00:00
|
|
|
return matched, childMayMatch, nil
|
2015-07-13 20:05:21 +00:00
|
|
|
}
|