Skip to content

Commit

Permalink
feat: add rejectDuplicateMapKeys option (default false)
Browse files Browse the repository at this point in the history
Closes: #72
  • Loading branch information
rvagg committed Jan 6, 2023
1 parent a7d9e99 commit 450bc8c
Show file tree
Hide file tree
Showing 5 changed files with 19 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ Decode valid CBOR bytes from a `Uint8Array` (or `Buffer`) and return a JavaScrip
* `strict` (boolean, default `false`): when decoding integers, including for lengths (arrays, maps, strings, bytes), values will be checked to see whether they were encoded in their smallest possible form. If not, an error will be thrown.
* Currently, this form of deterministic strictness cannot be enforced for float representations, or map key ordering (pull requests _very_ welcome).
* `useMaps` (boolean, default `false`): when decoding major 5 (map) entries, use a `Map` rather than a plain `Object`. This will nest for any encountered map. During encode, a `Map` will be interpreted as an `Object` and will round-trip as such unless `useMaps` is supplied, in which case, all `Map`s and `Object`s will round-trip as `Map`s. There is no way to retain the distinction during round-trip without using a custom tag.
* `rejectDuplicateMapKeys` (boolean, default `false`): when the decoder encounters duplicate keys for the same map, an error will be thrown when this option is set. This is an additional _strictness_ option, disallowing data-hiding and reducing the number of same-data different-bytes possibilities where it matters.
* `retainStringBytes` (boolean, default `false`): when decoding strings, retain the original bytes on the `Token` object as `byteValue`. Since it is possible to encode non-UTF-8 characters in strings in CBOR, and JavaScript doesn't properly handle non-UTF-8 in its conversion from bytes (`TextEncoder` or `Buffer`), this can result in a loss of data (and an inability to round-trip). Where this is important, a token stream should be consumed instead of a plain `decode()` and the `byteValue` property on string tokens can be inspected (see [lib/diagnostic.js](lib/diagnostic.js) for an example of its use.)
* `tags` (array): a mapping of tag number to tag decoder function. By default no tags are supported. See [Tag decoders](#tag-decoders).
* `tokenizer` (object): an object with two methods, `next()` which returns a `Token` and `done()` which returns a `boolean`. Can be used to implement custom input decoding. See the source code for examples.
Expand Down
1 change: 1 addition & 0 deletions interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface DecodeOptions {
allowBigInt?: boolean
strict?: boolean
useMaps?: boolean
rejectDuplicateMapKeys?: boolean
retainStringBytes?: boolean
tags?: TagDecoder[],
tokenizer?: DecodeTokenizer
Expand Down
6 changes: 6 additions & 0 deletions lib/decode.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ function tokenToMap (token, tokeniser, options) {
if (useMaps !== true && typeof key !== 'string') {
throw new Error(`${decodeErrPrefix} non-string keys not supported (got ${typeof key})`)
}
if (options.rejectDuplicateMapKeys === true) {
// @ts-ignore
if ((useMaps && m.has(key)) || (!useMaps && (key in obj))) {
throw new Error(`${decodeErrPrefix} found repeat map key "${key}"`)
}
}
const value = tokensToObject(tokeniser, options)
if (value === DONE) {
throw new Error(`${decodeErrPrefix} found map but not enough entries (got ${i} [no value], expected ${token.value})`)
Expand Down
6 changes: 6 additions & 0 deletions test/test-decode-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,10 @@ describe('decode errors', () => {
// two '1's
assert.throws(() => decode(fromHex('0101')), /too many terminals/)
})

it('rejectDuplicateMapKeys enabled on duplicate keys', () => {
assert.deepStrictEqual(decode(fromHex('a3636261720363666f6f0163666f6f02')), { foo: 2, bar: 3 })
assert.throws(() => decode(fromHex('a3636261720363666f6f0163666f6f02'), { rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/)
assert.throws(() => decode(fromHex('a3636261720363666f6f0163666f6f02'), { useMaps: true, rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/)
})
})
5 changes: 5 additions & 0 deletions test/test-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,9 @@ describe('json basics', () => {
assert.throws(() => decode(toBytes('[fa]')), 'CBOR decode error: unexpected end of input at position 1')
assert.throws(() => decode(toBytes('-0..1')), 'CBOR decode error: unexpected token at position 3')
})

it('should throw when rejectDuplicateMapKeys enabled on duplicate keys', () => {
assert.deepStrictEqual(decode(toBytes('{"foo":1,"foo":2}')), { foo: 2 })
assert.throws(() => decode(toBytes('{"foo":1,"foo":2}'), { rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/)
})
})

0 comments on commit 450bc8c

Please sign in to comment.