123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- // +build !windows
- // Licensed under the Apache License, Version 2.0; See LICENSE.APACHE
- package symlink
- import (
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
- )
- // TODO Windows: This needs some serious work to port to Windows. For now,
- // turning off testing in this package.
- type dirOrLink struct {
- path string
- target string
- }
- func makeFs(tmpdir string, fs []dirOrLink) error {
- for _, s := range fs {
- s.path = filepath.Join(tmpdir, s.path)
- if s.target == "" {
- os.MkdirAll(s.path, 0755)
- continue
- }
- if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
- return err
- }
- if err := os.Symlink(s.target, s.path); err != nil && !os.IsExist(err) {
- return err
- }
- }
- return nil
- }
- func testSymlink(tmpdir, path, expected, scope string) error {
- rewrite, err := FollowSymlinkInScope(filepath.Join(tmpdir, path), filepath.Join(tmpdir, scope))
- if err != nil {
- return err
- }
- expected, err = filepath.Abs(filepath.Join(tmpdir, expected))
- if err != nil {
- return err
- }
- if expected != rewrite {
- return fmt.Errorf("Expected %q got %q", expected, rewrite)
- }
- return nil
- }
- func TestFollowSymlinkAbsolute(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkAbsolute")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/d", target: "/b"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "testdata/fs/a/d/c/data", "testdata/b/c/data", "testdata"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkRelativePath(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/i", target: "a"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "testdata/fs/i", "testdata/fs/a", "testdata"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkSkipSymlinksOutsideScope(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkSkipSymlinksOutsideScope")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{
- {path: "linkdir", target: "realdir"},
- {path: "linkdir/foo/bar"},
- }); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "linkdir/foo/bar", "linkdir/foo/bar", "linkdir/foo"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkInvalidScopePathPair(t *testing.T) {
- if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil {
- t.Fatal("expected an error")
- }
- }
- func TestFollowSymlinkLastLink(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkLastLink")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/d", target: "/b"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "testdata/fs/a/d", "testdata/b", "testdata"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkRelativeLinkChangeScope(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkChangeScope")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/e", target: "../b"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "testdata/fs/a/e/c/data", "testdata/fs/b/c/data", "testdata"); err != nil {
- t.Fatal(err)
- }
- // avoid letting allowing symlink e lead us to ../b
- // normalize to the "testdata/fs/a"
- if err := testSymlink(tmpdir, "testdata/fs/a/e", "testdata/fs/a/b", "testdata/fs/a"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkDeepRelativeLinkChangeScope(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkDeepRelativeLinkChangeScope")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/a/f", target: "../../../../test"}}); err != nil {
- t.Fatal(err)
- }
- // avoid letting symlink f lead us out of the "testdata" scope
- // we don't normalize because symlink f is in scope and there is no
- // information leak
- if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/test", "testdata"); err != nil {
- t.Fatal(err)
- }
- // avoid letting symlink f lead us out of the "testdata/fs" scope
- // we don't normalize because symlink f is in scope and there is no
- // information leak
- if err := testSymlink(tmpdir, "testdata/fs/a/f", "testdata/fs/test", "testdata/fs"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkRelativeLinkChain(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativeLinkChain")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- // avoid letting symlink g (pointed at by symlink h) take out of scope
- // TODO: we should probably normalize to scope here because ../[....]/root
- // is out of scope and we leak information
- if err := makeFs(tmpdir, []dirOrLink{
- {path: "testdata/fs/b/h", target: "../g"},
- {path: "testdata/fs/g", target: "../../../../../../../../../../../../root"},
- }); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "testdata/fs/b/h", "testdata/root", "testdata"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkBreakoutPath(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkBreakoutPath")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- // avoid letting symlink -> ../directory/file escape from scope
- // normalize to "testdata/fs/j"
- if err := makeFs(tmpdir, []dirOrLink{{path: "testdata/fs/j/k", target: "../i/a"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "testdata/fs/j/k", "testdata/fs/j/i/a", "testdata/fs/j"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkToRoot(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkToRoot")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- // make sure we don't allow escaping to /
- // normalize to dir
- if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkSlashDotdot(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkSlashDotdot")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- tmpdir = filepath.Join(tmpdir, "dir", "subdir")
- // make sure we don't allow escaping to /
- // normalize to dir
- if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "/../../"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkDotdot(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkDotdot")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- tmpdir = filepath.Join(tmpdir, "dir", "subdir")
- // make sure we stay in scope without leaking information
- // this also checks for escaping to /
- // normalize to dir
- if err := makeFs(tmpdir, []dirOrLink{{path: "foo", target: "../../"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "foo", "", ""); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkRelativePath2(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRelativePath2")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{{path: "bar/foo", target: "baz/target"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "bar/foo", "bar/baz/target", ""); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkScopeLink(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkScopeLink")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{
- {path: "root2"},
- {path: "root", target: "root2"},
- {path: "root2/foo", target: "../bar"},
- }); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "root/foo", "root/bar", "root"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkRootScope(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkRootScope")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- expected, err := filepath.EvalSymlinks(tmpdir)
- if err != nil {
- t.Fatal(err)
- }
- rewrite, err := FollowSymlinkInScope(tmpdir, "/")
- if err != nil {
- t.Fatal(err)
- }
- if rewrite != expected {
- t.Fatalf("expected %q got %q", expected, rewrite)
- }
- }
- func TestFollowSymlinkEmpty(t *testing.T) {
- res, err := FollowSymlinkInScope("", "")
- if err != nil {
- t.Fatal(err)
- }
- wd, err := os.Getwd()
- if err != nil {
- t.Fatal(err)
- }
- if res != wd {
- t.Fatalf("expected %q got %q", wd, res)
- }
- }
- func TestFollowSymlinkCircular(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkCircular")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{{path: "root/foo", target: "foo"}}); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "root/foo", "", "root"); err == nil {
- t.Fatal("expected an error for foo -> foo")
- }
- if err := makeFs(tmpdir, []dirOrLink{
- {path: "root/bar", target: "baz"},
- {path: "root/baz", target: "../bak"},
- {path: "root/bak", target: "/bar"},
- }); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "root/foo", "", "root"); err == nil {
- t.Fatal("expected an error for bar -> baz -> bak -> bar")
- }
- }
- func TestFollowSymlinkComplexChainWithTargetPathsContainingLinks(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkComplexChainWithTargetPathsContainingLinks")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{
- {path: "root2"},
- {path: "root", target: "root2"},
- {path: "root/a", target: "r/s"},
- {path: "root/r", target: "../root/t"},
- {path: "root/root/t/s/b", target: "/../u"},
- {path: "root/u/c", target: "."},
- {path: "root/u/x/y", target: "../v"},
- {path: "root/u/v", target: "/../w"},
- }); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "root/a/b/c/x/y/z", "root/w/z", "root"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkBreakoutNonExistent(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkBreakoutNonExistent")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{
- {path: "root/slash", target: "/"},
- {path: "root/sym", target: "/idontexist/../slash"},
- }); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "root/sym/file", "root/file", "root"); err != nil {
- t.Fatal(err)
- }
- }
- func TestFollowSymlinkNoLexicalCleaning(t *testing.T) {
- tmpdir, err := ioutil.TempDir("", "TestFollowSymlinkNoLexicalCleaning")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpdir)
- if err := makeFs(tmpdir, []dirOrLink{
- {path: "root/sym", target: "/foo/bar"},
- {path: "root/hello", target: "/sym/../baz"},
- }); err != nil {
- t.Fatal(err)
- }
- if err := testSymlink(tmpdir, "root/hello", "root/foo/baz", "root"); err != nil {
- t.Fatal(err)
- }
- }
|