From 6afbffce283dd3f3f0372dc90c034f061b03dc5f Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 25 May 2023 09:41:18 -0400 Subject: [PATCH] Fix directory resolver to consider CWD and root path input correctly (#1840) * [wip] put in initial fix Signed-off-by: Alex Goodman * capture expected behavior of dir resolver in tests Signed-off-by: Alex Goodman * update tests + comments to reflect current dir resolver behavior Signed-off-by: Alex Goodman * add additional test cases Signed-off-by: Alex Goodman * fix linting Signed-off-by: Alex Goodman * fix additional tests Signed-off-by: Alex Goodman * fix bad merge conflict resolution Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- .../spdxhelpers/to_format_model_test.go | 5 +- syft/internal/fileresolver/directory.go | 11 +- .../fileresolver/directory_indexer.go | 96 ++++ .../fileresolver/directory_indexer_test.go | 69 ++- syft/internal/fileresolver/directory_test.go | 495 +++++++++++++++++ .../req-resp/path/to/rel-inside.txt | 1 + .../req-resp/path/to/the/file.txt | 1 + .../req-resp/path/to/the/rel-outside.txt | 1 + .../test-fixtures/req-resp/root-link | 1 + .../req-resp/somewhere/outside.txt | 1 + .../fileresolver/unindexed_directory_test.go | 503 ++++++++++++++++++ syft/pkg/cataloger/kernel/cataloger_test.go | 3 +- syft/pkg/license_set_test.go | 19 +- 13 files changed, 1177 insertions(+), 29 deletions(-) create mode 120000 syft/internal/fileresolver/test-fixtures/req-resp/path/to/rel-inside.txt create mode 100644 syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/file.txt create mode 120000 syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/rel-outside.txt create mode 120000 syft/internal/fileresolver/test-fixtures/req-resp/root-link create mode 100644 syft/internal/fileresolver/test-fixtures/req-resp/somewhere/outside.txt diff --git a/syft/formats/common/spdxhelpers/to_format_model_test.go b/syft/formats/common/spdxhelpers/to_format_model_test.go index e36e29435d1..411eed81da7 100644 --- a/syft/formats/common/spdxhelpers/to_format_model_test.go +++ b/syft/formats/common/spdxhelpers/to_format_model_test.go @@ -13,7 +13,6 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/sbom" - "github.com/anchore/syft/syft/source" ) // TODO: Add ToFormatModel tests @@ -505,14 +504,14 @@ func Test_toSPDXID(t *testing.T) { }{ { name: "short filename", - it: source.Coordinates{ + it: file.Coordinates{ RealPath: "/short/path/file.txt", }, expected: "File-short-path-file.txt", }, { name: "long filename", - it: source.Coordinates{ + it: file.Coordinates{ RealPath: "/some/long/path/with/a/lot/of-text/that-contains-a/file.txt", }, expected: "File-...a-lot-of-text-that-contains-a-file.txt", diff --git a/syft/internal/fileresolver/directory.go b/syft/internal/fileresolver/directory.go index a892360d480..2d634cf1eed 100644 --- a/syft/internal/fileresolver/directory.go +++ b/syft/internal/fileresolver/directory.go @@ -54,12 +54,6 @@ func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathI if err != nil { return nil, fmt.Errorf("could not get CWD: %w", err) } - // we have to account for the root being accessed through a symlink path and always resolve the real path. Otherwise - // we will not be able to normalize given paths that fall under the resolver - cleanCWD, err := filepath.EvalSymlinks(currentWD) - if err != nil { - return nil, fmt.Errorf("could not evaluate CWD symlinks: %w", err) - } cleanRoot, err := filepath.EvalSymlinks(root) if err != nil { @@ -80,7 +74,7 @@ func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathI var currentWdRelRoot string if path.IsAbs(cleanRoot) { - currentWdRelRoot, err = filepath.Rel(cleanCWD, cleanRoot) + currentWdRelRoot, err = filepath.Rel(currentWD, cleanRoot) if err != nil { return nil, fmt.Errorf("could not determine given root path to CWD: %w", err) } @@ -91,7 +85,7 @@ func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathI return &Directory{ path: cleanRoot, base: cleanBase, - currentWd: cleanCWD, + currentWd: currentWD, currentWdRelativeToRoot: currentWdRelRoot, tree: filetree.New(), index: filetree.NewIndex(), @@ -132,6 +126,7 @@ func (r Directory) requestPath(userPath string) (string, error) { return userPath, nil } +// responsePath takes a path from the underlying fs domain and converts it to a path that is relative to the root of the directory resolver. func (r Directory) responsePath(path string) string { // check to see if we need to encode back to Windows from posix if runtime.GOOS == WindowsOS { diff --git a/syft/internal/fileresolver/directory_indexer.go b/syft/internal/fileresolver/directory_indexer.go index c590e6caec0..b4383d75d02 100644 --- a/syft/internal/fileresolver/directory_indexer.go +++ b/syft/internal/fileresolver/directory_indexer.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "runtime" + "strings" "github.com/wagoodman/go-partybus" "github.com/wagoodman/go-progress" @@ -119,6 +120,22 @@ func (r *directoryIndexer) indexTree(root string, stager *progress.Stage) ([]str return roots, nil } + shouldIndexFullTree, err := isRealPath(root) + if err != nil { + return nil, err + } + + if !shouldIndexFullTree { + newRoots, err := r.indexBranch(root, stager) + if err != nil { + return nil, fmt.Errorf("unable to index branch=%q: %w", root, err) + } + + roots = append(roots, newRoots...) + + return roots, nil + } + err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { stager.Current = path @@ -143,6 +160,85 @@ func (r *directoryIndexer) indexTree(root string, stager *progress.Stage) ([]str return roots, nil } +func isRealPath(root string) (bool, error) { + rootParent := filepath.Clean(filepath.Dir(root)) + + realRootParent, err := filepath.EvalSymlinks(rootParent) + if err != nil { + return false, err + } + + realRootParent = filepath.Clean(realRootParent) + + return rootParent == realRootParent, nil +} + +func (r *directoryIndexer) indexBranch(root string, stager *progress.Stage) ([]string, error) { + rootRealPath, err := filepath.EvalSymlinks(root) + if err != nil { + return nil, err + } + + // there is a symlink within the path to the root, we need to index the real root parent first + // then capture the symlinks to the root path + roots, err := r.indexTree(rootRealPath, stager) + if err != nil { + return nil, fmt.Errorf("unable to index real root=%q: %w", rootRealPath, err) + } + + // walk down all ancestor paths and shallow-add non-existing elements to the tree + for idx, p := range allContainedPaths(root) { + var targetPath string + if idx != 0 { + parent := path.Dir(p) + cleanParent, err := filepath.EvalSymlinks(parent) + if err != nil { + return nil, fmt.Errorf("unable to evaluate symlink for contained path parent=%q: %w", parent, err) + } + targetPath = filepath.Join(cleanParent, filepath.Base(p)) + } else { + targetPath = p + } + + stager.Current = targetPath + + lstat, err := os.Lstat(targetPath) + newRoot, err := r.indexPath(targetPath, lstat, err) + if err != nil && !errors.Is(err, ErrSkipPath) && !errors.Is(err, fs.SkipDir) { + return nil, fmt.Errorf("unable to index ancestor path=%q: %w", targetPath, err) + } + if newRoot != "" { + roots = append(roots, newRoot) + } + } + + return roots, nil +} + +func allContainedPaths(p string) []string { + var all []string + var currentPath string + + cleanPath := strings.TrimSpace(p) + + if cleanPath == "" { + return nil + } + + // iterate through all parts of the path, replacing path elements with link resolutions where possible. + for idx, part := range strings.Split(filepath.Clean(cleanPath), file.DirSeparator) { + if idx == 0 && part == "" { + currentPath = file.DirSeparator + continue + } + + // cumulatively gather where we are currently at and provide a rich object + currentPath = path.Join(currentPath, part) + all = append(all, currentPath) + } + return all +} + func (r *directoryIndexer) indexPath(path string, info os.FileInfo, err error) (string, error) { // ignore any path which a filter function returns true for _, filterFn := range r.pathIndexVisitors { diff --git a/syft/internal/fileresolver/directory_indexer_test.go b/syft/internal/fileresolver/directory_indexer_test.go index cccacfc2c57..3e5c128cde3 100644 --- a/syft/internal/fileresolver/directory_indexer_test.go +++ b/syft/internal/fileresolver/directory_indexer_test.go @@ -226,8 +226,8 @@ func TestDirectoryIndexer_SkipsAlreadyVisitedLinkDestinations(t *testing.T) { var observedPaths []string pathObserver := func(p string, _ os.FileInfo, _ error) error { fields := strings.Split(p, "test-fixtures/symlinks-prune-indexing") - if len(fields) != 2 { - t.Fatalf("unable to parse path: %s", p) + if len(fields) < 2 { + return nil } clean := strings.TrimLeft(fields[1], "/") if clean != "" { @@ -261,9 +261,11 @@ func TestDirectoryIndexer_SkipsAlreadyVisitedLinkDestinations(t *testing.T) { "path/5/6/7/8/dont-index-me-twice-either.txt", "path/file.txt", // everything below is after the original tree is indexed, and we are now indexing additional roots from symlinks - "path", // considered from symlink before-path, but pruned - "before-path/file.txt", // considered from symlink c-file.txt, but pruned - "before-path", // considered from symlink c-path, but pruned + "path", // considered from symlink before-path, but pruned + "path/file.txt", // leaf + "before-path", // considered from symlink c-path, but pruned + "path/file.txt", // leaf + "before-path", // considered from symlink c-path, but pruned } assert.Equal(t, expected, observedPaths, "visited paths differ \n %s", cmp.Diff(expected, observedPaths)) @@ -282,7 +284,7 @@ func TestDirectoryIndexer_IndexesAllTypes(t *testing.T) { for _, ref := range allRefs { fields := strings.Split(string(ref.RealPath), "test-fixtures/symlinks-prune-indexing") if len(fields) != 2 { - t.Fatalf("unable to parse path: %s", ref.RealPath) + continue } clean := strings.TrimLeft(fields[1], "/") if clean == "" { @@ -326,3 +328,58 @@ func TestDirectoryIndexer_IndexesAllTypes(t *testing.T) { } } + +func Test_allContainedPaths(t *testing.T) { + + tests := []struct { + name string + path string + want []string + }{ + { + name: "empty", + path: "", + want: nil, + }, + { + name: "single relative", + path: "a", + want: []string{"a"}, + }, + { + name: "single absolute", + path: "/a", + want: []string{"/a"}, + }, + { + name: "multiple relative", + path: "a/b/c", + want: []string{"a", "a/b", "a/b/c"}, + }, + { + name: "multiple absolute", + path: "/a/b/c", + want: []string{"/a", "/a/b", "/a/b/c"}, + }, + { + name: "multiple absolute with extra slashs", + path: "///a/b//c/", + want: []string{"/a", "/a/b", "/a/b/c"}, + }, + { + name: "relative with single dot", + path: "a/./b", + want: []string{"a", "a/b"}, + }, + { + name: "relative with double single dot", + path: "a/../b", + want: []string{"b"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, allContainedPaths(tt.path)) + }) + } +} diff --git a/syft/internal/fileresolver/directory_test.go b/syft/internal/fileresolver/directory_test.go index 819c1df28b2..09d135f9c2b 100644 --- a/syft/internal/fileresolver/directory_test.go +++ b/syft/internal/fileresolver/directory_test.go @@ -22,6 +22,501 @@ import ( "github.com/anchore/syft/syft/file" ) +func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) { + // / + // somewhere/ + // outside.txt + // root-link -> ./ + // path/ + // to/ + // abs-inside.txt -> /path/to/the/file.txt # absolute link to somewhere inside of the root + // rel-inside.txt -> ./the/file.txt # relative link to somewhere inside of the root + // the/ + // file.txt + // abs-outside.txt -> /somewhere/outside.txt # absolute link to outside of the root + // rel-outside -> ../../../somewhere/outside.txt # relative link to outside of the root + // + + testDir, err := os.Getwd() + require.NoError(t, err) + relative := filepath.Join("test-fixtures", "req-resp") + absolute := filepath.Join(testDir, relative) + + absInsidePath := filepath.Join(absolute, "path", "to", "abs-inside.txt") + absOutsidePath := filepath.Join(absolute, "path", "to", "the", "abs-outside.txt") + + relativeViaLink := filepath.Join(relative, "root-link") + absoluteViaLink := filepath.Join(absolute, "root-link") + + relativeViaDoubleLink := filepath.Join(relative, "root-link", "root-link") + absoluteViaDoubleLink := filepath.Join(absolute, "root-link", "root-link") + + cleanup := func() { + _ = os.Remove(absInsidePath) + _ = os.Remove(absOutsidePath) + } + + // ensure the absolute symlinks are cleaned up from any previous runs + cleanup() + + require.NoError(t, os.Symlink(filepath.Join(absolute, "path", "to", "the", "file.txt"), absInsidePath)) + require.NoError(t, os.Symlink(filepath.Join(absolute, "somewhere", "outside.txt"), absOutsidePath)) + + t.Cleanup(cleanup) + + cases := []struct { + name string + cwd string + root string + base string + input string + expectedRealPath string + expectedVirtualPath string + }{ + { + name: "relative root, relative request, direct", + root: relative, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct", + root: absolute, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct", + root: relative, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct", + root: absolute, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within root... + { + name: "relative root, relative request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: "../../", + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: absolute, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: "../../", + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + + root: absolute, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within symlink root... + { + name: "relative root, relative request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./", + input: "path/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "path/to/the/file.txt", + expectedVirtualPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: absoluteViaLink, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./", + input: "/path/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "path/to/the/file.txt", + expectedVirtualPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: absoluteViaLink, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within symlink root, request nested within... + { + name: "relative root, relative nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./path", + input: "to/the/file.txt", + // note: why not expect "to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absoluteViaLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./path", + input: "/to/the/file.txt", + // note: why not expect "to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absoluteViaLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // cwd within DOUBLE symlink root... + { + name: "relative root, relative request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./", + input: "path/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "path/to/the/file.txt", + expectedVirtualPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: absoluteViaDoubleLink, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./", + input: "/path/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "path/to/the/file.txt", + expectedVirtualPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: absoluteViaDoubleLink, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within DOUBLE symlink root, request nested within... + { + name: "relative root, relative nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./path", + input: "to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./path", + input: "/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // cwd within DOUBLE symlink root, request nested DEEP within... + { + name: "relative root, relative nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: "../", + input: "to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: "../", + input: "/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // link to outside of root cases... + { + name: "relative root, relative request, abs indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, relative request, abs indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, abs request, abs indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, abs request, abs indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + // link to outside of root cases... cwd within symlink root + { + name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: "path", + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: "path", + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + // we need to mimic a shell, otherwise we won't get a path within a symlink + targetPath := filepath.Join(testDir, c.cwd) + t.Setenv("PWD", filepath.Clean(targetPath)) + + require.NoError(t, err) + require.NoError(t, os.Chdir(targetPath)) + t.Cleanup(func() { + require.NoError(t, os.Chdir(testDir)) + }) + + resolver, err := NewFromDirectory(c.root, c.base) + require.NoError(t, err) + require.NotNil(t, resolver) + + refs, err := resolver.FilesByPath(c.input) + require.NoError(t, err) + if c.expectedRealPath == "" { + require.Empty(t, refs) + return + } + require.Len(t, refs, 1) + assert.Equal(t, c.expectedRealPath, refs[0].RealPath, "real path different") + assert.Equal(t, c.expectedVirtualPath, refs[0].VirtualPath, "virtual path different") + }) + } +} + func TestDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) { cases := []struct { name string diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/path/to/rel-inside.txt b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/rel-inside.txt new file mode 120000 index 00000000000..f2bc06e87c4 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/rel-inside.txt @@ -0,0 +1 @@ +./the/file.txt \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/file.txt b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/file.txt new file mode 100644 index 00000000000..fbfd79f5e48 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/file.txt @@ -0,0 +1 @@ +file-1 diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/rel-outside.txt b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/rel-outside.txt new file mode 120000 index 00000000000..6ad08d35758 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/rel-outside.txt @@ -0,0 +1 @@ +../../../somewhere/outside.txt \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/root-link b/syft/internal/fileresolver/test-fixtures/req-resp/root-link new file mode 120000 index 00000000000..6a043149e81 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/root-link @@ -0,0 +1 @@ +./ \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/somewhere/outside.txt b/syft/internal/fileresolver/test-fixtures/req-resp/somewhere/outside.txt new file mode 100644 index 00000000000..37ad5611998 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/somewhere/outside.txt @@ -0,0 +1 @@ +file-2 diff --git a/syft/internal/fileresolver/unindexed_directory_test.go b/syft/internal/fileresolver/unindexed_directory_test.go index 14631fc4cd8..3714d8d55eb 100644 --- a/syft/internal/fileresolver/unindexed_directory_test.go +++ b/syft/internal/fileresolver/unindexed_directory_test.go @@ -23,6 +23,509 @@ import ( "github.com/anchore/syft/syft/file" ) +func Test_UnindexDirectoryResolver_RequestRelativePathWithinSymlink(t *testing.T) { + pwd, err := os.Getwd() + + // we need to mimic a shell, otherwise we won't get a path within a symlink + targetPath := filepath.Join(pwd, "./test-fixtures/symlinked-root/nested/link-root/nested") + t.Setenv("PWD", targetPath) + + require.NoError(t, err) + require.NoError(t, os.Chdir(targetPath)) + t.Cleanup(func() { + require.NoError(t, os.Chdir(pwd)) + }) + + resolver := NewFromUnindexedDirectory("./") + require.NoError(t, err) + + locations, err := resolver.FilesByPath("file2.txt") + require.NoError(t, err) + require.Len(t, locations, 1) + + // TODO: this is technically not correct behavior since this is reporting the symlink path (virtual path) and + // not the real path. + require.False(t, filepath.IsAbs(locations[0].RealPath), "should be relative path") +} + +func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) { + // / + // somewhere/ + // outside.txt + // root-link -> ./ + // path/ + // to/ + // abs-inside.txt -> /path/to/the/file.txt # absolute link to somewhere inside of the root + // rel-inside.txt -> ./the/file.txt # relative link to somewhere inside of the root + // the/ + // file.txt + // abs-outside.txt -> /somewhere/outside.txt # absolute link to outside of the root + // rel-outside -> ../../../somewhere/outside.txt # relative link to outside of the root + // + + testDir, err := os.Getwd() + require.NoError(t, err) + relative := filepath.Join("test-fixtures", "req-resp") + absolute := filepath.Join(testDir, relative) + + absInsidePath := filepath.Join(absolute, "path", "to", "abs-inside.txt") + absOutsidePath := filepath.Join(absolute, "path", "to", "the", "abs-outside.txt") + + relativeViaLink := filepath.Join(relative, "root-link") + absoluteViaLink := filepath.Join(absolute, "root-link") + + relativeViaDoubleLink := filepath.Join(relative, "root-link", "root-link") + absoluteViaDoubleLink := filepath.Join(absolute, "root-link", "root-link") + + cleanup := func() { + _ = os.Remove(absInsidePath) + _ = os.Remove(absOutsidePath) + } + + // ensure the absolute symlinks are cleaned up from any previous runs + cleanup() + + require.NoError(t, os.Symlink(filepath.Join(absolute, "path", "to", "the", "file.txt"), absInsidePath)) + require.NoError(t, os.Symlink(filepath.Join(absolute, "somewhere", "outside.txt"), absOutsidePath)) + + t.Cleanup(cleanup) + + cases := []struct { + name string + cwd string + root string + base string + input string + expectedRealPath string + expectedVirtualPath string + }{ + { + name: "relative root, relative request, direct", + root: relative, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct", + root: absolute, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct", + root: relative, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct", + root: absolute, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within root... + { + name: "relative root, relative request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: "../../", + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: absolute, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: "../../", + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + + root: absolute, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within symlink root... + { + name: "relative root, relative request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./", + input: "path/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: absoluteViaLink, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./", + input: "/path/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: absoluteViaLink, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within symlink root, request nested within... + { + name: "relative root, relative nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./path", + input: "to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absoluteViaLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./path", + input: "/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absoluteViaLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // cwd within DOUBLE symlink root... + { + name: "relative root, relative request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./", + input: "path/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: absoluteViaDoubleLink, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./", + input: "/path/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: absoluteViaDoubleLink, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within DOUBLE symlink root, request nested within... + { + name: "relative root, relative nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./path", + input: "to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./path", + input: "/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // cwd within DOUBLE symlink root, request nested DEEP within... + { + name: "relative root, relative nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: "../", + input: "to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: "../", + input: "/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // link to outside of root cases... + { + name: "relative root, relative request, abs indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, relative request, abs indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, abs request, abs indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, abs request, abs indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + // link to outside of root cases... cwd within symlink root + { + name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: "path", + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: "path", + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + // we need to mimic a shell, otherwise we won't get a path within a symlink + targetPath := filepath.Join(testDir, c.cwd) + t.Setenv("PWD", filepath.Clean(targetPath)) + + require.NoError(t, err) + require.NoError(t, os.Chdir(targetPath)) + t.Cleanup(func() { + require.NoError(t, os.Chdir(testDir)) + }) + + resolver := NewFromUnindexedDirectory(c.root) + require.NotNil(t, resolver) + + refs, err := resolver.FilesByPath(c.input) + require.NoError(t, err) + if c.expectedRealPath == "" { + require.Empty(t, refs) + return + } + require.Len(t, refs, 1) + assert.Equal(t, c.expectedRealPath, refs[0].RealPath, "real path different") + assert.Equal(t, c.expectedVirtualPath, refs[0].VirtualPath, "virtual path different") + }) + } +} + func Test_UnindexedDirectoryResolver_Basic(t *testing.T) { wd, err := os.Getwd() require.NoError(t, err) diff --git a/syft/pkg/cataloger/kernel/cataloger_test.go b/syft/pkg/cataloger/kernel/cataloger_test.go index f819e605a45..0557c4bd865 100644 --- a/syft/pkg/cataloger/kernel/cataloger_test.go +++ b/syft/pkg/cataloger/kernel/cataloger_test.go @@ -7,7 +7,6 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func Test_KernelCataloger(t *testing.T) { @@ -50,7 +49,7 @@ func Test_KernelCataloger(t *testing.T) { ), Licenses: pkg.NewLicenseSet( pkg.NewLicenseFromLocations("GPL v2", - source.NewVirtualLocation( + file.NewVirtualLocation( "/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko", "/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko", ), diff --git a/syft/pkg/license_set_test.go b/syft/pkg/license_set_test.go index 09c617b6095..7125c5411b8 100644 --- a/syft/pkg/license_set_test.go +++ b/syft/pkg/license_set_test.go @@ -8,7 +8,6 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" - "github.com/anchore/syft/syft/source" ) func TestLicenseSet_Add(t *testing.T) { @@ -59,15 +58,15 @@ func TestLicenseSet_Add(t *testing.T) { { name: "deduplicate licenses with locations", licenses: []License{ - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "2"})), }, want: []License{ NewLicenseFromLocations( "MIT", - file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"}), - file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"}), + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"}), + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "2"}), ), }, }, @@ -75,14 +74,14 @@ func TestLicenseSet_Add(t *testing.T) { name: "same licenses with different locations", licenses: []License{ NewLicense("MIT"), - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"})), - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "2"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"})), }, want: []License{ NewLicenseFromLocations( "MIT", - file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"}), - file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"}), + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"}), + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "2"}), ), }, },