diff --git a/backend/archive/archive.go b/backend/archive/archive.go index 25e489581..d60d7f012 100644 --- a/backend/archive/archive.go +++ b/backend/archive/archive.go @@ -36,6 +36,7 @@ import ( "github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/fspath" "github.com/rclone/rclone/fs/hash" + "github.com/rclone/rclone/lib/sanitize" ) // Register with Fs @@ -529,14 +530,14 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e // isDirectChild reports whether remote names an entry directly in dir // ("" being the root), with no ".." or other components in between. func isDirectChild(dir, remote string) bool { - if remote == "" || remote == "." || remote == ".." || strings.HasSuffix(remote, "/") { - return false + if dir != "" { + var ok bool + remote, ok = strings.CutPrefix(remote, dir+"/") + if !ok { + return false + } } - parent := path.Dir(remote) - if parent == "." || parent == "/" { - parent = "" - } - return parent == dir && path.Clean(remote) == remote + return sanitize.Leaf(remote) == nil } // NewObject creates a new remote archive file object diff --git a/backend/archive/archive_internal_test.go b/backend/archive/archive_internal_test.go index 75509e6f7..4d3cf2a0a 100644 --- a/backend/archive/archive_internal_test.go +++ b/backend/archive/archive_internal_test.go @@ -379,3 +379,27 @@ func TestArchiveEscapingArchiver(t *testing.T) { _, err = f.NewObject(ctx, "test.escaping/file.txt") assert.ErrorIs(t, err, fs.ErrorObjectNotFound) } + +// TestIsDirectChild checks the guard which decides whether an entry +// returned by an archiver belongs directly in the directory listed. +func TestIsDirectChild(t *testing.T) { + for _, test := range []struct { + dir, remote string + want bool + }{ + {"", "a.txt", true}, + {"", "/a.txt", false}, + {"", "a.txt/", false}, + {"", "../a.txt", false}, + {"", "sub/a.txt", false}, + {"d", "d/a.txt", true}, + {"d", "d", false}, + {"d", "d/", false}, + {"d", "d//a.txt", false}, + {"d", "d/../a.txt", false}, + {"d", "dd/a.txt", false}, + {"d", "a.txt", false}, + } { + assert.Equal(t, test.want, isDirectChild(test.dir, test.remote), "dir=%q remote=%q", test.dir, test.remote) + } +}