Skip to content

Commit

Permalink
Merge pull request #400 from fxamacker/fxamacker/add-wellformed-function
Browse files Browse the repository at this point in the history
Deprecate Valid() and add Wellformed() to replace it
  • Loading branch information
fxamacker authored May 1, 2023
2 parents 6dfffb1 + c9e9b26 commit 3343ed2
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 48 deletions.
82 changes: 73 additions & 9 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,32 @@ func Unmarshal(data []byte, v interface{}) error {
return defaultDecMode.Unmarshal(data, v)
}

// Valid checks whether the CBOR data is complete and well-formed.
// Valid checks whether data is a well-formed encoded CBOR data item and
// that it complies with default restrictions such as MaxNestedLevels,
// MaxArrayElements, MaxMapPairs, etc.
//
// If there are any remaining bytes after the CBOR data item,
// an ExtraneousDataError is returned.
//
// WARNING: Valid doesn't check if encoded CBOR data item is valid (i.e. validity)
// and RFC 8949 distinctly defines what is "Valid" and what is "Well-formed".
//
// Deprecated: Valid is kept for compatibility and should not be used.
// Use Wellformed instead because it has a more appropriate name.
func Valid(data []byte) error {
return defaultDecMode.Valid(data)
}

// Wellformed checks whether data is a well-formed encoded CBOR data item and
// that it complies with default restrictions such as MaxNestedLevels,
// MaxArrayElements, MaxMapPairs, etc.
//
// If there are any remaining bytes after the CBOR data item,
// an ExtraneousDataError is returned.
func Wellformed(data []byte) error {
return defaultDecMode.Wellformed(data)
}

// Unmarshaler is the interface implemented by types that wish to unmarshal
// CBOR data themselves. The input is a valid CBOR value. UnmarshalCBOR
// must copy the CBOR data if it needs to use it after returning.
Expand Down Expand Up @@ -499,10 +520,32 @@ type DecMode interface {
//
// See the documentation for Unmarshal for details.
Unmarshal(data []byte, v interface{}) error
// Valid checks whether the CBOR data is complete and well-formed.

// Valid checks whether data is a well-formed encoded CBOR data item and
// that it complies with configurable restrictions such as MaxNestedLevels,
// MaxArrayElements, MaxMapPairs, etc.
//
// If there are any remaining bytes after the CBOR data item,
// an ExtraneousDataError is returned.
//
// WARNING: Valid doesn't check if encoded CBOR data item is valid (i.e. validity)
// and RFC 8949 distinctly defines what is "Valid" and what is "Well-formed".
//
// Deprecated: Valid is kept for compatibility and should not be used.
// Use Wellformed instead because it has a more appropriate name.
Valid(data []byte) error

// Wellformed checks whether data is a well-formed encoded CBOR data item and
// that it complies with configurable restrictions such as MaxNestedLevels,
// MaxArrayElements, MaxMapPairs, etc.
//
// If there are any remaining bytes after the CBOR data item,
// an ExtraneousDataError is returned.
Wellformed(data []byte) error

// NewDecoder returns a new decoder that reads from r using dm DecMode.
NewDecoder(r io.Reader) *Decoder

// DecOptions returns user specified options used to create this DecMode.
DecOptions() DecOptions
}
Expand Down Expand Up @@ -550,21 +593,42 @@ func (dm *decMode) DecOptions() DecOptions {
func (dm *decMode) Unmarshal(data []byte, v interface{}) error {
d := decoder{data: data, dm: dm}

// check valid
off := d.off // Save offset before data validation
err := d.valid(false) // don't allow any extra data after valid data item.
d.off = off // Restore offset
// Check well-formedness.
off := d.off // Save offset before data validation
err := d.wellformed(false) // don't allow any extra data after valid data item.
d.off = off // Restore offset
if err != nil {
return err
}

return d.value(v)
}

// Valid checks whether the CBOR data is complete and well-formed.
// Valid checks whether data is a well-formed encoded CBOR data item and
// that it complies with configurable restrictions such as MaxNestedLevels,
// MaxArrayElements, MaxMapPairs, etc.
//
// If there are any remaining bytes after the CBOR data item,
// an ExtraneousDataError is returned.
//
// WARNING: Valid doesn't check if encoded CBOR data item is valid (i.e. validity)
// and RFC 8949 distinctly defines what is "Valid" and what is "Well-formed".
//
// Deprecated: Valid is kept for compatibility and should not be used.
// Use Wellformed instead because it has a more appropriate name.
func (dm *decMode) Valid(data []byte) error {
return dm.Wellformed(data)
}

// Wellformed checks whether data is a well-formed encoded CBOR data item and
// that it complies with configurable restrictions such as MaxNestedLevels,
// MaxArrayElements, MaxMapPairs, etc.
//
// If there are any remaining bytes after the CBOR data item,
// an ExtraneousDataError is returned.
func (dm *decMode) Wellformed(data []byte) error {
d := decoder{data: data, dm: dm}
return d.valid(false)
return d.wellformed(false)
}

// NewDecoder returns a new decoder that reads from r using dm DecMode.
Expand All @@ -581,7 +645,7 @@ type decoder struct {
// value decodes CBOR data item into the value pointed to by v.
// If CBOR data item fails to be decoded into v,
// error is returned and offset is moved to the next CBOR data item.
// Precondition: d.data contains at least one valid CBOR data item.
// Precondition: d.data contains at least one well-formed CBOR data item.
func (d *decoder) value(v interface{}) error {
// v can't be nil, non-pointer, or nil pointer value.
if v == nil {
Expand Down
2 changes: 1 addition & 1 deletion stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (dec *Decoder) readNext() (int, error) {
if dec.off < len(dec.buf) {
dec.d.reset(dec.buf[dec.off:])
off := dec.off // Save offset before data validation
validErr = dec.d.valid(true)
validErr = dec.d.wellformed(true)
dec.off = off // Restore offset

if validErr == nil {
Expand Down
40 changes: 20 additions & 20 deletions valid.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (e *TagsMdError) Error() string {
return "cbor: CBOR tag isn't allowed"
}

// ExtraneousDataError indicates found extraneous data following valid CBOR message.
// ExtraneousDataError indicates found extraneous data following well-formed CBOR data item.
type ExtraneousDataError struct {
numOfBytes int // number of bytes of extraneous data
index int // location of extraneous data
Expand All @@ -78,15 +78,15 @@ func (e *ExtraneousDataError) Error() string {
return "cbor: " + strconv.Itoa(e.numOfBytes) + " bytes of extraneous data starting at index " + strconv.Itoa(e.index)
}

// valid checks whether the CBOR data is complete and well-formed.
// wellformed checks whether the CBOR data item is well-formed.
// allowExtraData indicates if extraneous data is allowed after the CBOR data item.
// - use allowExtraData = true when using Decoder.Decode()
// - use allowExtraData = false when using Unmarshal()
func (d *decoder) valid(allowExtraData bool) error {
func (d *decoder) wellformed(allowExtraData bool) error {
if len(d.data) == d.off {
return io.EOF
}
_, err := d.validInternal(0)
_, err := d.wellformedInternal(0)
if err == nil {
if !allowExtraData && d.off != len(d.data) {
err = &ExtraneousDataError{len(d.data) - d.off, d.off}
Expand All @@ -95,9 +95,9 @@ func (d *decoder) valid(allowExtraData bool) error {
return err
}

// validInternal checks data's well-formedness and returns max depth and error.
func (d *decoder) validInternal(depth int) (int, error) {
t, ai, val, err := d.validHead()
// wellformedInternal checks data's well-formedness and returns max depth and error.
func (d *decoder) wellformedInternal(depth int) (int, error) {
t, ai, val, err := d.wellformedHead()
if err != nil {
return 0, err
}
Expand All @@ -108,7 +108,7 @@ func (d *decoder) validInternal(depth int) (int, error) {
if d.dm.indefLength == IndefLengthForbidden {
return 0, &IndefiniteLengthError{t}
}
return d.validIndefiniteString(t, depth)
return d.wellformedIndefiniteString(t, depth)
}
valInt := int(val)
if valInt < 0 {
Expand All @@ -129,7 +129,7 @@ func (d *decoder) validInternal(depth int) (int, error) {
if d.dm.indefLength == IndefLengthForbidden {
return 0, &IndefiniteLengthError{t}
}
return d.validIndefiniteArrayOrMap(t, depth)
return d.wellformedIndefiniteArrayOrMap(t, depth)
}

valInt := int(val)
Expand All @@ -156,7 +156,7 @@ func (d *decoder) validInternal(depth int) (int, error) {
for j := 0; j < count; j++ {
for i := 0; i < valInt; i++ {
var dpt int
if dpt, err = d.validInternal(depth); err != nil {
if dpt, err = d.wellformedInternal(depth); err != nil {
return 0, err
}
if dpt > maxDepth {
Expand All @@ -178,7 +178,7 @@ func (d *decoder) validInternal(depth int) (int, error) {
if cborType(d.data[d.off]&0xe0) != cborTypeTag {
break
}
if _, _, _, err = d.validHead(); err != nil {
if _, _, _, err = d.wellformedHead(); err != nil {
return 0, err
}
depth++
Expand All @@ -187,13 +187,13 @@ func (d *decoder) validInternal(depth int) (int, error) {
}
}
// Check tag content.
return d.validInternal(depth)
return d.wellformedInternal(depth)
}
return depth, nil
}

// validIndefiniteString checks indefinite length byte/text string's well-formedness and returns max depth and error.
func (d *decoder) validIndefiniteString(t cborType, depth int) (int, error) {
// wellformedIndefiniteString checks indefinite length byte/text string's well-formedness and returns max depth and error.
func (d *decoder) wellformedIndefiniteString(t cborType, depth int) (int, error) {
var err error
for {
if len(d.data) == d.off {
Expand All @@ -211,15 +211,15 @@ func (d *decoder) validIndefiniteString(t cborType, depth int) (int, error) {
if (d.data[d.off] & 0x1f) == 31 {
return 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"}
}
if depth, err = d.validInternal(depth); err != nil {
if depth, err = d.wellformedInternal(depth); err != nil {
return 0, err
}
}
return depth, nil
}

// validIndefiniteArrayOrMap checks indefinite length array/map's well-formedness and returns max depth and error.
func (d *decoder) validIndefiniteArrayOrMap(t cborType, depth int) (int, error) {
// wellformedIndefiniteArrayOrMap checks indefinite length array/map's well-formedness and returns max depth and error.
func (d *decoder) wellformedIndefiniteArrayOrMap(t cborType, depth int) (int, error) {
var err error
maxDepth := depth
i := 0
Expand All @@ -232,7 +232,7 @@ func (d *decoder) validIndefiniteArrayOrMap(t cborType, depth int) (int, error)
break
}
var dpt int
if dpt, err = d.validInternal(depth); err != nil {
if dpt, err = d.wellformedInternal(depth); err != nil {
return 0, err
}
if dpt > maxDepth {
Expand All @@ -255,7 +255,7 @@ func (d *decoder) validIndefiniteArrayOrMap(t cborType, depth int) (int, error)
return maxDepth, nil
}

func (d *decoder) validHead() (t cborType, ai byte, val uint64, err error) {
func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) {
dataLen := len(d.data) - d.off
if dataLen == 0 {
return 0, 0, 0, io.ErrUnexpectedEOF
Expand Down Expand Up @@ -308,7 +308,7 @@ func (d *decoder) validHead() (t cborType, ai byte, val uint64, err error) {
switch t {
case cborTypePositiveInt, cborTypeNegativeInt, cborTypeTag:
return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
case cborTypePrimitives: // 0xff (break code) should not be outside validIndefinite().
case cborTypePrimitives: // 0xff (break code) should not be outside wellformedIndefinite().
return 0, 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
}
return t, ai, val, nil
Expand Down
36 changes: 18 additions & 18 deletions valid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ import (

func TestValid1(t *testing.T) {
for _, mt := range marshalTests {
if err := Valid(mt.cborData); err != nil {
t.Errorf("Valid() returned error %v", err)
if err := Wellformed(mt.cborData); err != nil {
t.Errorf("Wellformed() returned error %v", err)
}
}
}

func TestValid2(t *testing.T) {
for _, mt := range marshalTests {
dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
if err := dm.Valid(mt.cborData); err != nil {
t.Errorf("Valid() returned error %v", err)
if err := dm.Wellformed(mt.cborData); err != nil {
t.Errorf("Wellformed() returned error %v", err)
}
}
}
Expand All @@ -39,17 +39,17 @@ func TestValidExtraneousData(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := Valid(tc.cborData)
err := Wellformed(tc.cborData)
if err == nil {
t.Errorf("Valid(0x%x) didn't return an error", tc.cborData)
t.Errorf("Wellformed(0x%x) didn't return an error", tc.cborData)
} else {
ederr, ok := err.(*ExtraneousDataError)
if !ok {
t.Errorf("Valid(0x%x) error type %T, want *ExtraneousDataError", tc.cborData, err)
t.Errorf("Wellformed(0x%x) error type %T, want *ExtraneousDataError", tc.cborData, err)
} else if ederr.numOfBytes != tc.extraneousDataNumOfBytes {
t.Errorf("Valid(0x%x) returned %d bytes of extraneous data, want %d", tc.cborData, ederr.numOfBytes, tc.extraneousDataNumOfBytes)
t.Errorf("Wellformed(0x%x) returned %d bytes of extraneous data, want %d", tc.cborData, ederr.numOfBytes, tc.extraneousDataNumOfBytes)
} else if ederr.index != tc.extraneousDataIndex {
t.Errorf("Valid(0x%x) returned extraneous data index %d, want %d", tc.cborData, ederr.index, tc.extraneousDataIndex)
t.Errorf("Wellformed(0x%x) returned extraneous data index %d, want %d", tc.cborData, ederr.index, tc.extraneousDataIndex)
}
}
})
Expand All @@ -63,8 +63,8 @@ func TestValidOnStreamingData(t *testing.T) {
}
d := decoder{data: buf.Bytes(), dm: defaultDecMode}
for i := 0; i < len(marshalTests); i++ {
if err := d.valid(true); err != nil {
t.Errorf("valid() returned error %v", err)
if err := d.wellformed(true); err != nil {
t.Errorf("wellformed() returned error %v", err)
}
}
}
Expand Down Expand Up @@ -111,12 +111,12 @@ func TestDepth(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
d := decoder{data: tc.cborData, dm: defaultDecMode}
depth, err := d.validInternal(0)
depth, err := d.wellformedInternal(0)
if err != nil {
t.Errorf("valid(0x%x) returned error %v", tc.cborData, err)
t.Errorf("wellformed(0x%x) returned error %v", tc.cborData, err)
}
if depth != tc.wantDepth {
t.Errorf("valid(0x%x) returned depth %d, want %d", tc.cborData, depth, tc.wantDepth)
t.Errorf("wellformed(0x%x) returned depth %d, want %d", tc.cborData, depth, tc.wantDepth)
}
})
}
Expand Down Expand Up @@ -176,12 +176,12 @@ func TestDepthError(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
dm, _ := tc.opts.decMode()
d := decoder{data: tc.cborData, dm: dm}
if _, err := d.validInternal(0); err == nil {
t.Errorf("valid(0x%x) didn't return an error", tc.cborData)
if _, err := d.wellformedInternal(0); err == nil {
t.Errorf("wellformed(0x%x) didn't return an error", tc.cborData)
} else if _, ok := err.(*MaxNestedLevelError); !ok {
t.Errorf("valid(0x%x) returned wrong error type %T, want (*MaxNestedLevelError)", tc.cborData, err)
t.Errorf("wellformed(0x%x) returned wrong error type %T, want (*MaxNestedLevelError)", tc.cborData, err)
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("valid(0x%x) returned error %q, want error %q", tc.cborData, err.Error(), tc.wantErrorMsg)
t.Errorf("wellformed(0x%x) returned error %q, want error %q", tc.cborData, err.Error(), tc.wantErrorMsg)
}
})
}
Expand Down

0 comments on commit 3343ed2

Please sign in to comment.