github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/common/cyclonedxhelpers/component.go (about)

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