diff --git a/syft/pkg/cataloger/java/parse_pom_xml.go b/syft/pkg/cataloger/java/parse_pom_xml.go index b343c8f46d3..35c0630b9b5 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml.go +++ b/syft/pkg/cataloger/java/parse_pom_xml.go @@ -4,6 +4,8 @@ import ( "encoding/xml" "fmt" "io" + "reflect" + "regexp" "strings" "github.com/vifraa/gopom" @@ -16,6 +18,8 @@ import ( const pomXMLGlob = "*pom.xml" const pomXMLDirGlob = "**/pom.xml" +var propertyMatcher = regexp.MustCompile("[$][{][^}]+[}]") + func parserPomXML(path string, content io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { pom, err := decodePomXML(content) if err != nil { @@ -24,7 +28,7 @@ func parserPomXML(path string, content io.Reader) ([]*pkg.Package, []artifact.Re var pkgs []*pkg.Package for _, dep := range pom.Dependencies { - p := newPackageFromPom(dep) + p := newPackageFromPom(pom, dep) if p.Name == "" { continue } @@ -46,27 +50,27 @@ func parsePomXMLProject(path string, reader io.Reader) (*pkg.PomProject, error) func newPomProject(path string, p gopom.Project) *pkg.PomProject { return &pkg.PomProject{ Path: path, - Parent: pomParent(p.Parent), - GroupID: p.GroupID, + Parent: pomParent(p, p.Parent), + GroupID: resolveProperty(p, p.GroupID), ArtifactID: p.ArtifactID, - Version: p.Version, + Version: resolveProperty(p, p.Version), Name: p.Name, Description: cleanDescription(p.Description), URL: p.URL, } } -func newPackageFromPom(dep gopom.Dependency) *pkg.Package { +func newPackageFromPom(pom gopom.Project, dep gopom.Dependency) *pkg.Package { p := &pkg.Package{ Name: dep.ArtifactID, - Version: dep.Version, + Version: resolveProperty(pom, dep.Version), Language: pkg.Java, Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet? MetadataType: pkg.JavaMetadataType, FoundBy: javaPomCataloger, Metadata: pkg.JavaMetadata{ PomProperties: &pkg.PomProperties{ - GroupID: dep.GroupID, + GroupID: resolveProperty(pom, dep.GroupID), }, }, } @@ -87,12 +91,12 @@ func decodePomXML(content io.Reader) (project gopom.Project, err error) { return project, nil } -func pomParent(parent gopom.Parent) (result *pkg.PomParent) { +func pomParent(pom gopom.Project, parent gopom.Parent) (result *pkg.PomParent) { if parent.ArtifactID != "" || parent.GroupID != "" || parent.Version != "" { result = &pkg.PomParent{ - GroupID: parent.GroupID, + GroupID: resolveProperty(pom, parent.GroupID), ArtifactID: parent.ArtifactID, - Version: parent.Version, + Version: resolveProperty(pom, parent.Version), } } return result @@ -109,3 +113,42 @@ func cleanDescription(original string) (cleaned string) { } return strings.TrimSpace(cleaned) } + +// resolveProperty emulates some maven property resolution logic by looking in the project's variables +// as well as supporting the project expressions like ${project.parent.groupId}. +// If no match is found, the entire expression including ${} is returned +func resolveProperty(pom gopom.Project, property string) string { + return propertyMatcher.ReplaceAllStringFunc(property, func(match string) string { + propertyName := strings.TrimSpace(match[2 : len(match)-1]) + if value, ok := pom.Properties.Entries[propertyName]; ok { + return value + } + // if we don't find anything directly in the pom properties, + // see if we have a project.x expression and process this based + // on the xml tags in gopom + parts := strings.Split(propertyName, ".") + numParts := len(parts) + if numParts > 1 && strings.TrimSpace(parts[0]) == "project" { + pomValue := reflect.ValueOf(pom) + pomValueType := pomValue.Type() + for partNum := 1; partNum < numParts; partNum++ { + if pomValueType.Kind() != reflect.Struct { + break + } + part := parts[partNum] + for fieldNum := 0; fieldNum < pomValueType.NumField(); fieldNum++ { + f := pomValueType.Field(fieldNum) + if part == f.Tag.Get("xml") { + pomValue = pomValue.Field(fieldNum) + pomValueType = pomValue.Type() + if partNum == numParts-1 { + return fmt.Sprintf("%v", pomValue.Interface()) + } + break + } + } + } + } + return match + }) +} diff --git a/syft/pkg/cataloger/java/parse_pom_xml_test.go b/syft/pkg/cataloger/java/parse_pom_xml_test.go index 8c25cad9d67..bbfc0595bcb 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml_test.go +++ b/syft/pkg/cataloger/java/parse_pom_xml_test.go @@ -57,6 +57,141 @@ func Test_parserPomXML(t *testing.T) { } } +func Test_parseCommonsTextPomXMLProject(t *testing.T) { + tests := []struct { + input string + expected []*pkg.Package + }{ + { + input: "test-fixtures/pom/commons-text.pom.xml", + expected: []*pkg.Package{ + { + Name: "commons-lang3", + Version: "3.12.0", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0", + }, + }, + { + Name: "junit-jupiter", + Version: "", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.junit.jupiter/junit-jupiter", + }, + }, + { + Name: "assertj-core", + Version: "3.23.1", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.assertj/assertj-core@3.23.1", + }, + }, + { + Name: "commons-io", + Version: "2.11.0", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/commons-io/commons-io@2.11.0", + }, + }, + { + Name: "mockito-inline", + Version: "4.8.0", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0", + }, + }, + { + Name: "js", + Version: "22.0.0.2", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2", + }, + }, + { + Name: "js-scriptengine", + Version: "22.0.0.2", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.graalvm.js/js-scriptengine@22.0.0.2", + }, + }, + { + Name: "commons-rng-simple", + Version: "1.4", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4", + }, + }, + { + Name: "jmh-core", + Version: "1.35", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35", + }, + }, + { + Name: "jmh-generator-annprocess", + Version: "1.35", + FoundBy: javaPomCataloger, + Language: pkg.Java, + Type: pkg.JavaPkg, + MetadataType: pkg.JavaMetadataType, + Metadata: pkg.JavaMetadata{ + PURL: "pkg:maven/org.openjdk.jmh/jmh-generator-annprocess@1.35", + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.input, func(t *testing.T) { + fixture, err := os.Open(test.input) + assert.NoError(t, err) + + actual, relationships, err := parserPomXML(fixture.Name(), fixture) + assert.NoError(t, err) + assert.Nil(t, relationships) + assert.Equal(t, test.expected, actual) + }) + } +} + func Test_parsePomXMLProject(t *testing.T) { tests := []struct { expected pkg.PomProject @@ -141,7 +276,7 @@ func Test_pomParent(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.expected, pomParent(test.input)) + assert.Equal(t, test.expected, pomParent(gopom.Project{}, test.input)) }) } } @@ -168,3 +303,50 @@ func Test_cleanDescription(t *testing.T) { }) } } + +func Test_resolveProperty(t *testing.T) { + tests := []struct { + name string + property string + pom gopom.Project + expected string + }{ + { + name: "property", + property: "${version.number}", + pom: gopom.Project{ + Properties: gopom.Properties{ + Entries: map[string]string{ + "version.number": "12.5.0", + }, + }, + }, + expected: "12.5.0", + }, + { + name: "groupId", + property: "${project.groupId}", + pom: gopom.Project{ + GroupID: "org.some.group", + }, + expected: "org.some.group", + }, + { + name: "parent groupId", + property: "${project.parent.groupId}", + pom: gopom.Project{ + Parent: gopom.Parent{ + GroupID: "org.some.parent", + }, + }, + expected: "org.some.parent", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resolved := resolveProperty(test.pom, test.property) + assert.Equal(t, test.expected, resolved) + }) + } +} diff --git a/syft/pkg/cataloger/java/test-fixtures/pom/commons-text.pom.xml b/syft/pkg/cataloger/java/test-fixtures/pom/commons-text.pom.xml new file mode 100644 index 00000000000..6f54a6ed6b1 --- /dev/null +++ b/syft/pkg/cataloger/java/test-fixtures/pom/commons-text.pom.xml @@ -0,0 +1,575 @@ + + + + 4.0.0 + + org.apache.commons + commons-parent + 54 + + commons-text + 1.10.0 + Apache Commons Text + Apache Commons Text is a library focused on algorithms working on strings. + https://commons.apache.org/proper/commons-text + + + ISO-8859-1 + UTF-8 + 1.8 + 1.8 + + text + org.apache.commons.text + + 1.10.0 + (Java 8+) + + TEXT + 12318221 + + text + https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-text + site-content + + 5.9.1 + 3.2.0 + 9.3 + + 4.7.2.0 + 4.7.2 + 3.19.0 + 6.49.0 + + 4.8.0 + 0.8.8 + + + 3.10.0 + 3.4.1 + + + 22.0.0.2 + 1.4 + + 0.16.0 + false + + 1.35 + 3.1.2 + + + 1.9 + RC1 + true + scm:svn:https://dist.apache.org/repos/dist/dev/commons/${commons.componentid} + Gary Gregory + 86fdc7e2a11262cb + + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + 3.23.1 + test + + + commons-io + commons-io + 2.11.0 + test + + + org.mockito + + mockito-inline + ${commons.mockito.version} + test + + + org.graalvm.js + js + ${graalvm.version} + test + + + org.graalvm.js + js-scriptengine + ${graalvm.version} + test + + + org.apache.commons + commons-rng-simple + ${commons.rng.version} + test + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + + + clean verify apache-rat:check japicmp:cmp checkstyle:check spotbugs:check javadoc:javadoc + + + + org.apache.rat + apache-rat-plugin + + + site-content/** + src/site/resources/download_lang.cgi + src/test/resources/org/apache/commons/text/stringEscapeUtilsTestData.txt + src/test/resources/org/apache/commons/text/lcs-perf-analysis-inputs.csv + src/site/resources/release-notes/RELEASE-NOTES-*.txt + + + + + maven-pmd-plugin + ${commons.pmd.version} + + ${maven.compiler.target} + + + + net.sourceforge.pmd + pmd-core + ${commons.pmd-impl.version} + + + net.sourceforge.pmd + pmd-java + ${commons.pmd-impl.version} + + + net.sourceforge.pmd + pmd-javascript + ${commons.pmd-impl.version} + + + net.sourceforge.pmd + pmd-jsp + ${commons.pmd-impl.version} + + + + + + + + maven-checkstyle-plugin + ${checkstyle.plugin.version} + + false + src/conf/checkstyle.xml + src/conf/checkstyle-header.txt + src/conf/checkstyle-suppressions.xml + src/conf/checkstyle-suppressions.xml + true + **/generated/**.java,**/jmh_generated/**.java + + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${commons.spotbugs.plugin.version} + + + com.github.spotbugs + spotbugs + ${commons.spotbugs.impl.version} + + + + src/conf/spotbugs-exclude-filter.xml + + + + maven-assembly-plugin + + + src/assembly/bin.xml + src/assembly/src.xml + + gnu + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + + ${commons.module.name} + + + + + + org.apache.maven.plugins + maven-scm-publish-plugin + + + javadocs + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + ${maven.compiler.source} + + + + + + + + + maven-checkstyle-plugin + ${checkstyle.plugin.version} + + false + src/conf/checkstyle.xml + src/conf/checkstyle-header.txt + src/conf/checkstyle-suppressions.xml + src/conf/checkstyle-suppressions.xml + true + **/generated/**.java,**/jmh_generated/**.java + + + + + checkstyle + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${commons.spotbugs.plugin.version} + + src/conf/spotbugs-exclude-filter.xml + + + + com.github.siom79.japicmp + japicmp-maven-plugin + + + maven-pmd-plugin + 3.19.0 + + ${maven.compiler.target} + + + + + pmd + cpd + + + + + + org.codehaus.mojo + taglist-maven-plugin + 3.0.0 + + + + + Needs Work + + + TODO + exact + + + FIXME + exact + + + XXX + exact + + + + + Noteable Markers + + + NOTE + exact + + + NOPMD + exact + + + NOSONAR + exact + + + + + + + + + + + 2014 + + + + kinow + Bruno P. Kinoshita + kinow@apache.org + + + britter + Benedikt Ritter + britter@apache.org + + + chtompki + Rob Tompkins + chtompki@apache.org + + + ggregory + Gary Gregory + ggregory at apache.org + https://www.garygregory.com + The Apache Software Foundation + https://www.apache.org/ + + PMC Member + + America/New_York + + https://people.apache.org/~ggregory/img/garydgregory80.png + + + + djones + Duncan Jones + djones@apache.org + + + + + + Don Jeba + donjeba@yahoo.com + + + Sampanna Kahu + + + Jarek Strzelecki + + + Lee Adcock + + + Amey Jadiye + ameyjadiye@gmail.com + + + Arun Vinud S S + + + Ioannis Sermetziadis + + + Jostein Tveit + + + Luciano Medallia + + + Jan Martin Keil + + + Nandor Kollar + + + Nick Wong + + + Ali Ghanbari + https://ali-ghanbari.github.io/ + + + + + scm:git:https://gitbox.apache.org/repos/asf/commons-text + scm:git:https://gitbox.apache.org/repos/asf/commons-text + https://gitbox.apache.org/repos/asf?p=commons-text.git + + + + jira + https://issues.apache.org/jira/browse/TEXT + + + + + apache.website + Apache Commons Site + scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-text/ + + + + + + setup-checkout + + + site-content + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + prepare-checkout + + run + + pre-site + + + + + + + + + + + + + + + + + + + + + + + + java9+ + + [9,) + + + + true + + + + benchmark + + true + org.apache + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + benchmark + test + + exec + + + test + java + + -classpath + + org.openjdk.jmh.Main + -rf + json + -rff + target/jmh-result.${benchmark}.json + ${benchmark} + + + + + + + + + + \ No newline at end of file