tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/workingpathfs/fs.go (about)

     1  // Copyright © 2014 Steve Francia <spf@spf13.com>.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package workingpathfs
    15  
    16  import (
    17  	"io/fs"
    18  	"os"
    19  	"path/filepath"
    20  	"runtime"
    21  	"strings"
    22  	"time"
    23  )
    24  
    25  // FS restricts all operations to a given path within an Fs.
    26  // The given file name to the operations on this Fs will be prepended with
    27  // the base path before calling the base Fs.
    28  // Any file name (after filepath.Clean()) outside this base path will be
    29  // treated as non existing file.
    30  //
    31  // Note that it does not clean the error messages on return, so you may
    32  // reveal the real path on errors.
    33  type FS struct {
    34  	fs.FS
    35  	path string
    36  }
    37  
    38  func New(source fs.FS, path string) *FS {
    39  	return &FS{FS: source, path: path}
    40  }
    41  
    42  // on a file outside the base path it returns the given file name and an error,
    43  // else the given file with the base path prepended
    44  func (b *FS) RealPath(name string) (path string, err error) {
    45  	if err := validateBasePathName(name); err != nil {
    46  		return name, err
    47  	}
    48  
    49  	bpath := filepath.Clean(b.path)
    50  	path = filepath.Clean(filepath.Join(bpath, name))
    51  	if !strings.HasPrefix(path, bpath) {
    52  		return name, os.ErrNotExist
    53  	}
    54  
    55  	return path, nil
    56  }
    57  
    58  func validateBasePathName(name string) error {
    59  	if runtime.GOOS != "windows" {
    60  		// Not much to do here;
    61  		// the virtual file paths all look absolute on *nix.
    62  		return nil
    63  	}
    64  
    65  	// On Windows a common mistake would be to provide an absolute OS path
    66  	// We could strip out the base part, but that would not be very portable.
    67  	if filepath.IsAbs(name) {
    68  		return os.ErrNotExist
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  func (b *FS) Chtimes(name string, atime, mtime time.Time) (err error) {
    75  	if name, err = b.RealPath(name); err != nil {
    76  		return &os.PathError{Op: "chtimes", Path: name, Err: err}
    77  	}
    78  	fsys, ok := b.FS.(interface {
    79  		Chtimes(name string, atime, mtime time.Time) (err error)
    80  	})
    81  	if !ok {
    82  		return fs.ErrPermission
    83  	}
    84  	return fsys.Chtimes(name, atime, mtime)
    85  }
    86  
    87  func (b *FS) Chmod(name string, mode fs.FileMode) (err error) {
    88  	if name, err = b.RealPath(name); err != nil {
    89  		return &os.PathError{Op: "chmod", Path: name, Err: err}
    90  	}
    91  	fsys, ok := b.FS.(interface {
    92  		Chmod(name string, mode fs.FileMode) (err error)
    93  	})
    94  	if !ok {
    95  		return fs.ErrPermission
    96  	}
    97  	return fsys.Chmod(name, mode)
    98  }
    99  
   100  func (b *FS) Chown(name string, uid, gid int) (err error) {
   101  	if name, err = b.RealPath(name); err != nil {
   102  		return &os.PathError{Op: "chown", Path: name, Err: err}
   103  	}
   104  	fsys, ok := b.FS.(interface {
   105  		Chown(name string, uid, gid int) (err error)
   106  	})
   107  	if !ok {
   108  		return fs.ErrPermission
   109  	}
   110  	return fsys.Chown(name, uid, gid)
   111  }
   112  
   113  func (b *FS) Stat(name string) (fi fs.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 fs.Stat(b.FS, name)
   118  }
   119  
   120  func (b *FS) 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  	fsys, ok := b.FS.(interface {
   128  		Rename(oldname, newname string) (err error)
   129  	})
   130  	if !ok {
   131  		return fs.ErrPermission
   132  	}
   133  	return fsys.Rename(oldname, newname)
   134  }
   135  
   136  func (b *FS) RemoveAll(name string) (err error) {
   137  	if name, err = b.RealPath(name); err != nil {
   138  		return &os.PathError{Op: "remove_all", Path: name, Err: err}
   139  	}
   140  	fsys, ok := b.FS.(interface {
   141  		RemoveAll(name string) (err error)
   142  	})
   143  	if !ok {
   144  		return fs.ErrPermission
   145  	}
   146  	return fsys.RemoveAll(name)
   147  }
   148  
   149  func (b *FS) Remove(name string) (err error) {
   150  	if name, err = b.RealPath(name); err != nil {
   151  		return &os.PathError{Op: "remove", Path: name, Err: err}
   152  	}
   153  	fsys, ok := b.FS.(interface {
   154  		Remove(name string) (err error)
   155  	})
   156  	if !ok {
   157  		return fs.ErrPermission
   158  	}
   159  	return fsys.Remove(name)
   160  }
   161  
   162  func (b *FS) OpenFile(name string, flag int, mode fs.FileMode) (f fs.File, err error) {
   163  	if name, err = b.RealPath(name); err != nil {
   164  		return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
   165  	}
   166  	fsys, ok := b.FS.(interface {
   167  		OpenFile(name string, flag int, mode fs.FileMode) (f fs.File, err error)
   168  	})
   169  	if !ok {
   170  		return nil, fs.ErrPermission
   171  	}
   172  	srcf, err := fsys.OpenFile(name, flag, mode)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	return srcf, nil
   177  }
   178  
   179  func (b *FS) Open(name string) (f fs.File, err error) {
   180  	if name, err = b.RealPath(name); err != nil {
   181  		return nil, &os.PathError{Op: "open", Path: name, Err: err}
   182  	}
   183  	fsys, ok := b.FS.(interface {
   184  		Open(name string) (f fs.File, err error)
   185  	})
   186  	if !ok {
   187  		return nil, fs.ErrPermission
   188  	}
   189  	srcf, err := fsys.Open(name)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	return srcf, nil
   194  }
   195  
   196  func (b *FS) Mkdir(name string, mode fs.FileMode) (err error) {
   197  	if name, err = b.RealPath(name); err != nil {
   198  		return &os.PathError{Op: "mkdir", Path: name, Err: err}
   199  	}
   200  	fsys, ok := b.FS.(interface {
   201  		Mkdir(name string, mode fs.FileMode) (err error)
   202  	})
   203  	if !ok {
   204  		return fs.ErrPermission
   205  	}
   206  	return fsys.Mkdir(name, mode)
   207  }
   208  
   209  func (b *FS) MkdirAll(name string, mode fs.FileMode) (err error) {
   210  	if name, err = b.RealPath(name); err != nil {
   211  		return &os.PathError{Op: "mkdir", Path: name, Err: err}
   212  	}
   213  	fsys, ok := b.FS.(interface {
   214  		MkdirAll(name string, mode fs.FileMode) (err error)
   215  	})
   216  	if !ok {
   217  		return fs.ErrPermission
   218  	}
   219  	return fsys.MkdirAll(name, mode)
   220  }
   221  
   222  func (b *FS) Create(name string) (f fs.File, err error) {
   223  	if name, err = b.RealPath(name); err != nil {
   224  		return nil, &os.PathError{Op: "create", Path: name, Err: err}
   225  	}
   226  	fsys, ok := b.FS.(interface {
   227  		Create(name string) (f fs.File, err error)
   228  	})
   229  	if !ok {
   230  		return nil, fs.ErrPermission
   231  	}
   232  	srcf, err := fsys.Create(name)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	return srcf, nil
   237  }