github.com/gozelle/afero@v0.0.0-20230510083704-09e2ff18f19e/basepath.go (about)

     1  package afero
     2  
     3  import (
     4  	"io/fs"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  	"time"
    10  )
    11  
    12  var (
    13  	_ Lstater        = (*BasePathFs)(nil)
    14  	_ fs.ReadDirFile = (*BasePathFile)(nil)
    15  )
    16  
    17  // The BasePathFs restricts all operations to a given path within an Fs.
    18  // The given file name to the operations on this Fs will be prepended with
    19  // the base path before calling the base Fs.
    20  // Any file name (after filepath.Clean()) outside this base path will be
    21  // treated as non existing file.
    22  //
    23  // Note that it does not clean the error messages on return, so you may
    24  // reveal the real path on errors.
    25  type BasePathFs struct {
    26  	source Fs
    27  	path   string
    28  }
    29  
    30  type BasePathFile struct {
    31  	File
    32  	path string
    33  }
    34  
    35  func (f *BasePathFile) Name() string {
    36  	sourcename := f.File.Name()
    37  	return strings.TrimPrefix(sourcename, filepath.Clean(f.path))
    38  }
    39  
    40  func (f *BasePathFile) ReadDir(n int) ([]fs.DirEntry, error) {
    41  	if rdf, ok := f.File.(fs.ReadDirFile); ok {
    42  		return rdf.ReadDir(n)
    43  	}
    44  	return readDirFile{f.File}.ReadDir(n)
    45  }
    46  
    47  func NewBasePathFs(source Fs, path string) Fs {
    48  	return &BasePathFs{source: source, path: path}
    49  }
    50  
    51  // on a file outside the base path it returns the given file name and an error,
    52  // else the given file with the base path prepended
    53  func (b *BasePathFs) RealPath(name string) (path string, err error) {
    54  	if err := validateBasePathName(name); err != nil {
    55  		return name, err
    56  	}
    57  
    58  	bpath := filepath.Clean(b.path)
    59  	path = filepath.Clean(filepath.Join(bpath, name))
    60  	if !strings.HasPrefix(path, bpath) {
    61  		return name, os.ErrNotExist
    62  	}
    63  
    64  	return path, nil
    65  }
    66  
    67  func validateBasePathName(name string) error {
    68  	if runtime.GOOS != "windows" {
    69  		// Not much to do here;
    70  		// the virtual file paths all look absolute on *nix.
    71  		return nil
    72  	}
    73  
    74  	// On Windows a common mistake would be to provide an absolute OS path
    75  	// We could strip out the base part, but that would not be very portable.
    76  	if filepath.IsAbs(name) {
    77  		return os.ErrNotExist
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) {
    84  	if name, err = b.RealPath(name); err != nil {
    85  		return &os.PathError{Op: "chtimes", Path: name, Err: err}
    86  	}
    87  	return b.source.Chtimes(name, atime, mtime)
    88  }
    89  
    90  func (b *BasePathFs) Chmod(name string, mode os.FileMode) (err error) {
    91  	if name, err = b.RealPath(name); err != nil {
    92  		return &os.PathError{Op: "chmod", Path: name, Err: err}
    93  	}
    94  	return b.source.Chmod(name, mode)
    95  }
    96  
    97  func (b *BasePathFs) Chown(name string, uid, gid int) (err error) {
    98  	if name, err = b.RealPath(name); err != nil {
    99  		return &os.PathError{Op: "chown", Path: name, Err: err}
   100  	}
   101  	return b.source.Chown(name, uid, gid)
   102  }
   103  
   104  func (b *BasePathFs) Name() string {
   105  	return "BasePathFs"
   106  }
   107  
   108  func (b *BasePathFs) Stat(name string) (fi os.FileInfo, err error) {
   109  	if name, err = b.RealPath(name); err != nil {
   110  		return nil, &os.PathError{Op: "stat", Path: name, Err: err}
   111  	}
   112  	return b.source.Stat(name)
   113  }
   114  
   115  func (b *BasePathFs) Rename(oldname, newname string) (err error) {
   116  	if oldname, err = b.RealPath(oldname); err != nil {
   117  		return &os.PathError{Op: "rename", Path: oldname, Err: err}
   118  	}
   119  	if newname, err = b.RealPath(newname); err != nil {
   120  		return &os.PathError{Op: "rename", Path: newname, Err: err}
   121  	}
   122  	return b.source.Rename(oldname, newname)
   123  }
   124  
   125  func (b *BasePathFs) RemoveAll(name string) (err error) {
   126  	if name, err = b.RealPath(name); err != nil {
   127  		return &os.PathError{Op: "remove_all", Path: name, Err: err}
   128  	}
   129  	return b.source.RemoveAll(name)
   130  }
   131  
   132  func (b *BasePathFs) Remove(name string) (err error) {
   133  	if name, err = b.RealPath(name); err != nil {
   134  		return &os.PathError{Op: "remove", Path: name, Err: err}
   135  	}
   136  	return b.source.Remove(name)
   137  }
   138  
   139  func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f File, err error) {
   140  	if name, err = b.RealPath(name); err != nil {
   141  		return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
   142  	}
   143  	sourcef, err := b.source.OpenFile(name, flag, mode)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	return &BasePathFile{sourcef, b.path}, nil
   148  }
   149  
   150  func (b *BasePathFs) Open(name string) (f File, err error) {
   151  	if name, err = b.RealPath(name); err != nil {
   152  		return nil, &os.PathError{Op: "open", Path: name, Err: err}
   153  	}
   154  	sourcef, err := b.source.Open(name)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	return &BasePathFile{File: sourcef, path: b.path}, nil
   159  }
   160  
   161  func (b *BasePathFs) Mkdir(name string, mode os.FileMode) (err error) {
   162  	if name, err = b.RealPath(name); err != nil {
   163  		return &os.PathError{Op: "mkdir", Path: name, Err: err}
   164  	}
   165  	return b.source.Mkdir(name, mode)
   166  }
   167  
   168  func (b *BasePathFs) MkdirAll(name string, mode os.FileMode) (err error) {
   169  	if name, err = b.RealPath(name); err != nil {
   170  		return &os.PathError{Op: "mkdir", Path: name, Err: err}
   171  	}
   172  	return b.source.MkdirAll(name, mode)
   173  }
   174  
   175  func (b *BasePathFs) Create(name string) (f File, err error) {
   176  	if name, err = b.RealPath(name); err != nil {
   177  		return nil, &os.PathError{Op: "create", Path: name, Err: err}
   178  	}
   179  	sourcef, err := b.source.Create(name)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	return &BasePathFile{File: sourcef, path: b.path}, nil
   184  }
   185  
   186  func (b *BasePathFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
   187  	name, err := b.RealPath(name)
   188  	if err != nil {
   189  		return nil, false, &os.PathError{Op: "lstat", Path: name, Err: err}
   190  	}
   191  	if lstater, ok := b.source.(Lstater); ok {
   192  		return lstater.LstatIfPossible(name)
   193  	}
   194  	fi, err := b.source.Stat(name)
   195  	return fi, false, err
   196  }
   197  
   198  func (b *BasePathFs) SymlinkIfPossible(oldname, newname string) error {
   199  	oldname, err := b.RealPath(oldname)
   200  	if err != nil {
   201  		return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
   202  	}
   203  	newname, err = b.RealPath(newname)
   204  	if err != nil {
   205  		return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
   206  	}
   207  	if linker, ok := b.source.(Linker); ok {
   208  		return linker.SymlinkIfPossible(oldname, newname)
   209  	}
   210  	return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: ErrNoSymlink}
   211  }
   212  
   213  func (b *BasePathFs) ReadlinkIfPossible(name string) (string, error) {
   214  	name, err := b.RealPath(name)
   215  	if err != nil {
   216  		return "", &os.PathError{Op: "readlink", Path: name, Err: err}
   217  	}
   218  	if reader, ok := b.source.(LinkReader); ok {
   219  		return reader.ReadlinkIfPossible(name)
   220  	}
   221  	return "", &os.PathError{Op: "readlink", Path: name, Err: ErrNoReadlink}
   222  }