Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CycloneDX decoder #811

Merged
merged 16 commits into from
Feb 18, 2022
17 changes: 16 additions & 1 deletion internal/formats/common/cyclonedxhelpers/author.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)

func Author(p pkg.Package) string {
func encodeAuthor(p pkg.Package) string {
if hasMetadata(p) {
switch metadata := p.Metadata.(type) {
case pkg.NpmPackageJSONMetadata:
Expand All @@ -30,3 +30,18 @@ func Author(p pkg.Package) string {
}
return ""
}

func decodeAuthor(author string, metadata interface{}) {
switch meta := metadata.(type) {
case *pkg.NpmPackageJSONMetadata:
meta.Author = author
case *pkg.PythonPackageMetadata:
parts := strings.SplitN(author, " <", 2)
meta.Author = parts[0]
if len(parts) > 1 {
meta.AuthorEmail = strings.TrimSuffix(parts[1], ">")
}
case *pkg.GemMetadata:
meta.Authors = strings.Split(author, ",")
}
}
4 changes: 2 additions & 2 deletions internal/formats/common/cyclonedxhelpers/author_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
)

func Test_Author(t *testing.T) {
func Test_encodeAuthor(t *testing.T) {
tests := []struct {
name string
input pkg.Package
Expand Down Expand Up @@ -81,7 +81,7 @@ func Test_Author(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, Author(test.input))
assert.Equal(t, test.expected, encodeAuthor(test.input))
})
}
}
155 changes: 146 additions & 9 deletions internal/formats/common/cyclonedxhelpers/component.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,164 @@
package cyclonedxhelpers

import (
"fmt"
"reflect"
"strconv"

"github.com/CycloneDX/cyclonedx-go"

"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)

func Component(p pkg.Package) cyclonedx.Component {
func encodeComponent(p pkg.Package) cyclonedx.Component {
return cyclonedx.Component{
Type: cyclonedx.ComponentTypeLibrary,
Name: p.Name,
Group: Group(p),
Group: encodeGroup(p),
Version: p.Version,
PackageURL: p.PURL,
Licenses: Licenses(p),
CPE: CPE(p),
Author: Author(p),
Publisher: Publisher(p),
Description: Description(p),
ExternalReferences: ExternalReferences(p),
Properties: Properties(p),
Licenses: encodeLicenses(p),
CPE: encodeCPE(p),
Author: encodeAuthor(p),
Publisher: encodePublisher(p),
Description: encodeDescription(p),
ExternalReferences: encodeExternalReferences(p),
Properties: encodeProperties(p),
}
}

func hasMetadata(p pkg.Package) bool {
return p.Metadata != nil
}

func decodeComponent(c *cyclonedx.Component) *pkg.Package {
typ := pkg.Type(findPropertyValue(c, "type"))
purl := c.PackageURL
if typ == "" && purl != "" {
typ = pkg.TypeFromPURL(purl)
}

metaType, meta := decodePackageMetadata(c)

p := &pkg.Package{
Name: c.Name,
Version: c.Version,
FoundBy: findPropertyValue(c, "foundBy"),
Locations: decodeLocations(c),
Licenses: decodeLicenses(c),
Language: pkg.Language(findPropertyValue(c, "language")),
Type: typ,
CPEs: decodeCPEs(c),
PURL: purl,
MetadataType: metaType,
Metadata: meta,
}

return p
}

func decodeLocations(c *cyclonedx.Component) (out []source.Location) {
if c.Properties != nil {
props := *c.Properties
for i := 0; i < len(props)-1; i++ {
if props[i].Name == "path" && props[i+1].Name == "layerID" {
kzantow marked this conversation as resolved.
Show resolved Hide resolved
out = append(out, source.Location{
Coordinates: source.Coordinates{
RealPath: props[i].Value,
FileSystemID: props[i+1].Value,
},
})
i++
}
}
}
return
}

func mapAllProps(c *cyclonedx.Component, obj reflect.Value) {
value := obj
if value.Kind() == reflect.Ptr {
value = value.Elem()
}

structType := value.Type()
if structType.Kind() != reflect.Struct {
return
}
for i := 0; i < value.NumField(); i++ {
field := structType.Field(i)
fieldType := field.Type
fieldValue := value.Field(i)

name, mapped := field.Tag.Lookup("cyclonedx")
if !mapped {
continue
}

if fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
if fieldValue.IsNil() {
newValue := reflect.New(fieldType)
fieldValue.Set(newValue)
}
fieldValue = fieldValue.Elem()
}

propertyValue := findPropertyValue(c, name)
switch fieldType.Kind() {
case reflect.String:
if fieldValue.CanSet() {
fieldValue.SetString(propertyValue)
} else {
msg := fmt.Sprintf("unable to set field: %s.%s", structType.Name(), field.Name)
log.Info(msg)
}
case reflect.Bool:
if b, err := strconv.ParseBool(propertyValue); err == nil {
fieldValue.SetBool(b)
}
case reflect.Int:
if i, err := strconv.Atoi(propertyValue); err == nil {
fieldValue.SetInt(int64(i))
}
case reflect.Float32, reflect.Float64:
if i, err := strconv.ParseFloat(propertyValue, 64); err == nil {
fieldValue.SetFloat(i)
}
case reflect.Struct:
mapAllProps(c, fieldValue)
case reflect.Complex128, reflect.Complex64:
fallthrough
case reflect.Ptr:
msg := fmt.Sprintf("decoding CycloneDX properties to a pointer is not supported: %s.%s", field.Type.Name(), field.Name)
log.Warnf(msg)
}
}
}

func decodePackageMetadata(c *cyclonedx.Component) (pkg.MetadataType, interface{}) {
if c.Properties != nil {
typ := pkg.MetadataType(findPropertyValue(c, "metadataType"))
if typ != "" {
meta := reflect.New(pkg.MetadataTypeByName[typ])
metaPtr := meta.Interface()

// Map all dynamic properties
mapAllProps(c, meta.Elem())

// Map all explicit metadata properties
decodeAuthor(c.Author, metaPtr)
decodeGroup(c.Group, metaPtr)
decodePublisher(c.Publisher, metaPtr)
decodeDescription(c.Description, metaPtr)
decodeExternalReferences(c, metaPtr)

// return the actual interface{} | struct ( not interface{} | *struct )
return typ, meta.Elem().Interface()
}
}

return pkg.UnknownMetadataType, nil
}
22 changes: 20 additions & 2 deletions internal/formats/common/cyclonedxhelpers/cpe.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
package cyclonedxhelpers

import "github.com/anchore/syft/syft/pkg"
import (
"github.com/CycloneDX/cyclonedx-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
)

func CPE(p pkg.Package) string {
func encodeCPE(p pkg.Package) string {
// Since the CPEs in a package are sorted by specificity
// we can extract the first CPE as the one to output in cyclonedx
if len(p.CPEs) > 0 {
return pkg.CPEString(p.CPEs[0])
}
return ""
}

func decodeCPEs(c *cyclonedx.Component) []pkg.CPE {
// FIXME we not encoding all the CPEs (see above), so here we just use the single provided one
kzantow marked this conversation as resolved.
Show resolved Hide resolved
if c.CPE != "" {
cp, err := pkg.NewCPE(c.CPE)
if err != nil {
log.Warnf("invalid CPE: %s", c.CPE)
} else {
return []pkg.CPE{cp}
}
}

return []pkg.CPE{}
}
4 changes: 2 additions & 2 deletions internal/formats/common/cyclonedxhelpers/cpe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
)

func Test_CPE(t *testing.T) {
func Test_encodeCPE(t *testing.T) {
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
tests := []struct {
Expand Down Expand Up @@ -51,7 +51,7 @@ func Test_CPE(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, CPE(test.input))
assert.Equal(t, test.expected, encodeCPE(test.input))
})
}
}
Loading