Skip to content

Commit fe81c11

Browse files
authored
Merge pull request #645 from fxamacker/fxamacker/port-pr-636-to-master-branch
Port updated error handling in RawTag.UnmarshalCBOR(), etc. to match cbor.Unmarshal()
2 parents d34c8ec + d81ecdd commit fe81c11

6 files changed

Lines changed: 400 additions & 1 deletion

File tree

bytestring.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ func (bs *ByteString) UnmarshalCBOR(data []byte) error {
5252

5353
d := decoder{data: data, dm: defaultDecMode}
5454

55+
// Check well-formedness of CBOR data item.
56+
// NOTE: well-formedness check here is redundant when
57+
// Unmarshal() invokes ByteString.UnmarshalCBOR().
58+
// However, ByteString.UnmarshalCBOR() is exported, so
59+
// the codec needs to support same behavior for:
60+
// - Unmarshal(data, *ByteString)
61+
// - ByteString.UnmarshalCBOR(data)
62+
err := d.wellformed(false, false)
63+
if err != nil {
64+
return err
65+
}
66+
67+
// Restore decoder offset after well-formedness check.
68+
d.off = 0
69+
5570
// Check if CBOR data type is byte string
5671
if typ := d.nextCBORType(); typ != cborTypeByteString {
5772
return &UnmarshalTypeError{CBORType: typ.String(), GoType: typeByteString.String()}

bytestring_test.go

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
package cbor
55

6-
import "testing"
6+
import (
7+
"io"
8+
"strings"
9+
"testing"
10+
)
711

812
func TestByteString(t *testing.T) {
913
type s1 struct {
@@ -99,3 +103,110 @@ func TestByteString(t *testing.T) {
99103
dm, _ := DecOptions{}.DecMode()
100104
testRoundTrip(t, testCases, em, dm)
101105
}
106+
107+
func TestUnmarshalByteStringOnBadData(t *testing.T) {
108+
testCases := []struct {
109+
name string
110+
data []byte
111+
errMsg string
112+
}{
113+
// Empty data
114+
{
115+
name: "nil data",
116+
data: nil,
117+
errMsg: io.EOF.Error(),
118+
},
119+
{
120+
name: "empty data",
121+
data: []byte{},
122+
errMsg: io.EOF.Error(),
123+
},
124+
125+
// Wrong CBOR types
126+
{
127+
name: "uint type",
128+
data: hexDecode("01"),
129+
errMsg: "cbor: cannot unmarshal positive integer into Go value of type cbor.ByteString",
130+
},
131+
{
132+
name: "int type",
133+
data: hexDecode("20"),
134+
errMsg: "cbor: cannot unmarshal negative integer into Go value of type cbor.ByteString",
135+
},
136+
{
137+
name: "string type",
138+
data: hexDecode("60"),
139+
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type cbor.ByteString",
140+
},
141+
{
142+
name: "array type",
143+
data: hexDecode("80"),
144+
errMsg: "cbor: cannot unmarshal array into Go value of type cbor.ByteString",
145+
},
146+
{
147+
name: "map type",
148+
data: hexDecode("a0"),
149+
errMsg: "cbor: cannot unmarshal map into Go value of type cbor.ByteString",
150+
},
151+
{
152+
name: "tag type",
153+
data: hexDecode("c074323031332d30332d32315432303a30343a30305a"),
154+
errMsg: "cbor: cannot unmarshal tag into Go value of type cbor.ByteString",
155+
},
156+
{
157+
name: "float type",
158+
data: hexDecode("f90000"),
159+
errMsg: "cbor: cannot unmarshal primitives into Go value of type cbor.ByteString",
160+
},
161+
162+
// Truncated CBOR data
163+
{
164+
name: "truncated head",
165+
data: hexDecode("18"),
166+
errMsg: io.ErrUnexpectedEOF.Error(),
167+
},
168+
169+
// Truncated CBOR byte string
170+
{
171+
name: "truncated byte string",
172+
data: hexDecode("44010203"),
173+
errMsg: io.ErrUnexpectedEOF.Error(),
174+
},
175+
176+
// Extraneous CBOR data
177+
{
178+
name: "extraneous data",
179+
data: hexDecode("c074323031332d30332d32315432303a30343a30305a00"),
180+
errMsg: "cbor: 1 bytes of extraneous data starting at index 22",
181+
},
182+
}
183+
184+
for _, tc := range testCases {
185+
t.Run(tc.name, func(t *testing.T) {
186+
// Test ByteString.UnmarshalCBOR(data)
187+
{
188+
var v ByteString
189+
190+
err := v.UnmarshalCBOR(tc.data)
191+
if err == nil {
192+
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
193+
}
194+
if !strings.HasPrefix(err.Error(), tc.errMsg) {
195+
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
196+
}
197+
}
198+
// Test Unmarshal(data, *ByteString), which calls ByteString.UnmarshalCBOR() under the hood
199+
{
200+
var v ByteString
201+
202+
err := Unmarshal(tc.data, &v)
203+
if err == nil {
204+
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
205+
}
206+
if !strings.HasPrefix(err.Error(), tc.errMsg) {
207+
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
208+
}
209+
}
210+
})
211+
}
212+
}

simplevalue.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
5555

5656
d := decoder{data: data, dm: defaultDecMode}
5757

58+
// Check well-formedness of CBOR data item.
59+
// NOTE: well-formedness check here is redundant when
60+
// Unmarshal() invokes SimpleValue.UnmarshalCBOR().
61+
// However, SimpleValue.UnmarshalCBOR() is exported, so
62+
// the codec needs to support same behavior for:
63+
// - Unmarshal(data, *SimpleValue)
64+
// - SimpleValue.UnmarshalCBOR(data)
65+
err := d.wellformed(false, false)
66+
if err != nil {
67+
return err
68+
}
69+
70+
// Restore decoder offset after well-formedness check.
71+
d.off = 0
72+
5873
typ, ai, val := d.getHead()
5974

6075
if typ != cborTypePrimitives {

simplevalue_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ package cbor
55

66
import (
77
"bytes"
8+
"io"
89
"reflect"
10+
"strings"
911
"testing"
1012
)
1113

@@ -51,6 +53,125 @@ func TestUnmarshalSimpleValue(t *testing.T) {
5153
})
5254
}
5355

56+
func TestUnmarshalSimpleValueOnBadData(t *testing.T) {
57+
testCases := []struct {
58+
name string
59+
data []byte
60+
errMsg string
61+
}{
62+
// Empty data
63+
{
64+
name: "nil data",
65+
data: nil,
66+
errMsg: io.EOF.Error(),
67+
},
68+
{
69+
name: "empty data",
70+
data: []byte{},
71+
errMsg: io.EOF.Error(),
72+
},
73+
74+
// Wrong CBOR types
75+
{
76+
name: "uint type",
77+
data: hexDecode("01"),
78+
errMsg: "cbor: cannot unmarshal positive integer into Go value of type SimpleValue",
79+
},
80+
{
81+
name: "int type",
82+
data: hexDecode("20"),
83+
errMsg: "cbor: cannot unmarshal negative integer into Go value of type SimpleValue",
84+
},
85+
{
86+
name: "byte string type",
87+
data: hexDecode("40"),
88+
errMsg: "cbor: cannot unmarshal byte string into Go value of type SimpleValue",
89+
},
90+
{
91+
name: "string type",
92+
data: hexDecode("60"),
93+
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type SimpleValue",
94+
},
95+
{
96+
name: "array type",
97+
data: hexDecode("80"),
98+
errMsg: "cbor: cannot unmarshal array into Go value of type SimpleValue",
99+
},
100+
{
101+
name: "map type",
102+
data: hexDecode("a0"),
103+
errMsg: "cbor: cannot unmarshal map into Go value of type SimpleValue",
104+
},
105+
{
106+
name: "tag type",
107+
data: hexDecode("c074323031332d30332d32315432303a30343a30305a"),
108+
errMsg: "cbor: cannot unmarshal tag into Go value of type SimpleValue",
109+
},
110+
{
111+
name: "float type",
112+
data: hexDecode("f90000"),
113+
errMsg: "cbor: cannot unmarshal primitives into Go value of type SimpleValue",
114+
},
115+
116+
// Truncated CBOR data
117+
{
118+
name: "truncated head",
119+
data: hexDecode("18"),
120+
errMsg: io.ErrUnexpectedEOF.Error(),
121+
},
122+
123+
// Truncated CBOR simple value
124+
{
125+
name: "truncated simple value",
126+
data: hexDecode("f8"),
127+
errMsg: io.ErrUnexpectedEOF.Error(),
128+
},
129+
130+
// Invalid simple value
131+
{
132+
name: "invalid simple value",
133+
data: hexDecode("f800"),
134+
errMsg: "cbor: invalid simple value 0 for type primitives",
135+
},
136+
137+
// Extraneous CBOR data
138+
{
139+
name: "extraneous data",
140+
data: hexDecode("f4f5"),
141+
errMsg: "cbor: 1 bytes of extraneous data starting at index 1",
142+
},
143+
}
144+
145+
for _, tc := range testCases {
146+
t.Run(tc.name, func(t *testing.T) {
147+
// Test SimpleValue.UnmarshalCBOR(data)
148+
{
149+
var v SimpleValue
150+
151+
err := v.UnmarshalCBOR(tc.data)
152+
if err == nil {
153+
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
154+
}
155+
if !strings.HasPrefix(err.Error(), tc.errMsg) {
156+
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
157+
}
158+
}
159+
// Test Unmarshal(data, *SimpleValue), which calls SimpleValue.UnmarshalCBOR() under the hood
160+
{
161+
var v SimpleValue
162+
163+
err := Unmarshal(tc.data, &v)
164+
if err == nil {
165+
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
166+
}
167+
if !strings.HasPrefix(err.Error(), tc.errMsg) {
168+
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
169+
}
170+
}
171+
})
172+
}
173+
}
174+
54175
func testUnmarshalInvalidSimpleValueToEmptyInterface(t *testing.T, data []byte) {
55176
var v any
56177
if err := Unmarshal(data, v); err == nil {

tag.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ func (t *RawTag) UnmarshalCBOR(data []byte) error {
3838

3939
d := decoder{data: data, dm: defaultDecMode}
4040

41+
// Check if data is a well-formed CBOR data item.
42+
// NOTE: well-formedness check here is redundant when
43+
// Unmarshal() invokes RawTag.UnmarshalCBOR().
44+
// However, RawTag.UnmarshalCBOR() is exported, so
45+
// the codec needs to support same behavior for:
46+
// - Unmarshal(data, *RawTag)
47+
// - RawTag.UnmarshalCBOR(data)
48+
err := d.wellformed(false, false)
49+
if err != nil {
50+
return err
51+
}
52+
53+
// Restore decoder offset after well-formedness check.
54+
d.off = 0
55+
4156
// Unmarshal tag number.
4257
typ, _, num := d.getHead()
4358
if typ != cborTypeTag {

0 commit comments

Comments
 (0)