From 15852305e6fcf03dba2376412e9608921fba7ae4 Mon Sep 17 00:00:00 2001 From: nitely Date: Tue, 25 Aug 2026 23:14:36 -0300 Subject: [PATCH 1/9] Fix missing UTF-8 string validation --- cbor_serialization/parser.nim | 15 ++++++++++++++- cbor_serialization/reader_desc.nim | 13 +++++++++++++ cbor_serialization/utils.nim | 5 ++++- tests/test_serialization.nim | 6 ++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/cbor_serialization/parser.nim b/cbor_serialization/parser.nim index 5ed762a..0649318 100644 --- a/cbor_serialization/parser.nim +++ b/cbor_serialization/parser.nim @@ -115,6 +115,9 @@ iterator parseStringLikeIt( for _ in 0 ..< strLen: yield p.read() +# String chunks must be utf-8 valid as per: +# https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3 +# https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1 proc parseStringLike[T: string or seq[byte]]( p: var CborParser, majorExpected: CborMajor, limit: int, val: var T ) {.raises: [IOError, CborReaderError].} = @@ -122,15 +125,25 @@ proc parseStringLike[T: string or seq[byte]]( val.setLen 0 var L = p.lenMaybe() var i = 0 + var i0 {.used.} = 0 parseStringLikeImpl(p, majorExpected, limit, strLen): + let pos {.used.} = p.stream.pos if L > -1: # can prealloc safely + i0 = i val.setLen val.len.uint64 + strLen for _ in 0 ..< strLen: val[i] = p.read ElmType inc i + when val is string: + if not validateUtf8(toOpenArray(val, i0, i-1)): + p.raiseInvalidUtf8(pos, "Invalid utf-8 string") else: for _ in 0 ..< strLen: - val.add p.read ElmType + val[i] = p.read ElmType + inc i + when val is string: + if not validateUtf8(toOpenArray(val, i0, i-1)): + p.raiseInvalidUtf8(pos, "Invalid utf-8 string") proc parseStringLike( p: var CborParser, majorExpected: CborMajor, limit: int, val: var CborVoid diff --git a/cbor_serialization/reader_desc.nim b/cbor_serialization/reader_desc.nim index 01f89fc..b7abb1b 100644 --- a/cbor_serialization/reader_desc.nim +++ b/cbor_serialization/reader_desc.nim @@ -44,6 +44,8 @@ type isNegative: bool absIntVal: BiggestUInt + CborInvalidUtf8Error* = object of CborReaderError + Cbor.setReader CborReader Cbor.defaultReaders() @@ -67,6 +69,9 @@ method formatMsg*(err: ref CborNotEnoughBytesError, filename: string): string = method formatMsg*(err: ref CborIncompleteObjectError, filename: string): string = fmt"{filename}({err.pos}) Not all required fields were specified when reading '{err.objectType}'" +method formatMsg*(err: ref CborInvalidUtf8Error, filename: string): string = + fmt"{filename}({err.pos}) {err.msg}" + func raiseUnexpectedValue*( p: CborParser, msg: string ) {.noreturn, raises: [CborReaderError].} = @@ -120,6 +125,14 @@ func raiseIncompleteObject*( template raiseIncompleteObject*(r: CborReader, objectType: cstring) = raiseIncompleteObject(r.parser, objectType) +func raiseInvalidUtf8*( + p: CborParser, pos: int, msg: string +) {.noreturn, raises: [CborReaderError].} = + var ex = new CborInvalidUtf8Error + ex.pos = pos + ex.msg = msg + raise ex + proc init*( T: type CborParser, stream: InputStream, diff --git a/cbor_serialization/utils.nim b/cbor_serialization/utils.nim index d46cace..b6896cf 100644 --- a/cbor_serialization/utils.nim +++ b/cbor_serialization/utils.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import std/[math] +import std/[math, unicode] proc ldexp(x: float64, exp: int): float64 = x * pow(2.0, float64(exp)) @@ -28,3 +28,6 @@ proc decodeHalf*(half: uint16): float = -val else: val + +proc validateUtf8*(s: openArray[char]): bool = + unicode.validateUtf8(s) == -1 diff --git a/tests/test_serialization.nim b/tests/test_serialization.nim index 7ea1a49..95df63f 100644 --- a/tests/test_serialization.nim +++ b/tests/test_serialization.nim @@ -891,6 +891,12 @@ suite "toCbor tests": # clarity regarding the memory allocation approach Cbor.decode("0xF6".unhex, cstring) + test "Invalid UTF-8": + const cbor = "0x63e228a1" + check Cbor.encode("\xE2\x28\xA1").hex == cbor + expect CborInvalidUtf8Error: + discard Cbor.decode(cbor.unhex, string) + suite "Custom parser tests": test "Fall back to int parser": customVisit = TokenRegistry.default From 24892fcb8d46296046d4f76c3e07288907e08dce Mon Sep 17 00:00:00 2001 From: nitely Date: Tue, 25 Aug 2026 23:18:05 -0300 Subject: [PATCH 2/9] format --- cbor_serialization/parser.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cbor_serialization/parser.nim b/cbor_serialization/parser.nim index 0649318..ea6b524 100644 --- a/cbor_serialization/parser.nim +++ b/cbor_serialization/parser.nim @@ -135,14 +135,14 @@ proc parseStringLike[T: string or seq[byte]]( val[i] = p.read ElmType inc i when val is string: - if not validateUtf8(toOpenArray(val, i0, i-1)): + if not validateUtf8(toOpenArray(val, i0, i - 1)): p.raiseInvalidUtf8(pos, "Invalid utf-8 string") else: for _ in 0 ..< strLen: val[i] = p.read ElmType inc i when val is string: - if not validateUtf8(toOpenArray(val, i0, i-1)): + if not validateUtf8(toOpenArray(val, i0, i - 1)): p.raiseInvalidUtf8(pos, "Invalid utf-8 string") proc parseStringLike( From a76b89324740eb539a34283eb9576ed47ab113c1 Mon Sep 17 00:00:00 2001 From: nitely Date: Tue, 25 Aug 2026 23:19:31 -0300 Subject: [PATCH 3/9] bump stew --- cbor_serialization.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbor_serialization.nimble b/cbor_serialization.nimble index eb15ffd..b2f68bc 100644 --- a/cbor_serialization.nimble +++ b/cbor_serialization.nimble @@ -17,7 +17,7 @@ license = "Apache License 2.0" skipDirs = @["tests", "fuzzer"] requires "nim >= 2.0.0", - "serialization >= 0.4.9", "stew >= 0.4.1", "npeg >= 1.3.0", "results", "unittest2" + "serialization >= 0.4.9", "stew >= 0.5.2", "npeg >= 1.3.0", "results", "unittest2" #feature "dev": # requires "bigints", "json_serialization" From 2452b0f988b2e8ada87a7e221c2fae133719c384 Mon Sep 17 00:00:00 2001 From: nitely Date: Wed, 26 Aug 2026 00:41:45 -0300 Subject: [PATCH 4/9] validateUtf8 setting --- cbor_serialization/format.nim | 7 +++ cbor_serialization/parser.nim | 91 ++++++++++++++++++++--------------- cbor_serialization/utils.nim | 4 +- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/cbor_serialization/format.nim b/cbor_serialization/format.nim index e1afd12..9a69725 100644 --- a/cbor_serialization/format.nim +++ b/cbor_serialization/format.nim @@ -166,6 +166,9 @@ template allowsUnknownFields*(F: type Cbor, T: type DefaultFlavor): bool = template skipsNullFields*(F: type Cbor, T: type DefaultFlavor): bool = false +template validatesUtf8*(F: type Cbor, T: type DefaultFlavor): bool = + true + var DefaultFlavorEnumRep {.compileTime.} = EnumAsString template enumRep*(F: type Cbor, T: type DefaultFlavor): EnumRepresentation = DefaultFlavorEnumRep @@ -200,6 +203,7 @@ template createCborFlavor*( omitOptionalFields = true, allowUnknownFields = true, skipNullFields = false, + validateUtf8 = true, ) {.dirty.} = bind EnumRepresentation @@ -232,6 +236,9 @@ template createCborFlavor*( template skipsNullFields*(F: type Cbor, T: type FlavorName): bool = skipNullFields + template validatesUtf8*(F: type Cbor, T: type FlavorName): bool = + validateUtf8 + var `FlavorName EnumRep` {.compileTime.} = EnumRepresentation.EnumAsString template enumRep*(F: type Cbor, T: type FlavorName): EnumRepresentation = `FlavorName EnumRep` diff --git a/cbor_serialization/parser.nim b/cbor_serialization/parser.nim index ea6b524..6d9e15e 100644 --- a/cbor_serialization/parser.nim +++ b/cbor_serialization/parser.nim @@ -119,7 +119,7 @@ iterator parseStringLikeIt( # https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3 # https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1 proc parseStringLike[T: string or seq[byte]]( - p: var CborParser, majorExpected: CborMajor, limit: int, val: var T + p: var CborParser, majorExpected: CborMajor, limit: int, validateUtf8: bool, val: var T ) {.raises: [IOError, CborReaderError].} = type ElmType = typeof val[0] val.setLen 0 @@ -127,26 +127,26 @@ proc parseStringLike[T: string or seq[byte]]( var i = 0 var i0 {.used.} = 0 parseStringLikeImpl(p, majorExpected, limit, strLen): + i0 = i let pos {.used.} = p.stream.pos if L > -1: # can prealloc safely - i0 = i val.setLen val.len.uint64 + strLen for _ in 0 ..< strLen: val[i] = p.read ElmType inc i when val is string: - if not validateUtf8(toOpenArray(val, i0, i - 1)): + if validateUtf8 and not verifyUtf8(toOpenArray(val, i0, i - 1)): p.raiseInvalidUtf8(pos, "Invalid utf-8 string") else: for _ in 0 ..< strLen: - val[i] = p.read ElmType + val.add p.read ElmType inc i when val is string: - if not validateUtf8(toOpenArray(val, i0, i - 1)): + if validateUtf8 and not verifyUtf8(toOpenArray(val, i0, i - 1)): p.raiseInvalidUtf8(pos, "Invalid utf-8 string") proc parseStringLike( - p: var CborParser, majorExpected: CborMajor, limit: int, val: var CborVoid + p: var CborParser, majorExpected: CborMajor, limit: int, validateUtf8: bool, val: var CborVoid ) {.raises: [IOError, CborReaderError].} = for _ in parseStringLikeIt(p, majorExpected, limit): discard val @@ -155,7 +155,7 @@ proc parseStringLike( proc parseByteString[T]( p: var CborParser, limit: int, val: var T ) {.raises: [IOError, CborReaderError].} = - parseStringLike(p, CborMajor.Bytes, limit, val) + parseStringLike(p, CborMajor.Bytes, limit, false, val) proc parseByteString[T]( p: var CborParser, val: var T @@ -164,14 +164,14 @@ proc parseByteString[T]( # https://www.rfc-editor.org/rfc/rfc8949#section-3.1-2.8 proc parseString[T]( - p: var CborParser, limit: int, val: var T + p: var CborParser, limit: int, validateUtf8: bool, val: var T ) {.raises: [IOError, CborReaderError].} = - parseStringLike(p, CborMajor.Text, limit, val) + parseStringLike(p, CborMajor.Text, limit, validateUtf8, val) proc parseString[T]( - p: var CborParser, val: var T + p: var CborParser, validateUtf8: bool, val: var T ) {.raises: [IOError, CborReaderError].} = - parseString[T](p, p.conf.stringLengthLimit, val) + parseString[T](p, p.conf.stringLengthLimit, validateUtf8, val) template enterNestedStructure(p: CborParser) = inc p.currDepth @@ -244,10 +244,10 @@ template parseObjectImpl(p: var CborParser, skipNullFields, keyAction, body: unt else: body -template parseObject(p: var CborParser, skipNullFields, key, body: untyped) = +template parseObject(p: var CborParser, skipNullFields, validateUtf8, key, body: untyped) = parseObjectImpl(p, skipNullFields): var key = "" - p.parseString(key) + p.parseString(validateUtf8, key) do: body @@ -458,6 +458,13 @@ proc parseInt*( toInt(val, T).valueOr: r.parser.raiseIntOverflow(val.integer, val.sign == CborSign.Neg) +template validateUtf8(r: CborReader): untyped = + mixin validatesUtf8 + + type Flavor = r.Flavor + const validateUtf8 = validatesUtf8(Cbor, Flavor) + validateUtf8 + iterator parseStringLikeIt( r: var CborReader, limit: int, safeBreak: static[bool], T: type ): byte {.inline, raises: [IOError, CborReaderError].} = @@ -470,7 +477,7 @@ iterator parseStringLikeIt( {.fatal: "`parseStringLikeIt` seq[byte] or string expected".} when safeBreak: var s: T - r.parser.parseStringLike(majorType, limit, s) + r.parser.parseStringLike(majorType, limit, r.validateUtf8, s) for x in s: yield x.byte else: @@ -503,13 +510,18 @@ iterator parseByteStringIt*( for x in r.parseByteStringIt(r.parser.conf.byteStringLengthLimit, safeBreak): yield x +proc parseString*( + r: var CborReader, limit: int, validateUtf8: bool +): string {.raises: [IOError, CborReaderError].} = + r.parser.parseString(limit, validateUtf8, result) + proc parseString*( r: var CborReader, limit: int ): string {.raises: [IOError, CborReaderError].} = - r.parser.parseString(limit, result) + r.parser.parseString(limit, r.validateUtf8, result) proc parseString*(r: var CborReader): string {.raises: [IOError, CborReaderError].} = - r.parser.parseString(r.parser.conf.stringLengthLimit, result) + r.parser.parseString(r.parser.conf.stringLengthLimit, r.validateUtf8, result) iterator parseStringIt*( r: var CborReader, limit: int, safeBreak: static[bool] = true @@ -518,6 +530,7 @@ iterator parseStringIt*( ## the iterator won't consume more than needed, but further ## parsing after breaking early will fail. ## If `safeBreak` is `true` the entire string is always consumed. + ## This does not provide utf-8 validation. for x in r.parseStringLikeIt(limit, safeBreak, string): yield x.char @@ -543,11 +556,12 @@ template skipNullFields(r: CborReader): untyped = const skipNullFields = skipsNullFields(Cbor, Flavor) skipNullFields +# XXX option to control validateUtf8 template parseObject*(r: var CborReader, key: untyped, body: untyped) = - parseObject(r.parser, r.skipNullFields, key, body) + parseObject(r.parser, r.skipNullFields, r.validateUtf8, key, body) template parseObjectWithoutSkip*(r: var CborReader, key: untyped, body: untyped) = - parseObject(r.parser, false, key, body) + parseObject(r.parser, false, r.validateUtf8, key, body) template parseTag*(r: var CborReader, tag: untyped, body: untyped) = parseTag(r.parser, tag, body) @@ -580,23 +594,26 @@ proc parseBool*(r: var CborReader): bool {.raises: [IOError, CborReaderError].} r.parser.raiseUnexpectedValue("bool", $val) proc parseValue( - p: var CborParser, val: var CborVoid + r: var CborReader, val: var CborVoid ) {.raises: [IOError, CborReaderError].} = + template p(): untyped = + r.parser + case p.cborKind() of CborValueKind.Unsigned, CborValueKind.Negative: parseNumber(p, val) of CborValueKind.Bytes: parseByteString(p, val) of CborValueKind.String: - parseString(p, val) + parseString(p, false, val) of CborValueKind.Array: parseArray(p, idx): - parseValue(p, val) + parseValue(r, val) of CborValueKind.Object: parseObjectImpl(p, false): - parseValue(p, val) + parseValue(r, val) do: - parseValue(p, val) + parseValue(r, val) of CborValueKind.Bool, CborValueKind.Null, CborValueKind.Undefined, CborValueKind.Simple: var sv: CborSimpleValue @@ -606,11 +623,14 @@ proc parseValue( of CborValueKind.Tag: var tag: uint64 parseTag(p, tag): - parseValue(p, val) + parseValue(r, val) -proc parseValue( - p: var CborParser, val: var CborValueRef +proc parseValue*( + r: var CborReader, val: var CborValueRef ) {.raises: [IOError, CborReaderError].} = + template p(): untyped = + r.parser + val = CborValueRef(kind: p.cborKind()) case val.kind of CborValueKind.Unsigned, CborValueKind.Negative: @@ -618,16 +638,16 @@ proc parseValue( of CborValueKind.Bytes: parseByteString(p, val.bytesVal) of CborValueKind.String: - parseString(p, val.strVal) + parseString(p, r.validateUtf8, val.strVal) of CborValueKind.Array: parseArray(p, idx): let lastPos = val.arrayVal.len val.arrayVal.setLen(lastPos + 1) - parseValue(p, val.arrayVal[lastPos]) + parseValue(r, val.arrayVal[lastPos]) of CborValueKind.Object: - parseObject(p, false, key): + parseObject(p, false, r.validateUtf8, key): var v: CborValueRef - parseValue(p, v) + parseValue(r, v) val.objVal[key] = v of CborValueKind.Bool: var sv: CborSimpleValue @@ -646,17 +666,12 @@ proc parseValue( var tag: uint64 parseTag(p, tag): val.tagVal = CborTag[CborValueRef](tag: tag) - parseValue(p, val.tagVal.val) - -proc parseValue*( - r: var CborReader, val: var CborValueRef -) {.raises: [IOError, CborReaderError].} = - parseValue(r.parser, val) + parseValue(r, val.tagVal.val) proc parseValue*( r: var CborReader ): CborValueRef {.raises: [IOError, CborReaderError].} = - parseValue(r.parser, result) + parseValue(r, result) proc parseValue*( r: var CborReader, val: var CborBytes @@ -692,6 +707,6 @@ template parseObjectCustomKey*(r: var CborReader, keyAction, body: untyped) = proc skipSingleValue*(r: var CborReader) {.raises: [IOError, CborReaderError].} = var val: CborVoid - r.parser.parseValue(val) + r.parseValue(val) {.pop.} diff --git a/cbor_serialization/utils.nim b/cbor_serialization/utils.nim index b6896cf..88f4aaf 100644 --- a/cbor_serialization/utils.nim +++ b/cbor_serialization/utils.nim @@ -29,5 +29,5 @@ proc decodeHalf*(half: uint16): float = else: val -proc validateUtf8*(s: openArray[char]): bool = - unicode.validateUtf8(s) == -1 +proc verifyUtf8*(s: openArray[char]): bool = + validateUtf8(s) == -1 From f2a490b6c735d4b9fb98abdb5c35d9c8e3d1a4ea Mon Sep 17 00:00:00 2001 From: nitely Date: Wed, 26 Aug 2026 00:42:35 -0300 Subject: [PATCH 5/9] nph --- cbor_serialization/parser.nim | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cbor_serialization/parser.nim b/cbor_serialization/parser.nim index 6d9e15e..75d48d0 100644 --- a/cbor_serialization/parser.nim +++ b/cbor_serialization/parser.nim @@ -119,7 +119,11 @@ iterator parseStringLikeIt( # https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3 # https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1 proc parseStringLike[T: string or seq[byte]]( - p: var CborParser, majorExpected: CborMajor, limit: int, validateUtf8: bool, val: var T + p: var CborParser, + majorExpected: CborMajor, + limit: int, + validateUtf8: bool, + val: var T, ) {.raises: [IOError, CborReaderError].} = type ElmType = typeof val[0] val.setLen 0 @@ -146,7 +150,11 @@ proc parseStringLike[T: string or seq[byte]]( p.raiseInvalidUtf8(pos, "Invalid utf-8 string") proc parseStringLike( - p: var CborParser, majorExpected: CborMajor, limit: int, validateUtf8: bool, val: var CborVoid + p: var CborParser, + majorExpected: CborMajor, + limit: int, + validateUtf8: bool, + val: var CborVoid, ) {.raises: [IOError, CborReaderError].} = for _ in parseStringLikeIt(p, majorExpected, limit): discard val @@ -244,7 +252,9 @@ template parseObjectImpl(p: var CborParser, skipNullFields, keyAction, body: unt else: body -template parseObject(p: var CborParser, skipNullFields, validateUtf8, key, body: untyped) = +template parseObject( + p: var CborParser, skipNullFields, validateUtf8, key, body: untyped +) = parseObjectImpl(p, skipNullFields): var key = "" p.parseString(validateUtf8, key) From 1ed8e97ad55b6c2b5ad04f238913b7b7bd382340 Mon Sep 17 00:00:00 2001 From: nitely Date: Wed, 26 Aug 2026 01:01:19 -0300 Subject: [PATCH 6/9] wip --- cbor_serialization/parser.nim | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cbor_serialization/parser.nim b/cbor_serialization/parser.nim index 75d48d0..239f601 100644 --- a/cbor_serialization/parser.nim +++ b/cbor_serialization/parser.nim @@ -125,29 +125,34 @@ proc parseStringLike[T: string or seq[byte]]( validateUtf8: bool, val: var T, ) {.raises: [IOError, CborReaderError].} = + template utf8Validation(s, pos: untyped): untyped = + if not verifyUtf8(s): + p.raiseInvalidUtf8(pos, "Invalid utf-8 string") + type ElmType = typeof val[0] val.setLen 0 var L = p.lenMaybe() var i = 0 var i0 {.used.} = 0 parseStringLikeImpl(p, majorExpected, limit, strLen): + when val is string: + let pos = p.stream.pos i0 = i - let pos {.used.} = p.stream.pos if L > -1: # can prealloc safely val.setLen val.len.uint64 + strLen for _ in 0 ..< strLen: val[i] = p.read ElmType inc i when val is string: - if validateUtf8 and not verifyUtf8(toOpenArray(val, i0, i - 1)): - p.raiseInvalidUtf8(pos, "Invalid utf-8 string") + if validateUtf8: + utf8Validation(toOpenArray(val, i0, i - 1), pos) else: for _ in 0 ..< strLen: val.add p.read ElmType inc i when val is string: - if validateUtf8 and not verifyUtf8(toOpenArray(val, i0, i - 1)): - p.raiseInvalidUtf8(pos, "Invalid utf-8 string") + if validateUtf8: + utf8Validation(toOpenArray(val, i0, i - 1), pos) proc parseStringLike( p: var CborParser, From baa4e4d365bf0c8c63b310e73f0a1f258957b29b Mon Sep 17 00:00:00 2001 From: nitely Date: Wed, 26 Aug 2026 01:45:14 -0300 Subject: [PATCH 7/9] wip --- cbor_serialization/parser.nim | 70 ++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/cbor_serialization/parser.nim b/cbor_serialization/parser.nim index 239f601..2fdc11c 100644 --- a/cbor_serialization/parser.nim +++ b/cbor_serialization/parser.nim @@ -125,9 +125,10 @@ proc parseStringLike[T: string or seq[byte]]( validateUtf8: bool, val: var T, ) {.raises: [IOError, CborReaderError].} = - template utf8Validation(s, pos: untyped): untyped = - if not verifyUtf8(s): - p.raiseInvalidUtf8(pos, "Invalid utf-8 string") + when val is string: + template utf8Validation(s, pos: untyped): untyped = + if not verifyUtf8(s): + p.raiseInvalidUtf8(pos, "Invalid utf-8 string") type ElmType = typeof val[0] val.setLen 0 @@ -609,11 +610,8 @@ proc parseBool*(r: var CborReader): bool {.raises: [IOError, CborReaderError].} r.parser.raiseUnexpectedValue("bool", $val) proc parseValue( - r: var CborReader, val: var CborVoid + p: var CborParser, val: var CborVoid ) {.raises: [IOError, CborReaderError].} = - template p(): untyped = - r.parser - case p.cborKind() of CborValueKind.Unsigned, CborValueKind.Negative: parseNumber(p, val) @@ -623,12 +621,12 @@ proc parseValue( parseString(p, false, val) of CborValueKind.Array: parseArray(p, idx): - parseValue(r, val) + parseValue(p, val) of CborValueKind.Object: parseObjectImpl(p, false): - parseValue(r, val) + parseValue(p, val) do: - parseValue(r, val) + parseValue(p, val) of CborValueKind.Bool, CborValueKind.Null, CborValueKind.Undefined, CborValueKind.Simple: var sv: CborSimpleValue @@ -638,14 +636,15 @@ proc parseValue( of CborValueKind.Tag: var tag: uint64 parseTag(p, tag): - parseValue(r, val) + parseValue(p, val) -proc parseValue*( - r: var CborReader, val: var CborValueRef -) {.raises: [IOError, CborReaderError].} = - template p(): untyped = - r.parser +proc skipSingleValue*(r: var CborReader) {.raises: [IOError, CborReaderError].} = + var val: CborVoid + parseValue(r.parser, val) +proc parseValue( + p: var CborParser, validateUtf8: bool, val: var CborValueRef +) {.raises: [IOError, CborReaderError].} = val = CborValueRef(kind: p.cborKind()) case val.kind of CborValueKind.Unsigned, CborValueKind.Negative: @@ -653,16 +652,16 @@ proc parseValue*( of CborValueKind.Bytes: parseByteString(p, val.bytesVal) of CborValueKind.String: - parseString(p, r.validateUtf8, val.strVal) + parseString(p, validateUtf8, val.strVal) of CborValueKind.Array: parseArray(p, idx): let lastPos = val.arrayVal.len val.arrayVal.setLen(lastPos + 1) - parseValue(r, val.arrayVal[lastPos]) + parseValue(p, validateUtf8, val.arrayVal[lastPos]) of CborValueKind.Object: - parseObject(p, false, r.validateUtf8, key): + parseObject(p, false, validateUtf8, key): var v: CborValueRef - parseValue(r, v) + parseValue(p, validateUtf8, v) val.objVal[key] = v of CborValueKind.Bool: var sv: CborSimpleValue @@ -681,19 +680,21 @@ proc parseValue*( var tag: uint64 parseTag(p, tag): val.tagVal = CborTag[CborValueRef](tag: tag) - parseValue(r, val.tagVal.val) + parseValue(p, validateUtf8, val.tagVal.val) + +proc parseValue*( + r: var CborReader, val: var CborValueRef +) {.raises: [IOError, CborReaderError].} = + parseValue(r.parser, r.validateUtf8, val) proc parseValue*( r: var CborReader ): CborValueRef {.raises: [IOError, CborReaderError].} = parseValue(r, result) -proc parseValue*( - r: var CborReader, val: var CborBytes +proc parseValue( + p: var CborParser, val: var CborBytes ) {.raises: [IOError, CborReaderError].} = - template p(): untyped = - r.parser - let c = p.peek() case c.major of CborMajor.Unsigned, CborMajor.Negative: @@ -704,24 +705,25 @@ proc parseValue*( parseRawStringLike(p, val, p.conf.stringLengthLimit) of CborMajor.Array: parseRawArrayLike(p, val, p.conf.arrayElementsLimit): - parseValue(r, val) + parseValue(p, val) of CborMajor.Map: parseRawArrayLike(p, val, p.conf.objectFieldsLimit): - parseValue(r, val) - parseValue(r, val) + parseValue(p, val) + parseValue(p, val) of CborMajor.Tag: enterNestedStructure(p) parseRawHead(p, val) - parseValue(r, val) + parseValue(p, val) exitNestedStructure(p) of CborMajor.SimpleOrFloat: parseRawHead(p, val) +proc parseValue*( + r: var CborReader, val: var CborBytes +) {.raises: [IOError, CborReaderError].} = + parseValue(r.parser, val) + template parseObjectCustomKey*(r: var CborReader, keyAction, body: untyped) = parseObjectImpl(r.parser, r.skipNullFields, keyAction, body) -proc skipSingleValue*(r: var CborReader) {.raises: [IOError, CborReaderError].} = - var val: CborVoid - r.parseValue(val) - {.pop.} From 5c3ee13e84a8d80a94ba51c2fb8585e79047ce5f Mon Sep 17 00:00:00 2001 From: nitely Date: Wed, 26 Aug 2026 01:58:46 -0300 Subject: [PATCH 8/9] wip --- cbor_serialization/parser.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/cbor_serialization/parser.nim b/cbor_serialization/parser.nim index 2fdc11c..1db0085 100644 --- a/cbor_serialization/parser.nim +++ b/cbor_serialization/parser.nim @@ -398,6 +398,7 @@ template parseRawArrayLike( body exitNestedStructure(p) +# XXX validate strings are utf8 template parseRawStringLikeImpl( p: var CborParser, val: var CborBytes, rawLen, body: untyped ) = From ad15d61f1ada0b093c4f0eb88be78e2d8b457977 Mon Sep 17 00:00:00 2001 From: nitely Date: Wed, 26 Aug 2026 02:01:04 -0300 Subject: [PATCH 9/9] wip --- tests/test_serialization.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_serialization.nim b/tests/test_serialization.nim index 95df63f..65da8b7 100644 --- a/tests/test_serialization.nim +++ b/tests/test_serialization.nim @@ -897,6 +897,10 @@ suite "toCbor tests": expect CborInvalidUtf8Error: discard Cbor.decode(cbor.unhex, string) + test "CborVoid skips invalid UTF-8": + const cbor = "0x63e228a1" + discard Cbor.decode(cbor.unhex, CborVoid) + suite "Custom parser tests": test "Fall back to int parser": customVisit = TokenRegistry.default