• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5package fs
6
7import (
8	"path"
9)
10
11// A GlobFS is a file system with a Glob method.
12type 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) ([]string, 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.
33func Glob(fsys FS, pattern string) (matches []string, err error) {
34	return globWithLimit(fsys, pattern, 0)
35}
36
37func globWithLimit(fsys FS, pattern string, depth int) (matches []string, 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 []string{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 []string
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.
86func cleanGlobPath(path string) string {
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.
99func glob(fs FS, dir, pattern string, matches []string) (m []string, 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.
121func hasMeta(path string) 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