fix:handle compound aliases like ``.tgz`` when cataloging archives (#4421)

---------
Signed-off-by: Yuntao Hu <victorhu493@gmail.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
VictorHuu
2025-12-02 16:55:32 -05:00
committed by GitHub
co-authored by Christopher Phillips
parent d37ed567a8
commit afe28a2fc0
9 changed files with 138 additions and 13 deletions
+46
View File
@@ -0,0 +1,46 @@
package file
import (
"context"
"io"
"path/filepath"
"strings"
"github.com/mholt/archives"
)
// compoundExtensionAliases maps shorthand archive extensions to their full forms.
// The mholt/archives library doesn't recognize these aliases natively.
//
// See: https://github.com/anchore/syft/issues/4416
// Reference: https://github.com/mholt/archives?tab=readme-ov-file#supported-compression-formats
var compoundExtensionAliases = map[string]string{
".tgz": ".tar.gz",
".tbz2": ".tar.bz2",
".txz": ".tar.xz",
".tlz": ".tar.lz",
".tzst": ".tar.zst",
}
// IdentifyArchive is a wrapper around archives.Identify that handles compound extension
// aliases (like .tgz -> .tar.gz) transparently. It first attempts filename-based detection
// using the alias map, and falls back to content-based detection if needed.
//
// This function is a drop-in replacement for archives.Identify that centralizes
// the compound alias handling logic in one place.
func IdentifyArchive(ctx context.Context, path string, r io.Reader) (archives.Format, io.Reader, error) {
// First, try to identify using the alias-mapped path (filename-based detection)
normalizedPath := handleCompoundArchiveAliases(path)
return archives.Identify(ctx, normalizedPath, r)
}
// handleCompoundArchiveAliases normalizes archive file paths that use compound extension
// aliases (like .tgz) to their full forms (like .tar.gz) for correct identification
// by the mholt/archives library.
func handleCompoundArchiveAliases(path string) string {
ext := filepath.Ext(path)
if newExt, ok := compoundExtensionAliases[ext]; ok {
return strings.TrimSuffix(path, ext) + newExt
}
return path
}
+73
View File
@@ -0,0 +1,73 @@
package file
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHandleCompoundArchiveAliases(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "tgz to tar.gz",
input: "/path/to/archive.tgz",
expected: "/path/to/archive.tar.gz",
},
{
name: "tbz2 to tar.bz2",
input: "/path/to/archive.tbz2",
expected: "/path/to/archive.tar.bz2",
},
{
name: "txz to tar.xz",
input: "/path/to/archive.txz",
expected: "/path/to/archive.tar.xz",
},
{
name: "tlz to tar.lz",
input: "/path/to/archive.tlz",
expected: "/path/to/archive.tar.lz",
},
{
name: "tzst to tar.zst",
input: "/path/to/archive.tzst",
expected: "/path/to/archive.tar.zst",
},
{
name: "standard tar.gz unchanged",
input: "/path/to/archive.tar.gz",
expected: "/path/to/archive.tar.gz",
},
{
name: "zip unchanged",
input: "/path/to/archive.zip",
expected: "/path/to/archive.zip",
},
{
name: "no extension unchanged",
input: "/path/to/archive",
expected: "/path/to/archive",
},
{
name: "case sensitive - TGZ not matched",
input: "/path/to/archive.TGZ",
expected: "/path/to/archive.TGZ",
},
{
name: "just filename with tgz",
input: "archive.tgz",
expected: "archive.tar.gz",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := handleCompoundArchiveAliases(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ func TraverseFilesInTar(ctx context.Context, archivePath string, visitor archive
}
defer internal.CloseAndLogError(tarReader, archivePath)
format, _, err := archives.Identify(ctx, archivePath, nil)
format, _, err := IdentifyArchive(ctx, archivePath, tarReader)
if err != nil {
return fmt.Errorf("failed to identify tar compression format: %w", err)
}