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