github.com/MRtecno98/afero@v1.9.3/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  	}
    45  	return readDirFile{f.File}.ReadDir(n)
    46  }
    47  
    48  func NewBasePathFs(source Fs, path string) Fs {
    49  	return &BasePathFs{source: source, path: path}
    50  }
    51  
    52  // on a file outside the base path it returns the given file name and an error,
    53  // else the given file with the base path prepended
    54  func (b *BasePathFs) RealPath(name string) (path string, err error) {
    55  	if err := validateBasePathName(name); err != nil {
    56  		return name, err
    57  	}
    58  
    59  	bpath := filepath.Clean(b.path)
    60  	path = filepath.Clean(filepath.Join(bpath, name))
    61  	if !strings.HasPrefix(path, bpath) {
    62  		return name, os.ErrNotExist
    63  	}
    64  
    65  	return path, nil
    66  }
    67  
    68  func validateBasePathName(name string) error {
    69  	if runtime.GOOS != "windows" {
    70  		// Not much to do here;
    71  		// the virtual file paths all look absolute on *nix.
    72  		return nil
    73  	}
    74  
    75  	// On Windows a common mistake would be to provide an absolute OS path
    76  	// We could strip out the base part, but that would not be very portable.
    77  	if filepath.IsAbs(name) {
    78  		return os.ErrNotExist
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) {
    85  	if name, err = b.RealPath(name); err != nil {
    86  		return &os.PathError{Op: "chtimes", Path: name, Err: err}
    87  	}
    88  	return b.source.Chtimes(name, atime, mtime)
    89  }
    90  
    91  func (b *BasePathFs) Chmod(name string, mode os.FileMode) (err error) {
    92  	if name, err = b.RealPath(name); err != nil {
    93  		return &os.PathError{Op: "chmod", Path: name, Err: err}
    94  	}
    95  	return b.source.Chmod(name, mode)
    96  }
    97  
    98  func (b *BasePathFs) Chown(name string, uid, gid int) (err error) {
    99  	if name, err = b.RealPath(name); err != nil {
   100  		return &os.PathError{Op: "chown", Path: name, Err: err}
   101  	}
   102  	return b.source.Chown(name, uid, gid)
   103  }
   104  
   105  func (b *BasePathFs) Name() string {
   106  	return "BasePathFs"
   107  }
   108  
   109  func (*BasePathFs) Close() {}
   110  
   111  func (b *BasePathFs) Stat(name string) (fi os.FileInfo, err error) {
   112  	if name, err = b.RealPath(name); err != nil {
   113  		return nil, &os.PathError{Op: "stat", Path: name, Err: err}
   114  	}
   115  	return b.source.Stat(name)
   116  }
   117  
   118  func (b *BasePathFs) Rename(oldname, newname string) (err error) {
   119  	if oldname, err = b.RealPath(oldname); err != nil {
   120  		return &os.PathError{Op: "rename", Path: oldname, Err: err}
   121  	}
   122  	if newname, err = b.RealPath(newname); err != nil {
   123  		return &os.PathError{Op: "rename", Path: newname, Err: err}
   124  	}
   125  	return b.source.Rename(oldname, newname)
   126  }
   127  
   128  func (b *BasePathFs) RemoveAll(name string) (err error) {
   129  	if name, err = b.RealPath(name); err != nil {
   130  		return &os.PathError{Op: "remove_all", Path: name, Err: err}
   131  	}
   132  	return b.source.RemoveAll(name)
   133  }
   134  
   135  func (b *BasePathFs) Remove(name string) (err error) {
   136  	if name, err = b.RealPath(name); err != nil {
   137  		return &os.PathError{Op: "remove", Path: name, Err: err}
   138  	}
   139  	return b.source.Remove(name)
   140  }
   141  
   142  func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f File, err error) {
   143  	if name, err = b.RealPath(name); err != nil {
   144  		return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
   145  	}
   146  	sourcef, err := b.source.OpenFile(name, flag, mode)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	return &BasePathFile{sourcef, b.path}, nil
   151  }
   152  
   153  func (b *BasePathFs) Open(name string) (f File, err error) {
   154  	if name, err = b.RealPath(name); err != nil {
   155  		return nil, &os.PathError{Op: "open", Path: name, Err: err}
   156  	}
   157  	sourcef, err := b.source.Open(name)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	return &BasePathFile{File: sourcef, path: b.path}, nil
   162  }
   163  
   164  func (b *BasePathFs) Mkdir(name string, mode os.FileMode) (err error) {
   165  	if name, err = b.RealPath(name); err != nil {
   166  		return &os.PathError{Op: "mkdir", Path: name, Err: err}
   167  	}
   168  	return b.source.Mkdir(name, mode)
   169  }
   170  
   171  func (b *BasePathFs) MkdirAll(name string, mode os.FileMode) (err error) {
   172  	if name, err = b.RealPath(name); err != nil {
   173  		return &os.PathError{Op: "mkdir", Path: name, Err: err}
   174  	}
   175  	return b.source.MkdirAll(name, mode)
   176  }
   177  
   178  func (b *BasePathFs) Create(name string) (f File, err error) {
   179  	if name, err = b.RealPath(name); err != nil {
   180  		return nil, &os.PathError{Op: "create", Path: name, Err: err}
   181  	}
   182  	sourcef, err := b.source.Create(name)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	return &BasePathFile{File: sourcef, path: b.path}, nil
   187  }
   188  
   189  func (b *BasePathFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
   190  	name, err := b.RealPath(name)
   191  	if err != nil {
   192  		return nil, false, &os.PathError{Op: "lstat", Path: name, Err: err}
   193  	}
   194  	if lstater, ok := b.source.(Lstater); ok {
   195  		return lstater.LstatIfPossible(name)
   196  	}
   197  	fi, err := b.source.Stat(name)
   198  	return fi, false, err
   199  }
   200  
   201  func (b *BasePathFs) SymlinkIfPossible(oldname, newname string) error {
   202  	oldname, err := b.RealPath(oldname)
   203  	if err != nil {
   204  		return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
   205  	}
   206  	newname, err = b.RealPath(newname)
   207  	if err != nil {
   208  		return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
   209  	}
   210  	if linker, ok := b.source.(Linker); ok {
   211  		return linker.SymlinkIfPossible(oldname, newname)
   212  	}
   213  	return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: ErrNoSymlink}
   214  }
   215  
   216  func (b *BasePathFs) ReadlinkIfPossible(name string) (string, error) {
   217  	name, err := b.RealPath(name)
   218  	if err != nil {
   219  		return "", &os.PathError{Op: "readlink", Path: name, Err: err}
   220  	}
   221  	if reader, ok := b.source.(LinkReader); ok {
   222  		return reader.ReadlinkIfPossible(name)
   223  	}
   224  	return "", &os.PathError{Op: "readlink", Path: name, Err: ErrNoReadlink}
   225  }