From f3528132a7c476ceb9c28850a1307ff5b2ad3f4a Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Tue, 8 Nov 2022 18:59:37 -0500 Subject: [PATCH] Support encoding map types to CycloneDX properties (#1332) --- syft/formats/common/property_encoder.go | 55 ++++++++++++++++++++ syft/formats/common/property_encoder_test.go | 13 +++++ 2 files changed, 68 insertions(+) diff --git a/syft/formats/common/property_encoder.go b/syft/formats/common/property_encoder.go index 271a22afa..9db409425 100644 --- a/syft/formats/common/property_encoder.go +++ b/syft/formats/common/property_encoder.go @@ -85,6 +85,7 @@ func Sorted(values map[string]string) (out []NameValue) { return } +//nolint:funlen func encode(out map[string]string, value reflect.Value, prefix string, fn FieldName) { if !value.IsValid() || value.Type() == nil { return @@ -130,6 +131,11 @@ func encode(out map[string]string, value reflect.Value, prefix string, fn FieldN } encode(out, pv, name, fn) } + case reflect.Map: + // currently only map[string]string is really supported + for _, key := range value.MapKeys() { + encode(out, value.MapIndex(key), fmt.Sprintf("%s:%v", prefix, key.Interface()), fn) + } default: log.Warnf("skipping encoding of unsupported property: %s", prefix) } @@ -284,6 +290,55 @@ func decode(vals map[string]string, value reflect.Value, prefix string, fn Field } else { return false } + case reflect.Map: + values := false + keyType := typ.Key() + valueType := typ.Elem() + outMap := reflect.MakeMap(typ) + str := fmt.Sprintf("%s:", prefix) + keyVals := map[string]string{} + // iterate through all keys to find those prefixed with a reference to this map + // NOTE: this will not work for nested maps + for key := range vals { + // test for map prefix + if strings.HasPrefix(key, str) { + keyVals[key] = strings.TrimPrefix(key, str) + // create new placeholder and decode key + newKeyType := keyType + if keyType.Kind() == reflect.Ptr { + newKeyType = keyType.Elem() + } + k := reflect.New(newKeyType) + if !decode(keyVals, k.Elem(), key, fn) { + log.Debugf("unable to decode key for: %s", key) + continue + } + if keyType.Kind() != reflect.Ptr { + k = k.Elem() + } + + // create new placeholder and decode value + newValueType := valueType + if valueType.Kind() == reflect.Ptr { + newValueType = valueType.Elem() + } + v := reflect.New(newValueType) + if decode(vals, v.Elem(), key, fn) { + if valueType.Kind() != reflect.Ptr { + v = v.Elem() + } + + // set in map + outMap.SetMapIndex(k, v) + values = true + } + } + } + if values { + value.Set(outMap) + } else { + return false + } case reflect.Struct: values := false for i := 0; i < typ.NumField(); i++ { diff --git a/syft/formats/common/property_encoder_test.go b/syft/formats/common/property_encoder_test.go index 2ff63a1f2..5a008cd0d 100644 --- a/syft/formats/common/property_encoder_test.go +++ b/syft/formats/common/property_encoder_test.go @@ -37,6 +37,10 @@ type T4 struct { IntPtr *int } +type T5 struct { + Map map[string]string +} + func Test_EncodeDecodeCycle(t *testing.T) { val := 99 @@ -118,6 +122,15 @@ func Test_EncodeDecodeCycle(t *testing.T) { {"t2 elem 1"}, }, }, + { + name: "map of strings", + value: &T5{ + Map: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + }, } for _, test := range tests {