glob.mx raw
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package fs
6
7 import (
8 "path"
9 )
10
11 // A GlobFS is a file system with a Glob method.
12 type GlobFS interface {
13 FS
14
15 // Glob returns the names of all files matching pattern,
16 // providing an implementation of the top-level
17 // Glob function.
18 Glob(pattern string) ([][]byte, error)
19 }
20
21 // Glob returns the names of all files matching pattern or nil
22 // if there is no matching file. The syntax of patterns is the same
23 // as in [path.Match]. The pattern may describe hierarchical names such as
24 // usr/*/bin/ed.
25 //
26 // Glob ignores file system errors such as I/O errors reading directories.
27 // The only possible returned error is [path.ErrBadPattern], reporting that
28 // the pattern is malformed.
29 //
30 // If fs implements [GlobFS], Glob calls fs.Glob.
31 // Otherwise, Glob uses [ReadDir] to traverse the directory tree
32 // and look for matches for the pattern.
33 func Glob(fsys FS, pattern []byte) (matches [][]byte, err error) {
34 return globWithLimit(fsys, pattern, 0)
35 }
36
37 func globWithLimit(fsys FS, pattern []byte, depth int) (matches [][]byte, err error) {
38 // This limit is added to prevent stack exhaustion issues. See
39 // CVE-2022-30630.
40 const pathSeparatorsLimit = 10000
41 if depth > pathSeparatorsLimit {
42 return nil, path.ErrBadPattern
43 }
44 if fsys, ok := fsys.(GlobFS); ok {
45 return fsys.Glob(pattern)
46 }
47
48 // Check pattern is well-formed.
49 if _, err := path.Match(pattern, ""); err != nil {
50 return nil, err
51 }
52 if !hasMeta(pattern) {
53 if _, err = Stat(fsys, pattern); err != nil {
54 return nil, nil
55 }
56 return [][]byte{pattern}, nil
57 }
58
59 dir, file := path.Split(pattern)
60 dir = cleanGlobPath(dir)
61
62 if !hasMeta(dir) {
63 return glob(fsys, dir, file, nil)
64 }
65
66 // Prevent infinite recursion. See issue 15879.
67 if dir == pattern {
68 return nil, path.ErrBadPattern
69 }
70
71 var m [][]byte
72 m, err = globWithLimit(fsys, dir, depth+1)
73 if err != nil {
74 return nil, err
75 }
76 for _, d := range m {
77 matches, err = glob(fsys, d, file, matches)
78 if err != nil {
79 return
80 }
81 }
82 return
83 }
84
85 // cleanGlobPath prepares path for glob matching.
86 func cleanGlobPath(path []byte) []byte {
87 switch path {
88 case "":
89 return "."
90 default:
91 return path[0 : len(path)-1] // chop off trailing separator
92 }
93 }
94
95 // glob searches for files matching pattern in the directory dir
96 // and appends them to matches, returning the updated slice.
97 // If the directory cannot be opened, glob returns the existing matches.
98 // New matches are added in lexicographical order.
99 func glob(fs FS, dir, pattern []byte, matches [][]byte) (m [][]byte, e error) {
100 m = matches
101 infos, err := ReadDir(fs, dir)
102 if err != nil {
103 return // ignore I/O error
104 }
105
106 for _, info := range infos {
107 n := info.Name()
108 matched, err := path.Match(pattern, n)
109 if err != nil {
110 return m, err
111 }
112 if matched {
113 m = append(m, path.Join(dir, n))
114 }
115 }
116 return
117 }
118
119 // hasMeta reports whether path contains any of the magic characters
120 // recognized by path.Match.
121 func hasMeta(path []byte) bool {
122 for i := 0; i < len(path); i++ {
123 switch path[i] {
124 case '*', '?', '[', '\\':
125 return true
126 }
127 }
128 return false
129 }
130