...

Source file src/io/fs/sub.go

Documentation: io/fs

		 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  	"errors"
		 9  	"path"
		10  )
		11  
		12  // A SubFS is a file system with a Sub method.
		13  type SubFS interface {
		14  	FS
		15  
		16  	// Sub returns an FS corresponding to the subtree rooted at dir.
		17  	Sub(dir string) (FS, error)
		18  }
		19  
		20  // Sub returns an FS corresponding to the subtree rooted at fsys's dir.
		21  //
		22  // If dir is ".", Sub returns fsys unchanged.
		23  // Otherwise, if fs implements SubFS, Sub returns fsys.Sub(dir).
		24  // Otherwise, Sub returns a new FS implementation sub that,
		25  // in effect, implements sub.Open(name) as fsys.Open(path.Join(dir, name)).
		26  // The implementation also translates calls to ReadDir, ReadFile, and Glob appropriately.
		27  //
		28  // Note that Sub(os.DirFS("/"), "prefix") is equivalent to os.DirFS("/prefix")
		29  // and that neither of them guarantees to avoid operating system
		30  // accesses outside "/prefix", because the implementation of os.DirFS
		31  // does not check for symbolic links inside "/prefix" that point to
		32  // other directories. That is, os.DirFS is not a general substitute for a
		33  // chroot-style security mechanism, and Sub does not change that fact.
		34  func Sub(fsys FS, dir string) (FS, error) {
		35  	if !ValidPath(dir) {
		36  		return nil, &PathError{Op: "sub", Path: dir, Err: errors.New("invalid name")}
		37  	}
		38  	if dir == "." {
		39  		return fsys, nil
		40  	}
		41  	if fsys, ok := fsys.(SubFS); ok {
		42  		return fsys.Sub(dir)
		43  	}
		44  	return &subFS{fsys, dir}, nil
		45  }
		46  
		47  type subFS struct {
		48  	fsys FS
		49  	dir	string
		50  }
		51  
		52  // fullName maps name to the fully-qualified name dir/name.
		53  func (f *subFS) fullName(op string, name string) (string, error) {
		54  	if !ValidPath(name) {
		55  		return "", &PathError{Op: op, Path: name, Err: errors.New("invalid name")}
		56  	}
		57  	return path.Join(f.dir, name), nil
		58  }
		59  
		60  // shorten maps name, which should start with f.dir, back to the suffix after f.dir.
		61  func (f *subFS) shorten(name string) (rel string, ok bool) {
		62  	if name == f.dir {
		63  		return ".", true
		64  	}
		65  	if len(name) >= len(f.dir)+2 && name[len(f.dir)] == '/' && name[:len(f.dir)] == f.dir {
		66  		return name[len(f.dir)+1:], true
		67  	}
		68  	return "", false
		69  }
		70  
		71  // fixErr shortens any reported names in PathErrors by stripping f.dir.
		72  func (f *subFS) fixErr(err error) error {
		73  	if e, ok := err.(*PathError); ok {
		74  		if short, ok := f.shorten(e.Path); ok {
		75  			e.Path = short
		76  		}
		77  	}
		78  	return err
		79  }
		80  
		81  func (f *subFS) Open(name string) (File, error) {
		82  	full, err := f.fullName("open", name)
		83  	if err != nil {
		84  		return nil, err
		85  	}
		86  	file, err := f.fsys.Open(full)
		87  	return file, f.fixErr(err)
		88  }
		89  
		90  func (f *subFS) ReadDir(name string) ([]DirEntry, error) {
		91  	full, err := f.fullName("read", name)
		92  	if err != nil {
		93  		return nil, err
		94  	}
		95  	dir, err := ReadDir(f.fsys, full)
		96  	return dir, f.fixErr(err)
		97  }
		98  
		99  func (f *subFS) ReadFile(name string) ([]byte, error) {
	 100  	full, err := f.fullName("read", name)
	 101  	if err != nil {
	 102  		return nil, err
	 103  	}
	 104  	data, err := ReadFile(f.fsys, full)
	 105  	return data, f.fixErr(err)
	 106  }
	 107  
	 108  func (f *subFS) Glob(pattern string) ([]string, error) {
	 109  	// Check pattern is well-formed.
	 110  	if _, err := path.Match(pattern, ""); err != nil {
	 111  		return nil, err
	 112  	}
	 113  	if pattern == "." {
	 114  		return []string{"."}, nil
	 115  	}
	 116  
	 117  	full := f.dir + "/" + pattern
	 118  	list, err := Glob(f.fsys, full)
	 119  	for i, name := range list {
	 120  		name, ok := f.shorten(name)
	 121  		if !ok {
	 122  			return nil, errors.New("invalid result from inner fsys Glob: " + name + " not in " + f.dir) // can't use fmt in this package
	 123  		}
	 124  		list[i] = name
	 125  	}
	 126  	return list, f.fixErr(err)
	 127  }
	 128  
	 129  func (f *subFS) Sub(dir string) (FS, error) {
	 130  	if dir == "." {
	 131  		return f, nil
	 132  	}
	 133  	full, err := f.fullName("sub", dir)
	 134  	if err != nil {
	 135  		return nil, err
	 136  	}
	 137  	return &subFS{f.fsys, full}, nil
	 138  }
	 139  

View as plain text