Skip to content

Commit f07895d

Browse files
committed
Error on conversion to unstructured for invalid json.Marshalers.
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.
1 parent 92c1d38 commit f07895d

File tree

2 files changed

+191
-22
lines changed

2 files changed

+191
-22
lines changed

Diff for: value/reflectcache.go

+34-19
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package value
1919
import (
2020
"bytes"
2121
"encoding/json"
22+
"errors"
2223
"fmt"
24+
"io"
2325
"reflect"
2426
"sort"
2527
"sync"
@@ -379,34 +381,47 @@ const maxDepth = 10000
379381
// unmarshal unmarshals the given data
380382
// If v is a *map[string]interface{}, numbers are converted to int64 or float64
381383
func unmarshal(data []byte, v interface{}) error {
384+
// Build a decoder from the given data
385+
decoder := json.NewDecoder(bytes.NewBuffer(data))
386+
// Preserve numbers, rather than casting to float64 automatically
387+
decoder.UseNumber()
388+
// Run the decode
389+
if err := decoder.Decode(v); err != nil {
390+
return err
391+
}
392+
next := decoder.InputOffset()
393+
if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
394+
tail := bytes.TrimLeft(data[next:], " \t\r\n")
395+
return fmt.Errorf("unexpected trailing data at offset %d", len(data)-len(tail))
396+
}
397+
398+
// If the decode succeeds, post-process the object to convert json.Number objects to int64 or float64
382399
switch v := v.(type) {
383400
case *map[string]interface{}:
384-
// Build a decoder from the given data
385-
decoder := json.NewDecoder(bytes.NewBuffer(data))
386-
// Preserve numbers, rather than casting to float64 automatically
387-
decoder.UseNumber()
388-
// Run the decode
389-
if err := decoder.Decode(v); err != nil {
390-
return err
391-
}
392-
// If the decode succeeds, post-process the map to convert json.Number objects to int64 or float64
393401
return convertMapNumbers(*v, 0)
394402

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

406+
case *interface{}:
407+
return convertInterfaceNumbers(v, 0)
408+
407409
default:
408-
return json.Unmarshal(data, v)
410+
return nil
411+
}
412+
}
413+
414+
func convertInterfaceNumbers(v *interface{}, depth int) error {
415+
var err error
416+
switch v2 := (*v).(type) {
417+
case json.Number:
418+
*v, err = convertNumber(v2)
419+
case map[string]interface{}:
420+
err = convertMapNumbers(v2, depth+1)
421+
case []interface{}:
422+
err = convertSliceNumbers(v2, depth+1)
409423
}
424+
return err
410425
}
411426

412427
// convertMapNumbers traverses the map, converting any json.Number values to int64 or float64.

Diff for: value/reflectcache_test.go

+157-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package value
1818

1919
import (
20+
"encoding/json"
21+
"fmt"
2022
"reflect"
2123
"testing"
2224
"time"
@@ -57,8 +59,9 @@ func (t Time) ToUnstructured() interface{} {
5759

5860
func TestToUnstructured(t *testing.T) {
5961
testcases := []struct {
60-
Data string
61-
Expected interface{}
62+
Data string
63+
Expected interface{}
64+
ExpectedErrorMessage string
6265
}{
6366
{Data: `null`, Expected: nil},
6467
{Data: `true`, Expected: true},
@@ -69,6 +72,12 @@ func TestToUnstructured(t *testing.T) {
6972
{Data: `{"a":1}`, Expected: map[string]interface{}{"a": int64(1)}},
7073
{Data: `0`, Expected: int64(0)},
7174
{Data: `0.0`, Expected: float64(0)},
75+
{Data: "{} \t\r\n", Expected: map[string]interface{}{}},
76+
{Data: "{} \t\r\n}", ExpectedErrorMessage: "error decoding object from json: unexpected trailing data at offset 6"},
77+
{Data: "{} \t\r\n{}", ExpectedErrorMessage: "error decoding object from json: unexpected trailing data at offset 6"},
78+
{Data: "[] \t\r\n", Expected: []interface{}{}},
79+
{Data: "[] \t\r\n]", ExpectedErrorMessage: "error decoding array from json: unexpected trailing data at offset 6"},
80+
{Data: "[] \t\r\n[]", ExpectedErrorMessage: "error decoding array from json: unexpected trailing data at offset 6"},
7281
}
7382

7483
for _, tc := range testcases {
@@ -84,7 +93,13 @@ func TestToUnstructured(t *testing.T) {
8493
rv := reflect.ValueOf(custom)
8594
result, err := TypeReflectEntryOf(rv.Type()).ToUnstructured(rv)
8695
if err != nil {
87-
t.Fatal(err)
96+
if tc.ExpectedErrorMessage == "" {
97+
t.Fatal(err)
98+
} else if got := err.Error(); got != tc.ExpectedErrorMessage {
99+
t.Fatalf("expected error message %q but got %q", tc.ExpectedErrorMessage, got)
100+
}
101+
} else if tc.ExpectedErrorMessage != "" {
102+
t.Fatalf("expected error message %q but got nil error", tc.ExpectedErrorMessage)
88103
}
89104
if !reflect.DeepEqual(result, tc.Expected) {
90105
t.Errorf("expected %#v but got %#v", tc.Expected, result)
@@ -199,3 +214,142 @@ func TestTypeReflectEntryOf(t *testing.T) {
199214
})
200215
}
201216
}
217+
218+
func TestUnmarshal(t *testing.T) {
219+
for _, tc := range []struct {
220+
JSON string
221+
IntoType reflect.Type
222+
Want interface{}
223+
WantError bool
224+
}{
225+
{
226+
JSON: "{}}",
227+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
228+
Want: map[string]interface{}{},
229+
WantError: true,
230+
},
231+
{
232+
JSON: `1.0`,
233+
IntoType: reflect.TypeOf(json.Number("")),
234+
Want: json.Number("1.0"),
235+
},
236+
{
237+
JSON: `1`,
238+
IntoType: reflect.TypeOf(json.Number("")),
239+
Want: json.Number("1"),
240+
},
241+
{
242+
JSON: `1.0`,
243+
IntoType: reflect.TypeOf(float64(0)),
244+
Want: float64(1),
245+
},
246+
{
247+
JSON: `1`,
248+
IntoType: reflect.TypeOf(float64(0)),
249+
Want: float64(1),
250+
},
251+
{
252+
JSON: `1.0`,
253+
IntoType: reflect.TypeOf(int64(0)),
254+
Want: int64(0),
255+
WantError: true,
256+
},
257+
{
258+
JSON: `1`,
259+
IntoType: reflect.TypeOf(int64(0)),
260+
Want: int64(1),
261+
},
262+
{
263+
JSON: `1.0`,
264+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
265+
Want: float64(1),
266+
},
267+
{
268+
JSON: `1`,
269+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
270+
Want: int64(1),
271+
},
272+
{
273+
JSON: `[1.0,[1.0],{"":1.0}]`,
274+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
275+
Want: []interface{}{
276+
float64(1),
277+
[]interface{}{float64(1)},
278+
map[string]interface{}{"": float64(1)},
279+
},
280+
},
281+
{
282+
JSON: `[1.0,[1.0],{"":1.0}]`,
283+
IntoType: reflect.TypeOf([]interface{}{}),
284+
Want: []interface{}{
285+
float64(1),
286+
[]interface{}{float64(1)},
287+
map[string]interface{}{"": float64(1)},
288+
},
289+
},
290+
{
291+
JSON: `[1,[1],{"":1}]`,
292+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
293+
Want: []interface{}{
294+
int64(1),
295+
[]interface{}{int64(1)},
296+
map[string]interface{}{"": int64(1)},
297+
},
298+
},
299+
{
300+
JSON: `[1,[1],{"":1}]`,
301+
IntoType: reflect.TypeOf([]interface{}{}),
302+
Want: []interface{}{
303+
int64(1),
304+
[]interface{}{int64(1)},
305+
map[string]interface{}{"": int64(1)},
306+
},
307+
},
308+
{
309+
JSON: `{"x":1.0,"y":[1.0],"z":{"":1.0}}`,
310+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
311+
Want: map[string]interface{}{
312+
"x": float64(1),
313+
"y": []interface{}{float64(1)},
314+
"z": map[string]interface{}{"": float64(1)},
315+
},
316+
},
317+
{
318+
JSON: `{"x":1.0,"y":[1.0],"z":{"":1.0}}`,
319+
IntoType: reflect.TypeOf(map[string]interface{}{}),
320+
Want: map[string]interface{}{
321+
"x": float64(1),
322+
"y": []interface{}{float64(1)},
323+
"z": map[string]interface{}{"": float64(1)},
324+
},
325+
},
326+
{
327+
JSON: `{"x":1,"y":[1],"z":{"":1}}`,
328+
IntoType: reflect.TypeOf([0]interface{}{}).Elem(),
329+
Want: map[string]interface{}{
330+
"x": int64(1),
331+
"y": []interface{}{int64(1)},
332+
"z": map[string]interface{}{"": int64(1)},
333+
},
334+
},
335+
{
336+
JSON: `{"x":1,"y":[1],"z":{"":1}}`,
337+
IntoType: reflect.TypeOf(map[string]interface{}{}),
338+
Want: map[string]interface{}{
339+
"x": int64(1),
340+
"y": []interface{}{int64(1)},
341+
"z": map[string]interface{}{"": int64(1)},
342+
},
343+
},
344+
} {
345+
t.Run(fmt.Sprintf("%s into %v", tc.JSON, reflect.PointerTo(tc.IntoType)), func(t *testing.T) {
346+
into := reflect.New(tc.IntoType)
347+
if err := unmarshal([]byte(tc.JSON), into.Interface()); tc.WantError != (err != nil) {
348+
t.Fatalf("unexpected error: %v", err)
349+
}
350+
if got := into.Elem().Interface(); !reflect.DeepEqual(tc.Want, got) {
351+
t.Errorf("want %#v (%T), got %#v (%T)", tc.Want, tc.Want, got, got)
352+
}
353+
})
354+
}
355+
}

0 commit comments

Comments
 (0)