Source file src/io/fs/glob.go

     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) ([]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.
    33  func Glob(fsys FS, pattern string) (matches []string, err error) {
    34  	return globWithLimit(fsys, pattern, 0)
    35  }
    36  
    37  func 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.
    86  func 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.
    99  func 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.
   121  func 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  

View as plain text