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