diff --git a/osfs/os_bound.go b/osfs/os_bound.go index c0a6109..5bca8fd 100644 --- a/osfs/os_bound.go +++ b/osfs/os_bound.go @@ -20,6 +20,7 @@ package osfs import ( + "errors" "fmt" "os" "path/filepath" @@ -29,6 +30,11 @@ import ( "github.com/go-git/go-billy/v5" ) +var ( + ErrBaseDirCannotBeRemoved = errors.New("base dir cannot be removed") + ErrBaseDirCannotBeRenamed = errors.New("base dir cannot be renamed") +) + // BoundOS is a fs implementation based on the OS filesystem which is bound to // a base dir. // Prefer this fs implementation over ChrootOS. @@ -54,6 +60,7 @@ func (fs *BoundOS) Create(filename string) (billy.File, error) { } func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { + filename = fs.expandDot(filename) fn, err := fs.abs(filename) if err != nil { return nil, err @@ -62,6 +69,7 @@ func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy. } func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) { + path = fs.expandDot(path) dir, err := fs.abs(path) if err != nil { return nil, err @@ -71,6 +79,12 @@ func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) { } func (fs *BoundOS) Rename(from, to string) error { + if from == "." || from == fs.baseDir { + return ErrBaseDirCannotBeRenamed + } + + from = fs.expandDot(from) + to = fs.expandDot(to) f, err := fs.abs(from) if err != nil { return err @@ -89,6 +103,7 @@ func (fs *BoundOS) Rename(from, to string) error { } func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error { + path = fs.expandDot(path) dir, err := fs.abs(path) if err != nil { return err @@ -97,10 +112,12 @@ func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error { } func (fs *BoundOS) Open(filename string) (billy.File, error) { + filename = fs.expandDot(filename) return fs.OpenFile(filename, os.O_RDONLY, 0) } func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) { + filename = fs.expandDot(filename) filename, err := fs.abs(filename) if err != nil { return nil, err @@ -109,6 +126,10 @@ func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) { } func (fs *BoundOS) Remove(filename string) error { + if filename == "." || filename == fs.baseDir { + return ErrBaseDirCannotBeRemoved + } + fn, err := fs.abs(filename) if err != nil { return err @@ -136,6 +157,11 @@ func (fs *BoundOS) Join(elem ...string) string { } func (fs *BoundOS) RemoveAll(path string) error { + if path == "." || path == fs.baseDir { + return ErrBaseDirCannotBeRemoved + } + + path = fs.expandDot(path) dir, err := fs.abs(path) if err != nil { return err @@ -144,6 +170,7 @@ func (fs *BoundOS) RemoveAll(path string) error { } func (fs *BoundOS) Symlink(target, link string) error { + link = fs.expandDot(link) ln, err := fs.abs(link) if err != nil { return err @@ -155,7 +182,20 @@ func (fs *BoundOS) Symlink(target, link string) error { return os.Symlink(target, ln) } +func (fs *BoundOS) expandDot(p string) string { + if p == "." { + return fs.baseDir + } + for _, prefix := range []string{"./", ".\\"} { + if strings.HasPrefix(p, prefix) { + return filepath.Join(fs.baseDir, strings.TrimPrefix(p, prefix)) + } + } + return p +} + func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) { + filename = fs.expandDot(filename) filename = filepath.Clean(filename) if !filepath.IsAbs(filename) { filename = filepath.Join(fs.baseDir, filename) @@ -167,6 +207,7 @@ func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) { } func (fs *BoundOS) Readlink(link string) (string, error) { + link = fs.expandDot(link) if !filepath.IsAbs(link) { link = filepath.Clean(filepath.Join(fs.baseDir, link)) } diff --git a/osfs/os_bound_test.go b/osfs/os_bound_test.go index ed446a6..df84a66 100644 --- a/osfs/os_bound_test.go +++ b/osfs/os_bound_test.go @@ -47,6 +47,14 @@ func TestOpen(t *testing.T) { }, filename: "test-file", }, + { + name: "file: dot rel same dir", + before: func(dir string) billy.Filesystem { + os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) + return newBoundOS(dir, true) + }, + filename: "./test-file", + }, { name: "file: rel path to above cwd", before: func(dir string) billy.Filesystem { @@ -160,6 +168,11 @@ func Test_Symlink(t *testing.T) { link: "symlink", target: filepath.FromSlash("/etc/passwd"), }, + { + name: "dot link to abs valid target", + link: "./symlink", + target: filepath.FromSlash("/etc/passwd"), + }, { name: "link to abs inexistent target", link: "symlink", @@ -507,6 +520,14 @@ func TestLstat(t *testing.T) { }, filename: "test-file", }, + { + name: "file: dot rel", + before: func(dir string) billy.Filesystem { + os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) + return newBoundOS(dir, true) + }, + filename: "./test-file", + }, { name: "file: abs", before: func(dir string) billy.Filesystem { @@ -610,6 +631,13 @@ func TestStat(t *testing.T) { }, filename: "test-file", }, + { + name: "rel dot dir", + before: func(dir string) billy.Filesystem { + return newBoundOS(dir, true) + }, + filename: ".", + }, { name: "abs file", before: func(dir string) billy.Filesystem { @@ -674,6 +702,14 @@ func TestRemove(t *testing.T) { filename: "inexistent", wantErr: notFoundError(), }, + { + name: "same dot dir", + before: func(dir string) billy.Filesystem { + return newBoundOS(dir, true) + }, + filename: ".", + wantErr: "base dir cannot be removed", + }, { name: "same dir file", before: func(dir string) billy.Filesystem { @@ -1105,10 +1141,10 @@ func TestReadDir(t *testing.T) { g.Expect(dirs).To(gomega.BeNil()) } -func TestInsideBaseDirEval(t*testing.T) { +func TestInsideBaseDirEval(t *testing.T) { g := gomega.NewWithT(t) fs := BoundOS{baseDir: "/"} - b, err := fs.insideBaseDirEval("a") + b, err := fs.insideBaseDirEval("a") g.Expect(b).To(gomega.BeTrue()) g.Expect(err).To(gomega.BeNil()) }