diff --git a/syft/pkg/cataloger/java/archive_parser.go b/syft/pkg/cataloger/java/archive_parser.go index c07699567..a3e3af3e9 100644 --- a/syft/pkg/cataloger/java/archive_parser.go +++ b/syft/pkg/cataloger/java/archive_parser.go @@ -184,14 +184,17 @@ func (j *archiveParser) discoverPkgsFromAllPomProperties(parentPkg *pkg.Package) continue } - pkgs = append(pkgs, j.packagesFromPomProperties(*pomProperties, parentPkg)...) + pkgFromPom := j.newPackageFromPomProperties(*pomProperties, parentPkg) + if pkgFromPom != nil { + pkgs = append(pkgs, *pkgFromPom) + } } return pkgs, nil } // packagesFromPomProperties 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. -func (j *archiveParser) packagesFromPomProperties(pomProperties pkg.PomProperties, parentPkg *pkg.Package) []pkg.Package { +func (j *archiveParser) newPackageFromPomProperties(pomProperties pkg.PomProperties, parentPkg *pkg.Package) *pkg.Package { // keep the artifact name within the virtual path if this package does not match the parent package vPathSuffix := "" if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) { @@ -213,45 +216,12 @@ func (j *archiveParser) packagesFromPomProperties(pomProperties pkg.PomPropertie }, } - pkgKey := uniquePkgKey(&p) - parentKey := uniquePkgKey(parentPkg) - - // the name/version pair matches... - matchesParentPkg := pkgKey == parentKey - - // the virtual path matches... - matchesParentPkg = matchesParentPkg || parentPkg.Metadata.(pkg.JavaMetadata).VirtualPath == virtualPath - - // the pom artifactId has the parent name or vice versa - if pomProperties.ArtifactID != "" { - matchesParentPkg = matchesParentPkg || strings.Contains(parentPkg.Name, pomProperties.ArtifactID) || strings.Contains(pomProperties.ArtifactID, parentPkg.Name) + if packageIdentitiesMatch(p, parentPkg) { + updatePackage(p, parentPkg) + return nil } - if !matchesParentPkg { - // only keep packages we haven't seen yet (and are not related to the parent package) - return []pkg.Package{p} - } - - // we've run across more information about our parent package, add this info to the parent package metadata - // the pom properties is typically a better source of information for name and version than the manifest - if parentPkg.Name == "" { - parentPkg.Name = p.Name - } - if parentPkg.Version == "" { - parentPkg.Version = p.Version - } - - // We may have learned more about the type via data in the pom properties - parentPkg.Type = p.Type - - // keep the pom properties, but don't overwrite existing pom properties - parentMetadata, ok := parentPkg.Metadata.(pkg.JavaMetadata) - if ok && parentMetadata.PomProperties == nil { - parentMetadata.PomProperties = &pomProperties - parentPkg.Metadata = parentMetadata - } - - return nil + return &p } // discoverPkgsFromNestedArchives finds Java archives within Java archives, returning all listed Java packages found and @@ -297,3 +267,50 @@ func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ( return pkgs, nil } + +func packageIdentitiesMatch(p pkg.Package, parentPkg *pkg.Package) bool { + // the name/version pair matches... + if uniquePkgKey(&p) == uniquePkgKey(parentPkg) { + return true + } + + metadata := p.Metadata.(pkg.JavaMetadata) + + // the virtual path matches... + if parentPkg.Metadata.(pkg.JavaMetadata).VirtualPath == metadata.VirtualPath { + return true + } + + // the pom artifactId is the parent name + // note: you CANNOT use name-is-subset-of-artifact-id or vice versa --this is too generic. Shaded jars are a good + // example of this: where the package name is "cloudbees-analytics-segment-driver" and a child is "analytics", but + // they do not indicate the same package. + if metadata.PomProperties.ArtifactID != "" && parentPkg.Name == metadata.PomProperties.ArtifactID { + return true + } + + return false +} + +func updatePackage(p pkg.Package, parentPkg *pkg.Package) { + // we've run across more information about our parent package, add this info to the parent package metadata + // the pom properties is typically a better source of information for name and version than the manifest + parentPkg.Name = p.Name + parentPkg.Version = p.Version + + // we may have learned more about the type via data in the pom properties + parentPkg.Type = p.Type + + metadata, ok := p.Metadata.(pkg.JavaMetadata) + if !ok { + return + } + pomPropertiesCopy := *metadata.PomProperties + + // keep the pom properties, but don't overwrite existing pom properties + parentMetadata, ok := parentPkg.Metadata.(pkg.JavaMetadata) + if ok && parentMetadata.PomProperties == nil { + parentMetadata.PomProperties = &pomPropertiesCopy + parentPkg.Metadata = parentMetadata + } +} diff --git a/syft/pkg/cataloger/java/archive_parser_test.go b/syft/pkg/cataloger/java/archive_parser_test.go index 8988bdaa5..d0453679f 100644 --- a/syft/pkg/cataloger/java/archive_parser_test.go +++ b/syft/pkg/cataloger/java/archive_parser_test.go @@ -564,11 +564,11 @@ func TestParseNestedJar(t *testing.T) { func TestPackagesFromPomProperties(t *testing.T) { virtualPath := "given/virtual/path" tests := []struct { - name string - props *pkg.PomProperties - parent *pkg.Package - expectedParent pkg.Package - expectedPackages []pkg.Package + name string + props *pkg.PomProperties + parent *pkg.Package + expectedParent pkg.Package + expectedPackage *pkg.Package }{ { name: "go case: get a single package from pom properties", @@ -599,30 +599,28 @@ func TestPackagesFromPomProperties(t *testing.T) { Parent: nil, }, }, - expectedPackages: []pkg.Package{ - { - Name: "some-artifact-id", - Version: "1.0", - Language: pkg.Java, - Type: pkg.JavaPkg, - MetadataType: pkg.JavaMetadataType, - Metadata: pkg.JavaMetadata{ - VirtualPath: virtualPath + ":" + "some-artifact-id", - PomProperties: &pkg.PomProperties{ - Name: "some-name", - GroupID: "some-group-id", - ArtifactID: "some-artifact-id", - Version: "1.0", - }, - Parent: &pkg.Package{ - Name: "some-parent-name", - Version: "2.0", - Metadata: pkg.JavaMetadata{ - VirtualPath: "some-parent-virtual-path", - Manifest: nil, - PomProperties: nil, - Parent: nil, - }, + expectedPackage: &pkg.Package{ + Name: "some-artifact-id", + Version: "1.0", + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + VirtualPath: virtualPath + ":" + "some-artifact-id", + PomProperties: &pkg.PomProperties{ + Name: "some-name", + GroupID: "some-group-id", + ArtifactID: "some-artifact-id", + Version: "1.0", + }, + Parent: &pkg.Package{ + Name: "some-parent-name", + Version: "2.0", + Metadata: pkg.JavaMetadata{ + VirtualPath: "some-parent-virtual-path", + Manifest: nil, + PomProperties: nil, + Parent: nil, }, }, }, @@ -657,30 +655,28 @@ func TestPackagesFromPomProperties(t *testing.T) { Parent: nil, }, }, - expectedPackages: []pkg.Package{ - { - Name: "some-artifact-id", - Version: "1.0", - Language: pkg.Java, - Type: pkg.JenkinsPluginPkg, - MetadataType: pkg.JavaMetadataType, - Metadata: pkg.JavaMetadata{ - VirtualPath: virtualPath + ":" + "some-artifact-id", - PomProperties: &pkg.PomProperties{ - Name: "some-name", - GroupID: "com.cloudbees.jenkins.plugins", - ArtifactID: "some-artifact-id", - Version: "1.0", - }, - Parent: &pkg.Package{ - Name: "some-parent-name", - Version: "2.0", - Metadata: pkg.JavaMetadata{ - VirtualPath: "some-parent-virtual-path", - Manifest: nil, - PomProperties: nil, - Parent: nil, - }, + expectedPackage: &pkg.Package{ + Name: "some-artifact-id", + Version: "1.0", + Language: pkg.Java, + Type: pkg.JenkinsPluginPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + VirtualPath: virtualPath + ":" + "some-artifact-id", + PomProperties: &pkg.PomProperties{ + Name: "some-name", + GroupID: "com.cloudbees.jenkins.plugins", + ArtifactID: "some-artifact-id", + Version: "1.0", + }, + Parent: &pkg.Package{ + Name: "some-parent-name", + Version: "2.0", + Metadata: pkg.JavaMetadata{ + VirtualPath: "some-parent-virtual-path", + Manifest: nil, + PomProperties: nil, + Parent: nil, }, }, }, @@ -723,7 +719,7 @@ func TestPackagesFromPomProperties(t *testing.T) { Parent: nil, }, }, - expectedPackages: nil, + expectedPackage: nil, }, { name: "child matches parent by key and is Jenkins plugin", @@ -761,45 +757,7 @@ func TestPackagesFromPomProperties(t *testing.T) { Parent: nil, }, }, - expectedPackages: nil, - }, - { - name: "child matches parent by virtual path", - props: &pkg.PomProperties{ - Name: "some-name", - GroupID: "some-group-id", - ArtifactID: "some-parent-name", // note: matches parent package - Version: "NOT_THE_PARENT_VERSION", // note: DOES NOT match parent package - }, - parent: &pkg.Package{ - Name: "some-parent-name", - Version: "2.0", - Type: pkg.JavaPkg, - Metadata: pkg.JavaMetadata{ - VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path - Manifest: nil, - PomProperties: nil, - Parent: nil, - }, - }, - expectedParent: pkg.Package{ - Name: "some-parent-name", - Version: "2.0", - Type: pkg.JavaPkg, - Metadata: pkg.JavaMetadata{ - VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path - Manifest: nil, - // note: we attach the discovered pom properties data - PomProperties: &pkg.PomProperties{ - Name: "some-name", - GroupID: "some-group-id", - ArtifactID: "some-parent-name", // note: matches parent package - Version: "NOT_THE_PARENT_VERSION", // note: DOES NOT match parent package - }, - Parent: nil, - }, - }, - expectedPackages: nil, + expectedPackage: nil, }, { name: "child matches parent by virtual path -- override name and version", @@ -810,11 +768,11 @@ func TestPackagesFromPomProperties(t *testing.T) { Version: "3.0", // note: DOES NOT match parent package }, parent: &pkg.Package{ - Name: "", // note: empty - Version: "", // note: empty + Name: "", // note: empty, so should not be matched on + Version: "", // note: empty, so should not be matched on Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ - VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path + VirtualPath: virtualPath, // note: matching virtual path Manifest: nil, PomProperties: nil, Parent: nil, @@ -825,56 +783,19 @@ func TestPackagesFromPomProperties(t *testing.T) { Version: "3.0", Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ - VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path + VirtualPath: virtualPath, Manifest: nil, // note: we attach the discovered pom properties data PomProperties: &pkg.PomProperties{ Name: "some-name", GroupID: "some-group-id", - ArtifactID: "some-parent-name", // note: DOES NOT match parent package - Version: "3.0", // note: DOES NOT match parent package + ArtifactID: "some-parent-name", + Version: "3.0", }, Parent: nil, }, }, - expectedPackages: nil, - }, - { - name: "child matches parent by virtual path -- do not override existing pom properties", - props: &pkg.PomProperties{ - Name: "some-name", - GroupID: "some-group-id", - ArtifactID: "some-parent-name", // note: matches parent package - Version: "NOT_THE_PARENT_VERSION", // note: DOES NOT match parent package - }, - parent: &pkg.Package{ - Name: "some-parent-name", - Version: "2.0", - Type: pkg.JavaPkg, - Metadata: pkg.JavaMetadata{ - VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path - Manifest: nil, - PomProperties: &pkg.PomProperties{ - Name: "EXISTS", // note: this already exists and should not be overridden - }, - Parent: nil, - }, - }, - expectedParent: pkg.Package{ - Name: "some-parent-name", - Version: "2.0", - Type: pkg.JavaPkg, - Metadata: pkg.JavaMetadata{ - VirtualPath: virtualPath + ":some-parent-name", // note: matching virtual path - Manifest: nil, - // note: we attach the discovered pom properties data - PomProperties: &pkg.PomProperties{ - Name: "EXISTS", // note: this already exists and should not be overridden - }, - Parent: nil, - }, - }, - expectedPackages: nil, + expectedPackage: nil, }, { name: "child matches parent by artifact id", @@ -898,7 +819,7 @@ func TestPackagesFromPomProperties(t *testing.T) { // note: the SAME as the original parent values expectedParent: pkg.Package{ Name: "some-parent-name", - Version: "2.0", + Version: "NOT_THE_PARENT_VERSION", // note: the version is updated from pom properties Type: pkg.JavaPkg, Metadata: pkg.JavaMetadata{ VirtualPath: virtualPath + ":NEW_VIRTUAL_PATH", @@ -907,13 +828,13 @@ func TestPackagesFromPomProperties(t *testing.T) { PomProperties: &pkg.PomProperties{ Name: "some-name", GroupID: "some-group-id", - ArtifactID: "some-parent-name", // note: matches parent package - Version: "NOT_THE_PARENT_VERSION", // note: DOES NOT match parent package + ArtifactID: "some-parent-name", + Version: "NOT_THE_PARENT_VERSION", }, Parent: nil, }, }, - expectedPackages: nil, + expectedPackage: nil, }, } @@ -929,9 +850,9 @@ func TestPackagesFromPomProperties(t *testing.T) { t.Cleanup(cleanup) // get the test data - actualPackages := parser.packagesFromPomProperties(*test.props, test.parent) - assert.Equal(t, test.expectedPackages, actualPackages) - assert.Equal(t, test.expectedParent, *test.parent) + actualPackage := parser.newPackageFromPomProperties(*test.props, test.parent) + assert.Equal(t, test.expectedPackage, actualPackage, "new package doesn't match") + assert.Equal(t, test.expectedParent, *test.parent, "parent doesn't match") }) } }