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
diff --git a/basepath.go b/basepath.go
index 3434137..4b325ed 100644
--- a/basepath.go
+++ b/basepath.go
@@ -1,8 +1,10 @@
package afero
import (
+ "errors"
"os"
"path/filepath"
+ "runtime"
"strings"
"time"
)
@@ -27,6 +29,11 @@
// 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
func (b *BasePathFs) RealPath(name string) (path string, err error) {
+
+ if err := validateBasePathName(name); err != nil {
+ return "", err
+ }
+
bpath := filepath.Clean(b.path)
path = filepath.Clean(filepath.Join(bpath, name))
if !strings.HasPrefix(path, bpath) {
@@ -35,6 +42,22 @@
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) {
if name, err = b.RealPath(name); err != nil {
return &os.PathError{"chtimes", name, err}
diff --git a/basepath_test.go b/basepath_test.go
index d2202f9..3c91f5d 100644
--- a/basepath_test.go
+++ b/basepath_test.go
@@ -2,6 +2,8 @@
import (
"os"
+ "path/filepath"
+ "runtime"
"testing"
)
@@ -35,3 +37,56 @@
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)
+ }
+ }
+
+}