chore: simplify and cleanup

Signed-off-by: Keith Zantow <keith.zantow@anchore.com>
This commit is contained in:
Keith Zantow
2026-09-22 00:23:36 -04:00
parent 53cd4254e4
commit 47cafae01b
75 changed files with 3128 additions and 8623 deletions
+14 -86
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"strings"
"github.com/bmatcuk/doublestar/v4"
"github.com/scylladb/go-set/strset"
"github.com/anchore/syft/internal"
@@ -14,30 +13,19 @@ import (
"github.com/anchore/syft/syft/file"
)
// archiveEntries is where an archive parser reads a java archive's entries from.
//
// A parser needs three things of an archive: which entries match a glob, the contents of some of
// them, and the digest of the archive as a whole. Everything else - which manifest wins, how
// pom.properties decides identity, license discovery - is logic over those answers, so naming the
// three lets the same logic run over an archive file and over one already extracted to a filesystem.
//
// Entry names are archive-relative with no leading slash, matching zip entry names, since they become
// the map keys and pom paths identity is derived from. An implementation over a filesystem must strip
// the root to match.
// archiveEntries is where the archive parser reads a java archive's entries from, so the same parser
// runs over an archive file and over an archive already extracted to a filesystem. Entry names are
// archive-relative with no leading slash, as zip entry names are.
type archiveEntries interface {
glob(caseInsensitive bool, patterns ...string) []string
// glob returns the entries matching any pattern. Patterns are written against a leading slash
// ("/META-INF/*"); the names returned have none.
glob(patterns ...string) []string
// contents reads the named entries, returning a map keyed by the same names.
// contents reads the named entries, keyed by the same names.
contents(ctx context.Context, names ...string) (map[string]string, error)
// digests returns the digests of the archive as a whole, a property of the archive file rather
// than of anything inside it.
digests(ctx context.Context) ([]file.Digest, error)
}
// zipEntries reads entries out of a java archive on disk, for scans where nothing has already
// extracted it. It copies the archive to a temp file, reads its central directory once, and reopens
// it for each set of entries it is asked for.
// zipEntries reads entries out of a java archive file on disk.
type zipEntries struct {
archivePath string
fileManifest intFile.ZipFileManifest
@@ -51,41 +39,20 @@ func newZipEntries(ctx context.Context, archivePath string) (*zipEntries, error)
return &zipEntries{archivePath: archivePath, fileManifest: fileManifest}, nil
}
func (z *zipEntries) glob(caseInsensitive bool, patterns ...string) []string {
return z.fileManifest.GlobMatch(caseInsensitive, patterns...)
func (z *zipEntries) glob(patterns ...string) []string {
return z.fileManifest.GlobMatch(patterns...)
}
func (z *zipEntries) contents(ctx context.Context, names ...string) (map[string]string, error) {
return intFile.ContentsFromZip(ctx, z.archivePath, names...)
}
func (z *zipEntries) digests(ctx context.Context) ([]file.Digest, error) {
return getDigestsFromArchive(ctx, z.archivePath)
}
// resolverEntries reads entries out of an archive already extracted and indexed by the archive
// cataloger task. Nothing is copied or reopened. The archive's own digests come from whoever
// extracted it, since the archive file is not present in its own contents.
// resolverEntries reads entries out of an archive the archive cataloger task has extracted.
type resolverEntries struct {
resolver file.Resolver
archiveDigests []file.Digest
resolver file.Resolver
}
func newResolverEntries(resolver file.Resolver, archiveDigests []file.Digest) *resolverEntries {
return &resolverEntries{
resolver: resolver,
archiveDigests: archiveDigests,
}
}
func (r *resolverEntries) glob(caseInsensitive bool, patterns ...string) []string {
if caseInsensitive {
return r.globCaseInsensitive(patterns...)
}
// deduplicated within this call only: the parser globs the same pattern more than once (identity
// resolution and aux package discovery both ask for the poms), so remembering across calls would
// return nothing the second time and lose every package derived from a bundled pom.properties
func (r *resolverEntries) glob(patterns ...string) []string {
seen := strset.New()
var names []string
for _, pattern := range patterns {
@@ -106,37 +73,6 @@ func (r *resolverEntries) glob(caseInsensitive bool, patterns ...string) []strin
return names
}
// globCaseInsensitive matches the way zipEntries does, by lowercasing both sides. The resolver's own
// glob is case sensitive, so this lists the archive's files once and matches them here. License
// discovery is the only caller.
func (r *resolverEntries) globCaseInsensitive(patterns ...string) []string {
all, err := r.resolver.FilesByGlob("**/*")
if err != nil {
log.WithFields("error", err).Trace("unable to list archive contents")
return nil
}
seen := strset.New()
var names []string
for _, pattern := range patterns {
lowered := strings.ToLower(pattern)
for _, location := range all {
// matched with a leading slash, since the patterns are written that way ("/META-INF/*") and
// zipEntries normalizes the same way; an extracted archive's resolver reports names without
// one, so matching raw would match nothing
name := strings.TrimPrefix(location.Path(), "/")
if ok, matchErr := doublestar.Match(lowered, "/"+strings.ToLower(name)); matchErr != nil || !ok {
continue
}
if seen.Has(name) {
continue
}
seen.Add(name)
names = append(names, name)
}
}
return names
}
func (r *resolverEntries) contents(_ context.Context, names ...string) (map[string]string, error) {
out := make(map[string]string, len(names))
for _, name := range names {
@@ -156,15 +92,7 @@ func (r *resolverEntries) contents(_ context.Context, names ...string) (map[stri
return out, nil
}
func (r *resolverEntries) digests(_ context.Context) ([]file.Digest, error) {
return r.archiveDigests, nil
}
// read reads one entry's content to the end and closes the stream, returning a string because every
// consumer parses text.
//
// The copy is bounded the way zipEntries bounds its own: an entry of an extracted archive is still
// attacker-supplied, and reading it into a string is the allocation a decompression bomb targets.
// read returns one entry's content, bounded against decompression bombs as zipEntries bounds its own.
func (r *resolverEntries) read(location file.Location) (string, error) {
reader, err := r.resolver.FileContentsByLocation(location)
if err != nil {
+91 -49
View File
@@ -17,8 +17,10 @@ import (
"golang.org/x/exp/maps"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/archive"
intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/tmpdir"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
@@ -56,27 +58,25 @@ var javaArchiveHashes = []crypto.Hash{
crypto.SHA1,
}
// archiveParser reads one java archive, whichever source it came from: everything it does is logic
// over an archiveSource's entries and identity, so it does not know whether this cataloger opened an
// archive file or the archive cataloger task already extracted one.
// archiveParser reads one java archive, whether this cataloger opened the archive file or the archive
// cataloger task already extracted it.
type archiveParser struct {
archiveSource
entries archiveEntries
// location is the archive's location in its parent filesystem; archive.VirtualPath(location) is its
// identity with the chain of containing archives prepended: "app.war:WEB-INF/lib/dep.jar"
location file.Location
fileInfo archiveFilename
// archiveDigests are of the archive file as a whole. When the archive cataloger task extracted the
// archive they come from that extraction, since the archive is not inside itself.
archiveDigests []file.Digest
// contentPath is the directory nested archives are unzipped into. Set only when this cataloger
// opened the archive itself, since only then does it recurse.
contentPath string
detectNested bool
cfg ArchiveCatalogerConfig
maven *maven.Resolver
}
// newArchiveParser builds the parser for one archive. detectNested is whether this parser recurses
// into the java archives inside this one, which only a file-backed source can do.
func newArchiveParser(src archiveSource, detectNested bool, cfg ArchiveCatalogerConfig) *archiveParser {
return &archiveParser{
archiveSource: src,
detectNested: detectNested,
cfg: cfg,
maven: maven.NewResolver(nil, cfg.mavenConfig()),
}
}
type genericArchiveParserAdapter struct {
cfg ArchiveCatalogerConfig
}
@@ -92,9 +92,7 @@ func (gap genericArchiveParserAdapter) parseJavaArchive(ctx context.Context, _ f
// processJavaArchive processes an archive for java contents, returning all Java libraries and nested archives
func (gap genericArchiveParserAdapter) processJavaArchive(ctx context.Context, reader file.LocationReadCloser, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
// when the archive cataloger task owns recursion, unarchiving here would catalog nested archives
// twice
parser, cleanupFn, err := newJavaArchiveParser(ctx, reader, !gap.cfg.NestedArchivesHandledExternally, gap.cfg)
parser, cleanupFn, err := newJavaArchiveParser(ctx, reader, !archive.NestedCatalogingEnabled(ctx), gap.cfg)
// note: even on error, we should always run cleanup functions
defer cleanupFn()
if err != nil {
@@ -111,14 +109,65 @@ func uniquePkgKey(groupID string, p *pkg.Package) string {
return fmt.Sprintf("%s|%s|%s", groupID, p.Name, p.Version)
}
// newJavaArchiveParser returns a new java archive parser for an archive file this cataloger opens
// itself. Can be configured to discover and parse nested archives or ignore them.
// newJavaArchiveParser returns a parser for an archive file this cataloger opens itself, copied to a
// temp dir. Can be configured to discover and parse nested archives or ignore them. The returned
// cleanup must run even when an error is returned.
func newJavaArchiveParser(ctx context.Context, reader file.LocationReadCloser, detectNested bool, cfg ArchiveCatalogerConfig) (*archiveParser, func(), error) {
src, cleanupFn, err := newFileArchiveSource(ctx, reader)
// fetch the last element of the virtual path
virtualElements := strings.Split(archive.VirtualPath(reader.Location), ":")
currentFilepath := virtualElements[len(virtualElements)-1]
td := tmpdir.FromContext(ctx)
if td == nil {
return nil, func() {}, fmt.Errorf("no temp dir factory in context")
}
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(td, currentFilepath, reader)
if err != nil {
return nil, cleanupFn, fmt.Errorf("unable to process java archive: %w", err)
}
entries, err := newZipEntries(ctx, archivePath)
if err != nil {
return nil, cleanupFn, err
}
return newArchiveParser(src, detectNested, cfg), cleanupFn, nil
digests, err := getDigestsFromArchive(ctx, archivePath)
if err != nil {
return nil, cleanupFn, err
}
return &archiveParser{
entries: entries,
location: reader.Location,
fileInfo: newJavaArchiveFilename(currentFilepath),
archiveDigests: digests,
contentPath: contentPath,
detectNested: detectNested,
cfg: cfg,
maven: maven.NewResolver(nil, cfg.mavenConfig()),
}, cleanupFn, nil
}
// newExtractedArchiveParser returns a parser for the archive whose extracted contents the resolver
// holds. Its identity comes from the traversal, since none of it is inside the archive, and it never
// recurses: archives nested inside it are the archive cataloger task's to find.
func newExtractedArchiveParser(trav *archive.Traversal, resolver file.Resolver, cfg ArchiveCatalogerConfig) (*archiveParser, error) {
if resolver == nil {
return nil, fmt.Errorf("no resolver for the contents of %q", archive.VirtualPath(trav.Location))
}
return &archiveParser{
entries: &resolverEntries{resolver: resolver},
location: trav.Location,
fileInfo: newJavaArchiveFilename(path.Base(trav.Location.Path())),
archiveDigests: trav.Digests,
cfg: cfg,
maven: maven.NewResolver(nil, cfg.mavenConfig()),
}, nil
}
// virtualPath is the archive's path with the chain of containing archives prepended.
func (j *archiveParser) virtualPath() string {
return archive.VirtualPath(j.location)
}
// parse the loaded archive and return all packages found.
@@ -177,11 +226,10 @@ func (j *archiveParser) parse(ctx context.Context, parentPkg *pkg.Package) ([]pk
}
pkgs = append(pkgs, nestedPkgs...)
relationships = append(relationships, nestedRelationships...)
} else if !j.cfg.NestedArchivesHandledExternally {
// nothing will catalog these: this parser is not recursing and the archive cataloger task is
// disabled, so record them as unknowns
} else if !archive.NestedCatalogingEnabled(ctx) {
// nothing will catalog these, so record them as unknowns
// .jar and .war files are present in archives, are others? or generally just consider them top-level?
nestedArchives := j.entries.glob(true, "**/*.jar", "**/*.war")
nestedArchives := j.entries.glob("**/*.jar", "**/*.war")
if len(nestedArchives) > 0 {
slices.Sort(nestedArchives)
errs = unknown.Appendf(errs, j.location, "nested archives not cataloged: %v", strings.Join(nestedArchives, ", "))
@@ -214,7 +262,7 @@ func finalizePackage(p *pkg.Package) {
// discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages.
func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package, error) {
// search and parse java manifest files
manifestMatches := j.entries.glob(false, manifestGlob)
manifestMatches := j.entries.glob(manifestGlob)
if len(manifestMatches) > 1 {
return nil, fmt.Errorf("found multiple manifests in the jar: %+v", manifestMatches)
} else if len(manifestMatches) == 0 {
@@ -230,7 +278,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
// parse the manifest file into a rich object
manifestContents := contents[manifestMatches[0]]
manifest, err := parseJavaManifest(j.virtualPath, strings.NewReader(manifestContents))
manifest, err := parseJavaManifest(j.virtualPath(), strings.NewReader(manifestContents))
if err != nil {
log.Debugf("failed to parse java manifest (%s): %+v", j.location, err)
return nil, nil
@@ -243,12 +291,6 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
return nil, nil
}
// grab and assign digest for the entire archive
digests, err := j.entries.digests(ctx)
if err != nil {
return nil, err
}
name, version, lics, parsedPom := j.discoverNameVersionLicense(ctx, manifest)
var pkgPomProject *pkg.JavaPomProject
if parsedPom != nil {
@@ -266,10 +308,10 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
),
Type: j.fileInfo.pkgType(),
Metadata: pkg.JavaArchive{
VirtualPath: j.virtualPath,
VirtualPath: j.virtualPath(),
Manifest: manifest,
PomProject: pkgPomProject,
ArchiveDigests: digests,
ArchiveDigests: j.archiveDigests,
},
}, nil
}
@@ -382,8 +424,8 @@ type parsedPomProject struct {
// discoverMainPackageFromPomInfo attempts to resolve maven groupId, artifactId, version and other info from found pom information
func (j *archiveParser) discoverMainPackageFromPomInfo(ctx context.Context) (group, name, version string, parsedPom *parsedPomProject) {
// Find the pom.properties/pom.xml if the names seem like a plausible match
properties, _ := pomPropertiesByParentPath(ctx, j.entries, j.location, j.entries.glob(false, pomPropertiesGlob))
projects, _ := pomProjectByParentPath(ctx, j.entries, j.location, j.entries.glob(false, pomXMLGlob))
properties, _ := pomPropertiesByParentPath(ctx, j.entries, j.location, j.entries.glob(pomPropertiesGlob))
projects, _ := pomProjectByParentPath(ctx, j.entries, j.location, j.entries.glob(pomXMLGlob))
artifactsMap := j.buildArtifactsMap(properties)
pomProperties, parsedPom := j.findBestPomMatch(properties, projects, artifactsMap)
@@ -514,13 +556,13 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(ctx context.Context, paren
var pkgs []pkg.Package
// pom.properties
properties, err := pomPropertiesByParentPath(ctx, j.entries, j.location, j.entries.glob(false, pomPropertiesGlob))
properties, err := pomPropertiesByParentPath(ctx, j.entries, j.location, j.entries.glob(pomPropertiesGlob))
if err != nil {
return nil, err
}
// pom.xml
projects, err := pomProjectByParentPath(ctx, j.entries, j.location, j.entries.glob(false, pomXMLGlob))
projects, err := pomProjectByParentPath(ctx, j.entries, j.location, j.entries.glob(pomXMLGlob))
if err != nil {
return nil, err
}
@@ -531,7 +573,7 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(ctx context.Context, paren
parsedPom = proj
}
pkgFromPom := newPackageFromMavenData(ctx, j.maven, propertiesObj, parsedPom, parentPkg, j.location, j.virtualPath)
pkgFromPom := newPackageFromMavenData(ctx, j.maven, propertiesObj, parsedPom, parentPkg, j.location)
if pkgFromPom != nil {
pkgs = append(pkgs, *pkgFromPom)
}
@@ -563,7 +605,7 @@ func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) []pkg.L
for _, glob := range []string{"/META-INF/*", "/*"} {
var licenseMatches []string
for _, f := range j.entries.glob(true, glob) {
for _, f := range j.entries.glob(glob) {
if licenses.IsLicenseFile(path.Base(f)) {
licenseMatches = append(licenseMatches, f)
}
@@ -614,7 +656,7 @@ func (j *archiveParser) versionFromPropertiesFile(ctx context.Context, manifest
return ""
}
matches := j.entries.glob(true, "/version.properties")
matches := j.entries.glob("/version.properties")
if len(matches) != 1 {
return ""
}
@@ -641,9 +683,9 @@ func (j *archiveParser) versionFromPropertiesFile(ctx context.Context, manifest
return ""
}
// discoverPkgsFromNestedArchives recurses into the java archives inside this one. Only zipEntries
// applies, since recursion needs an archive file to unzip; with resolverEntries the archive cataloger
// task has already extracted everything and meets those nested archives itself.
// discoverPkgsFromNestedArchives recurses into the java archives inside this one, which needs an
// archive file to unzip; an already-extracted archive's nested archives are the archive cataloger
// task's to find.
func (j *archiveParser) discoverPkgsFromNestedArchives(ctx context.Context, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
zipped, ok := j.entries.(*zipEntries)
if !ok {
@@ -657,7 +699,7 @@ func (j *archiveParser) discoverPkgsFromNestedArchives(ctx context.Context, pare
// associating each discovered package to the given parent package.
func discoverPkgsFromZip(ctx context.Context, location file.Location, archivePath, contentPath string, fileManifest intFile.ZipFileManifest, parentPkg *pkg.Package, cfg ArchiveCatalogerConfig) ([]pkg.Package, []artifact.Relationship, error) {
// search and parse pom.properties files & fetch the contents
openers, err := intFile.ExtractFromZipToUniqueTempFile(ctx, archivePath, contentPath, fileManifest.GlobMatch(false, archiveFormatGlobs...)...)
openers, err := intFile.ExtractFromZipToUniqueTempFile(ctx, archivePath, contentPath, fileManifest.GlobMatch(archiveFormatGlobs...)...)
if err != nil {
return nil, nil, fmt.Errorf("unable to extract files from zip: %w", err)
}
@@ -778,7 +820,7 @@ func pomProjectByParentPath(ctx context.Context, entries archiveEntries, locatio
// newPackageFromMavenData processes a single Maven POM properties for a given parent package, returning all listed Java packages found and
// associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not.
func newPackageFromMavenData(ctx context.Context, r *maven.Resolver, pomProperties pkg.JavaPomProperties, parsedPom *parsedPomProject, parentPkg *pkg.Package, location file.Location, virtualPath string) *pkg.Package {
func newPackageFromMavenData(ctx context.Context, r *maven.Resolver, pomProperties pkg.JavaPomProperties, parsedPom *parsedPomProject, parentPkg *pkg.Package, location file.Location) *pkg.Package {
// keep the artifact name within the virtual path if this package does not match the parent package
vPathSuffix := ""
groupID := ""
@@ -798,7 +840,7 @@ func newPackageFromMavenData(ctx context.Context, r *maven.Resolver, pomProperti
// https://github.com/anchore/syft/issues/1944
vPathSuffix += ":" + pomProperties.GroupID + ":" + pomProperties.ArtifactID
}
virtualPath += vPathSuffix
virtualPath := archive.VirtualPath(location) + vPathSuffix
var pkgPomProject *pkg.JavaPomProject
@@ -1155,7 +1155,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
test.expectedParent.Locations = locations
r := maven.NewResolver(nil, maven.DefaultConfig())
actualPackage := newPackageFromMavenData(pkgtest.Context(t), r, test.props, test.project, test.parent, file.NewLocation(virtualPath), virtualPath)
actualPackage := newPackageFromMavenData(pkgtest.Context(t), r, test.props, test.project, test.parent, file.NewLocation(virtualPath))
if test.expectedPackage == nil {
require.Nil(t, actualPackage)
} else {
@@ -3,6 +3,7 @@ package java
import (
"archive/zip"
"bytes"
"context"
"fmt"
"io"
"os"
@@ -20,68 +21,12 @@ import (
"github.com/anchore/syft/syft/source/directorysource"
)
func Test_virtualPathFromArchiveTraversal(t *testing.T) {
jarBytes := makeTestZip(t, map[string][]byte{
"META-INF/MANIFEST.MF": testManifest("example-app", "1.0.0"),
})
parse := func(t *testing.T, traversal *archive.Traversal, readerPath string) pkg.Package {
ctx := pkgtest.Context(t)
if traversal != nil {
ctx = archive.WithTraversal(ctx, traversal)
}
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{})
pkgs, _, err := gap.processJavaArchive(ctx, file.LocationReadCloser{
Location: file.NewLocation(readerPath),
ReadCloser: io.NopCloser(bytes.NewReader(jarBytes)),
}, nil)
require.NoError(t, err)
require.Len(t, pkgs, 1)
return pkgs[0]
}
t.Run("no traversal keeps reader path as virtual path", func(t *testing.T) {
p := parse(t, nil, "some/dir/example-app-1.0.0.jar")
metadata, ok := p.Metadata.(pkg.JavaArchive)
require.True(t, ok)
assert.Equal(t, "some/dir/example-app-1.0.0.jar", metadata.VirtualPath)
assert.Equal(t, "example-app", p.Name)
assert.Equal(t, "1.0.0", p.Version)
})
t.Run("traversal prepends containing archive chain", func(t *testing.T) {
trav := &archive.Traversal{
Location: file.NewLocation("/app.war"),
VirtualPath: "/app.war",
}
p := parse(t, trav, "/WEB-INF/lib/example-app-1.0.0.jar")
metadata, ok := p.Metadata.(pkg.JavaArchive)
require.True(t, ok)
assert.Equal(t, "/app.war:WEB-INF/lib/example-app-1.0.0.jar", metadata.VirtualPath)
// name/version inference from the last virtual path element is unaffected by the chain
assert.Equal(t, "example-app", p.Name)
assert.Equal(t, "1.0.0", p.Version)
})
t.Run("multi-level traversal chains all parents", func(t *testing.T) {
outer := &archive.Traversal{VirtualPath: "/dist.zip"}
inner := &archive.Traversal{
VirtualPath: outer.VirtualPathOf("/app.war"),
}
p := parse(t, inner, "/WEB-INF/lib/example-app-1.0.0.jar")
metadata, ok := p.Metadata.(pkg.JavaArchive)
require.True(t, ok)
assert.Equal(t, "/dist.zip:app.war:WEB-INF/lib/example-app-1.0.0.jar", metadata.VirtualPath)
})
}
// Test_virtualPathFromArchiveTraversal_colonInEntryPath is the java-cataloger-level counterpart of
// internal/archive's escaping tests: a package reached through an entry whose path contains a colon
// must carry it escaped as %3A end to end in its virtual path
// (decisions.md#colon-in-an-entry-path-is-escaped), while the maven-derived ":groupID:artifactID"
// suffix that distinguishes a pom.properties package from its containing archive is still appended
// with real, unescaped delimiters.
func Test_virtualPathFromArchiveTraversal_colonInEntryPath(t *testing.T) {
// must carry it escaped as %3A end to end in its virtual path, while the maven-derived
// ":groupID:artifactID" suffix that distinguishes a pom.properties package from its containing archive
// is still appended with real, unescaped delimiters.
func Test_virtualPathFromArchiveLocation_colonInEntryPath(t *testing.T) {
jarBytes := makeTestZip(t, map[string][]byte{
"META-INF/MANIFEST.MF": testManifest("outer-app", "1.0.0"),
"META-INF/maven/com.example/different-lib/pom.properties": []byte(
@@ -89,15 +34,13 @@ func Test_virtualPathFromArchiveTraversal_colonInEntryPath(t *testing.T) {
),
})
trav := &archive.Traversal{
Location: file.NewLocation("/outer.zip"),
VirtualPath: "outer.zip",
}
ctx := archive.WithTraversal(pkgtest.Context(t), trav)
// as the archive cataloger task's resolver would present an entry of outer.zip
location := file.NewLocation("/lib/dep:special.jar")
location.ArchivePath = "outer.zip"
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{})
pkgs, _, err := gap.processJavaArchive(ctx, file.LocationReadCloser{
Location: file.NewLocation("/lib/dep:special.jar"),
pkgs, _, err := gap.processJavaArchive(pkgtest.Context(t), file.LocationReadCloser{
Location: location,
ReadCloser: io.NopCloser(bytes.NewReader(jarBytes)),
}, nil)
require.NoError(t, err)
@@ -134,9 +77,9 @@ func Test_nestedArchiveOwnershipSwitch(t *testing.T) {
"lib/nested-lib-2.0.jar": nestedJar,
})
parse := func(t *testing.T, cfg ArchiveCatalogerConfig) []pkg.Package {
parse := func(t *testing.T, ctx context.Context, cfg ArchiveCatalogerConfig) []pkg.Package {
gap := newGenericArchiveParserAdapter(cfg)
pkgs, _, err := gap.processJavaArchive(pkgtest.Context(t), file.LocationReadCloser{
pkgs, _, err := gap.processJavaArchive(ctx, file.LocationReadCloser{
Location: file.NewLocation("example-app-1.0.0.jar"),
ReadCloser: io.NopCloser(bytes.NewReader(outerJar)),
}, nil)
@@ -145,7 +88,7 @@ func Test_nestedArchiveOwnershipSwitch(t *testing.T) {
}
t.Run("nested archive cataloging disabled: this cataloger unarchives nested jars itself", func(t *testing.T) {
pkgs := parse(t, DefaultArchiveCatalogerConfig()) // MaxDepth defaults to 0 (disabled)
pkgs := parse(t, pkgtest.Context(t), DefaultArchiveCatalogerConfig()) // MaxDepth defaults to 0 (disabled)
require.Len(t, pkgs, 2)
var names, virtualPaths []string
@@ -163,12 +106,11 @@ func Test_nestedArchiveOwnershipSwitch(t *testing.T) {
})
t.Run("nested archive cataloging enabled: this cataloger does not unarchive", func(t *testing.T) {
cfg := DefaultArchiveCatalogerConfig()
// only CreateSBOMConfig sets this in production; it is not reachable from user config
cfg.NestedArchivesHandledExternally = true
// only syft.CreateSBOM marks the context in production; it is not reachable from user config
ctx := archive.WithNestedCataloging(pkgtest.Context(t))
// no error expected: the nested jar is deliberately left to the archive cataloger task
pkgs := parse(t, cfg)
pkgs := parse(t, ctx, DefaultArchiveCatalogerConfig())
require.Len(t, pkgs, 1)
assert.Equal(t, "example-app", pkgs[0].Name)
})
@@ -179,9 +121,8 @@ func Test_nestedArchiveOwnershipSwitch(t *testing.T) {
// wrong silently drops nested jars for every consumer that is not the CLI.
cfg := DefaultArchiveCatalogerConfig()
cfg.ArchiveSearchConfig = cfg.ArchiveSearchConfig.WithMaxDepth(2)
require.False(t, cfg.NestedArchivesHandledExternally)
pkgs := parse(t, cfg)
pkgs := parse(t, pkgtest.Context(t), cfg)
require.Len(t, pkgs, 2, "this cataloger must still recurse itself")
})
}
@@ -195,7 +136,7 @@ func Test_NewArchiveCataloger_wrappedParserRegistration(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "placeholder.txt"), []byte("x"), 0o600))
queriedGlobs := func(t *testing.T, cfg ArchiveCatalogerConfig) *pkgtest.ObservingResolver {
queriedGlobs := func(t *testing.T, ctx context.Context, cfg ArchiveCatalogerConfig) *pkgtest.ObservingResolver {
t.Helper()
src, err := directorysource.NewFromPath(dir)
require.NoError(t, err)
@@ -203,7 +144,7 @@ func Test_NewArchiveCataloger_wrappedParserRegistration(t *testing.T) {
require.NoError(t, err)
observed := pkgtest.NewObservingResolver(resolver)
_, _, err = NewArchiveCataloger(cfg).Catalog(pkgtest.Context(t), observed)
_, _, err = NewArchiveCataloger(cfg).Catalog(ctx, observed)
require.NoError(t, err)
return observed
}
@@ -212,9 +153,8 @@ func Test_NewArchiveCataloger_wrappedParserRegistration(t *testing.T) {
cfg := DefaultArchiveCatalogerConfig()
cfg.IncludeIndexedArchives = true
cfg.IncludeUnindexedArchives = true
cfg.NestedArchivesHandledExternally = true
observed := queriedGlobs(t, cfg)
observed := queriedGlobs(t, archive.WithNestedCataloging(pkgtest.Context(t)), cfg)
// outside a traversal the cataloger opens no files at all: the task brings every archive back
// as an extracted filesystem, and each is described there
assert.False(t, observed.ObservedPathQuery("**/*.zip"), "no generic zip parser may be registered")
@@ -227,7 +167,7 @@ func Test_NewArchiveCataloger_wrappedParserRegistration(t *testing.T) {
cfg.IncludeIndexedArchives = true
cfg.IncludeUnindexedArchives = true
observed := queriedGlobs(t, cfg)
observed := queriedGlobs(t, pkgtest.Context(t), cfg)
assert.True(t, observed.ObservedPathQuery("**/*.zip"), "existing behavior must be unchanged")
assert.True(t, observed.ObservedPathQuery("**/*.tar"), "existing behavior must be unchanged")
})
@@ -237,7 +177,7 @@ func Test_NewArchiveCataloger_wrappedParserRegistration(t *testing.T) {
cfg.IncludeIndexedArchives = false
cfg.IncludeUnindexedArchives = false
observed := queriedGlobs(t, cfg)
observed := queriedGlobs(t, pkgtest.Context(t), cfg)
assert.False(t, observed.ObservedPathQuery("**/*.zip"))
assert.False(t, observed.ObservedPathQuery("**/*.tar"))
})
@@ -246,8 +186,8 @@ func Test_NewArchiveCataloger_wrappedParserRegistration(t *testing.T) {
func Test_mavenVirtualPathSuffixUnderTraversal(t *testing.T) {
// a package derived from a pom.properties whose artifact does not match the containing archive gets
// a trailing ":<groupID>:<artifactID>" appended to the archive's virtual path. That suffix form is in
// published SBOMs, and once the archive cataloger drives the parser the base comes from the traversal
// rather than the reader path - so the two must compose rather than one clobbering the other.
// published SBOMs, and once the archive cataloger drives the parser the base carries the location's
// archive path - so the two must compose rather than one clobbering the other.
jarBytes := makeTestZip(t, map[string][]byte{
"META-INF/MANIFEST.MF": testManifest("example-app", "1.0.0"),
"META-INF/maven/com.example.other/other-lib/pom.properties": []byte(
@@ -255,15 +195,13 @@ func Test_mavenVirtualPathSuffixUnderTraversal(t *testing.T) {
),
})
parse := func(t *testing.T, traversal *archive.Traversal, readerPath string) []pkg.Package {
parse := func(t *testing.T, archivePath, readerPath string) []pkg.Package {
t.Helper()
ctx := pkgtest.Context(t)
if traversal != nil {
ctx = archive.WithTraversal(ctx, traversal)
}
location := file.NewLocation(readerPath)
location.ArchivePath = archivePath
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{})
pkgs, _, err := gap.processJavaArchive(ctx, file.LocationReadCloser{
Location: file.NewLocation(readerPath),
pkgs, _, err := gap.processJavaArchive(pkgtest.Context(t), file.LocationReadCloser{
Location: location,
ReadCloser: io.NopCloser(bytes.NewReader(jarBytes)),
}, nil)
require.NoError(t, err)
@@ -284,14 +222,14 @@ func Test_mavenVirtualPathSuffixUnderTraversal(t *testing.T) {
return ""
}
t.Run("no traversal: suffix hangs off the reader path", func(t *testing.T) {
pkgs := parse(t, nil, "/example-app-1.0.0.jar")
t.Run("outside an archive: suffix hangs off the reader path", func(t *testing.T) {
pkgs := parse(t, "", "/example-app-1.0.0.jar")
assert.Equal(t, "/example-app-1.0.0.jar:com.example.other:other-lib",
virtualPathOf(t, pkgs, "other-lib"))
})
t.Run("under traversal: suffix hangs off the archive chain", func(t *testing.T) {
pkgs := parse(t, &archive.Traversal{VirtualPath: "app.war"}, "/WEB-INF/lib/example-app-1.0.0.jar")
t.Run("inside an archive: suffix hangs off the archive chain", func(t *testing.T) {
pkgs := parse(t, "app.war", "/WEB-INF/lib/example-app-1.0.0.jar")
assert.Equal(t, "app.war:WEB-INF/lib/example-app-1.0.0.jar:com.example.other:other-lib",
virtualPathOf(t, pkgs, "other-lib"))
})
-119
View File
@@ -1,119 +0,0 @@
package java
import (
"context"
"fmt"
"strings"
"github.com/bmatcuk/doublestar/v4"
"github.com/anchore/syft/internal/archive"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/tmpdir"
"github.com/anchore/syft/syft/file"
)
// archiveSource is one java archive to describe: where its entries are read from, and the identity
// that does not live inside them - the archive's own location, the colon-delimited virtual path
// SBOMs carry, and the file name that name and version fall back to.
//
// Two of these exist because two things can own extraction. When this cataloger owns it, the archive
// is a file it copies to a temp dir and reads as a zip. When the archive cataloger task owns it, the
// archive has already been extracted and indexed, and arrives as a resolver plus the traversal
// describing the file it came from. Everything past this point - which manifest wins, how
// pom.properties decides identity, license discovery - is logic over a source, so it runs unchanged
// either way.
type archiveSource struct {
entries archiveEntries
location file.Location
virtualPath string
fileInfo archiveFilename
// contentPath is the directory nested archives are extracted into. Set only when this cataloger
// opened the archive itself: recursing needs somewhere to unzip into, and an already-extracted
// archive has none. The archive file itself is held by zipEntries.
contentPath string
}
// newFileArchiveSource copies the archive to a temp dir and reads its central directory, for a scan
// where nothing has already extracted it. The returned cleanup must run even when an error is
// returned.
func newFileArchiveSource(ctx context.Context, reader file.LocationReadCloser) (archiveSource, func(), error) {
// the full virtual path of this archive: when the archive cataloger task drove us here, the context
// traversal supplies the containing chain; otherwise the reader path already carries any
// colon-delimited nesting from this cataloger's own recursion
virtualPath := archive.TraversalFromContext(ctx).VirtualPathOf(reader.Path())
// fetch the last element of the virtual path
virtualElements := strings.Split(virtualPath, ":")
currentFilepath := virtualElements[len(virtualElements)-1]
td := tmpdir.FromContext(ctx)
if td == nil {
return archiveSource{}, func() {}, fmt.Errorf("no temp dir factory in context")
}
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(td, currentFilepath, reader)
if err != nil {
return archiveSource{}, cleanupFn, fmt.Errorf("unable to process java archive: %w", err)
}
entries, err := newZipEntries(ctx, archivePath)
if err != nil {
return archiveSource{}, cleanupFn, err
}
return archiveSource{
entries: entries,
location: reader.Location,
virtualPath: virtualPath,
fileInfo: newJavaArchiveFilename(currentFilepath),
contentPath: contentPath,
}, cleanupFn, nil
}
// newExtractedArchiveSource describes the archive whose extracted contents the given resolver holds.
// Nothing is copied or reopened, and identity comes from the traversal: the location, the chain the
// archive cataloger task composed, the file name, and the digests taken at extraction - none of which
// are inside the archive.
func newExtractedArchiveSource(trav *archive.Traversal, resolver file.Resolver) (archiveSource, error) {
if resolver == nil {
return archiveSource{}, fmt.Errorf("no resolver for the contents of %q", trav.VirtualPath)
}
return archiveSource{
entries: newResolverEntries(resolver, trav.Digests),
location: trav.Location,
virtualPath: trav.VirtualPath,
fileInfo: newJavaArchiveFilename(archiveFileNameOf(trav)),
}, nil
}
// archiveFileNameOf returns the archive's own file name, where a jar's name and version come from
// when its manifest does not say. It takes the last element of the virtual path rather than the real
// path, so a nested chain and a plain scan agree.
func archiveFileNameOf(trav *archive.Traversal) string {
name := trav.VirtualPath
if name == "" {
name = trav.Location.Path()
}
for i := len(name) - 1; i >= 0; i-- {
if name[i] == ':' || name[i] == '/' {
return name[i+1:]
}
}
return name
}
// isJavaArchiveName reports whether the path is one of the java archive formats, by the same globs
// this cataloger registers against when it is the one opening files.
func isJavaArchiveName(path string) bool {
for _, glob := range archiveFormatGlobs {
if ok, err := doublestar.Match(glob, path); err == nil && ok {
return true
}
if ok, err := doublestar.Match(glob, "/"+path); err == nil && ok {
return true
}
}
log.WithFields("path", path).Trace("archive contents are not a java archive")
return false
}
+8 -21
View File
@@ -55,9 +55,8 @@ func Test_archiveContentsCataloger_agreesWithTheArchiveReadingCataloger(t *testi
digests := digestsOfFile(t, jarPath)
trav := &archive.Traversal{
Location: file.NewLocation(archivePath),
VirtualPath: archivePath,
Digests: digests,
Location: file.NewLocation(archivePath),
Digests: digests,
}
ctx := archive.WithTraversal(context.Background(), trav)
fromContents, _, err := NewArchiveCataloger(cfg).Catalog(ctx, contentsResolver)
@@ -70,16 +69,14 @@ func Test_archiveContentsCataloger_reportsNothingOutsideAnArchive(t *testing.T)
// with the archive cataloger task owning extraction, every archive comes back with a traversal
// naming it. Outside one there is nothing to describe: opening archive files here as well would
// catalog each of them a second time.
cfg := DefaultArchiveCatalogerConfig()
cfg.NestedArchivesHandledExternally = true
dir := t.TempDir()
jarPath := writeJar(t, dir, "example-lib-1.2.3.jar", jarEntries())
require.FileExists(t, jarPath)
resolver, err := fileresolver.NewFromDirectory(dir, "")
require.NoError(t, err)
pkgs, _, err := NewArchiveCataloger(cfg).Catalog(context.Background(), resolver)
ctx := archive.WithNestedCataloging(context.Background())
pkgs, _, err := NewArchiveCataloger(DefaultArchiveCatalogerConfig()).Catalog(ctx, resolver)
require.NoError(t, err)
assert.Empty(t, pkgs)
}
@@ -93,8 +90,7 @@ func Test_archiveContentsCataloger_reportsNothingForAnArchiveThatIsNotJava(t *te
require.NoError(t, err)
ctx := archive.WithTraversal(context.Background(), &archive.Traversal{
Location: file.NewLocation("/bundle.zip"),
VirtualPath: "/bundle.zip",
Location: file.NewLocation("/bundle.zip"),
})
pkgs, _, err := NewArchiveCataloger(DefaultArchiveCatalogerConfig()).Catalog(ctx, resolver)
require.NoError(t, err)
@@ -117,8 +113,7 @@ func Test_archiveContentsCataloger_noManifestIsNoPackage(t *testing.T) {
require.NoError(t, err)
ctx := archive.WithTraversal(context.Background(), &archive.Traversal{
Location: file.NewLocation("no-manifest-1.0.0.jar"),
VirtualPath: "no-manifest-1.0.0.jar",
Location: file.NewLocation("no-manifest-1.0.0.jar"),
})
pkgs, _, err := NewArchiveCataloger(DefaultArchiveCatalogerConfig()).Catalog(ctx, resolver)
assert.Empty(t, pkgs)
@@ -135,11 +130,6 @@ func Test_archiveContentsCataloger_readsWhatWasExtractedAndNoMore(t *testing.T)
// extraction produced. An archive truncated by a limit yields a package built from the entries that
// were written, so identity degrades rather than disappearing - the manifest is there, the maven
// metadata is not.
//
// Observed on a real artifact: jenkins-core-2.578.jar holds more than the default 10,000-entry bound,
// so its META-INF/maven pom is never extracted and its group id falls back from org.jenkins-ci.main
// to the artifact id. The archive-reading cataloger never felt this, reading the archive's own
// central directory with no bound on it.
entries := map[string]string{
"META-INF/MANIFEST.MF": "Manifest-Version: 1.0\n" +
"Implementation-Title: example-lib\nImplementation-Version: 1.2.3\n",
@@ -153,8 +143,7 @@ func Test_archiveContentsCataloger_readsWhatWasExtractedAndNoMore(t *testing.T)
require.NoError(t, err)
ctx := archive.WithTraversal(context.Background(), &archive.Traversal{
Location: file.NewLocation("example-lib-1.2.3.jar"),
VirtualPath: "example-lib-1.2.3.jar",
Location: file.NewLocation("example-lib-1.2.3.jar"),
})
pkgs, _, err := NewArchiveCataloger(DefaultArchiveCatalogerConfig()).Catalog(ctx, resolver)
require.NoError(t, err)
@@ -249,9 +238,7 @@ func joinSorted(in []string) string {
func digestsOfFile(t *testing.T, path string) []file.Digest {
t.Helper()
entries, err := newZipEntries(context.Background(), path)
require.NoError(t, err)
digests, err := entries.digests(context.Background())
digests, err := getDigestsFromArchive(context.Background(), path)
require.NoError(t, err)
require.NotEmpty(t, digests)
return digests
+33 -34
View File
@@ -6,6 +6,8 @@ package java
import (
"context"
"github.com/bmatcuk/doublestar/v4"
"github.com/anchore/syft/internal/archive"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/artifact"
@@ -14,46 +16,39 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const ArchiveCatalogerName = "java-archive-cataloger"
// NewArchiveCataloger returns a new Java archive cataloger object for detecting packages with archives (jar, war, ear, par, sar, jpi, hpi, and native-image formats)
func NewArchiveCataloger(cfg ArchiveCatalogerConfig) pkg.Cataloger {
gap := newGenericArchiveParserAdapter(cfg)
files := generic.NewCataloger(ArchiveCatalogerName).
c := generic.NewCataloger("java-archive-cataloger").
WithParserByGlobs(gap.parseJavaArchive, archiveFormatGlobs...)
if cfg.IncludeIndexedArchives {
// java archives wrapped within zip files
gzp := newGenericZipWrappedJavaArchiveParser(cfg)
files.WithParserByGlobs(gzp.parseZipWrappedJavaArchive, genericZipGlobs...)
c.WithParserByGlobs(gzp.parseZipWrappedJavaArchive, genericZipGlobs...)
}
if cfg.IncludeUnindexedArchives {
// java archives wrapped within tar files
gtp := newGenericTarWrappedJavaArchiveParser(cfg)
files.WithParserByGlobs(gtp.parseTarWrappedJavaArchive, genericTarGlobs...)
c.WithParserByGlobs(gtp.parseTarWrappedJavaArchive, genericTarGlobs...)
}
return &archiveCataloger{cfg: cfg, files: files}
return &archiveCataloger{cfg: cfg, extractingCataloger: c}
}
// archiveCataloger describes java archives from whichever source this scan makes them available in.
//
// Which source that is turns on who owns extraction, and there are only two answers. Inside an
// archive the archive cataloger task extracted, the resolver is that archive's contents and the
// traversal names the file it came from, so the archive to describe is the one this resolver *is*.
// Everywhere else the archives are files in the resolver, and the cataloger opens each itself.
//
// One cataloger rather than one per source, so a package carries the same FoundBy and answers to the
// same --select-catalogers name however the scan was configured.
// archiveCataloger catalogs java archives two ways. Inside an archive the archive cataloger task has
// extracted, the resolver is that archive's contents and the traversal on the context names the
// archive, so the archive to describe is the one the resolver holds. Everywhere else, archives are
// files in the resolver that this cataloger opens itself.
type archiveCataloger struct {
cfg ArchiveCatalogerConfig
files *generic.Cataloger
cfg ArchiveCatalogerConfig
extractingCataloger *generic.Cataloger
}
func (c *archiveCataloger) Name() string {
return ArchiveCatalogerName
return c.extractingCataloger.Name()
}
func (c *archiveCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
@@ -61,42 +56,46 @@ func (c *archiveCataloger) Catalog(ctx context.Context, resolver file.Resolver)
return c.catalogExtracted(ctx, trav, resolver)
}
if c.cfg.NestedArchivesHandledExternally {
// every archive in this scan comes back through the archive cataloger task with a traversal
// naming it, and is described above. Opening archive files here as well would catalog each of
// them a second time.
if archive.NestedCatalogingEnabled(ctx) {
// the archive cataloger task extracts every archive and runs this cataloger inside each; opening
// them here too would catalog each twice
return nil, nil, nil
}
return c.files.Catalog(ctx, resolver)
// process archives directly, using the original locally managed java archive extraction
return c.extractingCataloger.Catalog(ctx, resolver)
}
// catalogExtracted reports the java package of the archive whose contents this resolver holds, if it
// is a java archive at all.
//
// It does not glob for the archives inside it: the task that extracted this one meets those on its
// own, at the next nesting level. See syft/java-archive-nesting#single-owner-of-archive-recursion.
// catalogExtracted reports the java package of the archive whose contents the resolver holds, if it
// is a java archive. Archives nested inside it are left to the archive cataloger task.
func (c *archiveCataloger) catalogExtracted(ctx context.Context, trav *archive.Traversal, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
if !isJavaArchiveName(trav.Location.Path()) {
// the rule the globs apply when this cataloger opens files: a .zip is an archive but not a java
// archive
return nil, nil, nil
}
src, err := newExtractedArchiveSource(trav, resolver)
parser, err := newExtractedArchiveParser(trav, resolver, c.cfg)
if err != nil {
return nil, nil, err
}
pkgs, relationships, err := newArchiveParser(src, false, c.cfg).parse(ctx, nil)
pkgs, relationships, err := parser.parse(ctx, nil)
if err != nil {
// attributed to the archive rather than returned bare, as the generic cataloger attributes a
// parser's failure to the file it was parsing, so the scan carries on
// attributed to the archive, as the generic cataloger attributes a parser failure to its file
return pkgs, relationships, unknown.New(trav.Location, err)
}
return pkgs, relationships, nil
}
// isJavaArchiveName reports whether the path matches the globs this cataloger opens files by.
func isJavaArchiveName(path string) bool {
for _, glob := range archiveFormatGlobs {
if ok, _ := doublestar.Match(glob, path); ok {
return true
}
}
return false
}
// NewPomCataloger returns a cataloger capable of parsing dependencies from a pom.xml file.
// Pom files list dependencies that maybe not be locally installed yet.
func NewPomCataloger(cfg ArchiveCatalogerConfig) pkg.Cataloger {
-9
View File
@@ -33,15 +33,6 @@ type ArchiveCatalogerConfig struct {
// ResolveTransitiveDependencies enables resolving transitive dependencies for java packages found within archives.
// app-config: java.resolve-transitive-dependencies
ResolveTransitiveDependencies bool `yaml:"resolve-transitive-dependencies" json:"resolve-transitive-dependencies" mapstructure:"resolve-transitive-dependencies"`
// NestedArchivesHandledExternally indicates that another mechanism owns recursion into nested
// archives, so this cataloger must not unarchive them itself. It has no yaml/json/mapstructure
// key, so no config file, flag or environment variable reaches it.
//
// Only syft.CreateSBOMConfig writes it, deriving it from the nested-archive cataloging depth so
// the two cannot disagree. Callers building this config should leave it alone; the zero value
// recurses, this cataloger's default.
NestedArchivesHandledExternally bool `yaml:"-" json:"-" mapstructure:"-"`
}
func DefaultArchiveCatalogerConfig() ArchiveCatalogerConfig {
+1 -3
View File
@@ -3,7 +3,6 @@ package java
import (
"context"
"errors"
"path/filepath"
"strings"
"github.com/anchore/syft/internal"
@@ -309,8 +308,7 @@ func pomParent(ctx context.Context, r *maven.Resolver, pom *maven.Project) *pkg.
// The java-archive-cataloger identifies these archives from pom.properties, whose values are always
// resolved, so skipping them here avoids phantom dependency packages.
func isArchiveMetaPom(location file.Location) bool {
p := filepath.ToSlash(location.Path())
return strings.Contains(p, "META-INF/maven/")
return strings.Contains(location.Path(), "META-INF/maven/")
}
func cleanDescription(original string) (cleaned string) {
@@ -17,13 +17,13 @@ import (
func readManifest(t *testing.T, ctx context.Context, parser *archiveParser) *pkg.JavaManifest {
t.Helper()
matches := parser.entries.glob(false, manifestGlob)
matches := parser.entries.glob(manifestGlob)
require.Len(t, matches, 1)
contents, err := parser.entries.contents(ctx, matches...)
require.NoError(t, err)
manifest, err := parseJavaManifest(parser.virtualPath, strings.NewReader(contents[matches[0]]))
manifest, err := parseJavaManifest(parser.virtualPath(), strings.NewReader(contents[matches[0]]))
require.NoError(t, err)
return manifest