From f8be64d3123d0e6ead9eeab695f0d96965e7bf94 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Tue, 15 Nov 2022 13:00:10 -0500 Subject: [PATCH] fix: Unmarshal Syft JSON with missing metadata (#1338) --- .../common/cyclonedxhelpers/component_test.go | 32 ++++++++++++++++++- syft/formats/common/property_encoder.go | 2 +- syft/formats/syftjson/model/package.go | 6 ++-- syft/formats/syftjson/model/package_test.go | 31 ++++++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/syft/formats/common/cyclonedxhelpers/component_test.go b/syft/formats/common/cyclonedxhelpers/component_test.go index 1353c33c467..d1f7835ae3c 100644 --- a/syft/formats/common/cyclonedxhelpers/component_test.go +++ b/syft/formats/common/cyclonedxhelpers/component_test.go @@ -2,6 +2,7 @@ package cyclonedxhelpers import ( "fmt" + "reflect" "testing" "github.com/CycloneDX/cyclonedx-go" @@ -200,6 +201,7 @@ func Test_decodeComponent(t *testing.T) { component cyclonedx.Component wantLanguage pkg.Language wantMetadataType pkg.MetadataType + wantMetadata interface{} }{ { name: "derive language from pURL if missing", @@ -213,7 +215,7 @@ func Test_decodeComponent(t *testing.T) { wantLanguage: pkg.Java, }, { - name: "handle existing RpmdbMetadata type", + name: "handle RpmdbMetadata type without properties", component: cyclonedx.Component{ Name: "acl", Version: "2.2.53-1.el8", @@ -228,6 +230,31 @@ func Test_decodeComponent(t *testing.T) { }, }, wantMetadataType: pkg.RpmMetadataType, + wantMetadata: pkg.RpmMetadata{}, + }, + { + name: "handle RpmdbMetadata type with properties", + component: cyclonedx.Component{ + Name: "acl", + Version: "2.2.53-1.el8", + PackageURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", + Type: "library", + BOMRef: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64&upstream=acl-2.2.53-1.el8.src.rpm&distro=centos-8", + Properties: &[]cyclonedx.Property{ + { + Name: "syft:package:metadataType", + Value: "RpmMetadata", + }, + { + Name: "syft:metadata:release", + Value: "some-release", + }, + }, + }, + wantMetadataType: pkg.RpmMetadataType, + wantMetadata: pkg.RpmMetadata{ + Release: "some-release", + }, }, } @@ -240,6 +267,9 @@ func Test_decodeComponent(t *testing.T) { if tt.wantMetadataType != "" { assert.Equal(t, tt.wantMetadataType, p.MetadataType) } + if tt.wantMetadata != nil { + assert.Truef(t, reflect.DeepEqual(tt.wantMetadata, p.Metadata), "metadata should match: %+v != %+v", tt.wantMetadata, p.Metadata) + } }) } } diff --git a/syft/formats/common/property_encoder.go b/syft/formats/common/property_encoder.go index 9db409425cd..e22772db0eb 100644 --- a/syft/formats/common/property_encoder.go +++ b/syft/formats/common/property_encoder.go @@ -364,7 +364,7 @@ func decode(vals map[string]string, value reflect.Value, prefix string, fn Field func PtrToStruct(ptr interface{}) interface{} { v := reflect.ValueOf(ptr) - if v.IsZero() { + if v.IsZero() && v.Type().Kind() != reflect.Struct { return nil } switch v.Type().Kind() { diff --git a/syft/formats/syftjson/model/package.go b/syft/formats/syftjson/model/package.go index 67d9cc8b84b..4567a139aeb 100644 --- a/syft/formats/syftjson/model/package.go +++ b/syft/formats/syftjson/model/package.go @@ -78,8 +78,10 @@ func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error { typ, ok := pkg.MetadataTypeByName[p.MetadataType] if ok { val := reflect.New(typ).Interface() - if err := json.Unmarshal(unpacker.Metadata, val); err != nil { - return err + if len(unpacker.Metadata) > 0 { + if err := json.Unmarshal(unpacker.Metadata, val); err != nil { + return err + } } p.Metadata = reflect.ValueOf(val).Elem().Interface() return nil diff --git a/syft/formats/syftjson/model/package_test.go b/syft/formats/syftjson/model/package_test.go index eb57dbbc0da..34677b20bf2 100644 --- a/syft/formats/syftjson/model/package_test.go +++ b/syft/formats/syftjson/model/package_test.go @@ -212,6 +212,37 @@ func Test_unpackMetadata(t *testing.T) { "thing": "thing-1", }, }, + { + name: "can handle package with metadata type but missing metadata", + packageData: []byte(`{ + "metadataType": "GolangBinMetadata" + }`), + metadataType: pkg.GolangBinMetadataType, + wantMetadata: pkg.GolangBinMetadata{}, + }, + { + name: "can handle package with unknonwn metadata type and missing metadata", + packageData: []byte(`{ + "metadataType": "BadMetadata" + }`), + wantErr: require.Error, + metadataType: "BadMetadata", + wantMetadata: nil, + }, + { + name: "can handle package with unknonwn metadata type and metadata", + packageData: []byte(`{ + "metadataType": "BadMetadata", + "metadata": { + "random": "thing" + } + }`), + wantErr: require.Error, + metadataType: "BadMetadata", + wantMetadata: map[string]interface{}{ + "random": "thing", + }, + }, } for _, test := range tests {