Skip to content

Commit

Permalink
Add CycloneDX decoder (anchore#811)
Browse files Browse the repository at this point in the history
  • Loading branch information
kzantow authored Feb 18, 2022
1 parent 4be72fd commit 6022813
Show file tree
Hide file tree
Showing 34 changed files with 1,027 additions and 66 deletions.
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" {
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
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

0 comments on commit 6022813

Please sign in to comment.