Source file src/io/fs/sub.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  	"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 fsys 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,
    27  // ReadLink, Lstat, and Glob appropriately. Sub does not check if the
    28  // directory currently exists.
    29  //
    30  // Note that Sub(os.DirFS("/"), "prefix") is equivalent to os.DirFS("/prefix")
    31  // and that neither of them guarantees to avoid operating system
    32  // accesses outside "/prefix", because the implementation of [os.DirFS]
    33  // does not check for symbolic links inside "/prefix" that point to
    34  // other directories. That is, [os.DirFS] is not a general substitute for a
    35  // chroot-style security mechanism, and Sub does not change that fact.
    36  // Use [os.Root] to constrain access to particular directory trees.
    37  func Sub(fsys FS, dir string) (FS, error) {
    38  	if !ValidPath(dir) {
    39  		return nil, &PathError{Op: "sub", Path: dir, Err: ErrInvalid}
    40  	}
    41  	if dir == "." {
    42  		return fsys, nil
    43  	}
    44  	if fsys, ok := fsys.(SubFS); ok {
    45  		return fsys.Sub(dir)
    46  	}
    47  	return &subFS{fsys, dir}, nil
    48  }
    49  
    50  var _ FS = (*subFS)(nil)
    51  var _ ReadDirFS = (*subFS)(nil)
    52  var _ ReadFileFS = (*subFS)(nil)
    53  var _ ReadLinkFS = (*subFS)(nil)
    54  var _ GlobFS = (*subFS)(nil)
    55  
    56  type subFS struct {
    57  	fsys FS
    58  	dir  string
    59  }
    60  
    61  // fullName maps name to the fully-qualified name dir/name.
    62  func (f *subFS) fullName(op string, name string) (string, error) {
    63  	if !ValidPath(name) {
    64  		return "", &PathError{Op: op, Path: name, Err: ErrInvalid}
    65  	}
    66  	return path.Join(f.dir, name), nil
    67  }
    68  
    69  // shorten maps name, which should start with f.dir, back to the suffix after f.dir.
    70  func (f *subFS) shorten(name string) (rel string, ok bool) {
    71  	if name == f.dir {
    72  		return ".", true
    73  	}
    74  	if len(name) >= len(f.dir)+2 && name[len(f.dir)] == '/' && name[:len(f.dir)] == f.dir {
    75  		return name[len(f.dir)+1:], true
    76  	}
    77  	return "", false
    78  }
    79  
    80  // fixErr shortens any reported names in PathErrors by stripping f.dir.
    81  func (f *subFS) fixErr(err error) error {
    82  	if e, ok := err.(*PathError); ok {
    83  		if short, ok := f.shorten(e.Path); ok {
    84  			e.Path = short
    85  		}
    86  	}
    87  	return err
    88  }
    89  
    90  func (f *subFS) Open(name string) (File, error) {
    91  	full, err := f.fullName("open", name)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	file, err := f.fsys.Open(full)
    96  	return file, f.fixErr(err)
    97  }
    98  
    99  func (f *subFS) ReadDir(name string) ([]DirEntry, error) {
   100  	full, err := f.fullName("read", name)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	dir, err := ReadDir(f.fsys, full)
   105  	return dir, f.fixErr(err)
   106  }
   107  
   108  func (f *subFS) ReadFile(name string) ([]byte, error) {
   109  	full, err := f.fullName("read", name)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	data, err := ReadFile(f.fsys, full)
   114  	return data, f.fixErr(err)
   115  }
   116  
   117  func (f *subFS) ReadLink(name string) (string, error) {
   118  	full, err := f.fullName("readlink", name)
   119  	if err != nil {
   120  		return "", err
   121  	}
   122  	target, err := ReadLink(f.fsys, full)
   123  	if err != nil {
   124  		return "", f.fixErr(err)
   125  	}
   126  	return target, nil
   127  }
   128  
   129  func (f *subFS) Lstat(name string) (FileInfo, error) {
   130  	full, err := f.fullName("lstat", name)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	info, err := Lstat(f.fsys, full)
   135  	if err != nil {
   136  		return nil, f.fixErr(err)
   137  	}
   138  	return info, nil
   139  }
   140  
   141  func (f *subFS) Glob(pattern string) ([]string, error) {
   142  	// Check pattern is well-formed.
   143  	if _, err := path.Match(pattern, ""); err != nil {
   144  		return nil, err
   145  	}
   146  	if pattern == "." {
   147  		return []string{"."}, nil
   148  	}
   149  
   150  	full := f.dir + "/" + pattern
   151  	list, err := Glob(f.fsys, full)
   152  	for i, name := range list {
   153  		name, ok := f.shorten(name)
   154  		if !ok {
   155  			return nil, errors.New("invalid result from inner fsys Glob: " + name + " not in " + f.dir) // can't use fmt in this package
   156  		}
   157  		list[i] = name
   158  	}
   159  	return list, f.fixErr(err)
   160  }
   161  
   162  func (f *subFS) Sub(dir string) (FS, error) {
   163  	if dir == "." {
   164  		return f, nil
   165  	}
   166  	full, err := f.fullName("sub", dir)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	return &subFS{f.fsys, full}, nil
   171  }
   172  

View as plain text