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 }