github.com/anchore/syft@v1.38.2/syft/format/internal/cyclonedxutil/helpers/component.go (about)

     1  package helpers
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/CycloneDX/cyclonedx-go"
     9  
    10  	"github.com/anchore/packageurl-go"
    11  	"github.com/anchore/syft/internal/packagemetadata"
    12  	"github.com/anchore/syft/syft/file"
    13  	"github.com/anchore/syft/syft/format/internal"
    14  	"github.com/anchore/syft/syft/pkg"
    15  )
    16  
    17  func EncodeComponent(p pkg.Package, supplier string, locationSorter func(a, b file.Location) int) cyclonedx.Component {
    18  	props := EncodeProperties(p, "syft:package")
    19  
    20  	if p.Metadata != nil {
    21  		// encode the metadataType as a property, something that doesn't exist on the core model
    22  		props = append(props, cyclonedx.Property{
    23  			Name:  "syft:package:metadataType",
    24  			Value: packagemetadata.JSONName(p.Metadata),
    25  		})
    26  	}
    27  
    28  	props = append(props, encodeCPEs(p)...)
    29  	locations := p.Locations.ToSlice(locationSorter)
    30  	if len(locations) > 0 {
    31  		props = append(props, EncodeProperties(locations, "syft:location")...)
    32  	}
    33  	if hasMetadata(p) {
    34  		props = append(props, EncodeProperties(p.Metadata, "syft:metadata")...)
    35  	}
    36  
    37  	var properties *[]cyclonedx.Property
    38  	if len(props) > 0 {
    39  		properties = &props
    40  	}
    41  
    42  	componentType := cyclonedx.ComponentTypeLibrary
    43  	switch p.Type {
    44  	case pkg.BinaryPkg:
    45  		componentType = cyclonedx.ComponentTypeApplication
    46  	case pkg.ModelPkg:
    47  		componentType = cyclonedx.ComponentTypeMachineLearningModel
    48  	}
    49  
    50  	return cyclonedx.Component{
    51  		Type:               componentType,
    52  		Name:               p.Name,
    53  		Group:              encodeGroup(p),
    54  		Version:            p.Version,
    55  		Supplier:           encodeSupplier(p, supplier),
    56  		PackageURL:         p.PURL,
    57  		Licenses:           encodeLicenses(p),
    58  		CPE:                encodeSingleCPE(p),
    59  		Author:             encodeAuthor(p),
    60  		Publisher:          encodePublisher(p),
    61  		Description:        encodeDescription(p),
    62  		ExternalReferences: encodeExternalReferences(p),
    63  		Properties:         properties,
    64  		BOMRef:             DeriveBomRef(p),
    65  	}
    66  }
    67  
    68  // TODO: we eventually want to update this so that we can read "supplier" from different syft metadata
    69  func encodeSupplier(_ pkg.Package, sbomSupplier string) *cyclonedx.OrganizationalEntity {
    70  	if sbomSupplier != "" {
    71  		return &cyclonedx.OrganizationalEntity{
    72  			Name: sbomSupplier,
    73  		}
    74  	}
    75  	return nil
    76  }
    77  
    78  func DeriveBomRef(p pkg.Package) string {
    79  	// try and parse the PURL if possible and append syft id to it, to make
    80  	// the purl unique in the BOM.
    81  	// TODO: In the future we may want to dedupe by PURL and combine components with
    82  	// the same PURL while preserving their unique metadata.
    83  	if parsedPURL, err := packageurl.FromString(p.PURL); err == nil {
    84  		parsedPURL.Qualifiers = append(parsedPURL.Qualifiers, packageurl.Qualifier{Key: "package-id", Value: string(p.ID())})
    85  		return parsedPURL.ToString()
    86  	}
    87  	// fallback is to use strictly the ID if there is no valid pURL
    88  	return string(p.ID())
    89  }
    90  
    91  func hasMetadata(p pkg.Package) bool {
    92  	return p.Metadata != nil
    93  }
    94  
    95  func decodeComponent(c *cyclonedx.Component) *pkg.Package {
    96  	values := map[string]string{}
    97  	if c.Properties != nil {
    98  		for _, p := range *c.Properties {
    99  			values[p.Name] = p.Value
   100  		}
   101  	}
   102  
   103  	p := &pkg.Package{
   104  		Version:   c.Version,
   105  		Locations: decodeLocations(values),
   106  		Licenses:  pkg.NewLicenseSet(decodeLicenses(c)...),
   107  		CPEs:      decodeCPEs(c),
   108  	}
   109  
   110  	// note: this may write in syft package type information
   111  	DecodeInto(p, values, "syft:package", CycloneDXFields)
   112  
   113  	metadataType := values["syft:package:metadataType"]
   114  
   115  	p.Metadata = decodePackageMetadata(values, c, metadataType)
   116  
   117  	// this will either use the purl from the component or generate a new one based off of any type information
   118  	// that was decoded above.
   119  	p.PURL = getPURL(c, p.Type)
   120  
   121  	if p.Type == "" {
   122  		p.Type = pkg.TypeFromPURL(p.PURL)
   123  	}
   124  
   125  	setPackageName(p, c)
   126  
   127  	internal.Backfill(p)
   128  	p.SetID()
   129  
   130  	return p
   131  }
   132  
   133  func getPURL(c *cyclonedx.Component, ty pkg.Type) string {
   134  	if c.PackageURL != "" {
   135  		// if there is a purl that where the namespace does not match the group information, we may
   136  		// accidentally drop group. We should consider adding group as a top-level syft package field.
   137  		return c.PackageURL
   138  	}
   139  
   140  	if strings.HasPrefix(c.BOMRef, "pkg:") {
   141  		// the bomref is a purl, so try to use that as the purl
   142  		_, err := packageurl.FromString(c.BOMRef)
   143  		if err == nil {
   144  			return c.BOMRef
   145  		}
   146  	}
   147  
   148  	if ty == "" {
   149  		return ""
   150  	}
   151  
   152  	tyStr := ty.PackageURLType()
   153  	switch tyStr {
   154  	case "", packageurl.TypeGeneric:
   155  		return ""
   156  	}
   157  
   158  	purl := packageurl.PackageURL{
   159  		Type:      tyStr,
   160  		Namespace: c.Group,
   161  		Name:      c.Name,
   162  		Version:   c.Version,
   163  	}
   164  
   165  	return purl.ToString()
   166  }
   167  
   168  func setPackageName(p *pkg.Package, c *cyclonedx.Component) {
   169  	name := c.Name
   170  	if c.Group != "" {
   171  		switch p.Type {
   172  		case pkg.JavaPkg:
   173  			if p.Metadata == nil {
   174  				p.Metadata = pkg.JavaArchive{}
   175  			}
   176  			var pomProperties *pkg.JavaPomProperties
   177  			javaMetadata, ok := p.Metadata.(pkg.JavaArchive)
   178  			if ok {
   179  				pomProperties = javaMetadata.PomProperties
   180  				if pomProperties == nil {
   181  					pomProperties = &pkg.JavaPomProperties{}
   182  					javaMetadata.PomProperties = pomProperties
   183  					p.Metadata = javaMetadata
   184  				}
   185  			}
   186  			if pomProperties != nil {
   187  				if pomProperties.ArtifactID == "" {
   188  					pomProperties.ArtifactID = c.Name
   189  				}
   190  				if pomProperties.GroupID == "" {
   191  					pomProperties.GroupID = c.Group
   192  				}
   193  				if pomProperties.Version == "" {
   194  					pomProperties.Version = p.Version
   195  				}
   196  			}
   197  		default:
   198  			name = fmt.Sprintf("%s/%s", c.Group, name)
   199  		}
   200  	}
   201  	p.Name = name
   202  }
   203  
   204  func decodeLocations(vals map[string]string) file.LocationSet {
   205  	v := Decode(reflect.TypeOf([]file.Location{}), vals, "syft:location", CycloneDXFields)
   206  	out, ok := v.([]file.Location)
   207  	if !ok {
   208  		out = nil
   209  	}
   210  	return file.NewLocationSet(out...)
   211  }
   212  
   213  func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typeName string) interface{} {
   214  	if typeName != "" && c.Properties != nil {
   215  		metadataType := packagemetadata.ReflectTypeFromJSONName(typeName)
   216  		if metadataType == nil {
   217  			return nil
   218  		}
   219  		metaPtrTyp := reflect.PointerTo(metadataType)
   220  		metaPtr := Decode(metaPtrTyp, vals, "syft:metadata", CycloneDXFields)
   221  
   222  		// Map all explicit metadata properties
   223  		decodeAuthor(c.Author, metaPtr)
   224  		decodeGroup(c.Group, metaPtr)
   225  		decodePublisher(c.Publisher, metaPtr)
   226  		decodeDescription(c.Description, metaPtr)
   227  		decodeExternalReferences(c, metaPtr)
   228  
   229  		// return the actual interface{} -> struct ... not interface{} -> *struct
   230  		return PtrToStruct(metaPtr)
   231  	}
   232  
   233  	return nil
   234  }