Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion syft/source/filesource/file_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,19 @@ func fileAnalysisPath(path string, skipExtractArchive bool) (string, func() erro
return analysisPath, cleanupFn, nil
}

envelopedUnarchiver, _, err := intFile.IdentifyArchive(context.Background(), path, nil)
// Pass the file contents to archive identification rather than relying on the
// filename alone. mholt/archives matches filenames with strings.Contains, so a
// regular file whose name merely contains an archive extension as a substring
// (e.g. "sample.tar.gz_hashed.json") is otherwise misidentified as an archive
// and fails to "unarchive". With the reader, content-based detection runs and
// non-archives fall through to regular file analysis. See issue #4582.
f, err := os.Open(path)
if err != nil {
return "", cleanupFn, fmt.Errorf("unable to open source file: %w", err)
}
defer f.Close()

envelopedUnarchiver, _, err := intFile.IdentifyArchive(context.Background(), path, f)
if unarchiver, ok := envelopedUnarchiver.(archives.Extractor); err == nil && ok {
analysisPath, cleanupFn, err = unarchiveToTmp(path, unarchiver)
if err != nil {
Expand Down
44 changes: 44 additions & 0 deletions syft/source/filesource/file_source_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package filesource

import (
"archive/tar"
"compress/gzip"
"io"
"os"
"os/exec"
Expand Down Expand Up @@ -169,6 +171,48 @@ func TestNewFromFile_WithArchive(t *testing.T) {
}
}

func Test_fileAnalysisPath(t *testing.T) {
t.Run("regular file with archive extension in its name is not unarchived", func(t *testing.T) {
// a regular JSON file whose name contains ".tar.gz" as a substring should be
// analyzed as-is, not misidentified as an archive. See issue #4582.
dir := t.TempDir()
p := filepath.Join(dir, "sample.tar.gz_hashed.json")
require.NoError(t, os.WriteFile(p, []byte(`{"hello":"world"}`), 0o600))

analysisPath, cleanupFn, err := fileAnalysisPath(p, false)
t.Cleanup(func() { assert.NoError(t, cleanupFn()) })

require.NoError(t, err)
// the file is not an archive, so analysis happens on the original path
assert.Equal(t, p, analysisPath)
})

t.Run("real gzipped tar is still extracted", func(t *testing.T) {
dir := t.TempDir()
p := filepath.Join(dir, "real.tar.gz")

f, err := os.Create(p)
require.NoError(t, err)
gw := gzip.NewWriter(f)
tw := tar.NewWriter(gw)
contents := []byte("hi")
require.NoError(t, tw.WriteHeader(&tar.Header{Name: "a.txt", Mode: 0o600, Size: int64(len(contents))}))
_, err = tw.Write(contents)
require.NoError(t, err)
require.NoError(t, tw.Close())
require.NoError(t, gw.Close())
require.NoError(t, f.Close())

analysisPath, cleanupFn, err := fileAnalysisPath(p, false)
t.Cleanup(func() { assert.NoError(t, cleanupFn()) })

require.NoError(t, err)
// a genuine archive is extracted to a temp dir, so the analysis path differs
assert.NotEqual(t, p, analysisPath)
assert.FileExists(t, filepath.Join(analysisPath, "a.txt"))
})
}

// setupArchiveTest encapsulates common test setup work for tar file tests. It returns a cleanup function,
// which should be called (typically deferred) by the caller, the path of the created tar archive, and an error,
// which should trigger a fatal test failure in the consuming test. The returned cleanup function will never be nil
Expand Down
Loading