123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- package safepath
- import (
- "context"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "testing"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- )
- func TestJoinEscapingSymlink(t *testing.T) {
- type testCase struct {
- name string
- target string
- }
- var cases []testCase
- if runtime.GOOS == "windows" {
- cases = []testCase{
- {name: "root", target: `C:\`},
- {name: "absolute file", target: `C:\Windows\System32\cmd.exe`},
- }
- } else {
- cases = []testCase{
- {name: "root", target: "/"},
- {name: "absolute file", target: "/etc/passwd"},
- }
- }
- cases = append(cases, testCase{name: "relative", target: "../../"})
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- tempDir := t.TempDir()
- dir, err := filepath.EvalSymlinks(tempDir)
- assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir)
- err = os.Symlink(tc.target, filepath.Join(dir, "link"))
- assert.NilError(t, err, "failed to create symlink to %s", tc.target)
- safe, err := Join(context.Background(), dir, "link")
- if err == nil {
- safe.Close(context.Background())
- }
- assert.ErrorType(t, err, &ErrEscapesBase{})
- })
- }
- }
- func TestJoinGoodSymlink(t *testing.T) {
- tempDir := t.TempDir()
- dir, err := filepath.EvalSymlinks(tempDir)
- assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir)
- assert.Assert(t, os.WriteFile(filepath.Join(dir, "foo"), []byte("bar"), 0o744), "failed to create file 'foo'")
- assert.Assert(t, os.Mkdir(filepath.Join(dir, "subdir"), 0o744), "failed to create directory 'subdir'")
- assert.Assert(t, os.WriteFile(filepath.Join(dir, "subdir/hello.txt"), []byte("world"), 0o744), "failed to create file 'subdir/hello.txt'")
- assert.Assert(t, os.Symlink(filepath.Join(dir, "subdir"), filepath.Join(dir, "subdir_link_absolute")), "failed to create absolute symlink to directory 'subdir'")
- assert.Assert(t, os.Symlink("subdir", filepath.Join(dir, "subdir_link_relative")), "failed to create relative symlink to directory 'subdir'")
- assert.Assert(t, os.Symlink(filepath.Join(dir, "foo"), filepath.Join(dir, "foo_link_absolute")), "failed to create absolute symlink to file 'foo'")
- assert.Assert(t, os.Symlink("foo", filepath.Join(dir, "foo_link_relative")), "failed to create relative symlink to file 'foo'")
- for _, target := range []string{
- "foo", "subdir",
- "subdir_link_absolute", "foo_link_absolute",
- "subdir_link_relative", "foo_link_relative",
- } {
- t.Run(target, func(t *testing.T) {
- safe, err := Join(context.Background(), dir, target)
- assert.NilError(t, err)
- defer safe.Close(context.Background())
- if strings.HasPrefix(target, "subdir") {
- data, err := os.ReadFile(filepath.Join(safe.Path(), "hello.txt"))
- assert.NilError(t, err)
- assert.Assert(t, is.Equal(string(data), "world"))
- }
- })
- }
- }
- func TestJoinWithSymlinkReplace(t *testing.T) {
- tempDir := t.TempDir()
- dir, err := filepath.EvalSymlinks(tempDir)
- assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir)
- link := filepath.Join(dir, "link")
- target := filepath.Join(dir, "foo")
- err = os.WriteFile(target, []byte("bar"), 0o744)
- assert.NilError(t, err, "failed to create test file")
- err = os.Symlink(target, link)
- assert.Check(t, err, "failed to create symlink to foo")
- safe, err := Join(context.Background(), dir, "link")
- assert.NilError(t, err)
- defer safe.Close(context.Background())
- // Delete the link target.
- err = os.Remove(target)
- if runtime.GOOS == "windows" {
- // On Windows it shouldn't be possible.
- assert.Assert(t, is.ErrorType(err, &os.PathError{}), "link shouldn't be deletable before cleanup")
- } else {
- // On Linux we can delete it just fine.
- assert.NilError(t, err, "failed to remove symlink")
- // Replace target with a symlink to /etc/paswd
- err = os.Symlink("/etc/passwd", target)
- assert.NilError(t, err, "failed to create symlink")
- }
- // The returned safe path should still point to the old file.
- data, err := os.ReadFile(safe.Path())
- assert.NilError(t, err, "failed to read file")
- assert.Check(t, is.Equal(string(data), "bar"))
- }
- func TestJoinCloseInvalidates(t *testing.T) {
- tempDir := t.TempDir()
- dir, err := filepath.EvalSymlinks(tempDir)
- assert.NilError(t, err)
- foo := filepath.Join(dir, "foo")
- err = os.WriteFile(foo, []byte("bar"), 0o744)
- assert.NilError(t, err, "failed to create test file")
- safe, err := Join(context.Background(), dir, "foo")
- assert.NilError(t, err)
- assert.Check(t, safe.IsValid())
- assert.NilError(t, safe.Close(context.Background()))
- assert.Check(t, !safe.IsValid())
- }
|