This commit is contained in:
@@ -88,28 +88,10 @@ func (f *BasicFilesystem) rooted(rel string) (string, error) {
|
||||
expectedPrefix += pathSep
|
||||
}
|
||||
|
||||
// The relative path should be clean from internal dotdots and similar
|
||||
// funkyness.
|
||||
rel = filepath.FromSlash(rel)
|
||||
if filepath.Clean(rel) != rel {
|
||||
return "", ErrInvalidFilename
|
||||
}
|
||||
|
||||
// It is not acceptable to attempt to traverse upwards.
|
||||
switch rel {
|
||||
case "..", pathSep:
|
||||
return "", ErrNotRelative
|
||||
}
|
||||
if strings.HasPrefix(rel, ".."+pathSep) {
|
||||
return "", ErrNotRelative
|
||||
}
|
||||
|
||||
if strings.HasPrefix(rel, pathSep+pathSep) {
|
||||
// The relative path may pretend to be an absolute path within the
|
||||
// root, but the double path separator on Windows implies something
|
||||
// else. It would get cleaned by the Join below, but it's out of
|
||||
// spec anyway.
|
||||
return "", ErrNotRelative
|
||||
var err error
|
||||
rel, err = Canonicalize(rel)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// The supposedly correct path is the one filepath.Join will return, as
|
||||
|
||||
@@ -364,17 +364,17 @@ func TestRooted(t *testing.T) {
|
||||
{"baz/foo/", "bar/baz", "baz/foo/bar/baz", true},
|
||||
{"baz/foo/", "/bar/baz", "baz/foo/bar/baz", true},
|
||||
|
||||
// Not escape attempts, but oddly formatted relative paths. Disallowed.
|
||||
{"foo", "./bar", "", false},
|
||||
{"baz/foo", "./bar", "", false},
|
||||
{"foo", "./bar/baz", "", false},
|
||||
{"baz/foo", "./bar/baz", "", false},
|
||||
{"baz/foo", "bar/../baz", "", false},
|
||||
{"baz/foo", "/bar/../baz", "", false},
|
||||
{"baz/foo", "./bar/../baz", "", false},
|
||||
{"baz/foo", "bar/../baz", "", false},
|
||||
{"baz/foo", "/bar/../baz", "", false},
|
||||
{"baz/foo", "./bar/../baz", "", false},
|
||||
// Not escape attempts, but oddly formatted relative paths.
|
||||
{"foo", "", "foo/", true},
|
||||
{"foo", "/", "foo/", true},
|
||||
{"foo", "/..", "foo/", true},
|
||||
{"foo", "./bar", "foo/bar", true},
|
||||
{"baz/foo", "./bar", "baz/foo/bar", true},
|
||||
{"foo", "./bar/baz", "foo/bar/baz", true},
|
||||
{"baz/foo", "./bar/baz", "baz/foo/bar/baz", true},
|
||||
{"baz/foo", "bar/../baz", "baz/foo/baz", true},
|
||||
{"baz/foo", "/bar/../baz", "baz/foo/baz", true},
|
||||
{"baz/foo", "./bar/../baz", "baz/foo/baz", true},
|
||||
|
||||
// Results in an allowed path, but does it by probing. Disallowed.
|
||||
{"foo", "../foo", "", false},
|
||||
@@ -385,10 +385,7 @@ func TestRooted(t *testing.T) {
|
||||
{"baz/foo", "bar/../../../baz/foo/bar", "", false},
|
||||
|
||||
// Escape attempts.
|
||||
{"foo", "", "", false},
|
||||
{"foo", "/", "", false},
|
||||
{"foo", "..", "", false},
|
||||
{"foo", "/..", "", false},
|
||||
{"foo", "../", "", false},
|
||||
{"foo", "../bar", "", false},
|
||||
{"foo", "../foobar", "", false},
|
||||
@@ -413,8 +410,8 @@ func TestRooted(t *testing.T) {
|
||||
{"/", "/foo", "/foo", true},
|
||||
{"/", "../foo", "", false},
|
||||
{"/", "..", "", false},
|
||||
{"/", "/", "", false},
|
||||
{"/", "", "", false},
|
||||
{"/", "/", "/", true},
|
||||
{"/", "", "/", true},
|
||||
|
||||
// special case for filesystems to be able to MkdirAll('.') for example
|
||||
{"/", ".", "/", true},
|
||||
@@ -427,11 +424,11 @@ func TestRooted(t *testing.T) {
|
||||
{`c:\`, `\foo`, `c:\foo`, true},
|
||||
{`\\?\c:\`, `\foo`, `\\?\c:\foo`, true},
|
||||
{`c:\`, `\\foo`, ``, false},
|
||||
{`c:\`, ``, ``, false},
|
||||
{`c:\`, `\`, ``, false},
|
||||
{`c:\`, ``, `c:\`, true},
|
||||
{`c:\`, `\`, `c:\`, true},
|
||||
{`\\?\c:\`, `\\foo`, ``, false},
|
||||
{`\\?\c:\`, ``, ``, false},
|
||||
{`\\?\c:\`, `\`, ``, false},
|
||||
{`\\?\c:\`, ``, `\\?\c:\`, true},
|
||||
{`\\?\c:\`, `\`, `\\?\c:\`, true},
|
||||
|
||||
// makes no sense, but will be treated simply as a bad filename
|
||||
{`c:\foo`, `d:\bar`, `c:\foo\d:\bar`, true},
|
||||
|
||||
@@ -198,3 +198,39 @@ func IsInternal(file string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Canonicalize checks that the file path is valid and returns it in the "canonical" form:
|
||||
// - /foo/bar -> foo/bar
|
||||
// - / -> "."
|
||||
func Canonicalize(file string) (string, error) {
|
||||
pathSep := string(PathSeparator)
|
||||
|
||||
if strings.HasPrefix(file, pathSep+pathSep) {
|
||||
// The relative path may pretend to be an absolute path within
|
||||
// the root, but the double path separator on Windows implies
|
||||
// something else and is out of spec.
|
||||
return "", ErrNotRelative
|
||||
}
|
||||
|
||||
// The relative path should be clean from internal dotdots and similar
|
||||
// funkyness.
|
||||
file = filepath.Clean(file)
|
||||
|
||||
// It is not acceptable to attempt to traverse upwards.
|
||||
switch file {
|
||||
case "..":
|
||||
return "", ErrNotRelative
|
||||
}
|
||||
if strings.HasPrefix(file, ".."+pathSep) {
|
||||
return "", ErrNotRelative
|
||||
}
|
||||
|
||||
if strings.HasPrefix(file, pathSep) {
|
||||
if file == pathSep {
|
||||
return ".", nil
|
||||
}
|
||||
return file[1:], nil
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
@@ -41,3 +41,60 @@ func TestIsInternal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalize(t *testing.T) {
|
||||
type testcase struct {
|
||||
path string
|
||||
expected string
|
||||
ok bool
|
||||
}
|
||||
cases := []testcase{
|
||||
// Valid cases
|
||||
{"/bar", "bar", true},
|
||||
{"/bar/baz", "bar/baz", true},
|
||||
{"bar", "bar", true},
|
||||
{"bar/baz", "bar/baz", true},
|
||||
|
||||
// Not escape attempts, but oddly formatted relative paths
|
||||
{"", ".", true},
|
||||
{"/", ".", true},
|
||||
{"/..", ".", true},
|
||||
{"./bar", "bar", true},
|
||||
{"./bar/baz", "bar/baz", true},
|
||||
{"bar/../baz", "baz", true},
|
||||
{"/bar/../baz", "baz", true},
|
||||
{"./bar/../baz", "baz", true},
|
||||
|
||||
// Results in an allowed path, but does it by probing. Disallowed.
|
||||
{"../foo", "", false},
|
||||
{"../foo/bar", "", false},
|
||||
{"../foo/bar", "", false},
|
||||
{"../../baz/foo/bar", "", false},
|
||||
{"bar/../../foo/bar", "", false},
|
||||
{"bar/../../../baz/foo/bar", "", false},
|
||||
|
||||
// Escape attempts.
|
||||
{"..", "", false},
|
||||
{"../", "", false},
|
||||
{"../bar", "", false},
|
||||
{"../foobar", "", false},
|
||||
{"bar/../../quux/baz", "", false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res, err := Canonicalize(tc.path)
|
||||
if tc.ok {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for Canonicalize(%q): %v", tc.path, err)
|
||||
continue
|
||||
}
|
||||
exp := filepath.FromSlash(tc.expected)
|
||||
if res != exp {
|
||||
t.Errorf("Unexpected result for Canonicalize(%q): %q != expected %q", tc.path, res, exp)
|
||||
}
|
||||
} else if err == nil {
|
||||
t.Errorf("Unexpected pass for Canonicalize(%q) => %q", tc.path, res)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,12 @@ func NewWalkFilesystem(next Filesystem) Filesystem {
|
||||
|
||||
// walk recursively descends path, calling walkFn.
|
||||
func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc) error {
|
||||
err := walkFn(path, info, nil)
|
||||
path, err := Canonicalize(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = walkFn(path, info, nil)
|
||||
if err != nil {
|
||||
if info.IsDir() && err == SkipDir {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user