diff --git a/syft/pkg/cataloger/cpe.go b/syft/pkg/cataloger/cpe.go index 52fb74c79..a972cf535 100644 --- a/syft/pkg/cataloger/cpe.go +++ b/syft/pkg/cataloger/cpe.go @@ -13,6 +13,47 @@ import ( // this is functionally equivalent to "*" and consistent with no input given (thus easier to test) const any = "" +// this is a static mapping of known package names (keys) to official cpe names for each package +type candidateStore map[pkg.Type]map[string][]string + +var productCandidatesByPkgType = candidateStore{ + pkg.JavaPkg: { + "springframework": []string{"spring_framework", "springsource_spring_framework"}, + "spring-core": []string{"spring_framework", "springsource_spring_framework"}, + }, + pkg.NpmPkg: { + "hapi": []string{"hapi_server_framework"}, + "handlebars.js": []string{"handlebars"}, + "is-my-json-valid": []string{"is_my_json_valid"}, + "mustache": []string{"mustache.js"}, + }, + pkg.GemPkg: { + "Arabic-Prawn": []string{"arabic_prawn"}, + "bio-basespace-sdk": []string{"basespace_ruby_sdk"}, + "cremefraiche": []string{"creme_fraiche"}, + "html-sanitizer": []string{"html_sanitizer"}, + "sentry-raven": []string{"raven-ruby"}, + "RedCloth": []string{"redcloth_library"}, + "VladTheEnterprising": []string{"vladtheenterprising"}, + "yajl-ruby": []string{"yajl-ruby_gem"}, + }, + pkg.PythonPkg: { + "python-rrdtool": []string{"rrdtool"}, + }, +} + +func (s candidateStore) getCandidates(t pkg.Type, key string) []string { + if _, ok := s[t]; !ok { + return nil + } + value, ok := s[t][key] + if !ok { + return nil + } + + return value +} + func newCPE(product, vendor, version, targetSW string) wfn.Attributes { cpe := *(wfn.NewAttributesWithAny()) cpe.Part = "a" @@ -98,8 +139,8 @@ func candidateVendors(p pkg.Package) []string { func candidateProducts(p pkg.Package) []string { var products = []string{p.Name} - switch p.Language { - case pkg.Java: + + if p.Language == pkg.Java { if p.MetadataType == pkg.JavaMetadataType { if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil { // derive the product from the groupID (e.g. org.sonatype.nexus --> nexus) @@ -111,8 +152,8 @@ func candidateProducts(p pkg.Package) []string { } } } - default: - return products } - return products + + // return any known product name swaps prepended to the results + return append(productCandidatesByPkgType.getCandidates(p.Type, p.Name), products...) } diff --git a/syft/pkg/cataloger/cpe_test.go b/syft/pkg/cataloger/cpe_test.go index 2fad0ea99..e5a0be66c 100644 --- a/syft/pkg/cataloger/cpe_test.go +++ b/syft/pkg/cataloger/cpe_test.go @@ -1,14 +1,14 @@ package cataloger import ( + "fmt" "sort" "testing" - "github.com/scylladb/go-set/strset" - - "github.com/scylladb/go-set" - "github.com/anchore/syft/syft/pkg" + "github.com/scylladb/go-set" + "github.com/scylladb/go-set/strset" + "github.com/stretchr/testify/assert" ) func TestGenerate(t *testing.T) { @@ -180,3 +180,45 @@ func TestGenerate(t *testing.T) { }) } } + +func TestCandidateProducts(t *testing.T) { + tests := []struct { + p pkg.Package + expected []string + }{ + { + p: pkg.Package{ + Name: "springframework", + Type: pkg.JavaPkg, + }, + expected: []string{"spring_framework", "springsource_spring_framework" /* <-- known good names | default guess --> */, "springframework"}, + }, + { + p: pkg.Package{ + Name: "handlebars.js", + Type: pkg.NpmPkg, + }, + expected: []string{"handlebars" /* <-- known good names | default guess --> */, "handlebars.js"}, + }, + { + p: pkg.Package{ + Name: "RedCloth", + Type: pkg.GemPkg, + }, + expected: []string{"redcloth_library" /* <-- known good names | default guess --> */, "RedCloth"}, + }, + { + p: pkg.Package{ + Name: "python-rrdtool", + Type: pkg.PythonPkg, + }, + expected: []string{"rrdtool" /* <-- known good names | default guess --> */, "python-rrdtool"}, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%+v %+v", test.p, test.expected), func(t *testing.T) { + assert.Equal(t, test.expected, candidateProducts(test.p)) + }) + } +}