Skip to content

Commit

Permalink
Error on conversion to unstructured for invalid json.Marshalers.
Browse files Browse the repository at this point in the history
If a type's implementation of json.Marshaler returns bytes representing a valid JSON object or array
followed by anything other than trailing whitespace, return an error rather than ignoring the
trailing data. The documentation for the Marshaler interface indicates that implementations
shouldn't do this, but it is safer to check (as json.Marshal does) than to rely on it.
  • Loading branch information
benluddy committed Jul 5, 2024
1 parent 92c1d38 commit c0ae2d4
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 22 deletions.
37 changes: 18 additions & 19 deletions value/reflectcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package value
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"sort"
"sync"
Expand Down Expand Up @@ -379,33 +381,30 @@ const maxDepth = 10000
// unmarshal unmarshals the given data
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
func unmarshal(data []byte, v interface{}) error {
// Build a decoder from the given data
decoder := json.NewDecoder(bytes.NewBuffer(data))
// Preserve numbers, rather than casting to float64 automatically
decoder.UseNumber()
// Run the decode
if err := decoder.Decode(v); err != nil {
return err
}
next := decoder.InputOffset()
if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
tail := bytes.TrimLeft(data[next:], " \t\r\n")
return fmt.Errorf("unexpected trailing data at offset %d", len(data)-len(tail))
}

// If the decode succeeds, post-process the object to convert json.Number objects to int64 or float64
switch v := v.(type) {
case *map[string]interface{}:
// Build a decoder from the given data
decoder := json.NewDecoder(bytes.NewBuffer(data))
// Preserve numbers, rather than casting to float64 automatically
decoder.UseNumber()
// Run the decode
if err := decoder.Decode(v); err != nil {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertMapNumbers(*v, 0)

case *[]interface{}:
// Build a decoder from the given data
decoder := json.NewDecoder(bytes.NewBuffer(data))
// Preserve numbers, rather than casting to float64 automatically
decoder.UseNumber()
// Run the decode
if err := decoder.Decode(v); err != nil {
return err
}
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
return convertSliceNumbers(*v, 0)

default:
return json.Unmarshal(data, v)
return nil
}
}

Expand Down
19 changes: 16 additions & 3 deletions value/reflectcache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ func (t Time) ToUnstructured() interface{} {

func TestToUnstructured(t *testing.T) {
testcases := []struct {
Data string
Expected interface{}
Data string
Expected interface{}
ExpectedErrorMessage string
}{
{Data: `null`, Expected: nil},
{Data: `true`, Expected: true},
Expand All @@ -69,6 +70,12 @@ func TestToUnstructured(t *testing.T) {
{Data: `{"a":1}`, Expected: map[string]interface{}{"a": int64(1)}},
{Data: `0`, Expected: int64(0)},
{Data: `0.0`, Expected: float64(0)},
{Data: "{} \t\r\n", Expected: map[string]interface{}{}},
{Data: "{} \t\r\n}", ExpectedErrorMessage: "error decoding object from json: unexpected trailing data at offset 6"},
{Data: "{} \t\r\n{}", ExpectedErrorMessage: "error decoding object from json: unexpected trailing data at offset 6"},
{Data: "[] \t\r\n", Expected: []interface{}{}},
{Data: "[] \t\r\n]", ExpectedErrorMessage: "error decoding array from json: unexpected trailing data at offset 6"},
{Data: "[] \t\r\n[]", ExpectedErrorMessage: "error decoding array from json: unexpected trailing data at offset 6"},
}

for _, tc := range testcases {
Expand All @@ -84,7 +91,13 @@ func TestToUnstructured(t *testing.T) {
rv := reflect.ValueOf(custom)
result, err := TypeReflectEntryOf(rv.Type()).ToUnstructured(rv)
if err != nil {
t.Fatal(err)
if tc.ExpectedErrorMessage == "" {
t.Fatal(err)
} else if got := err.Error(); got != tc.ExpectedErrorMessage {
t.Fatalf("expected error message %q but got %q", tc.ExpectedErrorMessage, got)
}
} else if tc.ExpectedErrorMessage != "" {
t.Fatalf("expected error message %q but got nil error", tc.ExpectedErrorMessage)
}
if !reflect.DeepEqual(result, tc.Expected) {
t.Errorf("expected %#v but got %#v", tc.Expected, result)
Expand Down

0 comments on commit c0ae2d4

Please sign in to comment.