github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/format/internal/cyclonedxutil/helpers/component.go (about)

     1  package helpers
     2  
     3  import (
     4  	"reflect"
     5  
     6  	"github.com/CycloneDX/cyclonedx-go"
     7  
     8  	"github.com/anchore/packageurl-go"
     9  	"github.com/anchore/syft/syft/file"
    10  	"github.com/anchore/syft/syft/internal/packagemetadata"
    11  	"github.com/anchore/syft/syft/pkg"
    12  )
    13  
    14  func EncodeComponent(p pkg.Package) cyclonedx.Component {
    15  	props := EncodeProperties(p, "syft:package")
    16  
    17  	if p.Metadata != nil {
    18  		// encode the metadataType as a property, something that doesn't exist on the core model
    19  		props = append(props, cyclonedx.Property{
    20  			Name:  "syft:package:metadataType",
    21  			Value: packagemetadata.JSONName(p.Metadata),
    22  		})
    23  	}
    24  
    25  	props = append(props, encodeCPEs(p)...)
    26  	locations := p.Locations.ToSlice()
    27  	if len(locations) > 0 {
    28  		props = append(props, EncodeProperties(locations, "syft:location")...)
    29  	}
    30  	if hasMetadata(p) {
    31  		props = append(props, EncodeProperties(p.Metadata, "syft:metadata")...)
    32  	}
    33  
    34  	var properties *[]cyclonedx.Property
    35  	if len(props) > 0 {
    36  		properties = &props
    37  	}
    38  
    39  	componentType := cyclonedx.ComponentTypeLibrary
    40  	if p.Type == pkg.BinaryPkg {
    41  		componentType = cyclonedx.ComponentTypeApplication
    42  	}
    43  
    44  	return cyclonedx.Component{
    45  		Type:               componentType,
    46  		Name:               p.Name,
    47  		Group:              encodeGroup(p),
    48  		Version:            p.Version,
    49  		PackageURL:         p.PURL,
    50  		Licenses:           encodeLicenses(p),
    51  		CPE:                encodeSingleCPE(p),
    52  		Author:             encodeAuthor(p),
    53  		Publisher:          encodePublisher(p),
    54  		Description:        encodeDescription(p),
    55  		ExternalReferences: encodeExternalReferences(p),
    56  		Properties:         properties,
    57  		BOMRef:             DeriveBomRef(p),
    58  	}
    59  }
    60  
    61  func DeriveBomRef(p pkg.Package) string {
    62  	// try and parse the PURL if possible and append syft id to it, to make
    63  	// the purl unique in the BOM.
    64  	// TODO: In the future we may want to dedupe by PURL and combine components with
    65  	// the same PURL while preserving their unique metadata.
    66  	if parsedPURL, err := packageurl.FromString(p.PURL); err == nil {
    67  		parsedPURL.Qualifiers = append(parsedPURL.Qualifiers, packageurl.Qualifier{Key: "package-id", Value: string(p.ID())})
    68  		return parsedPURL.ToString()
    69  	}
    70  	// fallback is to use strictly the ID if there is no valid pURL
    71  	return string(p.ID())
    72  }
    73  
    74  func hasMetadata(p pkg.Package) bool {
    75  	return p.Metadata != nil
    76  }
    77  
    78  func decodeComponent(c *cyclonedx.Component) *pkg.Package {
    79  	values := map[string]string{}
    80  	if c.Properties != nil {
    81  		for _, p := range *c.Properties {
    82  			values[p.Name] = p.Value
    83  		}
    84  	}
    85  
    86  	p := &pkg.Package{
    87  		Name:      c.Name,
    88  		Version:   c.Version,
    89  		Locations: decodeLocations(values),
    90  		Licenses:  pkg.NewLicenseSet(decodeLicenses(c)...),
    91  		CPEs:      decodeCPEs(c),
    92  		PURL:      c.PackageURL,
    93  	}
    94  
    95  	DecodeInto(p, values, "syft:package", CycloneDXFields)
    96  
    97  	metadataType := values["syft:package:metadataType"]
    98  
    99  	p.Metadata = decodePackageMetadata(values, c, metadataType)
   100  
   101  	if p.Type == "" {
   102  		p.Type = pkg.TypeFromPURL(p.PURL)
   103  	}
   104  
   105  	if p.Language == "" {
   106  		p.Language = pkg.LanguageFromPURL(p.PURL)
   107  	}
   108  
   109  	return p
   110  }
   111  
   112  func decodeLocations(vals map[string]string) file.LocationSet {
   113  	v := Decode(reflect.TypeOf([]file.Location{}), vals, "syft:location", CycloneDXFields)
   114  	out, ok := v.([]file.Location)
   115  	if !ok {
   116  		out = nil
   117  	}
   118  	return file.NewLocationSet(out...)
   119  }
   120  
   121  func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typeName string) interface{} {
   122  	if typeName != "" && c.Properties != nil {
   123  		metadataType := packagemetadata.ReflectTypeFromJSONName(typeName)
   124  		if metadataType == nil {
   125  			return nil
   126  		}
   127  		metaPtrTyp := reflect.PtrTo(metadataType)
   128  		metaPtr := Decode(metaPtrTyp, vals, "syft:metadata", CycloneDXFields)
   129  
   130  		// Map all explicit metadata properties
   131  		decodeAuthor(c.Author, metaPtr)
   132  		decodeGroup(c.Group, metaPtr)
   133  		decodePublisher(c.Publisher, metaPtr)
   134  		decodeDescription(c.Description, metaPtr)
   135  		decodeExternalReferences(c, metaPtr)
   136  
   137  		// return the actual interface{} -> struct ... not interface{} -> *struct
   138  		return PtrToStruct(metaPtr)
   139  	}
   140  
   141  	return nil
   142  }