Improve absolute file paths handling in BasePathFs

A common mistake in using the BasePathFs is to give it a real
OS absolute file path instead of a virtual one relative to the virtual base.

This commit adds some tests and returns an error on Windows in this case.

On Unix we have to train the users to do a better job.

See https://github.com/spf13/hugo/issues/1800
This commit is contained in:
bep 2016-02-15 13:34:47 +01:00 committed by Bjørn Erik Pedersen
parent 3c51231761
commit ddb4d0857d
2 changed files with 78 additions and 0 deletions

View File

@ -1,8 +1,10 @@
package afero package afero
import ( import (
"errors"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"time" "time"
) )
@ -27,6 +29,11 @@ func NewBasePathFs(source Fs, path string) Fs {
// on a file outside the base path it returns the given file name and an error, // on a file outside the base path it returns the given file name and an error,
// else the given file with the base path prepended // else the given file with the base path prepended
func (b *BasePathFs) RealPath(name string) (path string, err error) { func (b *BasePathFs) RealPath(name string) (path string, err error) {
if err := validateBasePathName(name); err != nil {
return "", err
}
bpath := filepath.Clean(b.path) bpath := filepath.Clean(b.path)
path = filepath.Clean(filepath.Join(bpath, name)) path = filepath.Clean(filepath.Join(bpath, name))
if !strings.HasPrefix(path, bpath) { if !strings.HasPrefix(path, bpath) {
@ -35,6 +42,22 @@ func (b *BasePathFs) RealPath(name string) (path string, err error) {
return path, nil 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.PathError{"realPath", name, errors.New("got a real OS path instead of a virtual")}
}
return nil
}
func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) { func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) {
if name, err = b.RealPath(name); err != nil { if name, err = b.RealPath(name); err != nil {
return &os.PathError{"chtimes", name, err} return &os.PathError{"chtimes", name, err}

View File

@ -2,6 +2,8 @@ package afero
import ( import (
"os" "os"
"path/filepath"
"runtime"
"testing" "testing"
) )
@ -35,3 +37,56 @@ func TestBasePathRoot(t *testing.T) {
t.Error(err) t.Error(err)
} }
} }
func TestRealPath(t *testing.T) {
fs := NewOsFs()
baseDir, err := TempDir(fs, "", "base")
if err != nil {
t.Fatal("error creating tempDir", err)
}
defer fs.RemoveAll(baseDir)
anotherDir, err := TempDir(fs, "", "another")
if err != nil {
t.Fatal("error creating tempDir", err)
}
defer fs.RemoveAll(anotherDir)
bp := NewBasePathFs(fs, baseDir).(*BasePathFs)
subDir := filepath.Join(baseDir, "s1")
realPath, err := bp.RealPath("/s1")
if err != nil {
t.Errorf("Got error %s", err)
}
if realPath != subDir {
t.Errorf("Expected \n%s got \n%s", subDir, realPath)
}
if runtime.GOOS == "windows" {
_, err = bp.RealPath(anotherDir)
if err == nil {
t.Errorf("Expected error")
}
} else {
// on *nix we have no way of just looking at the path and tell that anotherDir
// is not inside the base file system.
// The user will receive an os.ErrNotExist later.
surrealPath, err := bp.RealPath(anotherDir)
if err != nil {
t.Errorf("Got error %s", err)
}
excpected := filepath.Join(baseDir, anotherDir)
if surrealPath != excpected {
t.Errorf("Expected \n%s got \n%s", excpected, surrealPath)
}
}
}