mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 06:56:40 +01:00
146 lines
4.7 KiB
Go
146 lines
4.7 KiB
Go
package archive
|
|
|
|
import (
|
|
"archive/tar"
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/anchore/syft/internal/tmpdir"
|
|
"github.com/anchore/syft/syft/file"
|
|
)
|
|
|
|
func TestExtractToResolver_writesUnderTheScansTempRoot(t *testing.T) {
|
|
// where an archive's scratch space lands is the scan's decision, not this package's: the root on
|
|
// the context is what a caller configured and what gets cleaned up if an archive's own cleanup is
|
|
// ever missed. Reaching for os.MkdirTemp instead put archive work outside all of that.
|
|
root := t.TempDir()
|
|
ctx := tmpdir.WithValue(context.Background(), tmpdir.FromPath(root))
|
|
|
|
var got Overflow
|
|
factory := func(overflow Overflow) (file.Resolver, IndexResult, error) {
|
|
got = overflow
|
|
return nil, IndexResult{Records: 1}, nil
|
|
}
|
|
|
|
extracted, err := ExtractToResolver(
|
|
ctx, newTestZip(t, map[string]string{"hello.txt": "hi"}), "app.zip", "", "app.zip",
|
|
DefaultExtractors(), nil, ExtractionLimits{}, factory, nil, nil,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, extracted)
|
|
t.Cleanup(extracted.Cleanup)
|
|
|
|
assert.True(t, strings.HasPrefix(got.TarPath, root+string(os.PathSeparator)),
|
|
"the archive's entries must be written under the scan's temp root, got %q", got.TarPath)
|
|
|
|
// and the archive still cleans up after itself, so the root is a safety net rather than the only
|
|
// thing reclaiming the space
|
|
extracted.Cleanup()
|
|
_, statErr := os.Stat(got.RootDir)
|
|
assert.True(t, os.IsNotExist(statErr), "expected the archive work directory to be removed by Cleanup")
|
|
}
|
|
|
|
func TestExtractToResolver(t *testing.T) {
|
|
content := newTestZip(t, map[string]string{"dir/hello.txt": "hello world"})
|
|
|
|
var got Overflow
|
|
factory := func(overflow Overflow) (file.Resolver, IndexResult, error) {
|
|
got = overflow
|
|
return nil, IndexResult{Records: 1}, nil
|
|
}
|
|
|
|
extracted, err := ExtractToResolver(
|
|
context.Background(), content, "some/path/app.zip", "parentFS", "app.war:some/path/app.zip",
|
|
DefaultExtractors(), nil, ExtractionLimits{}, factory, nil, nil,
|
|
)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, extracted)
|
|
t.Cleanup(extracted.Cleanup)
|
|
|
|
// the filesystem the archive lives on is passed through unchanged; the nesting chain is carried
|
|
// separately as the archive path, and both are handed to the resolver factory
|
|
assert.Equal(t, "parentFS", extracted.FileSystemID)
|
|
assert.Equal(t, "parentFS", got.FileSystemID)
|
|
assert.Equal(t, "app.war:some/path/app.zip", extracted.ArchivePath)
|
|
assert.Equal(t, "app.war:some/path/app.zip", got.ArchivePath)
|
|
|
|
// the archive's entries are in the one tar handed to the factory, and its logical root is an
|
|
// empty directory beside it: an entry's path is reported relative to that root, and nothing is
|
|
// written into it
|
|
entries, err := os.ReadDir(got.RootDir)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, entries, "the archive root holds no files: the entries are in the tar")
|
|
|
|
body := readTarEntry(t, got.TarPath, "dir/hello.txt")
|
|
assert.Equal(t, "hello world", body)
|
|
|
|
// the count of entries this archive contributes is the index's answer, not the extractor's
|
|
assert.Equal(t, 1, extracted.Result.FilesExtracted)
|
|
|
|
// Cleanup removes the temp tree
|
|
extracted.Cleanup()
|
|
_, statErr := os.Stat(got.TarPath)
|
|
assert.True(t, os.IsNotExist(statErr), "expected the overflow tar to be removed by Cleanup")
|
|
_, statErr = os.Stat(got.RootDir)
|
|
assert.True(t, os.IsNotExist(statErr), "expected temp dir to be removed by Cleanup")
|
|
}
|
|
|
|
// readTarEntry returns the content of one entry of a tar on disk.
|
|
func readTarEntry(t *testing.T, tarPath, name string) string {
|
|
t.Helper()
|
|
f, err := os.Open(tarPath)
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
|
|
tr := tar.NewReader(f)
|
|
for {
|
|
hdr, err := tr.Next()
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
require.NoError(t, err)
|
|
if hdr.Name != name {
|
|
continue
|
|
}
|
|
body, err := io.ReadAll(tr)
|
|
require.NoError(t, err)
|
|
return string(body)
|
|
}
|
|
t.Fatalf("no entry %q in %q", name, tarPath)
|
|
return ""
|
|
}
|
|
|
|
func TestExtractToResolver_notAnArchive(t *testing.T) {
|
|
factory := func(Overflow) (file.Resolver, IndexResult, error) { return nil, IndexResult{}, nil }
|
|
|
|
extracted, err := ExtractToResolver(
|
|
context.Background(), strings.NewReader("this is not an archive"), "notes.txt", "", "notes.txt",
|
|
DefaultExtractors(), nil, ExtractionLimits{}, factory, nil, nil,
|
|
)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, extracted, "non-archive content should yield a nil ExtractedArchive")
|
|
}
|
|
|
|
func newTestZip(t *testing.T, files map[string]string) *bytes.Buffer {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
zw := zip.NewWriter(&buf)
|
|
for name, body := range files {
|
|
w, err := zw.Create(name)
|
|
require.NoError(t, err)
|
|
_, err = w.Write([]byte(body))
|
|
require.NoError(t, err)
|
|
}
|
|
require.NoError(t, zw.Close())
|
|
return &buf
|
|
}
|