// 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 }