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 }