mirror of
https://github.com/anchore/syft.git
synced 2026-11-03 07:56:36 +01:00
build source PURL from metadata and gate it to CycloneDX 1.6+
- the source PURL is built from package metadata instead of editing the binary PURL: only `distro` (plus `epoch` and `rpmmod` for rpm) is carried over, subpath is dropped, and the ref is skipped when the PURL type does not match the metadata ecosystem - one emission rule for all ecosystems: only when the source name or version differs from the binary, so self-sourced rpm and alpm packages no longer get a redundant ref - source RPM names must end in `.src.rpm` or `.nosrc.rpm`, and `nosrc` maps to `arch=nosrc` - `source-distribution` refs are dropped when encoding below spec 1.6, since cyclonedx-go does not downgrade them (CycloneDX/cyclonedx-go#291) Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -128,6 +128,31 @@ func TestCycloneDxSourcePackageEncoder(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCycloneDxSourcePackageEncoder_SpecVersions(t *testing.T) {
|
||||||
|
s := testutil.SourcePackagesInput(t, t.TempDir())
|
||||||
|
tests := []struct {
|
||||||
|
version string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{version: "1.4", want: false},
|
||||||
|
{version: "1.5", want: false},
|
||||||
|
{version: "1.6", want: true},
|
||||||
|
{version: cyclonedxutil.DefaultVersion, want: true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.version, func(t *testing.T) {
|
||||||
|
enc, err := NewFormatEncoderWithConfig(EncoderConfig{Version: tt.version})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
require.NoError(t, enc.Encode(&buf, s))
|
||||||
|
|
||||||
|
// the source-distribution reference type only exists from spec 1.6
|
||||||
|
assert.Equal(t, tt.want, strings.Contains(buf.String(), `"source-distribution"`))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func redactor(values ...string) testutil.Redactor {
|
func redactor(values ...string) testutil.Redactor {
|
||||||
return testutil.NewRedactions().
|
return testutil.NewRedactions().
|
||||||
WithValuesRedacted(values...).
|
WithValuesRedacted(values...).
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cyclonedxutil
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/CycloneDX/cyclonedx-go"
|
"github.com/CycloneDX/cyclonedx-go"
|
||||||
|
|
||||||
@@ -29,9 +30,35 @@ func NewEncoder(version string, format cyclonedx.BOMFileFormat, pretty bool) (En
|
|||||||
|
|
||||||
func (e Encoder) Encode(writer io.Writer, s sbom.SBOM) error {
|
func (e Encoder) Encode(writer io.Writer, s sbom.SBOM) error {
|
||||||
bom := cyclonedxhelpers.ToFormatModel(s)
|
bom := cyclonedxhelpers.ToFormatModel(s)
|
||||||
|
if e.version < cyclonedx.SpecVersion1_6 {
|
||||||
|
// cyclonedx-go does not downgrade this 1.6+ reference type for older spec versions
|
||||||
|
// (see https://github.com/CycloneDX/cyclonedx-go/issues/291), so drop it rather than emit an invalid document
|
||||||
|
dropExternalReferenceType(bom.Components, cyclonedx.ERTypeSourceDistribution)
|
||||||
|
}
|
||||||
enc := cyclonedx.NewBOMEncoder(writer, e.format)
|
enc := cyclonedx.NewBOMEncoder(writer, e.format)
|
||||||
enc.SetPretty(e.pretty)
|
enc.SetPretty(e.pretty)
|
||||||
enc.SetEscapeHTML(false)
|
enc.SetEscapeHTML(false)
|
||||||
|
|
||||||
return enc.EncodeVersion(bom, e.version)
|
return enc.EncodeVersion(bom, e.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dropExternalReferenceType(components *[]cyclonedx.Component, refType cyclonedx.ExternalReferenceType) {
|
||||||
|
if components == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range *components {
|
||||||
|
c := &(*components)[i]
|
||||||
|
dropExternalReferenceType(c.Components, refType)
|
||||||
|
if c.ExternalReferences == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
refs := slices.DeleteFunc(*c.ExternalReferences, func(r cyclonedx.ExternalReference) bool {
|
||||||
|
return r.Type == refType
|
||||||
|
})
|
||||||
|
if len(refs) == 0 {
|
||||||
|
c.ExternalReferences = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.ExternalReferences = &refs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,14 +47,6 @@ func EncodeComponent(p pkg.Package, supplier string, locationSorter func(a, b fi
|
|||||||
componentType = cyclonedx.ComponentTypeMachineLearningModel
|
componentType = cyclonedx.ComponentTypeMachineLearningModel
|
||||||
}
|
}
|
||||||
|
|
||||||
externalRefs := encodeExternalReferences(p)
|
|
||||||
if srcRef := encodeSourcePackageExternalReference(p); srcRef != nil {
|
|
||||||
if externalRefs == nil {
|
|
||||||
externalRefs = &[]cyclonedx.ExternalReference{}
|
|
||||||
}
|
|
||||||
*externalRefs = append(*externalRefs, *srcRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cyclonedx.Component{
|
return cyclonedx.Component{
|
||||||
Type: componentType,
|
Type: componentType,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
@@ -67,7 +59,7 @@ func EncodeComponent(p pkg.Package, supplier string, locationSorter func(a, b fi
|
|||||||
Author: encodeAuthor(p),
|
Author: encodeAuthor(p),
|
||||||
Publisher: encodePublisher(p),
|
Publisher: encodePublisher(p),
|
||||||
Description: encodeDescription(p),
|
Description: encodeDescription(p),
|
||||||
ExternalReferences: externalRefs,
|
ExternalReferences: encodeExternalReferences(p),
|
||||||
Properties: properties,
|
Properties: properties,
|
||||||
BOMRef: DeriveBomRef(p),
|
BOMRef: DeriveBomRef(p),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,42 +80,51 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if srcRef := encodeSourcePackageExternalReference(p); srcRef != nil {
|
||||||
|
refs = append(refs, *srcRef)
|
||||||
|
}
|
||||||
if len(refs) > 0 {
|
if len(refs) > 0 {
|
||||||
return &refs
|
return &refs
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encodeSourcePackageExternalReference returns a "source-distribution" reference holding the PURL of the source
|
||||||
|
// package that an OS package was built from. It is only emitted when the source differs from the binary package
|
||||||
|
// (by name or version) and when the package PURL is of the type implied by the metadata.
|
||||||
func encodeSourcePackageExternalReference(p pkg.Package) *cyclonedx.ExternalReference {
|
func encodeSourcePackageExternalReference(p pkg.Package) *cyclonedx.ExternalReference {
|
||||||
name, version, arch := sourceCoordinates(p)
|
|
||||||
if name == "" || version == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if p.PURL == "" {
|
if p.PURL == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
src := sourcePackageOf(p)
|
||||||
pkgPurl, err := packageurl.FromString(p.PURL)
|
if src == nil {
|
||||||
if err != nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
qualifiers := pkgPurl.Qualifiers.Map()
|
// the binary PURL is only consulted for the namespace and a small set of qualifiers (e.g. distro), everything
|
||||||
delete(qualifiers, pkg.PURLQualifierUpstream)
|
// else describes the binary package and must not be restated as a fact about the source package.
|
||||||
delete(qualifiers, pkg.PURLQualifierArch)
|
binPurl, err := packageurl.FromString(p.PURL)
|
||||||
|
if err != nil || binPurl.Type != src.purlType {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if arch != "" {
|
qualifiers := map[string]string{}
|
||||||
qualifiers[pkg.PURLQualifierArch] = arch
|
for _, q := range binPurl.Qualifiers {
|
||||||
|
if src.keepQualifier(q.Key) {
|
||||||
|
qualifiers[q.Key] = q.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if src.arch != "" {
|
||||||
|
qualifiers[pkg.PURLQualifierArch] = src.arch
|
||||||
}
|
}
|
||||||
|
|
||||||
srcPurl := packageurl.NewPackageURL(
|
srcPurl := packageurl.NewPackageURL(
|
||||||
pkgPurl.Type,
|
src.purlType,
|
||||||
pkgPurl.Namespace,
|
binPurl.Namespace,
|
||||||
name,
|
src.name,
|
||||||
version,
|
src.version,
|
||||||
packageurl.QualifiersFromMap(qualifiers),
|
packageurl.QualifiersFromMap(qualifiers),
|
||||||
pkgPurl.Subpath,
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
return &cyclonedx.ExternalReference{
|
return &cyclonedx.ExternalReference{
|
||||||
@@ -124,51 +133,91 @@ func encodeSourcePackageExternalReference(p pkg.Package) *cyclonedx.ExternalRefe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sourceCoordinates(p pkg.Package) (name, version, arch string) {
|
// sourcePackage is the identity of the source package a binary OS package was built from.
|
||||||
switch metadata := p.Metadata.(type) {
|
type sourcePackage struct {
|
||||||
|
purlType string
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
// arch is the ecosystem's source architecture marker, empty when the ecosystem has no such convention
|
||||||
|
arch string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sourcePackage) keepQualifier(key string) bool {
|
||||||
|
switch key {
|
||||||
|
case pkg.PURLQualifierDistro:
|
||||||
|
return true
|
||||||
|
case pkg.PURLQualifierEpoch, pkg.PURLQualifierRpmModularity:
|
||||||
|
return s.purlType == pkg.RpmPkg.PackageURLType()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourcePackageOf returns the source package for p, or nil when there is none or it is the same as the binary.
|
||||||
|
func sourcePackageOf(p pkg.Package) *sourcePackage {
|
||||||
|
var src sourcePackage
|
||||||
|
var binName, binVersion string
|
||||||
|
switch m := p.Metadata.(type) {
|
||||||
case pkg.ApkDBEntry:
|
case pkg.ApkDBEntry:
|
||||||
if metadata.OriginPackage != "" && metadata.OriginPackage != metadata.Package {
|
// apk has no source arch convention, so the source PURL carries no arch
|
||||||
return metadata.OriginPackage, metadata.Version, ""
|
src = sourcePackage{purlType: pkg.ApkPkg.PackageURLType(), name: m.OriginPackage, version: m.Version}
|
||||||
}
|
binName, binVersion = m.Package, m.Version
|
||||||
case pkg.DpkgDBEntry:
|
case pkg.DpkgDBEntry:
|
||||||
return dpkgSourceCoordinates(metadata)
|
src = dpkgSourcePackage(m)
|
||||||
|
binName, binVersion = m.Package, m.Version
|
||||||
case pkg.DpkgArchiveEntry:
|
case pkg.DpkgArchiveEntry:
|
||||||
return dpkgSourceCoordinates(pkg.DpkgDBEntry(metadata))
|
src = dpkgSourcePackage(pkg.DpkgDBEntry(m))
|
||||||
|
binName, binVersion = m.Package, m.Version
|
||||||
case pkg.RpmDBEntry:
|
case pkg.RpmDBEntry:
|
||||||
return rpmSourceCoordinates(metadata.SourceRpm)
|
src = rpmSourcePackage(m.SourceRpm)
|
||||||
|
binName, binVersion = m.Name, m.Version+"-"+m.Release
|
||||||
case pkg.RpmArchive:
|
case pkg.RpmArchive:
|
||||||
return rpmSourceCoordinates(metadata.SourceRpm)
|
src = rpmSourcePackage(m.SourceRpm)
|
||||||
|
binName, binVersion = m.Name, m.Version+"-"+m.Release
|
||||||
case pkg.AlpmDBEntry:
|
case pkg.AlpmDBEntry:
|
||||||
if metadata.BasePackage != "" {
|
// alpm has no source arch convention, so the source PURL carries no arch
|
||||||
return metadata.BasePackage, metadata.Version, ""
|
src = sourcePackage{purlType: pkg.AlpmPkg.PackageURLType(), name: m.BasePackage, version: m.Version}
|
||||||
}
|
binName, binVersion = m.Package, m.Version
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return "", "", ""
|
if src.name == "" || src.version == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if src.name == binName && src.version == binVersion {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &src
|
||||||
}
|
}
|
||||||
|
|
||||||
func dpkgSourceCoordinates(entry pkg.DpkgDBEntry) (name, version, arch string) {
|
func dpkgSourcePackage(entry pkg.DpkgDBEntry) sourcePackage {
|
||||||
if entry.Source == "" {
|
version := entry.SourceVersion
|
||||||
return "", "", ""
|
if version == "" {
|
||||||
|
version = entry.Version
|
||||||
}
|
}
|
||||||
if entry.SourceVersion != "" {
|
return sourcePackage{purlType: pkg.DebPkg.PackageURLType(), name: entry.Source, version: version, arch: "source"}
|
||||||
return entry.Source, entry.SourceVersion, "source"
|
|
||||||
}
|
|
||||||
return entry.Source, entry.Version, "source"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpmSourceCoordinates(sourceRpm string) (name, version, arch string) {
|
// rpmSourcePackage parses a source RPM filename of the form <name>-<version>-<release>.(no)src.rpm.
|
||||||
// <name>-<version>-<release>.src.rpm
|
func rpmSourcePackage(sourceRpm string) sourcePackage {
|
||||||
sourceRpm = strings.TrimSuffix(sourceRpm, ".rpm")
|
var arch string
|
||||||
sourceRpm = strings.TrimSuffix(strings.TrimSuffix(sourceRpm, ".src"), ".nosrc")
|
switch {
|
||||||
release := strings.LastIndex(sourceRpm, "-")
|
case strings.HasSuffix(sourceRpm, ".src.rpm"):
|
||||||
|
arch = "src"
|
||||||
|
case strings.HasSuffix(sourceRpm, ".nosrc.rpm"):
|
||||||
|
arch = "nosrc"
|
||||||
|
default:
|
||||||
|
return sourcePackage{}
|
||||||
|
}
|
||||||
|
nvr := strings.TrimSuffix(sourceRpm, "."+arch+".rpm")
|
||||||
|
release := strings.LastIndex(nvr, "-")
|
||||||
if release < 0 {
|
if release < 0 {
|
||||||
return "", "", ""
|
return sourcePackage{}
|
||||||
}
|
}
|
||||||
ver := strings.LastIndex(sourceRpm[:release], "-")
|
ver := strings.LastIndex(nvr[:release], "-")
|
||||||
if ver < 0 {
|
if ver < 0 {
|
||||||
return "", "", ""
|
return sourcePackage{}
|
||||||
}
|
}
|
||||||
return sourceRpm[:ver], sourceRpm[ver+1:], "src"
|
return sourcePackage{purlType: pkg.RpmPkg.PackageURLType(), name: nvr[:ver], version: nvr[ver+1:], arch: arch}
|
||||||
}
|
}
|
||||||
|
|
||||||
// supported algorithm in cycloneDX as of 1.4
|
// supported algorithm in cycloneDX as of 1.4
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ func Test_encodeSourcePackage(t *testing.T) {
|
|||||||
Type: pkg.ApkPkg,
|
Type: pkg.ApkPkg,
|
||||||
Metadata: pkg.ApkDBEntry{
|
Metadata: pkg.ApkDBEntry{
|
||||||
Package: "some-package",
|
Package: "some-package",
|
||||||
OriginPackage: "some-package",
|
OriginPackage: "some-origin",
|
||||||
Version: "1.0.0-r1",
|
Version: "1.0.0-r1",
|
||||||
Architecture: "x86_64",
|
Architecture: "x86_64",
|
||||||
},
|
},
|
||||||
@@ -190,7 +190,6 @@ func Test_encodeSourcePackage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Matching existing implementation where upstream qualifier is omitted when OriginPackage = Package for Alpine
|
|
||||||
name: "from apk with source = bin",
|
name: "from apk with source = bin",
|
||||||
input: pkg.Package{
|
input: pkg.Package{
|
||||||
Name: "some-package",
|
Name: "some-package",
|
||||||
@@ -312,9 +311,7 @@ func Test_encodeSourcePackage(t *testing.T) {
|
|||||||
SourceRpm: "some-package-1.0.0-1.el8.src.rpm",
|
SourceRpm: "some-package-1.0.0-1.el8.src.rpm",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &cyclonedx.ExternalReference{
|
expected: nil,
|
||||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:rpm/centos/some-package@1.0.0-1.el8?arch=src&distro=centos-8&epoch=32",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "from rpm archive",
|
name: "from rpm archive",
|
||||||
@@ -355,8 +352,6 @@ func Test_encodeSourcePackage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Matching existing implementation where upstream qualifier is *not* omitted when BasePackage == Package for Alpm
|
|
||||||
// This differs from the Alpine case where it is omitted.
|
|
||||||
name: "from alpm with source = bin",
|
name: "from alpm with source = bin",
|
||||||
input: pkg.Package{
|
input: pkg.Package{
|
||||||
Name: "some-package",
|
Name: "some-package",
|
||||||
@@ -370,9 +365,147 @@ func Test_encodeSourcePackage(t *testing.T) {
|
|||||||
Architecture: "x86_64",
|
Architecture: "x86_64",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &cyclonedx.ExternalReference{
|
expected: nil,
|
||||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:alpm/arch/some-package@1.0.0?distro=arch-rolling",
|
},
|
||||||
|
{
|
||||||
|
name: "from rpm with dashes in the source name",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "perl-Foo-Bar-libs",
|
||||||
|
Type: pkg.RpmPkg,
|
||||||
|
PURL: "pkg:rpm/redhat/perl-Foo-Bar-libs@1.0-1.el8?arch=x86_64&distro=rhel-8",
|
||||||
|
Metadata: pkg.RpmDBEntry{
|
||||||
|
Name: "perl-Foo-Bar-libs",
|
||||||
|
Version: "1.0",
|
||||||
|
Release: "1.el8",
|
||||||
|
SourceRpm: "perl-Foo-Bar-1.0-1.el8.src.rpm",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
expected: &cyclonedx.ExternalReference{
|
||||||
|
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:rpm/redhat/perl-Foo-Bar@1.0-1.el8?arch=src&distro=rhel-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from rpm nosrc",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "some-firmware",
|
||||||
|
Type: pkg.RpmPkg,
|
||||||
|
PURL: "pkg:rpm/redhat/some-firmware@1.0-1.el8?arch=noarch&distro=rhel-8",
|
||||||
|
Metadata: pkg.RpmDBEntry{
|
||||||
|
Name: "some-firmware",
|
||||||
|
Version: "1.0",
|
||||||
|
Release: "1.el8",
|
||||||
|
SourceRpm: "firmware-1.0-1.el8.nosrc.rpm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &cyclonedx.ExternalReference{
|
||||||
|
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:rpm/redhat/firmware@1.0-1.el8?arch=nosrc&distro=rhel-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from rpm keeps rpmmod",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "nodejs-libs",
|
||||||
|
Type: pkg.RpmPkg,
|
||||||
|
PURL: "pkg:rpm/redhat/nodejs-libs@18.0-1.el8?arch=x86_64&distro=rhel-8&rpmmod=nodejs%3A18",
|
||||||
|
Metadata: pkg.RpmDBEntry{
|
||||||
|
Name: "nodejs-libs",
|
||||||
|
Version: "18.0",
|
||||||
|
Release: "1.el8",
|
||||||
|
SourceRpm: "nodejs-18.0-1.el8.src.rpm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &cyclonedx.ExternalReference{
|
||||||
|
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:rpm/redhat/nodejs@18.0-1.el8?arch=src&distro=rhel-8&rpmmod=nodejs%3A18",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from rpm with malformed source rpm",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "gpg-pubkey",
|
||||||
|
Type: pkg.RpmPkg,
|
||||||
|
PURL: "pkg:rpm/redhat/gpg-pubkey@1-1?distro=rhel-8",
|
||||||
|
Metadata: pkg.RpmDBEntry{
|
||||||
|
Name: "gpg-pubkey",
|
||||||
|
Version: "1",
|
||||||
|
Release: "1",
|
||||||
|
SourceRpm: "(none)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from rpm with non-source rpm filename",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "bash",
|
||||||
|
Type: pkg.RpmPkg,
|
||||||
|
PURL: "pkg:rpm/redhat/bash@5.1-2?arch=x86_64&distro=rhel-8",
|
||||||
|
Metadata: pkg.RpmDBEntry{
|
||||||
|
Name: "bash",
|
||||||
|
Version: "5.1",
|
||||||
|
Release: "2",
|
||||||
|
SourceRpm: "bash-5.1-2.x86_64.rpm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "from rpm with too few dashes",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "bash",
|
||||||
|
Type: pkg.RpmPkg,
|
||||||
|
PURL: "pkg:rpm/redhat/bash@5.1-2?arch=x86_64&distro=rhel-8",
|
||||||
|
Metadata: pkg.RpmDBEntry{
|
||||||
|
Name: "bash",
|
||||||
|
Version: "5.1",
|
||||||
|
Release: "2",
|
||||||
|
SourceRpm: "bash-5.1.src.rpm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "binary qualifiers and subpath are not carried over",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "libssl3",
|
||||||
|
Type: pkg.DebPkg,
|
||||||
|
PURL: "pkg:deb/debian/libssl3@3.0.0-1?arch=amd64&distro=debian-12&checksum=sha256:dead&download_url=https://example.com/x.deb&cpes=cpe:2.3:a:x:x:1:*:*:*:*:*:*:*#sub/dir",
|
||||||
|
Metadata: pkg.DpkgDBEntry{
|
||||||
|
Package: "libssl3",
|
||||||
|
Version: "3.0.0-1",
|
||||||
|
Source: "openssl",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &cyclonedx.ExternalReference{
|
||||||
|
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:deb/debian/openssl@3.0.0-1?arch=source&distro=debian-12",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PURL type does not match metadata",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "x",
|
||||||
|
Type: pkg.DebPkg,
|
||||||
|
PURL: "pkg:npm/x@1",
|
||||||
|
Metadata: pkg.DpkgDBEntry{
|
||||||
|
Package: "x",
|
||||||
|
Version: "1",
|
||||||
|
Source: "lodash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid PURL",
|
||||||
|
input: pkg.Package{
|
||||||
|
Name: "x",
|
||||||
|
Type: pkg.DebPkg,
|
||||||
|
PURL: "not-a-purl",
|
||||||
|
Metadata: pkg.DpkgDBEntry{
|
||||||
|
Package: "x",
|
||||||
|
Version: "1",
|
||||||
|
Source: "y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
Reference in New Issue
Block a user