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 }