package main import ( "os" "path/filepath" "runtime" "strings" "github.com/spf13/afero" ) // RecursiveBasePathFs is an overloaded afero.BasePathFs that has a recursive RealPath(). type RecursiveBasePathFs struct { afero.Fs source afero.Fs path string } // NewRecursiveBasePathFs returns a new RecursiveBasePathFs. func NewRecursiveBasePathFs(source afero.Fs, path string) *RecursiveBasePathFs { return &RecursiveBasePathFs{ Fs: afero.NewBasePathFs(source, path), source: source, path: path, } } // RealPath returns the real path to a file, "breaking out" of the RecursiveBasePathFs. func (b *RecursiveBasePathFs) RealPath(name string) (path string, err error) { if err := validateBasePathName(name); err != nil { return name, err } bpath := filepath.Clean(b.path) path = filepath.Clean(filepath.Join(bpath, name)) if parentRecursiveBasePathFs, ok := b.source.(*RecursiveBasePathFs); ok { return parentRecursiveBasePathFs.RealPath(path) } else if parentRecursiveBasePathFs, ok := b.source.(*afero.BasePathFs); ok { return parentRecursiveBasePathFs.RealPath(path) } if !strings.HasPrefix(path, bpath) { return name, os.ErrNotExist } return path, nil } func validateBasePathName(name string) error { if runtime.GOOS != "windows" { // Not much to do here; // the virtual file paths all look absolute on *nix. return nil } // On Windows a common mistake would be to provide an absolute OS path // We could strip out the base part, but that would not be very portable. if filepath.IsAbs(name) { return os.ErrNotExist } return nil }