From 611667e2c9ecf0d86495ed7fe2bc6c8879e4189f Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 15 Nov 2022 11:38:31 -0500 Subject: [PATCH] fix apk decode for older data shapes (#1341) Signed-off-by: Alex Goodman Signed-off-by: Alex Goodman --- syft/pkg/apk_metadata.go | 57 +++++++++++++ syft/pkg/apk_metadata_test.go | 156 ++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 syft/pkg/apk_metadata_test.go diff --git a/syft/pkg/apk_metadata.go b/syft/pkg/apk_metadata.go index d6739f57a10..104564883ca 100644 --- a/syft/pkg/apk_metadata.go +++ b/syft/pkg/apk_metadata.go @@ -1,8 +1,13 @@ package pkg import ( + "encoding/json" + "fmt" + "reflect" "sort" + "strings" + "github.com/mitchellh/mapstructure" "github.com/scylladb/go-set/strset" "github.com/anchore/syft/syft/file" @@ -35,6 +40,58 @@ type ApkMetadata struct { Files []ApkFileRecord `json:"files"` } +type spaceDelimitedStringSlice []string + +func (m *ApkMetadata) UnmarshalJSON(data []byte) error { + var fields []reflect.StructField + t := reflect.TypeOf(ApkMetadata{}) + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.Name == "Dependencies" { + f.Type = reflect.TypeOf(spaceDelimitedStringSlice{}) + } + fields = append(fields, f) + } + apkMetadata := reflect.StructOf(fields) + inst := reflect.New(apkMetadata) + if err := json.Unmarshal(data, inst.Interface()); err != nil { + return err + } + + return mapstructure.Decode(inst.Elem().Interface(), m) +} + +func (a *spaceDelimitedStringSlice) UnmarshalJSON(data []byte) error { + var jsonObj interface{} + + if err := json.Unmarshal(data, &jsonObj); err != nil { + return err + } + + switch obj := jsonObj.(type) { + case string: + if obj == "" { + *a = nil + } else { + *a = strings.Split(obj, " ") + } + return nil + case []interface{}: + s := make([]string, 0, len(obj)) + for _, v := range obj { + value, ok := v.(string) + if !ok { + return fmt.Errorf("invalid type for string array element: %T", v) + } + s = append(s, value) + } + *a = s + return nil + default: + return fmt.Errorf("invalid type for string array: %T", obj) + } +} + // ApkFileRecord represents a single file listing and metadata from a APK DB entry (which may have many of these file records). type ApkFileRecord struct { Path string `json:"path"` diff --git a/syft/pkg/apk_metadata_test.go b/syft/pkg/apk_metadata_test.go new file mode 100644 index 00000000000..8eaa210d736 --- /dev/null +++ b/syft/pkg/apk_metadata_test.go @@ -0,0 +1,156 @@ +package pkg + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestApkMetadata_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want ApkMetadata + wantErr require.ErrorAssertionFunc + }{ + { + name: "empty", + input: "{}", + want: ApkMetadata{}, + }, + { + name: "string array dependencies", + input: `{ +"package": "scanelf", +"originPackage": "pax-utils", +"maintainer": "Natanael Copa ", +"version": "1.3.4-r0", +"license": "GPL-2.0-only", +"architecture": "x86_64", +"url": "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities", +"description": "Scan ELF binaries for stuff", +"size": 36745, +"installedSize": 94208, +"pullChecksum": "Q1Gcqe+ND8DFOlhM3R0o5KyZjR2oE=", +"gitCommitOfApkPort": "d7ae612a3cc5f827289d915783b4cbf8c7207947", +"files": [ + { + "path": "/usr" + } +], +"pullDependencies": ["foo", "bar"] +}`, + want: ApkMetadata{ + Package: "scanelf", + OriginPackage: "pax-utils", + Maintainer: "Natanael Copa ", + Version: "1.3.4-r0", + License: "GPL-2.0-only", + Architecture: "x86_64", + URL: "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities", + Description: "Scan ELF binaries for stuff", + Size: 36745, + InstalledSize: 94208, + Dependencies: []string{"foo", "bar"}, + Checksum: "Q1Gcqe+ND8DFOlhM3R0o5KyZjR2oE=", + GitCommit: "d7ae612a3cc5f827289d915783b4cbf8c7207947", + Files: []ApkFileRecord{{Path: "/usr"}}, + }, + }, + { + name: "single string dependencies", + input: `{ +"package": "scanelf", +"originPackage": "pax-utils", +"maintainer": "Natanael Copa ", +"version": "1.3.4-r0", +"license": "GPL-2.0-only", +"architecture": "x86_64", +"url": "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities", +"description": "Scan ELF binaries for stuff", +"size": 36745, +"installedSize": 94208, +"pullChecksum": "Q1Gcqe+ND8DFOlhM3R0o5KyZjR2oE=", +"gitCommitOfApkPort": "d7ae612a3cc5f827289d915783b4cbf8c7207947", +"files": [ + { + "path": "/usr" + } +], +"pullDependencies": "foo bar" +}`, + want: ApkMetadata{ + Package: "scanelf", + OriginPackage: "pax-utils", + Maintainer: "Natanael Copa ", + Version: "1.3.4-r0", + License: "GPL-2.0-only", + Architecture: "x86_64", + URL: "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities", + Description: "Scan ELF binaries for stuff", + Size: 36745, + InstalledSize: 94208, + Dependencies: []string{"foo", "bar"}, + Checksum: "Q1Gcqe+ND8DFOlhM3R0o5KyZjR2oE=", + GitCommit: "d7ae612a3cc5f827289d915783b4cbf8c7207947", + Files: []ApkFileRecord{{Path: "/usr"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + var got ApkMetadata + err := json.Unmarshal([]byte(tt.input), &got) + tt.wantErr(t, err) + if err != nil { + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestSpaceDelimitedStringSlice_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + data string + want []string + wantErr require.ErrorAssertionFunc + }{ + { + name: "empty string", + data: `""`, + want: nil, + }, + { + name: "single string with one elements", + data: `"foo"`, + want: []string{"foo"}, + }, + { + name: "single string with multiple elements", + data: `"foo bar"`, + want: []string{"foo", "bar"}, + }, + { + name: "string array", + data: `["foo", "bar"]`, + want: []string{"foo", "bar"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + element := spaceDelimitedStringSlice{} + tt.wantErr(t, element.UnmarshalJSON([]byte(tt.data))) + assert.Equal(t, tt.want, []string(element)) + }) + } +}