diff options
Diffstat (limited to 'src/io/fs/glob.go')
-rw-r--r-- | src/io/fs/glob.go | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/src/io/fs/glob.go b/src/io/fs/glob.go new file mode 100644 index 0000000..db17156 --- /dev/null +++ b/src/io/fs/glob.go @@ -0,0 +1,129 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "path" +) + +// A GlobFS is a file system with a Glob method. +type GlobFS interface { + FS + + // Glob returns the names of all files matching pattern, + // providing an implementation of the top-level + // Glob function. + Glob(pattern string) ([]string, error) +} + +// Glob returns the names of all files matching pattern or nil +// if there is no matching file. The syntax of patterns is the same +// as in [path.Match]. The pattern may describe hierarchical names such as +// usr/*/bin/ed. +// +// Glob ignores file system errors such as I/O errors reading directories. +// The only possible returned error is [path.ErrBadPattern], reporting that +// the pattern is malformed. +// +// If fs implements [GlobFS], Glob calls fs.Glob. +// Otherwise, Glob uses [ReadDir] to traverse the directory tree +// and look for matches for the pattern. +func Glob(fsys FS, pattern string) (matches []string, err error) { + return globWithLimit(fsys, pattern, 0) +} + +func globWithLimit(fsys FS, pattern string, depth int) (matches []string, err error) { + // This limit is added to prevent stack exhaustion issues. See + // CVE-2022-30630. + const pathSeparatorsLimit = 10000 + if depth > pathSeparatorsLimit { + return nil, path.ErrBadPattern + } + if fsys, ok := fsys.(GlobFS); ok { + return fsys.Glob(pattern) + } + + // Check pattern is well-formed. + if _, err := path.Match(pattern, ""); err != nil { + return nil, err + } + if !hasMeta(pattern) { + if _, err = Stat(fsys, pattern); err != nil { + return nil, nil + } + return []string{pattern}, nil + } + + dir, file := path.Split(pattern) + dir = cleanGlobPath(dir) + + if !hasMeta(dir) { + return glob(fsys, dir, file, nil) + } + + // Prevent infinite recursion. See issue 15879. + if dir == pattern { + return nil, path.ErrBadPattern + } + + var m []string + m, err = globWithLimit(fsys, dir, depth+1) + if err != nil { + return nil, err + } + for _, d := range m { + matches, err = glob(fsys, d, file, matches) + if err != nil { + return + } + } + return +} + +// cleanGlobPath prepares path for glob matching. +func cleanGlobPath(path string) string { + switch path { + case "": + return "." + default: + return path[0 : len(path)-1] // chop off trailing separator + } +} + +// glob searches for files matching pattern in the directory dir +// and appends them to matches, returning the updated slice. +// If the directory cannot be opened, glob returns the existing matches. +// New matches are added in lexicographical order. +func glob(fs FS, dir, pattern string, matches []string) (m []string, e error) { + m = matches + infos, err := ReadDir(fs, dir) + if err != nil { + return // ignore I/O error + } + + for _, info := range infos { + n := info.Name() + matched, err := path.Match(pattern, n) + if err != nil { + return m, err + } + if matched { + m = append(m, path.Join(dir, n)) + } + } + return +} + +// hasMeta reports whether path contains any of the magic characters +// recognized by path.Match. +func hasMeta(path string) bool { + for i := 0; i < len(path); i++ { + switch path[i] { + case '*', '?', '[', '\\': + return true + } + } + return false +} |