mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
232 lines
6.2 KiB
Go
232 lines
6.2 KiB
Go
package helpers
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/CycloneDX/cyclonedx-go"
|
|
|
|
"github.com/anchore/packageurl-go"
|
|
"github.com/anchore/syft/internal/packagemetadata"
|
|
"github.com/anchore/syft/syft/file"
|
|
"github.com/anchore/syft/syft/format/internal"
|
|
"github.com/anchore/syft/syft/pkg"
|
|
)
|
|
|
|
func EncodeComponent(p pkg.Package, supplier string, locationSorter func(a, b file.Location) int) cyclonedx.Component {
|
|
props := EncodeProperties(p, "syft:package")
|
|
|
|
if p.Metadata != nil {
|
|
// encode the metadataType as a property, something that doesn't exist on the core model
|
|
props = append(props, cyclonedx.Property{
|
|
Name: "syft:package:metadataType",
|
|
Value: packagemetadata.JSONName(p.Metadata),
|
|
})
|
|
}
|
|
|
|
props = append(props, encodeCPEs(p)...)
|
|
locations := p.Locations.ToSlice(locationSorter)
|
|
if len(locations) > 0 {
|
|
props = append(props, EncodeProperties(locations, "syft:location")...)
|
|
}
|
|
if hasMetadata(p) {
|
|
props = append(props, EncodeProperties(p.Metadata, "syft:metadata")...)
|
|
}
|
|
|
|
var properties *[]cyclonedx.Property
|
|
if len(props) > 0 {
|
|
properties = &props
|
|
}
|
|
|
|
componentType := cyclonedx.ComponentTypeLibrary
|
|
if p.Type == pkg.BinaryPkg {
|
|
componentType = cyclonedx.ComponentTypeApplication
|
|
}
|
|
|
|
return cyclonedx.Component{
|
|
Type: componentType,
|
|
Name: p.Name,
|
|
Group: encodeGroup(p),
|
|
Version: p.Version,
|
|
Supplier: encodeSupplier(p, supplier),
|
|
PackageURL: p.PURL,
|
|
Licenses: encodeLicenses(p),
|
|
CPE: encodeSingleCPE(p),
|
|
Author: encodeAuthor(p),
|
|
Publisher: encodePublisher(p),
|
|
Description: encodeDescription(p),
|
|
ExternalReferences: encodeExternalReferences(p),
|
|
Properties: properties,
|
|
BOMRef: DeriveBomRef(p),
|
|
}
|
|
}
|
|
|
|
// TODO: we eventually want to update this so that we can read "supplier" from different syft metadata
|
|
func encodeSupplier(_ pkg.Package, sbomSupplier string) *cyclonedx.OrganizationalEntity {
|
|
if sbomSupplier != "" {
|
|
return &cyclonedx.OrganizationalEntity{
|
|
Name: sbomSupplier,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DeriveBomRef(p pkg.Package) string {
|
|
// try and parse the PURL if possible and append syft id to it, to make
|
|
// the purl unique in the BOM.
|
|
// TODO: In the future we may want to dedupe by PURL and combine components with
|
|
// the same PURL while preserving their unique metadata.
|
|
if parsedPURL, err := packageurl.FromString(p.PURL); err == nil {
|
|
parsedPURL.Qualifiers = append(parsedPURL.Qualifiers, packageurl.Qualifier{Key: "package-id", Value: string(p.ID())})
|
|
return parsedPURL.ToString()
|
|
}
|
|
// fallback is to use strictly the ID if there is no valid pURL
|
|
return string(p.ID())
|
|
}
|
|
|
|
func hasMetadata(p pkg.Package) bool {
|
|
return p.Metadata != nil
|
|
}
|
|
|
|
func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
|
values := map[string]string{}
|
|
if c.Properties != nil {
|
|
for _, p := range *c.Properties {
|
|
values[p.Name] = p.Value
|
|
}
|
|
}
|
|
|
|
p := &pkg.Package{
|
|
Version: c.Version,
|
|
Locations: decodeLocations(values),
|
|
Licenses: pkg.NewLicenseSet(decodeLicenses(c)...),
|
|
CPEs: decodeCPEs(c),
|
|
}
|
|
|
|
// note: this may write in syft package type information
|
|
DecodeInto(p, values, "syft:package", CycloneDXFields)
|
|
|
|
metadataType := values["syft:package:metadataType"]
|
|
|
|
p.Metadata = decodePackageMetadata(values, c, metadataType)
|
|
|
|
// this will either use the purl from the component or generate a new one based off of any type information
|
|
// that was decoded above.
|
|
p.PURL = getPURL(c, p.Type)
|
|
|
|
if p.Type == "" {
|
|
p.Type = pkg.TypeFromPURL(p.PURL)
|
|
}
|
|
|
|
setPackageName(p, c)
|
|
|
|
internal.Backfill(p)
|
|
p.SetID()
|
|
|
|
return p
|
|
}
|
|
|
|
func getPURL(c *cyclonedx.Component, ty pkg.Type) string {
|
|
if c.PackageURL != "" {
|
|
// if there is a purl that where the namespace does not match the group information, we may
|
|
// accidentally drop group. We should consider adding group as a top-level syft package field.
|
|
return c.PackageURL
|
|
}
|
|
|
|
if strings.HasPrefix(c.BOMRef, "pkg:") {
|
|
// the bomref is a purl, so try to use that as the purl
|
|
_, err := packageurl.FromString(c.BOMRef)
|
|
if err == nil {
|
|
return c.BOMRef
|
|
}
|
|
}
|
|
|
|
if ty == "" {
|
|
return ""
|
|
}
|
|
|
|
tyStr := ty.PackageURLType()
|
|
switch tyStr {
|
|
case "", packageurl.TypeGeneric:
|
|
return ""
|
|
}
|
|
|
|
purl := packageurl.PackageURL{
|
|
Type: tyStr,
|
|
Namespace: c.Group,
|
|
Name: c.Name,
|
|
Version: c.Version,
|
|
}
|
|
|
|
return purl.ToString()
|
|
}
|
|
|
|
func setPackageName(p *pkg.Package, c *cyclonedx.Component) {
|
|
name := c.Name
|
|
if c.Group != "" {
|
|
switch p.Type {
|
|
case pkg.JavaPkg:
|
|
if p.Metadata == nil {
|
|
p.Metadata = pkg.JavaArchive{}
|
|
}
|
|
var pomProperties *pkg.JavaPomProperties
|
|
javaMetadata, ok := p.Metadata.(pkg.JavaArchive)
|
|
if ok {
|
|
pomProperties = javaMetadata.PomProperties
|
|
if pomProperties == nil {
|
|
pomProperties = &pkg.JavaPomProperties{}
|
|
javaMetadata.PomProperties = pomProperties
|
|
p.Metadata = javaMetadata
|
|
}
|
|
}
|
|
if pomProperties != nil {
|
|
if pomProperties.ArtifactID == "" {
|
|
pomProperties.ArtifactID = c.Name
|
|
}
|
|
if pomProperties.GroupID == "" {
|
|
pomProperties.GroupID = c.Group
|
|
}
|
|
if pomProperties.Version == "" {
|
|
pomProperties.Version = p.Version
|
|
}
|
|
}
|
|
default:
|
|
name = fmt.Sprintf("%s/%s", c.Group, name)
|
|
}
|
|
}
|
|
p.Name = name
|
|
}
|
|
|
|
func decodeLocations(vals map[string]string) file.LocationSet {
|
|
v := Decode(reflect.TypeOf([]file.Location{}), vals, "syft:location", CycloneDXFields)
|
|
out, ok := v.([]file.Location)
|
|
if !ok {
|
|
out = nil
|
|
}
|
|
return file.NewLocationSet(out...)
|
|
}
|
|
|
|
func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typeName string) interface{} {
|
|
if typeName != "" && c.Properties != nil {
|
|
metadataType := packagemetadata.ReflectTypeFromJSONName(typeName)
|
|
if metadataType == nil {
|
|
return nil
|
|
}
|
|
metaPtrTyp := reflect.PointerTo(metadataType)
|
|
metaPtr := Decode(metaPtrTyp, vals, "syft:metadata", CycloneDXFields)
|
|
|
|
// Map all explicit metadata properties
|
|
decodeAuthor(c.Author, metaPtr)
|
|
decodeGroup(c.Group, metaPtr)
|
|
decodePublisher(c.Publisher, metaPtr)
|
|
decodeDescription(c.Description, metaPtr)
|
|
decodeExternalReferences(c, metaPtr)
|
|
|
|
// return the actual interface{} -> struct ... not interface{} -> *struct
|
|
return PtrToStruct(metaPtr)
|
|
}
|
|
|
|
return nil
|
|
}
|