mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Support Windows Directory Resolver Add function that converts windows to posix functionality Add function that converts posix to windows Add build tags to remove windows developer environment errors redact carriage return specific windows issues Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
334 lines
7.7 KiB
Go
334 lines
7.7 KiB
Go
//go:build !windows
|
|
// +build !windows
|
|
|
|
package file
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-test/deep"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func equal(r1, r2 io.Reader) (bool, error) {
|
|
w1 := sha256.New()
|
|
w2 := sha256.New()
|
|
n1, err1 := io.Copy(w1, r1)
|
|
if err1 != nil {
|
|
return false, err1
|
|
}
|
|
n2, err2 := io.Copy(w2, r2)
|
|
if err2 != nil {
|
|
return false, err2
|
|
}
|
|
|
|
var b1, b2 [sha256.Size]byte
|
|
copy(b1[:], w1.Sum(nil))
|
|
copy(b2[:], w2.Sum(nil))
|
|
|
|
return n1 != n2 || b1 == b2, nil
|
|
}
|
|
|
|
func TestUnzipToDir(t *testing.T) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
goldenRootDir := filepath.Join(cwd, "test-fixtures")
|
|
sourceDirPath := path.Join(goldenRootDir, "zip-source")
|
|
archiveFilePath := setupZipFileTest(t, sourceDirPath)
|
|
|
|
unzipDestinationDir, err := ioutil.TempDir("", "syft-ziputil-contents-TEST-")
|
|
t.Cleanup(assertNoError(t, func() error {
|
|
return os.RemoveAll(unzipDestinationDir)
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("unable to create tempdir: %+v", err)
|
|
}
|
|
|
|
t.Logf("content path: %s", unzipDestinationDir)
|
|
|
|
expectedPaths := len(expectedZipArchiveEntries)
|
|
observedPaths := 0
|
|
|
|
err = UnzipToDir(archiveFilePath, unzipDestinationDir)
|
|
if err != nil {
|
|
t.Fatalf("unable to unzip archive: %+v", err)
|
|
}
|
|
|
|
// compare the source dir tree and the unzipped tree
|
|
err = filepath.Walk(unzipDestinationDir,
|
|
func(path string, info os.FileInfo, err error) error {
|
|
// We don't unzip the root archive dir, since there's no archive entry for it
|
|
if path != unzipDestinationDir {
|
|
t.Logf("unzipped path: %s", path)
|
|
observedPaths++
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("this should not happen")
|
|
return err
|
|
}
|
|
|
|
goldenPath := filepath.Join(sourceDirPath, strings.TrimPrefix(path, unzipDestinationDir))
|
|
|
|
if info.IsDir() {
|
|
i, err := os.Stat(goldenPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to stat golden path: %+v", err)
|
|
}
|
|
if !i.IsDir() {
|
|
t.Fatalf("mismatched file types: %s", goldenPath)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// this is a file, not a dir...
|
|
|
|
testFile, err := os.Open(path)
|
|
if err != nil {
|
|
t.Fatalf("unable to open test file=%s :%+v", path, err)
|
|
}
|
|
|
|
goldenFile, err := os.Open(goldenPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to open golden file=%s :%+v", goldenPath, err)
|
|
}
|
|
|
|
same, err := equal(testFile, goldenFile)
|
|
if err != nil {
|
|
t.Fatalf("could not compare files (%s, %s): %+v", goldenPath, path, err)
|
|
}
|
|
|
|
if !same {
|
|
t.Errorf("paths are not the same (%s, %s)", goldenPath, path)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Errorf("failed to walk dir: %+v", err)
|
|
}
|
|
|
|
if observedPaths != expectedPaths {
|
|
t.Errorf("missed test paths: %d != %d", observedPaths, expectedPaths)
|
|
}
|
|
}
|
|
|
|
func TestContentsFromZip(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
archivePrep func(tb testing.TB) string
|
|
}{
|
|
{
|
|
name: "standard, non-nested zip",
|
|
archivePrep: prepZipSourceFixture,
|
|
},
|
|
{
|
|
name: "zip with prepended bytes",
|
|
archivePrep: prependZipSourceFixtureWithString(t, "junk at the beginning of the file..."),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
archivePath := test.archivePrep(t)
|
|
expected := zipSourceFixtureExpectedContents()
|
|
|
|
var paths []string
|
|
for p := range expected {
|
|
paths = append(paths, p)
|
|
}
|
|
|
|
actual, err := ContentsFromZip(archivePath, paths...)
|
|
if err != nil {
|
|
t.Fatalf("unable to extract from unzip archive: %+v", err)
|
|
}
|
|
|
|
assertZipSourceFixtureContents(t, actual, expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func prependZipSourceFixtureWithString(tb testing.TB, value string) func(tb testing.TB) string {
|
|
if len(value) == 0 {
|
|
tb.Fatalf("no bytes given to prefix")
|
|
}
|
|
return func(t testing.TB) string {
|
|
archivePath := prepZipSourceFixture(t)
|
|
|
|
// create a temp file
|
|
tmpFile, err := ioutil.TempFile("", "syft-ziputil-prependZipSourceFixtureWithString-")
|
|
if err != nil {
|
|
t.Fatalf("unable to create tempfile: %+v", err)
|
|
}
|
|
defer tmpFile.Close()
|
|
|
|
// write value to the temp file
|
|
if _, err := tmpFile.WriteString(value); err != nil {
|
|
t.Fatalf("unable to write to tempfile: %+v", err)
|
|
}
|
|
|
|
// open the original archive
|
|
sourceFile, err := os.Open(archivePath)
|
|
if err != nil {
|
|
t.Fatalf("unable to read source file: %+v", err)
|
|
}
|
|
|
|
// copy all contents from the archive to the temp file
|
|
if _, err := io.Copy(tmpFile, sourceFile); err != nil {
|
|
t.Fatalf("unable to copy source to dest: %+v", err)
|
|
}
|
|
|
|
sourceFile.Close()
|
|
|
|
// remove the original archive and replace it with the temp file
|
|
if err := os.Remove(archivePath); err != nil {
|
|
t.Fatalf("unable to remove original source archive (%q): %+v", archivePath, err)
|
|
}
|
|
|
|
if err := os.Rename(tmpFile.Name(), archivePath); err != nil {
|
|
t.Fatalf("unable to move new archive to old path (%q): %+v", tmpFile.Name(), err)
|
|
}
|
|
|
|
return archivePath
|
|
}
|
|
}
|
|
|
|
func prepZipSourceFixture(t testing.TB) string {
|
|
t.Helper()
|
|
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-prepZipSourceFixture-")
|
|
if err != nil {
|
|
t.Fatalf("unable to create tempfile: %+v", err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
assert.NoError(t, os.Remove(archivePrefix.Name()))
|
|
})
|
|
|
|
// the zip utility will add ".zip" to the end of the given name
|
|
archivePath := archivePrefix.Name() + ".zip"
|
|
|
|
t.Cleanup(func() {
|
|
assert.NoError(t, os.Remove(archivePath))
|
|
})
|
|
|
|
t.Logf("archive path: %s", archivePath)
|
|
|
|
createZipArchive(t, "zip-source", archivePrefix.Name())
|
|
|
|
return archivePath
|
|
}
|
|
|
|
func zipSourceFixtureExpectedContents() map[string]string {
|
|
return map[string]string{
|
|
filepath.Join("some-dir", "a-file.txt"): "A file! nice!",
|
|
filepath.Join("b-file.txt"): "B file...",
|
|
}
|
|
}
|
|
|
|
func assertZipSourceFixtureContents(t testing.TB, actual map[string]string, expected map[string]string) {
|
|
t.Helper()
|
|
diffs := deep.Equal(actual, expected)
|
|
if len(diffs) > 0 {
|
|
for _, d := range diffs {
|
|
t.Errorf("diff: %+v", d)
|
|
}
|
|
|
|
b, err := json.MarshalIndent(actual, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("can't show results: %+v", err)
|
|
}
|
|
|
|
t.Errorf("full result: %s", string(b))
|
|
}
|
|
}
|
|
|
|
// looks like there isn't a helper for this yet? https://github.com/stretchr/testify/issues/497
|
|
func assertErrorAs(expectedErr interface{}) assert.ErrorAssertionFunc {
|
|
return func(t assert.TestingT, actualErr error, i ...interface{}) bool {
|
|
return errors.As(actualErr, &expectedErr)
|
|
}
|
|
}
|
|
|
|
func TestSafeJoin(t *testing.T) {
|
|
tests := []struct {
|
|
prefix string
|
|
args []string
|
|
expected string
|
|
errAssertion assert.ErrorAssertionFunc
|
|
}{
|
|
// go cases...
|
|
{
|
|
prefix: "/a/place",
|
|
args: []string{
|
|
"somewhere/else",
|
|
},
|
|
expected: "/a/place/somewhere/else",
|
|
errAssertion: assert.NoError,
|
|
},
|
|
{
|
|
prefix: "/a/place",
|
|
args: []string{
|
|
"somewhere/../else",
|
|
},
|
|
expected: "/a/place/else",
|
|
errAssertion: assert.NoError,
|
|
},
|
|
{
|
|
prefix: "/a/../place",
|
|
args: []string{
|
|
"somewhere/else",
|
|
},
|
|
expected: "/place/somewhere/else",
|
|
errAssertion: assert.NoError,
|
|
},
|
|
// zip slip examples....
|
|
{
|
|
prefix: "/a/place",
|
|
args: []string{
|
|
"../../../etc/passwd",
|
|
},
|
|
expected: "",
|
|
errAssertion: assertErrorAs(&errZipSlipDetected{}),
|
|
},
|
|
{
|
|
prefix: "/a/place",
|
|
args: []string{
|
|
"../",
|
|
"../",
|
|
},
|
|
expected: "",
|
|
errAssertion: assertErrorAs(&errZipSlipDetected{}),
|
|
},
|
|
{
|
|
prefix: "/a/place",
|
|
args: []string{
|
|
"../",
|
|
},
|
|
expected: "",
|
|
errAssertion: assertErrorAs(&errZipSlipDetected{}),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%+v:%+v", test.prefix, test.args), func(t *testing.T) {
|
|
actual, err := safeJoin(test.prefix, test.args...)
|
|
test.errAssertion(t, err)
|
|
assert.Equal(t, test.expected, actual)
|
|
})
|
|
}
|
|
}
|