Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: does not include dependency management in pomxml extractor #515

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 20 additions & 117 deletions extractor/filesystem/language/java/pomxml/pomxml.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import (
"regexp"
"strings"

"deps.dev/util/maven"
"golang.org/x/exp/maps"

"github.com/google/osv-scalibr/extractor"
"github.com/google/osv-scalibr/extractor/filesystem"
"github.com/google/osv-scalibr/extractor/filesystem/language/java/javalockfile"
"github.com/google/osv-scalibr/log"
"github.com/google/osv-scalibr/plugin"
"github.com/google/osv-scalibr/purl"
)
Expand All @@ -41,21 +41,10 @@ const (
// "Constant" at the top to compile this regex only once.
var (
versionRequirementReg = regexp.MustCompile(`[[(]?(.*?)(?:,|[)\]]|$)`)
interpolationReg = regexp.MustCompile(`\${(.+)}`)
)

type mavenLockDependency struct {
XMLName xml.Name `xml:"dependency"`
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
Version string `xml:"version"`
Scope string `xml:"scope"`
Type string `xml:"type"`
Classifier string `xml:"classifier"`
}

func (mld mavenLockDependency) parseResolvedVersion(version string) string {
results := versionRequirementReg.FindStringSubmatch(version)
func parseResolvedVersion(version maven.String) string {
results := versionRequirementReg.FindStringSubmatch(string(version))
// First capture group will always exist, but might be empty, therefore the slice will always
// have a length of 2.
if results == nil || results[1] == "" {
Expand All @@ -65,75 +54,6 @@ func (mld mavenLockDependency) parseResolvedVersion(version string) string {
return results[1]
}

func (mld mavenLockDependency) resolveVersionValue(lockfile mavenLockFile) string {
// results will always either be nil or have a length of 2
results := interpolationReg.FindStringSubmatch(mld.Version)

// no interpolation, so just return the version as-is
if results == nil {
return mld.Version
}
if val, ok := lockfile.Properties.m[results[1]]; ok {
return val
}

log.Errorf(
"Failed to resolve version of %s: property \"%s\" could not be found for \"%s\"\n",
mld.GroupID+":"+mld.ArtifactID,
results[1],
lockfile.GroupID+":"+lockfile.ArtifactID,
)

return "0"
}

func (mld mavenLockDependency) ResolveVersion(lockfile mavenLockFile) string {
version := mld.resolveVersionValue(lockfile)

return mld.parseResolvedVersion(version)
}

type mavenLockFile struct {
XMLName xml.Name `xml:"project"`
ModelVersion string `xml:"modelVersion"`
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
Properties mavenLockProperties `xml:"properties"`
Dependencies []mavenLockDependency `xml:"dependencies>dependency"`
ManagedDependencies []mavenLockDependency `xml:"dependencyManagement>dependencies>dependency"`
}

type mavenLockProperties struct {
m map[string]string
}

func (p *mavenLockProperties) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
p.m = map[string]string{}

for {
t, err := d.Token()
if err != nil {
return err
}

switch tt := t.(type) {
case xml.StartElement:
var s string

if err := d.DecodeElement(&s, &tt); err != nil {
return fmt.Errorf("%w", err)
}

p.m[tt.Name.Local] = s

case xml.EndElement:
if tt.Name == start.Name {
return nil
}
}
}
}

// Extractor extracts Maven packages from pom.xml files.
type Extractor struct{}

Expand All @@ -158,58 +78,41 @@ func (e Extractor) FileRequired(api filesystem.FileAPI) bool {

// Extract extracts packages from pom.xml files passed through the scan input.
func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) ([]*extractor.Inventory, error) {
var parsedLockfile *mavenLockFile

err := xml.NewDecoder(input.Reader).Decode(&parsedLockfile)
var project *maven.Project

err := xml.NewDecoder(input.Reader).Decode(&project)
if err != nil {
return nil, fmt.Errorf("could not extract from %s: %w", input.Path, err)
}
if err := project.Interpolate(); err != nil {
return nil, fmt.Errorf("failed to interpolate pom.xml %s: %w", input.Path, err)
}

details := map[string]*extractor.Inventory{}

for _, lockPackage := range parsedLockfile.ManagedDependencies {
finalName := lockPackage.GroupID + ":" + lockPackage.ArtifactID
metadata := javalockfile.Metadata{
ArtifactID: lockPackage.ArtifactID,
GroupID: lockPackage.GroupID,
DepGroupVals: []string{},
}

pkgDetails := &extractor.Inventory{
Name: finalName,
Version: lockPackage.ResolveVersion(*parsedLockfile),
Locations: []string{input.Path},
Metadata: &metadata,
for _, dep := range project.Dependencies {
g, a, found := strings.Cut(dep.Name(), ":")
if !found {
return nil, fmt.Errorf("invalid package name: %s", dep.Name())
}
if scope := strings.TrimSpace(lockPackage.Scope); scope != "" && scope != "compile" {
// Only append non-default scope (compile is the default scope).
metadata.DepGroupVals = []string{scope}
}
details[finalName] = pkgDetails
}

// standard dependencies take precedent over managed dependencies
for _, lockPackage := range parsedLockfile.Dependencies {
finalName := lockPackage.GroupID + ":" + lockPackage.ArtifactID
metadata := javalockfile.Metadata{
ArtifactID: lockPackage.ArtifactID,
GroupID: lockPackage.GroupID,
Type: lockPackage.Type,
Classifier: lockPackage.Classifier,
ArtifactID: a,
GroupID: g,
Type: string(dep.Type),
Classifier: string(dep.Classifier),
DepGroupVals: []string{},
}
pkgDetails := &extractor.Inventory{
Name: finalName,
Version: lockPackage.ResolveVersion(*parsedLockfile),
Name: dep.Name(),
Version: parseResolvedVersion(dep.Version),
Locations: []string{input.Path},
Metadata: &metadata,
}
if scope := strings.TrimSpace(lockPackage.Scope); scope != "" && scope != "compile" {
if scope := strings.TrimSpace(string(dep.Scope)); scope != "" && scope != "compile" {
// Only append non-default scope (compile is the default scope).
metadata.DepGroupVals = []string{scope}
}
details[finalName] = pkgDetails
details[dep.Name()] = pkgDetails
}

return maps.Values(details), nil
Expand Down
10 changes: 0 additions & 10 deletions extractor/filesystem/language/java/pomxml/pomxml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,6 @@ func TestExtractor_Extract(t *testing.T) {
DepGroupVals: []string{},
},
},
{
Name: "com.google.code.findbugs:jsr305",
Version: "3.0.2",
Locations: []string{"testdata/with-dependency-management.xml"},
Metadata: &javalockfile.Metadata{
ArtifactID: "jsr305",
GroupID: "com.google.code.findbugs",
DepGroupVals: []string{},
},
},
},
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,11 @@
<artifactId>my.package</artifactId>
<version>${my.package.version}</version>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.mine</groupId>
<artifactId>ranged-package</artifactId>
<version>${version-range}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.mine</groupId>
<artifactId>ranged-package</artifactId>
<version>${version-range}</version>
</dependency>
</dependencies>
</project>