fix apk decode for older data shapes (#1341)

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2022-11-15 11:38:31 -05:00 committed by GitHub
parent 09bf5b062c
commit 1ae577a035
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 213 additions and 0 deletions

View File

@ -1,8 +1,13 @@
package pkg package pkg
import ( import (
"encoding/json"
"fmt"
"reflect"
"sort" "sort"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/scylladb/go-set/strset" "github.com/scylladb/go-set/strset"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
@ -35,6 +40,58 @@ type ApkMetadata struct {
Files []ApkFileRecord `json:"files"` 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). // ApkFileRecord represents a single file listing and metadata from a APK DB entry (which may have many of these file records).
type ApkFileRecord struct { type ApkFileRecord struct {
Path string `json:"path"` Path string `json:"path"`

View File

@ -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 <ncopa@alpinelinux.org>",
"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 <ncopa@alpinelinux.org>",
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 <ncopa@alpinelinux.org>",
"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 <ncopa@alpinelinux.org>",
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))
})
}
}