diff --git a/go.mod b/go.mod index cef2802..fc0979a 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,19 @@ module github.com/go-git/go-billy/v5 // go-git supports the last 3 stable Go versions. go 1.19 -replace github.com/cyphar/filepath-securejoin => github.com/pjbgf/filepath-securejoin v0.0.0-20230821001828-0ca74e6d4bf8 - require ( - github.com/cyphar/filepath-securejoin v0.2.3 + github.com/cyphar/filepath-securejoin v0.2.4 github.com/onsi/gomega v1.27.10 - golang.org/x/sys v0.11.0 + golang.org/x/sys v0.12.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) require ( github.com/google/go-cmp v0.5.9 // indirect - github.com/kr/pretty v0.2.1 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/text v0.11.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/text v0.13.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 418c52e..2c9b5b2 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -13,14 +16,16 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= -github.com/pjbgf/filepath-securejoin v0.0.0-20230821001828-0ca74e6d4bf8 h1:nqjCeQ2TVnccihhBoVBd0p+70hCFT4yqJKhfc8l1D50= -github.com/pjbgf/filepath-securejoin v0.0.0-20230821001828-0ca74e6d4bf8/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/osfs/os.go b/osfs/os.go index cb7489a..a7fe79f 100644 --- a/osfs/os.go +++ b/osfs/os.go @@ -22,14 +22,18 @@ const ( var Default = &ChrootOS{} // New returns a new OS filesystem. +// By default paths are deduplicated, but still enforced +// under baseDir. For more info refer to WithDeduplicatePath. func New(baseDir string, opts ...Option) billy.Filesystem { - o := &options{} + o := &options{ + deduplicatePath: true, + } for _, opt := range opts { opt(o) } if o.Type == BoundOSFS { - return newBoundOS(baseDir) + return newBoundOS(baseDir, o.deduplicatePath) } return newChrootOS(baseDir) @@ -49,8 +53,23 @@ func WithChrootOS() Option { } } +// WithDeduplicatePath toggles the deduplication of the base dir in the path. +// This occurs when absolute links are being used. +// Assuming base dir /base/dir and an absolute symlink /base/dir/target: +// +// With DeduplicatePath (default): /base/dir/target +// Without DeduplicatePath: /base/dir/base/dir/target +// +// This option is only used by the BoundOS OS type. +func WithDeduplicatePath(enabled bool) Option { + return func(o *options) { + o.deduplicatePath = enabled + } +} + type options struct { Type + deduplicatePath bool } type Type int diff --git a/osfs/os_bound.go b/osfs/os_bound.go index 5e64ebd..b4b6dbc 100644 --- a/osfs/os_bound.go +++ b/osfs/os_bound.go @@ -41,11 +41,12 @@ import ( // 3. Readlink and Lstat ensures that the link file is located within the base // dir, evaluating any symlinks that file or base dir may contain. type BoundOS struct { - baseDir string + baseDir string + deduplicatePath bool } -func newBoundOS(d string) billy.Filesystem { - return &BoundOS{baseDir: d} +func newBoundOS(d string, deduplicatePath bool) billy.Filesystem { + return &BoundOS{baseDir: d, deduplicatePath: deduplicatePath} } func (fs *BoundOS) Create(filename string) (billy.File, error) { @@ -212,10 +213,21 @@ func (fs *BoundOS) createDir(fullpath string) error { func (fs *BoundOS) abs(filename string) (string, error) { if filename == fs.baseDir { filename = string(filepath.Separator) - } else if cw := fs.baseDir + string(filepath.Separator); strings.HasPrefix(filename, cw) { - filename = strings.TrimPrefix(filename, cw) } - return securejoin.SecureJoin(fs.baseDir, filename) + + path, err := securejoin.SecureJoin(fs.baseDir, filename) + if err != nil { + return "", nil + } + + if fs.deduplicatePath { + vol := filepath.VolumeName(fs.baseDir) + dup := filepath.Join(fs.baseDir, fs.baseDir[len(vol):]) + if strings.HasPrefix(path, dup+string(filepath.Separator)) { + return fs.abs(path[len(dup):]) + } + } + return path, nil } // insideBaseDir checks whether filename is located within diff --git a/osfs/os_bound_test.go b/osfs/os_bound_test.go index 8307028..bf98931 100644 --- a/osfs/os_bound_test.go +++ b/osfs/os_bound_test.go @@ -43,7 +43,7 @@ func TestOpen(t *testing.T) { name: "file: rel same dir", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "test-file", }, @@ -51,7 +51,7 @@ func TestOpen(t *testing.T) { name: "file: rel path to above cwd", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "rel-above-cwd"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "../../rel-above-cwd", }, @@ -60,7 +60,7 @@ func TestOpen(t *testing.T) { before: func(dir string) billy.Filesystem { os.Mkdir(filepath.Join(dir, "sub"), 0o700) os.WriteFile(filepath.Join(dir, "sub/rel-below-cwd"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "sub/rel-below-cwd", }, @@ -68,7 +68,7 @@ func TestOpen(t *testing.T) { name: "file: abs inside cwd", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "abs-test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "abs-test-file", makeAbs: true, @@ -76,7 +76,7 @@ func TestOpen(t *testing.T) { { name: "file: abs outside cwd", before: func(dir string) billy.Filesystem { - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "/some/path/outside/cwd", wantErr: notFoundError(), @@ -87,7 +87,7 @@ func TestOpen(t *testing.T) { target := filepath.Join(dir, "target-file") os.WriteFile(target, []byte("anything"), 0o600) os.Symlink(target, filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", }, @@ -95,7 +95,7 @@ func TestOpen(t *testing.T) { name: "symlink: rel outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../outside/cwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, @@ -105,7 +105,7 @@ func TestOpen(t *testing.T) { name: "symlink: abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/some/path/outside/cwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, @@ -116,7 +116,7 @@ func TestOpen(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) @@ -185,7 +185,7 @@ func Test_Symlink(t *testing.T) { link: "new-dir/symlink", before: func(dir string) billy.Filesystem { os.Mkdir(filepath.Join(dir, "new-dir"), 0o701) - return newBoundOS(dir) + return newBoundOS(dir, true) }, target: filepath.FromSlash("../../../some/random/path"), }, @@ -194,7 +194,7 @@ func Test_Symlink(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) @@ -238,7 +238,7 @@ func Test_Symlink(t *testing.T) { func TestTempFile(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) f, err := fs.TempFile("", "prefix") g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -266,7 +266,7 @@ func TestTempFile(t *testing.T) { func TestChroot(t *testing.T) { g := gomega.NewWithT(t) tmp := t.TempDir() - fs := newBoundOS(tmp) + fs := newBoundOS(tmp, true) f, err := fs.Chroot("test") g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -277,7 +277,7 @@ func TestChroot(t *testing.T) { func TestRoot(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) root := fs.Root() g.Expect(root).To(gomega.Equal(dir)) @@ -297,7 +297,7 @@ func TestReadLink(t *testing.T) { name: "symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", expected: filepath.FromSlash("/etc/passwd"), @@ -311,7 +311,7 @@ func TestReadLink(t *testing.T) { name: "symlink: abs symlink pointing outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, @@ -329,7 +329,7 @@ func TestReadLink(t *testing.T) { os.Symlink(outside, filepath.Join(cwd, "symlink")) os.WriteFile(filepath.Join(outside, "file"), []byte("anything"), 0o600) - return newBoundOS(cwd) + return newBoundOS(cwd, true) }, filename: "current-dir/symlink/file", makeAbs: true, @@ -348,7 +348,7 @@ func TestReadLink(t *testing.T) { os.Symlink(cwdTarget, cwd) os.Symlink(cwdTarget, cwdAlt) os.Symlink(filepath.Join(cwdTarget, "file"), filepath.Join(cwdAlt, "symlink-file")) - return newBoundOS(cwd) + return newBoundOS(cwd, true) }, filename: "symlink-file", expected: filepath.Join("cwd-target/file"), @@ -369,7 +369,7 @@ func TestReadLink(t *testing.T) { os.Symlink(cwdTarget, cwd) os.Symlink(outsideDir, outside) os.Symlink(filepath.Join(cwdTarget, "file"), filepath.Join(outside, "symlink-file")) - return newBoundOS(cwd) + return newBoundOS(cwd, true) }, filename: "symlink-outside/symlink-file", wantErr: "path outside base dir", @@ -379,7 +379,7 @@ func TestReadLink(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) @@ -420,7 +420,7 @@ func TestLstat(t *testing.T) { name: "rel symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", }, @@ -428,7 +428,7 @@ func TestLstat(t *testing.T) { name: "rel symlink: pointing to rel path above cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../../../etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", }, @@ -436,7 +436,7 @@ func TestLstat(t *testing.T) { name: "abs symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, @@ -445,7 +445,7 @@ func TestLstat(t *testing.T) { name: "abs symlink: pointing to rel outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../../../etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", makeAbs: false, @@ -463,7 +463,7 @@ func TestLstat(t *testing.T) { os.Symlink(cwdTarget, cwd) os.Symlink(cwdTarget, cwdAlt) os.Symlink(filepath.Join(cwdTarget, "file"), filepath.Join(cwdAlt, "symlink-file")) - return newBoundOS(cwd) + return newBoundOS(cwd, true) }, filename: "symlink-file", makeAbs: false, @@ -483,7 +483,7 @@ func TestLstat(t *testing.T) { os.Symlink(cwdTarget, cwd) os.Symlink(outsideDir, outside) os.Symlink(filepath.Join(cwdTarget, "file"), filepath.Join(outside, "symlink-file")) - return newBoundOS(cwd) + return newBoundOS(cwd, true) }, filename: "symlink-outside/symlink-file", makeAbs: false, @@ -503,7 +503,7 @@ func TestLstat(t *testing.T) { name: "file: rel", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "test-file", }, @@ -511,7 +511,7 @@ func TestLstat(t *testing.T) { name: "file: abs", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "test-file", makeAbs: true, @@ -521,7 +521,7 @@ func TestLstat(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) @@ -557,7 +557,7 @@ func TestStat(t *testing.T) { name: "rel symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", wantErr: notFoundError(), @@ -566,7 +566,7 @@ func TestStat(t *testing.T) { name: "rel symlink: pointing to rel path above cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../../../etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", wantErr: notFoundError(), @@ -576,7 +576,7 @@ func TestStat(t *testing.T) { name: "abs symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, @@ -586,7 +586,7 @@ func TestStat(t *testing.T) { name: "abs symlink: pointing to rel outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../../../etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", makeAbs: false, @@ -606,7 +606,7 @@ func TestStat(t *testing.T) { name: "rel file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "test-file", }, @@ -614,7 +614,7 @@ func TestStat(t *testing.T) { name: "abs file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "test-file", makeAbs: true, @@ -624,7 +624,7 @@ func TestStat(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) @@ -669,7 +669,7 @@ func TestRemove(t *testing.T) { { name: "inexistent dir", before: func(dir string) billy.Filesystem { - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "inexistent", wantErr: notFoundError(), @@ -678,7 +678,7 @@ func TestRemove(t *testing.T) { name: "same dir file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "test-file", }, @@ -688,7 +688,7 @@ func TestRemove(t *testing.T) { target := filepath.Join(dir, "target-file") os.WriteFile(target, []byte("anything"), 0o600) os.Symlink(target, filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", }, @@ -696,7 +696,7 @@ func TestRemove(t *testing.T) { name: "rel path to file above cwd", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "rel-above-cwd"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "../../rel-above-cwd", }, @@ -704,7 +704,7 @@ func TestRemove(t *testing.T) { name: "abs file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "abs-test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "abs-test-file", makeAbs: true, @@ -719,7 +719,7 @@ func TestRemove(t *testing.T) { os.MkdirAll(filepath.Dir(outsideFile), 0o700) os.WriteFile(outsideFile, []byte("anything"), 0o600) os.Symlink(outsideFile, filepath.Join(cwd, "remove-abs-symlink")) - return newBoundOS(cwd) + return newBoundOS(cwd, true) }, filename: "remove-abs-symlink", wantErr: notFoundError(), @@ -734,7 +734,7 @@ func TestRemove(t *testing.T) { os.MkdirAll(filepath.Dir(outsideFile), 0o700) os.WriteFile(outsideFile, []byte("anything"), 0o600) os.Symlink(filepath.Join("..", "outside-cwd", "file2"), filepath.Join(cwd, "remove-abs-symlink2")) - return newBoundOS(cwd) + return newBoundOS(cwd, true) }, filename: "remove-rel-symlink", wantErr: notFoundError(), @@ -744,7 +744,7 @@ func TestRemove(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) @@ -778,7 +778,7 @@ func TestRemoveAll(t *testing.T) { name: "parent with children", before: func(dir string) billy.Filesystem { os.MkdirAll(filepath.Join(dir, "parent/children"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "parent", }, @@ -790,7 +790,7 @@ func TestRemoveAll(t *testing.T) { name: "same dir file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "test-file", }, @@ -800,7 +800,7 @@ func TestRemoveAll(t *testing.T) { target := filepath.Join(dir, "target-file") os.WriteFile(target, []byte("anything"), 0o600) os.Symlink(target, filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", }, @@ -808,7 +808,7 @@ func TestRemoveAll(t *testing.T) { name: "rel path to file above cwd", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "rel-above-cwd"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "../../rel-above-cwd", }, @@ -816,7 +816,7 @@ func TestRemoveAll(t *testing.T) { name: "abs file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "abs-test-file"), []byte("anything"), 0o600) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "abs-test-file", makeAbs: true, @@ -825,7 +825,7 @@ func TestRemoveAll(t *testing.T) { name: "abs symlink", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) - return newBoundOS(dir) + return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, @@ -835,7 +835,7 @@ func TestRemoveAll(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir).(*BoundOS) + fs := newBoundOS(dir, true).(*BoundOS) if tt.before != nil { fs = tt.before(dir).(*BoundOS) @@ -886,7 +886,7 @@ func TestJoin(t *testing.T) { for _, tt := range tests { t.Run(tt.wanted, func(t *testing.T) { g := gomega.NewWithT(t) - fs := newBoundOS(t.TempDir()) + fs := newBoundOS(t.TempDir(), true) got := fs.Join(tt.elems...) g.Expect(got).To(gomega.Equal(tt.wanted)) @@ -903,6 +903,7 @@ func TestAbs(t *testing.T) { expected string makeExpectedAbs bool wantErr string + deduplicatePath bool before func(dir string) }{ { @@ -936,10 +937,17 @@ func TestAbs(t *testing.T) { expected: filepath.FromSlash("/working/dir/file"), }, { - name: "path: abs file within cwd", + name: "path: abs file within cwd", + cwd: filepath.FromSlash("/working/dir"), + filename: filepath.FromSlash("/working/dir/abs-file"), + expected: filepath.FromSlash("/working/dir/abs-file"), + deduplicatePath: true, + }, + { + name: "path: abs file within cwd wo deduplication", cwd: filepath.FromSlash("/working/dir"), filename: filepath.FromSlash("/working/dir/abs-file"), - expected: filepath.FromSlash("/working/dir/abs-file"), + expected: filepath.FromSlash("/working/dir/working/dir/abs-file"), }, { name: "path: abs file within cwd", @@ -956,6 +964,7 @@ func TestAbs(t *testing.T) { before: func(dir string) { os.Symlink(filepath.Join(dir, "within-cwd"), filepath.Join(dir, "ln-cwd-cwd")) }, + deduplicatePath: true, }, { name: "abs symlink: within cwd w rel descending target", @@ -966,6 +975,7 @@ func TestAbs(t *testing.T) { before: func(dir string) { os.Symlink("within-cwd", filepath.Join(dir, "ln-rel-cwd-cwd")) }, + deduplicatePath: true, }, { name: "abs symlink: within cwd w abs ascending target", @@ -976,6 +986,7 @@ func TestAbs(t *testing.T) { before: func(dir string) { os.Symlink("/some/outside/dir", filepath.Join(dir, "ln-cwd-up")) }, + deduplicatePath: true, }, { name: "abs symlink: within cwd w rel ascending target", @@ -986,6 +997,7 @@ func TestAbs(t *testing.T) { before: func(dir string) { os.Symlink("../../outside-cwd", filepath.Join(dir, "ln-rel-cwd-up")) }, + deduplicatePath: true, }, { name: "rel symlink: within cwd w abs descending target", @@ -995,6 +1007,7 @@ func TestAbs(t *testing.T) { before: func(dir string) { os.Symlink(filepath.Join(dir, "within-cwd"), filepath.Join(dir, "ln-cwd-cwd")) }, + deduplicatePath: true, }, { name: "rel symlink: within cwd w rel descending target", @@ -1032,7 +1045,7 @@ func TestAbs(t *testing.T) { cwd = t.TempDir() } - fs := newBoundOS(cwd).(*BoundOS) + fs := newBoundOS(cwd, tt.deduplicatePath).(*BoundOS) if tt.before != nil { tt.before(cwd) } @@ -1063,7 +1076,7 @@ func TestAbs(t *testing.T) { func TestReadDir(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) f, err := os.Create(filepath.Join(dir, "file1")) g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -1098,7 +1111,7 @@ func TestMkdirAll(t *testing.T) { cwd := filepath.Join(root, "cwd") target := "abc" targetAbs := filepath.Join(cwd, target) - fs := newBoundOS(cwd) + fs := newBoundOS(cwd, true) // Even if CWD is changed outside of the fs instance, // the base dir must still be observed. @@ -1132,7 +1145,7 @@ func TestMkdirAll(t *testing.T) { func TestRename(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() - fs := newBoundOS(dir) + fs := newBoundOS(dir, true) oldFile := "old-file" newFile := filepath.Join("newdir", "newfile")