From 7529c8a4ca212d73f02699147e4f3c1061d1e712 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 19 Jun 2023 14:37:29 +0200 Subject: [PATCH] - --- libs/filer/local_client.go | 7 +--- libs/filer/root_path.go | 5 +++ libs/filer/unix_root_path.go | 4 -- libs/filer/windows_root_path.go | 39 +++++++++++++++++ libs/filer/windows_root_path_test.go | 63 ++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 libs/filer/windows_root_path.go create mode 100644 libs/filer/windows_root_path_test.go diff --git a/libs/filer/local_client.go b/libs/filer/local_client.go index 44d3cb95f..14d916155 100644 --- a/libs/filer/local_client.go +++ b/libs/filer/local_client.go @@ -18,11 +18,8 @@ type LocalClient struct { } func NewLocalClient(root string) (Filer, error) { - if runtime.GOOS == "windows" && root == "/" { - // Windows file systems do not have a "root" directory. Instead paths require - // a Volume/Drive letter specified. This allows us to refer to files across - // different drives from a single client - return &LocalClient{root: NopRootPath{}}, nil + if runtime.GOOS == "windows" { + return &LocalClient{root: WindowsRootPath{root}}, nil } return &LocalClient{ root: NewUnixRootPath(root), diff --git a/libs/filer/root_path.go b/libs/filer/root_path.go index c14ca27ed..f95717b49 100644 --- a/libs/filer/root_path.go +++ b/libs/filer/root_path.go @@ -1,7 +1,12 @@ package filer +// RootPath can be joined with a relative path and ensures that +// the returned path is always a strict child of the root path. type RootPath interface { + // Join returns the specified path name joined to the root. + // It returns an error if the resulting path is not a strict child of the root path. Join(string) (string, error) + Root() string } diff --git a/libs/filer/unix_root_path.go b/libs/filer/unix_root_path.go index 1d9dc0ccc..944cf2d64 100644 --- a/libs/filer/unix_root_path.go +++ b/libs/filer/unix_root_path.go @@ -6,8 +6,6 @@ import ( "strings" ) -// UnixRootPath can be joined with a relative path and ensures that -// the returned path is always a strict child of the root path. type UnixRootPath struct { rootPath string } @@ -20,8 +18,6 @@ func NewUnixRootPath(name string) UnixRootPath { } } -// Join returns the specified path name joined to the root. -// It returns an error if the resulting path is not a strict child of the root path. func (p UnixRootPath) Join(name string) (string, error) { absPath := path.Join(p.rootPath, name) diff --git a/libs/filer/windows_root_path.go b/libs/filer/windows_root_path.go new file mode 100644 index 000000000..70d91b746 --- /dev/null +++ b/libs/filer/windows_root_path.go @@ -0,0 +1,39 @@ +package filer + +import ( + "fmt" + "path/filepath" + "strings" +) + +type WindowsRootPath struct { + rootPath string +} + +func NewWindowsRootPath(name string) WindowsRootPath { + // Windows file systems do not have a "root" directory. Instead paths require + // a Volume/Drive letter specified. If a user of this struct specifies "/" then + // we treat it as the "root" and skip any validation + if name == "/" { + return WindowsRootPath{""} + } + + return WindowsRootPath{filepath.Clean(name)} +} + +// Join returns the specified path name joined to the root. +// It returns an error if the resulting path is not a strict child of the root path. +func (p WindowsRootPath) Join(name string) (string, error) { + absPath := filepath.Join(p.rootPath, name) + + // Don't allow escaping the specified root using relative paths. + if !strings.HasPrefix(absPath, p.rootPath) { + return "", fmt.Errorf("relative path escapes root: %s", name) + } + + return absPath, nil +} + +func (p WindowsRootPath) Root() string { + return p.rootPath +} diff --git a/libs/filer/windows_root_path_test.go b/libs/filer/windows_root_path_test.go new file mode 100644 index 000000000..96fa9a89c --- /dev/null +++ b/libs/filer/windows_root_path_test.go @@ -0,0 +1,63 @@ +package filer + +import ( + "runtime" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWindowsRootPathForRoot(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("this test is meant for windows") + } + + rp := NewWindowsRootPath("/") + + // Assert root value returned + assert.Equal(t, "", rp.Root()) + + // case: absolute windows path + path, err := rp.Join(`c:\a\b`) + assert.NoError(t, err) + assert.Equal(t, `c:\a\b`, path) + + // case: absolute windows path following file URI scheme + path, err = rp.Join(`D:/a/b`) + assert.NoError(t, err) + assert.Equal(t, `D:/a/b`, path) + + // case: relative windows paths + path, err = rp.Join(`c:a\b`) + assert.NoError(t, err) + assert.Equal(t, `c:a\b`, path) + + path, err = rp.Join(`c:a`) + assert.NoError(t, err) + assert.Equal(t, `c:a`, path) + + // case: relative windows paths following file URI scheme + path, err = rp.Join(`c:a/b`) + assert.NoError(t, err) + assert.Equal(t, `C:a/b`, path) +} + +func TestWindowsRootPath(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("this test is meant for windows") + } + + tmpDir := t.TempDir() + rp := NewWindowsRootPath(t.TempDir()) + + // Assert root value returned + assert.Equal(t, tmpDir, rp.Root()) + + path, err := rp.Join(`a\b\c`) + assert.NoError(t, err) + assert.Equal(t, tmpDir+`\a\b`, path) + + path, err = rp.Join("a/b") + assert.NoError(t, err) + assert.Equal(t, tmpDir + `\a/b`, path) +}