diff --git a/syft/cpe/by_specificity.go b/syft/cpe/by_specificity.go index ad1a9adb8..2ab457a68 100644 --- a/syft/cpe/by_specificity.go +++ b/syft/cpe/by_specificity.go @@ -2,13 +2,11 @@ package cpe import ( "sort" - - "github.com/facebookincubator/nvdtools/wfn" ) var _ sort.Interface = (*BySpecificity)(nil) -type BySpecificity []wfn.Attributes +type BySpecificity []CPE func (c BySpecificity) Len() int { return len(c) } @@ -35,17 +33,17 @@ func (c BySpecificity) Less(i, j int) bool { return c[i].BindToFmtString() < c[j].BindToFmtString() } -func countFieldLength(cpe wfn.Attributes) int { +func countFieldLength(cpe CPE) int { return len(cpe.Part + cpe.Vendor + cpe.Product + cpe.Version + cpe.TargetSW) } -func weightedCountForSpecifiedFields(cpe wfn.Attributes) int { - checksForSpecifiedField := []func(cpe wfn.Attributes) (bool, int){ - func(cpe wfn.Attributes) (bool, int) { return cpe.Part != "", 2 }, - func(cpe wfn.Attributes) (bool, int) { return cpe.Vendor != "", 3 }, - func(cpe wfn.Attributes) (bool, int) { return cpe.Product != "", 4 }, - func(cpe wfn.Attributes) (bool, int) { return cpe.Version != "", 1 }, - func(cpe wfn.Attributes) (bool, int) { return cpe.TargetSW != "", 1 }, +func weightedCountForSpecifiedFields(cpe CPE) int { + checksForSpecifiedField := []func(cpe CPE) (bool, int){ + func(cpe CPE) (bool, int) { return cpe.Part != "", 2 }, + func(cpe CPE) (bool, int) { return cpe.Vendor != "", 3 }, + func(cpe CPE) (bool, int) { return cpe.Product != "", 4 }, + func(cpe CPE) (bool, int) { return cpe.Version != "", 1 }, + func(cpe CPE) (bool, int) { return cpe.TargetSW != "", 1 }, } weightedCount := 0 diff --git a/syft/cpe/cpe.go b/syft/cpe/cpe.go index 410b802d7..293d9efe1 100644 --- a/syft/cpe/cpe.go +++ b/syft/cpe/cpe.go @@ -8,7 +8,35 @@ import ( "github.com/facebookincubator/nvdtools/wfn" ) -type CPE = wfn.Attributes +type CPE struct { + Part string + Vendor string + Product string + Version string + Update string + Edition string + SWEdition string + TargetSW string + TargetHW string + Other string + Language string +} + +func (c CPE) asAttributes() wfn.Attributes { + return wfn.Attributes(c) +} + +func fromAttributes(a wfn.Attributes) CPE { + return CPE(a) +} + +func (c CPE) BindToFmtString() string { + return c.asAttributes().BindToFmtString() +} + +func NewWithAny() CPE { + return fromAttributes(*(wfn.NewAttributesWithAny())) +} const ( allowedCPEPunctuation = "-!\"#$%&'()+,./:;<=>@[]^`{|}~" @@ -34,7 +62,7 @@ func New(cpeStr string) (CPE, error) { } // ensure that this CPE can be validated after being fully sanitized - if ValidateString(String(c)) != nil { + if ValidateString(c.String()) != nil { return CPE{}, err } @@ -71,20 +99,22 @@ func newWithoutValidation(cpeStr string) (CPE, error) { return CPE{}, fmt.Errorf("failed to parse CPE=%q", cpeStr) } - // we need to compare the raw data since we are constructing CPEs in other locations - value.Vendor = normalizeField(value.Vendor) - value.Product = normalizeField(value.Product) - value.Language = normalizeField(value.Language) - value.Version = normalizeField(value.Version) - value.TargetSW = normalizeField(value.TargetSW) - value.Part = normalizeField(value.Part) - value.Edition = normalizeField(value.Edition) - value.Other = normalizeField(value.Other) - value.SWEdition = normalizeField(value.SWEdition) - value.TargetHW = normalizeField(value.TargetHW) - value.Update = normalizeField(value.Update) + syftCPE := fromAttributes(*value) - return *value, nil + // we need to compare the raw data since we are constructing CPEs in other locations + syftCPE.Vendor = normalizeField(syftCPE.Vendor) + syftCPE.Product = normalizeField(syftCPE.Product) + syftCPE.Language = normalizeField(syftCPE.Language) + syftCPE.Version = normalizeField(syftCPE.Version) + syftCPE.TargetSW = normalizeField(syftCPE.TargetSW) + syftCPE.Part = normalizeField(syftCPE.Part) + syftCPE.Edition = normalizeField(syftCPE.Edition) + syftCPE.Other = normalizeField(syftCPE.Other) + syftCPE.SWEdition = normalizeField(syftCPE.SWEdition) + syftCPE.TargetHW = normalizeField(syftCPE.TargetHW) + syftCPE.Update = normalizeField(syftCPE.Update) + + return syftCPE, nil } func normalizeField(field string) string { @@ -112,7 +142,7 @@ func stripSlashes(s string) string { return sb.String() } -func String(c CPE) string { +func (c CPE) String() string { output := CPE{} output.Vendor = sanitize(c.Vendor) output.Product = sanitize(c.Product) diff --git a/syft/cpe/cpe_test.go b/syft/cpe/cpe_test.go index 9af8f2105..3d33e87e5 100644 --- a/syft/cpe/cpe_test.go +++ b/syft/cpe/cpe_test.go @@ -3,6 +3,7 @@ package cpe import ( "encoding/json" "fmt" + "github.com/google/go-cmp/cmp" "os" "strings" "testing" @@ -41,8 +42,8 @@ func Test_New(t *testing.T) { t.Fatalf("got an error while creating CPE: %+v", err) } - if String(actual) != String(test.expected) { - t.Errorf("mismatched entries:\n\texpected:%+v\n\t actual:%+v\n", String(test.expected), String(actual)) + if d := cmp.Diff(actual, test.expected); d != "" { + t.Errorf("CPE mismatch (-want +got):\n%s", d) } }) @@ -98,7 +99,7 @@ func Test_CPEParser(t *testing.T) { assert.Equal(t, c1, c2) assert.Equal(t, c1, test.WFN) assert.Equal(t, c2, test.WFN) - assert.Equal(t, String(test.WFN), test.CPEString) + assert.Equal(t, test.WFN.String(), test.CPEString) }) } } @@ -164,12 +165,12 @@ func Test_InvalidCPE(t *testing.T) { if test.expectedErr { assert.Error(t, err) if t.Failed() { - t.Logf("got CPE: %q details: %+v", String(c), c) + t.Logf("got CPE: %q details: %+v", c, c) } return } require.NoError(t, err) - assert.Equal(t, test.expected, String(c)) + assert.Equal(t, test.expected, c.String()) }) } } @@ -215,13 +216,13 @@ func Test_RoundTrip(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { // CPE string must be preserved through a round trip - assert.Equal(t, test.cpe, String(Must(test.cpe))) + assert.Equal(t, test.cpe, Must(test.cpe).String()) // The parsed CPE must be the same after a round trip - assert.Equal(t, Must(test.cpe), Must(String(Must(test.cpe)))) + assert.Equal(t, Must(test.cpe), Must(Must(test.cpe).String())) // The test case parsed CPE must be the same after parsing the input string assert.Equal(t, test.parsedCPE, Must(test.cpe)) // The test case parsed CPE must produce the same string as the input cpe - assert.Equal(t, String(test.parsedCPE), test.cpe) + assert.Equal(t, test.parsedCPE.String(), test.cpe) }) } } diff --git a/syft/format/common/cyclonedxhelpers/cpe.go b/syft/format/common/cyclonedxhelpers/cpe.go index 89813e8aa..cbe19f6b6 100644 --- a/syft/format/common/cyclonedxhelpers/cpe.go +++ b/syft/format/common/cyclonedxhelpers/cpe.go @@ -12,7 +12,7 @@ func encodeSingleCPE(p pkg.Package) string { // Since the CPEs in a package are sorted by specificity // we can extract the first CPE as the one to output in cyclonedx if len(p.CPEs) > 0 { - return cpe.String(p.CPEs[0]) + return p.CPEs[0].String() } return "" } @@ -25,7 +25,7 @@ func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) { } out = append(out, cyclonedx.Property{ Name: "syft:cpe23", - Value: cpe.String(c), + Value: c.String(), }) } return diff --git a/syft/format/common/cyclonedxhelpers/format.go b/syft/format/common/cyclonedxhelpers/format.go index 548a65c24..ab942e571 100644 --- a/syft/format/common/cyclonedxhelpers/format.go +++ b/syft/format/common/cyclonedxhelpers/format.go @@ -107,7 +107,7 @@ func formatCPE(cpeString string) string { log.Debugf("skipping invalid CPE: %s", cpeString) return "" } - return cpe.String(c) + return c.String() } // NewBomDescriptor returns a new BomDescriptor tailored for the current time and "syft" tool details. diff --git a/syft/format/common/spdxhelpers/external_refs.go b/syft/format/common/spdxhelpers/external_refs.go index 031726210..fa3fbc7b5 100644 --- a/syft/format/common/spdxhelpers/external_refs.go +++ b/syft/format/common/spdxhelpers/external_refs.go @@ -1,7 +1,6 @@ package spdxhelpers import ( - "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/pkg" ) @@ -11,7 +10,7 @@ func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) { for _, c := range p.CPEs { externalRefs = append(externalRefs, ExternalRef{ ReferenceCategory: SecurityReferenceCategory, - ReferenceLocator: cpe.String(c), + ReferenceLocator: c.String(), ReferenceType: Cpe23ExternalRefType, }) } diff --git a/syft/format/common/spdxhelpers/external_refs_test.go b/syft/format/common/spdxhelpers/external_refs_test.go index 08c4ab5c4..68a950e84 100644 --- a/syft/format/common/spdxhelpers/external_refs_test.go +++ b/syft/format/common/spdxhelpers/external_refs_test.go @@ -27,7 +27,7 @@ func Test_ExternalRefs(t *testing.T) { expected: []ExternalRef{ { ReferenceCategory: SecurityReferenceCategory, - ReferenceLocator: cpe.String(testCPE), + ReferenceLocator: testCPE.String(), ReferenceType: Cpe23ExternalRefType, }, { diff --git a/syft/format/syftjson/to_format_model.go b/syft/format/syftjson/to_format_model.go index a05b17bfa..017d997bd 100644 --- a/syft/format/syftjson/to_format_model.go +++ b/syft/format/syftjson/to_format_model.go @@ -9,7 +9,6 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/format/syftjson/model" "github.com/anchore/syft/syft/internal/packagemetadata" @@ -232,7 +231,7 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) { func toPackageModel(p pkg.Package, cfg EncoderConfig) model.Package { var cpes = make([]string, len(p.CPEs)) for i, c := range p.CPEs { - cpes[i] = cpe.String(c) + cpes[i] = c.String() } // we want to make sure all catalogers are diff --git a/syft/pkg/cataloger/binary/classifier_test.go b/syft/pkg/cataloger/binary/classifier_test.go index 43397189d..eb37dd2c8 100644 --- a/syft/pkg/cataloger/binary/classifier_test.go +++ b/syft/pkg/cataloger/binary/classifier_test.go @@ -79,7 +79,7 @@ func Test_ClassifierCPEs(t *testing.T) { var cpes []string for _, c := range p.CPEs { - cpes = append(cpes, cpe.String(c)) + cpes = append(cpes, c.String()) } require.Equal(t, test.cpes, cpes) }) diff --git a/syft/pkg/cataloger/common/cpe/filter.go b/syft/pkg/cataloger/common/cpe/filter.go index 5f252a7d4..cedf3e2dd 100644 --- a/syft/pkg/cataloger/common/cpe/filter.go +++ b/syft/pkg/cataloger/common/cpe/filter.go @@ -36,7 +36,7 @@ cpeLoop: } func disallowNonParseableCPEs(c cpe.CPE, _ pkg.Package) bool { - v := cpe.String(c) + v := c.String() _, err := cpe.New(v) cannotParse := err != nil diff --git a/syft/pkg/cataloger/common/cpe/generate.go b/syft/pkg/cataloger/common/cpe/generate.go index 2077d7e3b..1ff7c0d94 100644 --- a/syft/pkg/cataloger/common/cpe/generate.go +++ b/syft/pkg/cataloger/common/cpe/generate.go @@ -23,14 +23,14 @@ import ( // the CPE database, so they will be preferred over other candidates: var knownVendors = strset.New("apache") -func newCPE(product, vendor, version, targetSW string) *wfn.Attributes { - c := *(wfn.NewAttributesWithAny()) +func newCPE(product, vendor, version, targetSW string) *cpe.CPE { + c := cpe.NewWithAny() c.Part = "a" c.Product = product c.Vendor = vendor c.Version = version c.TargetSW = targetSW - if cpe.ValidateString(cpe.String(c)) != nil { + if cpe.ValidateString(c.String()) != nil { return nil } return &c diff --git a/syft/pkg/cataloger/common/cpe/generate_test.go b/syft/pkg/cataloger/common/cpe/generate_test.go index ed082257b..d039f633e 100644 --- a/syft/pkg/cataloger/common/cpe/generate_test.go +++ b/syft/pkg/cataloger/common/cpe/generate_test.go @@ -10,7 +10,6 @@ import ( "github.com/scylladb/go-set/strset" "github.com/stretchr/testify/assert" - "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/pkg" ) @@ -717,7 +716,7 @@ func TestGeneratePackageCPEs(t *testing.T) { expectedCpeSet := set.NewStringSet(test.expected...) actualCpeSet := set.NewStringSet() for _, a := range actual { - actualCpeSet.Add(cpe.String(a)) + actualCpeSet.Add(a.String()) } extra := strset.Difference(actualCpeSet, expectedCpeSet).List() diff --git a/syft/pkg/cataloger/golang/cataloger_test.go b/syft/pkg/cataloger/golang/cataloger_test.go index 2ba71854b..067efd11d 100644 --- a/syft/pkg/cataloger/golang/cataloger_test.go +++ b/syft/pkg/cataloger/golang/cataloger_test.go @@ -5,7 +5,6 @@ import ( "github.com/stretchr/testify/assert" - "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" ) @@ -82,7 +81,7 @@ func Test_Binary_Cataloger_Stdlib_Cpe(t *testing.T) { t.Run(tc.name, func(t *testing.T) { got, err := generateStdlibCpe(tc.candidate) assert.NoError(t, err, "expected no err; got %v", err) - assert.Equal(t, cpe.String(got), tc.want) + assert.Equal(t, got.String(), tc.want) }) } }