-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathglob.go
More file actions
148 lines (131 loc) · 3.51 KB
/
Copy pathglob.go
File metadata and controls
148 lines (131 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package fs
import (
"context"
"errors"
"lesiw.io/fs/path"
)
// A GlobFS is a file system with the Glob method.
//
// If not implemented, Glob falls back to pattern matching using
// StatFS and ReadDirFS.
type GlobFS interface {
FS
// Glob returns the names of all files matching pattern.
// The pattern syntax is the same as in [path.Match].
Glob(ctx context.Context, pattern string) ([]string, error)
}
// Glob returns the names of all files matching pattern.
// Analogous to: [io/fs.Glob], [path.Match], glob, find, 9P walk.
//
// The pattern syntax 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.
//
// Requires: [GlobFS] ||
// ([StatFS] && ([ReadDirFS] || [WalkFS]))
func Glob(ctx context.Context, fsys FS, pattern string) ([]string, error) {
if gfs, ok := fsys.(GlobFS); ok {
matches, err := gfs.Glob(ctx, pattern)
if err != nil && !errors.Is(err, ErrUnsupported) {
return matches, err
}
if err == nil {
return matches, nil
}
// Fall through to fallback if ErrUnsupported
}
// Fallback requires StatFS and (ReadDirFS or WalkFS)
_, hasStat := fsys.(StatFS)
_, hasReadDir := fsys.(ReadDirFS)
_, hasWalk := fsys.(WalkFS)
if !hasStat || (!hasReadDir && !hasWalk) {
return nil, &PathError{
Op: "glob",
Path: pattern,
Err: ErrUnsupported,
}
}
return globWithLimit(ctx, fsys, pattern, 0)
}
func globWithLimit(
ctx context.Context, 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
}
// Check pattern is well-formed.
if _, err := path.Match(pattern, ""); err != nil {
return nil, err
}
if !hasMeta(pattern) {
if _, err = Stat(ctx, fsys, pattern); err != nil {
return nil, nil
}
return []string{pattern}, nil
}
dir, file := path.Split(pattern)
// Our Split already returns clean dir without trailing separator
if dir == "" {
dir = "."
}
if !hasMeta(dir) {
return glob(ctx, fsys, dir, file, nil)
}
// Prevent infinite recursion. See issue 15879.
if dir == pattern {
return nil, path.ErrBadPattern
}
var m []string
m, err = globWithLimit(ctx, fsys, dir, depth+1)
if err != nil {
return nil, err
}
for _, d := range m {
matches, err = glob(ctx, fsys, d, file, matches)
if err != nil {
return
}
}
return
}
// 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(
ctx context.Context, fsys FS, dir, pattern string, matches []string,
) (m []string, e error) {
m = matches
// Read directory using ReadDir
for info, err := range ReadDir(ctx, fsys, dir) {
if err != nil {
return m, nil // ignore I/O error
}
n := info.Name()
matched, matchErr := path.Match(pattern, n)
if matchErr != nil {
return m, matchErr
}
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(p string) bool {
for i := 0; i < len(p); i++ {
switch p[i] {
case '*', '?', '[', '\\':
return true
}
}
return false
}