github.com/IBM/fsgo@v0.0.0-20220920202152-e16fd2119d49/basepath.go (about)

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