From 0cabb4afed56ab0cd5a48381cb4f52d74ae6437f Mon Sep 17 00:00:00 2001 From: Ben Luddy Date: Fri, 31 May 2024 16:03:22 -0400 Subject: [PATCH] Disable conflicting encode options when marshaling cbor.Tag. Encode options, especially those that control the mapping from Go type to CBOR type, can result in output containing tag validity errors. For tag numbers that are built in, it's possible to "do the right thing" and override those options on a case-by-case basis. This can't and does not prevent tag validity errors for unrecognized tag numbers. Signed-off-by: Ben Luddy --- encode.go | 15 ++++++++++++++- tag.go | 4 +++- tag_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/encode.go b/encode.go index b45147f..ebef78f 100644 --- a/encode.go +++ b/encode.go @@ -1648,8 +1648,21 @@ func encodeTag(e *bytes.Buffer, em *encMode, v reflect.Value) error { // Marshal tag number encodeHead(e, byte(cborTypeTag), t.Number) + vem := *em // shallow copy + + // For built-in tags, disable settings that may introduce tag validity errors when + // marshaling certain Content values. + switch t.Number { + case tagNumRFC3339Time: + vem.stringType = StringToTextString + vem.stringMajorType = cborTypeTextString + case tagNumUnsignedBignum, tagNumNegativeBignum: + vem.byteSlice = ByteSliceToByteString + vem.byteSliceEncodingTag = 0 + } + // Marshal tag content - return encode(e, em, reflect.ValueOf(t.Content)) + return encode(e, &vem, reflect.ValueOf(t.Content)) } // encodeHead writes CBOR head of specified type t and returns number of bytes written. diff --git a/tag.go b/tag.go index 4c9c99f..5c4d2b7 100644 --- a/tag.go +++ b/tag.go @@ -7,7 +7,9 @@ import ( "sync" ) -// Tag represents CBOR tag data, including tag number and unmarshaled tag content. +// Tag represents CBOR tag data, including tag number and unmarshaled tag content. Marshaling and +// unmarshaling of tag content is subject to any encode and decode options that would apply to +// enclosed data item if it were to appear outside of a tag. type Tag struct { Number uint64 Content interface{} diff --git a/tag_test.go b/tag_test.go index fd9246d..f5917c4 100644 --- a/tag_test.go +++ b/tag_test.go @@ -1490,3 +1490,49 @@ func TestMarshalRawTagContainingMalformedCBORData(t *testing.T) { }) } } + +// TestEncodeBuiltinTag tests that marshaling a value of type Tag "does the right thing" when +// marshaling the enclosed data item of a built-in tag number. +func TestEncodeBuiltinTag(t *testing.T) { + for _, tc := range []struct { + name string + tag Tag + opts EncOptions + want []byte + }{ + { + name: "unsigned bignum content not enclosed in expected encoding tag", + tag: Tag{Number: tagNumUnsignedBignum, Content: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + opts: EncOptions{ByteSlice: ByteSliceExpectedEncodingBase16}, + want: []byte{0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + }, + { + name: "negative bignum content not enclosed in expected encoding tag", + tag: Tag{Number: tagNumNegativeBignum, Content: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + opts: EncOptions{ByteSlice: ByteSliceExpectedEncodingBase16}, + want: []byte{0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + }, + { + name: "rfc 3339 content is not encoded as byte string", + tag: Tag{Number: tagNumRFC3339Time, Content: "2013-03-21T20:04:00Z"}, + opts: EncOptions{String: StringToByteString}, + want: hexDecode("c074323031332d30332d32315432303a30343a30305a"), + }, + } { + t.Run(tc.name, func(t *testing.T) { + em, err := tc.opts.EncMode() + if err != nil { + t.Fatal(err) + } + + got, err := em.Marshal(tc.tag) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(got, tc.want) { + t.Errorf("unexpected difference\ngot: 0x%x\nwant: 0x%x", got, tc.want) + } + }) + } +}