Skip to content

Commit

Permalink
fix: Resolve Maven POM expressions (anchore#1251) (anchore#1278)
Browse files Browse the repository at this point in the history
  • Loading branch information
chtompki authored Oct 27, 2022
1 parent d47f0de commit 0fd3258
Show file tree
Hide file tree
Showing 3 changed files with 811 additions and 11 deletions.
63 changes: 53 additions & 10 deletions syft/pkg/cataloger/java/parse_pom_xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/xml"
"fmt"
"io"
"reflect"
"regexp"
"strings"

"github.com/vifraa/gopom"
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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),
},
},
}
Expand All @@ -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
Expand All @@ -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
})
}
184 changes: 183 additions & 1 deletion syft/pkg/cataloger/java/parse_pom_xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]",
},
},
{
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/[email protected]",
},
},
{
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/[email protected]",
},
},
{
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/[email protected]",
},
},
{
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/[email protected]",
},
},
{
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/[email protected]",
},
},
{
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/[email protected]",
},
},
{
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/[email protected]",
},
},
{
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/[email protected]",
},
},
},
},
}

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
Expand Down Expand Up @@ -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))
})
}
}
Expand All @@ -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)
})
}
}
Loading

0 comments on commit 0fd3258

Please sign in to comment.