Files
syft/syft/format/common/spdxhelpers/to_syft_model.go
T
2026-10-28 21:30:36 -04:00

875 lines
24 KiB
Go

package spdxhelpers
import (
"context"
"errors"
"fmt"
"net/url"
"path"
"regexp"
"strconv"
"strings"
"github.com/scylladb/go-set/strset"
"github.com/spdx/tools-golang/spdx"
"github.com/spdx/tools-golang/spdx/v2/common"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/spdxlicense"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/format/internal"
"github.com/anchore/syft/syft/format/internal/spdxutil/helpers"
"github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) {
if doc == nil {
return nil, errors.New("cannot convert SPDX document to Syft model because document is nil")
}
removeNilElements(doc)
spdxIDMap := make(map[string]any)
// found before the root package and its relationships are removed
sourceFile := findSourceFile(doc)
s := &sbom.SBOM{
Source: extractSource(spdxIDMap, doc),
Artifacts: sbom.Artifacts{
Packages: pkg.NewCollection(),
FileMetadata: map[file.Coordinates]file.Metadata{},
FileDigests: map[file.Coordinates][]file.Digest{},
LinuxDistribution: findLinuxReleaseByPURL(doc),
},
}
collectSyftPackages(s, spdxIDMap, doc)
collectSyftFiles(s, spdxIDMap, doc, sourceFile)
s.Relationships = toSyftRelationships(spdxIDMap, doc)
return s, nil
}
// removeNilElements drops null entries from the document's lists. A JSON null (or an element the SPDX version
// converter cannot map) decodes to a nil pointer, and the rest of the conversion dereferences these elements.
func removeNilElements(doc *spdx.Document) {
doc.Packages = withoutNil(doc.Packages)
for _, p := range doc.Packages {
p.Files = withoutNil(p.Files)
p.PackageExternalReferences = withoutNil(p.PackageExternalReferences)
}
doc.Files = withoutNil(doc.Files)
doc.Relationships = withoutNil(doc.Relationships)
}
func withoutNil[T any](items []*T) []*T {
var out []*T
for _, item := range items {
if item != nil {
out = append(out, item)
}
}
return out
}
func isDirectory(name string) bool {
if name == "." || name == ".." || strings.HasSuffix(name, "/") || !strings.Contains(path.Base(name), ".") {
return true
}
return false
}
func removePackage(packages []*spdx.Package, remove *spdx.Package) (pkgs []*spdx.Package) {
for _, p := range packages {
if p == remove {
continue
}
pkgs = append(pkgs, p)
}
return
}
func removeRelationships(relationships []*spdx.Relationship, spdxID spdx.ElementID) (relations []*spdx.Relationship) {
for _, r := range relationships {
if r.RefA.ElementRefID == spdxID || r.RefB.ElementRefID == spdxID {
continue
}
relations = append(relations, r)
}
return
}
func findRootPackages(doc *spdx.Document) (out []*spdx.Package) {
// index the DESCRIBES / DESCRIBED_BY relationships once, counting how many
// times each element is described by the document, so that the result
// matches the previous packages x relationships scan exactly (one entry per
// matching relationship) without the quadratic cost.
describedCount := make(map[spdx.ElementID]int)
for _, r := range doc.Relationships {
if r == nil {
continue
}
switch {
case r.RefA.ElementRefID == "DOCUMENT" && r.Relationship == spdx.RelationshipDescribes:
describedCount[r.RefB.ElementRefID]++
case r.RefB.ElementRefID == "DOCUMENT" && r.Relationship == spdx.RelationshipDescribedBy:
describedCount[r.RefA.ElementRefID]++
}
}
if len(describedCount) == 0 {
return nil
}
for _, p := range doc.Packages {
if p == nil {
continue
}
for i := 0; i < describedCount[p.PackageSPDXIdentifier]; i++ {
out = append(out, p)
}
}
return
}
func extractSource(spdxIDMap map[string]any, doc *spdx.Document) source.Description {
src := extractSourceFromNamespace(doc.DocumentNamespace)
rootPackages := findRootPackages(doc)
if len(rootPackages) != 1 {
return src
}
p := rootPackages[0]
rootType := syftRootType(p.PackageSPDXIdentifier)
purpose := p.PrimaryPackagePurpose
if purpose == "" {
// SPDX 2.1 and 2.2 have no primary package purpose, so fall back to the source type syft puts in the root's ID
purpose = syftRootPurpose(rootType)
}
switch purpose {
case spdxPrimaryPurposeContainer:
src = containerSource(p)
case spdxPrimaryPurposeFile:
src = fileSource(p)
case spdxPrimaryPurposeOther:
// syft writes a root package even for a source it knows nothing about, which is not a package in the SBOM
if rootType != prefixUnknown {
return src
}
default:
return src
}
spdxIDMap[string(p.PackageSPDXIdentifier)] = src
doc.Packages = removePackage(doc.Packages, p)
doc.Relationships = removeRelationships(doc.Relationships, p.PackageSPDXIdentifier)
return src
}
// syftRootType returns the source type syft encodes in a root package ID ("DocumentRoot-<type>-<name>"), or "" for
// any other ID.
func syftRootType(id spdx.ElementID) string {
rest, ok := strings.CutPrefix(string(id), "DocumentRoot-")
if !ok {
return ""
}
rootType, _, _ := strings.Cut(rest, "-")
return rootType
}
// syftRootPurpose returns the primary package purpose syft writes for a root package of the given source type.
func syftRootPurpose(rootType string) string {
switch rootType {
case prefixImage, prefixOCIModel, prefixSnap:
return spdxPrimaryPurposeContainer
case prefixDirectory, prefixFile:
return spdxPrimaryPurposeFile
case prefixUnknown:
return spdxPrimaryPurposeOther
}
return ""
}
func containerSource(p *spdx.Package) source.Description {
id := string(p.PackageSPDXIdentifier)
container := p.PackageName
v := p.PackageVersion
if v != "" {
container += ":" + v
}
digest := ""
if len(p.PackageChecksums) > 0 {
c := p.PackageChecksums[0]
digest = fmt.Sprintf("%s:%s", fromChecksumAlgorithm(c.Algorithm), c.Value)
}
supplier := ""
if p.PackageSupplier != nil {
// we also don't want NOASSERTION transferred to the syft format
// NOASSERTION == ""
if p.PackageSupplier.Supplier != helpers.NOASSERTION && p.PackageSupplier.SupplierType == helpers.SUPPLIERORG {
supplier = p.PackageSupplier.Supplier
}
}
return source.Description{
ID: id,
Name: p.PackageName,
Version: p.PackageVersion,
Supplier: supplier,
Metadata: source.ImageMetadata{
UserInput: container,
ID: id,
Layers: nil, // TODO handle formats with nested layer packages like Tern and K8s BOM tool
ManifestDigest: digest,
},
}
}
func fileSource(p *spdx.Package) source.Description {
typeRegex := regexp.MustCompile("^DocumentRoot-([^-]+)-.*$")
typeName := typeRegex.ReplaceAllString(string(p.PackageSPDXIdentifier), "$1")
var version string
var metadata any
switch {
case typeName == prefixDirectory:
// is a Syft SBOM, explicitly a directory source
metadata, version = directorySourceMetadata(p)
case typeName == prefixFile:
// is a Syft SBOM, explicitly a file source
metadata, version = fileSourceMetadata(p)
case isDirectory(p.PackageName):
// is a non-Syft SBOM, which looks like a directory
metadata, version = directorySourceMetadata(p)
default:
// is a non-Syft SBOM, which is probably a file
metadata, version = fileSourceMetadata(p)
}
supplier := ""
if p.PackageSupplier != nil && p.PackageSupplier.Supplier != helpers.NOASSERTION {
supplier = p.PackageSupplier.Supplier
}
return source.Description{
ID: string(p.PackageSPDXIdentifier),
Name: p.PackageName,
Version: version,
Supplier: supplier,
Metadata: metadata,
}
}
func fileSourceMetadata(p *spdx.Package) (any, string) {
version := p.PackageVersion
m := source.FileMetadata{
Path: p.PackageName,
}
// if this is a Syft SBOM, we might have output a digest as the version
checksum := toChecksum(p.PackageVersion)
for _, d := range p.PackageChecksums {
if checksum != nil && checksum.Value == d.Value {
version = ""
}
m.Digests = append(m.Digests, file.Digest{
Algorithm: fromChecksumAlgorithm(d.Algorithm),
Value: d.Value,
})
}
return m, version
}
func directorySourceMetadata(p *spdx.Package) (any, string) {
return source.DirectoryMetadata{
Path: p.PackageName,
Base: "",
}, p.PackageVersion
}
// NOTE(jonas): SPDX doesn't inform what an SBOM is about,
// image, directory, for example. This is our best effort to determine
// the scheme. Syft-generated SBOMs have in the namespace
// field a type encoded, which we try to identify here.
func extractSourceFromNamespace(ns string) source.Description {
u, err := url.Parse(ns)
if err != nil {
return source.Description{
Metadata: nil,
}
}
parts := strings.Split(u.Path, "/")
for _, p := range parts {
switch p {
case helpers.InputFile:
return source.Description{
Metadata: source.FileMetadata{},
}
case helpers.InputImage:
return source.Description{
Metadata: source.ImageMetadata{},
}
case helpers.InputDirectory:
return source.Description{
Metadata: source.DirectoryMetadata{},
}
}
}
return source.Description{}
}
func findLinuxReleaseByPURL(doc *spdx.Document) *linux.Release {
for _, p := range doc.Packages {
purlValue := findPURLValue(p)
if purlValue == "" {
continue
}
purl, err := packageurl.FromString(purlValue)
if err != nil {
log.Warnf("unable to parse purl: %s", purlValue)
continue
}
distro := findQualifierValue(purl, pkg.PURLQualifierDistro)
if distro != "" {
parts := strings.Split(distro, "-")
name := parts[0]
version := ""
if len(parts) > 1 {
version = parts[1]
}
return &linux.Release{
PrettyName: name,
Name: name,
ID: name,
IDLike: []string{name},
Version: version,
VersionID: version,
}
}
}
return nil
}
func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]any, doc *spdx.Document) {
skipIDs := packageIDsToSkip(doc)
for _, p := range doc.Packages {
if p == nil || skipIDs.Has(string(p.PackageSPDXIdentifier)) {
continue
}
syftPkg := toSyftPackage(p)
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
s.Artifacts.Packages.Add(syftPkg)
}
}
// findSourceFile returns the file the root package DESCRIBES when it CONTAINS files: an archive scanned as the source
// is the scanned root, so what it contains is at the root rather than within an archive.
func findSourceFile(doc *spdx.Document) spdx.ElementID {
roots := findRootPackages(doc)
if len(roots) != 1 {
return ""
}
var described spdx.ElementID
containers := map[spdx.ElementID]bool{}
for _, r := range doc.Relationships {
switch helpers.RelationshipType(r.Relationship) {
case helpers.DescribesRelationship:
if r.RefA.ElementRefID == roots[0].PackageSPDXIdentifier {
described = r.RefB.ElementRefID
}
case helpers.ContainsRelationship:
containers[r.RefA.ElementRefID] = true
}
}
if !containers[described] {
return ""
}
return described
}
func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]any, doc *spdx.Document, sourceFile spdx.ElementID) {
files := map[spdx.ElementID]*spdx.File{}
for _, p := range doc.Packages {
for _, f := range p.Files {
files[f.FileSPDXIdentifier] = f
}
}
for _, f := range doc.Files {
files[f.FileSPDXIdentifier] = f
}
nested := newNestedFiles[spdx.ElementID]()
for id, f := range files {
nested.own[id] = toSyftCoordinates(f)
}
if c, ok := nested.own[sourceFile]; ok {
// the scanned root
c.RealPath = ""
nested.own[sourceFile] = c
}
for _, r := range doc.Relationships {
container, entry := r.RefA.ElementRefID, r.RefB.ElementRefID
switch helpers.RelationshipType(r.Relationship) {
case helpers.ContainsRelationship:
case helpers.ContainedByRelationship:
container, entry = entry, container
default:
continue
}
// the scanned root holds what is at the root, not within an archive
if files[container] != nil && files[entry] != nil && !isScanRoot(nested.own[container]) {
nested.container[entry] = container
}
}
for id, f := range files {
l := file.NewLocationFromCoordinates(nested.coordinates(id))
spdxIDMap[string(id)] = l
s.Artifacts.FileMetadata[l.Coordinates] = toFileMetadata(f)
s.Artifacts.FileDigests[l.Coordinates] = toFileDigests(f)
}
}
// nestedFiles resolves the coordinates of files within archives, where an archive file CONTAINS a file
// named by its path within the archive.
type nestedFiles[K comparable] struct {
own map[K]file.Coordinates
container map[K]K
resolved map[K]file.Coordinates
}
func newNestedFiles[K comparable]() nestedFiles[K] {
return nestedFiles[K]{own: map[K]file.Coordinates{}, container: map[K]K{}, resolved: map[K]file.Coordinates{}}
}
func (n nestedFiles[K]) coordinates(id K) file.Coordinates {
if c, ok := n.resolved[id]; ok {
return c
}
c := n.own[id]
if container, ok := n.container[id]; ok {
// a containment cycle ends where it started
delete(n.container, id)
archive := n.coordinates(container)
c = file.Coordinates{
RealPath: archive.RealPath,
FileSystemID: archive.FileSystemID,
FullPath: archive.FullNestedPath() + file.ArchivePathSeparator + strings.TrimPrefix(c.RealPath, "/"),
}
}
n.resolved[id] = c
return c
}
func toFileDigests(f *spdx.File) (digests []file.Digest) {
for _, digest := range f.Checksums {
digests = append(digests, file.Digest{
Algorithm: fromChecksumAlgorithm(digest.Algorithm),
Value: digest.Value,
})
}
return digests
}
func fromChecksumAlgorithm(algorithm common.ChecksumAlgorithm) string {
return strings.ToLower(string(algorithm))
}
func toFileMetadata(f *spdx.File) (meta file.Metadata) {
// FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
for _, typ := range f.FileTypes {
switch helpers.FileType(typ) {
case helpers.ImageFileType:
meta.MIMEType = "image/"
case helpers.VideoFileType:
meta.MIMEType = "video/"
case helpers.ApplicationFileType:
meta.MIMEType = "application/"
case helpers.TextFileType:
meta.MIMEType = "text/"
case helpers.AudioFileType:
meta.MIMEType = "audio/"
case helpers.BinaryFileType:
case helpers.ArchiveFileType:
case helpers.OtherFileType:
}
}
return meta
}
func toSyftRelationships(spdxIDMap map[string]any, doc *spdx.Document) []artifact.Relationship {
out := collectDocRelationships(spdxIDMap, doc)
out = append(out, collectPackageFileRelationships(spdxIDMap, doc)...)
return out
}
func collectDocRelationships(spdxIDMap map[string]any, doc *spdx.Document) (out []artifact.Relationship) {
for _, r := range doc.Relationships {
// FIXME what to do with r.RefA.DocumentRefID and r.RefA.SpecialID
if r.RefA.DocumentRefID != "" && requireAndTrimPrefix(r.RefA.DocumentRefID, "DocumentRef-") != string(doc.SPDXIdentifier) {
log.Debugf("ignoring relationship to external document: %+v", r)
continue
}
a := spdxIDMap[string(r.RefA.ElementRefID)]
b := spdxIDMap[string(r.RefB.ElementRefID)]
if rel, ok := fileContainsRelationship(helpers.RelationshipType(r.Relationship), a, b); ok {
out = append(out, rel)
continue
}
from, fromOk := a.(pkg.Package)
toPackage, toPackageOk := b.(pkg.Package)
toLocation, toLocationOk := b.(file.Location)
//nolint:staticcheck
if !fromOk || !(toPackageOk || toLocationOk) {
log.Debugf("unable to find valid relationship mapping from SPDX, ignoring: (from: %+v) (to: %+v)", a, b)
continue
}
var to artifact.Identifiable
var typ artifact.RelationshipType
if toLocationOk {
switch helpers.RelationshipType(r.Relationship) {
case helpers.ContainsRelationship:
typ = artifact.ContainsRelationship
to = toLocation
case helpers.OtherRelationship:
// Encoding uses a specifically formatted comment...
if strings.HasPrefix(r.RelationshipComment, string(artifact.EvidentByRelationship)) {
typ = artifact.EvidentByRelationship
to = toLocation
}
}
} else {
switch helpers.RelationshipType(r.Relationship) {
case helpers.DependencyOfRelationship:
typ = artifact.DependencyOfRelationship
to = toPackage
case helpers.DependsOnRelationship:
typ = artifact.DependencyOfRelationship
to = from
from = toPackage
case helpers.ContainsRelationship:
typ = artifact.ContainsRelationship
to = toPackage
case helpers.OtherRelationship:
// Encoding uses a specifically formatted comment...
if strings.HasPrefix(r.RelationshipComment, string(artifact.OwnershipByFileOverlapRelationship)) {
typ = artifact.OwnershipByFileOverlapRelationship
to = toPackage
}
}
}
if typ != "" && to != nil {
out = append(out, artifact.Relationship{
From: from,
To: to,
Type: typ,
})
}
}
return out
}
// fileContainsRelationship decodes an archive file containing a file or package within it.
func fileContainsRelationship(typ helpers.RelationshipType, a, b any) (artifact.Relationship, bool) {
switch typ {
case helpers.ContainsRelationship:
case helpers.ContainedByRelationship:
a, b = b, a
default:
return artifact.Relationship{}, false
}
container, ok := a.(file.Location)
if !ok || isScanRoot(container.Coordinates) {
return artifact.Relationship{}, false
}
var entry artifact.Identifiable
switch to := b.(type) {
case file.Location:
entry = to.Coordinates
case pkg.Package:
entry = to
default:
return artifact.Relationship{}, false
}
return artifact.Relationship{From: container.Coordinates, To: entry, Type: artifact.ContainsRelationship}, true
}
// collectPackageFileRelationships add relationships for direct files
func collectPackageFileRelationships(spdxIDMap map[string]any, doc *spdx.Document) (out []artifact.Relationship) {
for _, p := range doc.Packages {
a := spdxIDMap[string(p.PackageSPDXIdentifier)]
from, fromOk := a.(pkg.Package)
if !fromOk {
continue
}
for _, f := range p.Files {
b := spdxIDMap[string(f.FileSPDXIdentifier)]
to, toLocationOk := b.(file.Location)
if !toLocationOk {
continue
}
out = append(out, artifact.Relationship{
From: from,
To: to,
Type: artifact.ContainsRelationship,
})
}
}
return out
}
func toSyftCoordinates(f *spdx.File) file.Coordinates {
return coordinatesFromFile(f.FileName, f.FileComment, string(f.FileSPDXIdentifier))
}
func coordinatesFromFile(name, comment, id string) file.Coordinates {
// the scanned root
if name == "." {
name = ""
}
c := file.NewCoordinates(name, "")
if v, ok := strings.CutPrefix(comment, layerIDCommentPrefix); ok {
c.FileSystemID = v
}
if v, ok := strings.CutPrefix(id, layerIDCommentPrefix); ok {
c.FileSystemID = v
}
return c
}
func requireAndTrimPrefix(val any, prefix string) string {
if v, ok := val.(string); ok {
if strings.HasPrefix(v, prefix) {
return strings.TrimPrefix(v, prefix)
}
}
return ""
}
type pkgInfo struct {
purl packageurl.PackageURL
typ pkg.Type
lang pkg.Language
}
func (p *pkgInfo) qualifierValue(name string) string {
return findQualifierValue(p.purl, name)
}
func findQualifierValue(purl packageurl.PackageURL, qualifier string) string {
for _, q := range purl.Qualifiers {
if q.Key == qualifier {
return q.Value
}
}
return ""
}
func extractPkgInfo(p *spdx.Package) pkgInfo {
pu := findPURLValue(p)
purl, err := packageurl.FromString(pu)
if err != nil {
return pkgInfo{}
}
return pkgInfo{
purl,
pkg.TypeByName(purl.Type),
pkg.LanguageByName(purl.Type),
}
}
func toSyftPackage(p *spdx.Package) pkg.Package {
info := extractPkgInfo(p)
sP := &pkg.Package{
Type: info.typ,
Name: p.PackageName,
Version: p.PackageVersion,
Licenses: pkg.NewLicenseSet(parseSPDXLicenses(p)...),
CPEs: extractCPEs(p),
PURL: purlValue(info.purl),
Language: info.lang,
Metadata: extractMetadata(p, info),
}
internal.Backfill(sP)
if p.PackageSPDXIdentifier != "" {
// always prefer the IDs from the SBOM over derived IDs
sP.OverrideID(artifact.ID(p.PackageSPDXIdentifier))
} else {
sP.SetID()
}
return *sP
}
func purlValue(purl packageurl.PackageURL) string {
val := purl.String()
if _, err := packageurl.FromString(val); err != nil {
return ""
}
return val
}
func parseSPDXLicenses(p *spdx.Package) []pkg.License {
licenses := make([]pkg.License, 0)
// concluded
if p.PackageLicenseConcluded != helpers.NOASSERTION && p.PackageLicenseConcluded != helpers.NONE && p.PackageLicenseConcluded != "" {
l := pkg.NewLicenseWithContext(context.TODO(), cleanSPDXID(p.PackageLicenseConcluded))
l.Type = license.Concluded
licenses = append(licenses, l)
}
// declared
if p.PackageLicenseDeclared != helpers.NOASSERTION && p.PackageLicenseDeclared != helpers.NONE && p.PackageLicenseDeclared != "" {
l := pkg.NewLicenseWithContext(context.TODO(), cleanSPDXID(p.PackageLicenseDeclared))
l.Type = license.Declared
licenses = append(licenses, l)
}
return licenses
}
func cleanSPDXID(id string) string {
return strings.TrimPrefix(id, spdxlicense.LicenseRefPrefix)
}
//nolint:funlen
func extractMetadata(p *spdx.Package, info pkgInfo) any {
arch := info.qualifierValue(pkg.PURLQualifierArch)
upstreamValue := info.qualifierValue(pkg.PURLQualifierUpstream)
upstream := strings.SplitN(upstreamValue, "@", 2)
upstreamName := upstream[0]
upstreamVersion := ""
if len(upstream) > 1 {
upstreamVersion = upstream[1]
}
supplier := ""
if p.PackageSupplier != nil {
supplier = p.PackageSupplier.Supplier
}
originator := ""
if p.PackageOriginator != nil {
originator = p.PackageOriginator.Originator
}
switch info.typ {
case pkg.ApkPkg:
return pkg.ApkDBEntry{
Package: p.PackageName,
OriginPackage: upstreamName,
Maintainer: supplier,
Version: p.PackageVersion,
Architecture: arch,
URL: p.PackageHomePage,
Description: p.PackageDescription,
}
case pkg.RpmPkg:
converted, err := strconv.Atoi(info.qualifierValue(pkg.PURLQualifierEpoch))
var epoch *int
if err != nil {
epoch = nil
} else {
epoch = &converted
}
return pkg.RpmDBEntry{
Name: p.PackageName,
Version: p.PackageVersion,
Epoch: epoch,
Arch: arch,
SourceRpm: upstreamValue,
Vendor: originator,
}
case pkg.DebPkg:
return pkg.DpkgDBEntry{
Package: p.PackageName,
Source: upstreamName,
Version: p.PackageVersion,
SourceVersion: upstreamVersion,
Architecture: arch,
Maintainer: originator,
}
case pkg.JavaPkg:
var digests []file.Digest
for _, value := range p.PackageChecksums {
digests = append(digests, file.Digest{Algorithm: fromChecksumAlgorithm(value.Algorithm), Value: value.Value})
}
return pkg.JavaArchive{
ArchiveDigests: digests,
}
case pkg.GoModulePkg:
var h1Digest string
for _, value := range p.PackageChecksums {
digest, err := helpers.HDigestFromSHA(fromChecksumAlgorithm(value.Algorithm), value.Value)
if err != nil {
log.Debugf("invalid h1digest: %v %v", value, err)
continue
}
h1Digest = digest
break
}
return pkg.GolangBinaryBuildinfoEntry{
H1Digest: h1Digest,
}
}
return nil
}
func findPURLValue(p *spdx.Package) string {
for _, r := range p.PackageExternalReferences {
if r.RefType == string(helpers.PurlExternalRefType) {
return r.Locator
}
}
return ""
}
func extractCPEs(p *spdx.Package) (cpes []cpe.CPE) {
for _, r := range p.PackageExternalReferences {
if r.RefType == string(helpers.Cpe23ExternalRefType) {
c, err := cpe.New(r.Locator, cpe.DeclaredSource)
if err != nil {
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err)
continue
}
cpes = append(cpes, c)
}
}
return cpes
}
// packageIDsToSkip returns a set of packageIDs that should not be imported
func packageIDsToSkip(doc *spdx.Document) *strset.Set {
skipIDs := strset.New()
for i := 0; i < len(doc.Relationships); i++ {
r := doc.Relationships[i]
if r != nil && r.Relationship == spdx.RelationshipGeneratedFrom {
skipIDs.Add(string(r.RefB.ElementRefID))
}
}
return skipIDs
}