github.com/maxnasonov/afero@v1.8.4/basepath.go (about)

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