cbor-x 1.6.5 → 1.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/decode.d.ts +1 -1
- package/decode.js +89 -52
- package/dist/decode-no-eval.cjs +91 -52
- package/dist/decode-no-eval.cjs.map +1 -1
- package/dist/index-no-eval.cjs +96 -54
- package/dist/index-no-eval.cjs.map +1 -1
- package/dist/index-no-eval.min.js +1 -1
- package/dist/index-no-eval.min.js.map +1 -1
- package/dist/index.js +96 -54
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/node.cjs +109 -70
- package/dist/node.cjs.map +1 -1
- package/dist/test.js +90 -52
- package/dist/test.js.map +1 -1
- package/encode.js +1 -1
- package/index.d.ts +1 -1
- package/iterators.js +6 -4
- package/package.json +1 -1
- package/stream.js +11 -13
package/README.md
CHANGED
|
@@ -230,17 +230,23 @@ Note, that the performance is decreased with decimal rounding by about 20-25%, a
|
|
|
230
230
|
In addition, msgpackr exports a `roundFloat32(number)` function that can be used to round floating point numbers to the maximum significant decimal digits that can be stored in 32-bit float, just as DECIMAL_ROUND does when decoding. This can be useful for determining how a number will be decoded prior to encoding it.
|
|
231
231
|
|
|
232
232
|
### Setting Size Limits
|
|
233
|
-
You can set size limits on objects, arrays, and maps to prevent resource exhaustion when decoding. This can be done by calling the
|
|
233
|
+
You can set size limits on objects, arrays, and maps to prevent resource exhaustion when decoding. This can be done by calling the setSizeLimits export. Each of the properties are optional (only provide
|
|
234
234
|
the properties you want to change), for example (with the defaults):
|
|
235
235
|
```JavaScript
|
|
236
|
-
import {
|
|
237
|
-
|
|
236
|
+
import { setSizeLimits } from 'cbor-x';
|
|
237
|
+
setSizeLimits({
|
|
238
238
|
maxArraySize: 112810000,
|
|
239
239
|
maxMapSize: 16810000,
|
|
240
240
|
maxObjectSize : 16710000
|
|
241
241
|
});
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
+
`maxMapSize` limits the entries of a decoded `Map`. A map decoded as an object (with `mapsAsObjects`) is limited to the smaller of `maxObjectSize` and `maxMapSize`.
|
|
245
|
+
|
|
246
|
+
Declared array and map lengths are also always validated against the number of bytes remaining in the source, so truncated or malformed input is rejected immediately instead of allocating for the length it claims. These limits are for bounding legitimately encoded (but unwanted) large data.
|
|
247
|
+
|
|
248
|
+
Nesting depth is bounded by the JavaScript call stack: input nested deeper than the stack allows (typically several thousand levels) throws the engine's stack overflow error (a `RangeError` in V8 and JavaScriptCore). Like other malformed input, this is not marked as `incomplete`, so a `DecoderStream` emits it as an error rather than waiting for more data. (An error from the last few bytes of a chunk may only be reported once the next chunk arrives.)
|
|
249
|
+
|
|
244
250
|
## Performance
|
|
245
251
|
Cbor-x is fast. Really fast. Here is comparison with the next fastest JS projects using the benchmark tool from `msgpack-lite` (and the sample data is from some clinical research data we use that has a good mix of different value types and structures). It also includes comparison to V8 native JSON functionality, and JavaScript Avro (`avsc`, a very optimized Avro implementation):
|
|
246
252
|
|
package/decode.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { decode, decodeMultiple, Decoder, addExtension, clearSource,roundFloat32, isNativeAccelerationEnabled,
|
|
2
|
-
Extension, Options, FLOAT32_OPTIONS,
|
|
2
|
+
Extension, Options, FLOAT32_OPTIONS, setSizeLimits, SizeLimitOptions } from '.'
|
package/decode.js
CHANGED
|
@@ -19,8 +19,9 @@ let maxArraySize = 112810000 // This is the maximum array size in V8. We would p
|
|
|
19
19
|
let maxMapSize = 16810000 // JavaScript has a fixed maximum map size of about 16710000, but JS itself enforces this,
|
|
20
20
|
// so we don't need to
|
|
21
21
|
|
|
22
|
-
let maxObjectSize = 16710000; //
|
|
23
|
-
//
|
|
22
|
+
let maxObjectSize = 16710000; // It takes over a minute to create this many keys in an object, so this is probably a
|
|
23
|
+
// reasonable choice.
|
|
24
|
+
let objectSizeLimit = maxObjectSize // objects are limited by both maxObjectSize and maxMapSize
|
|
24
25
|
let strings = EMPTY_ARRAY
|
|
25
26
|
let stringPosition = 0
|
|
26
27
|
let currentDecoder = {}
|
|
@@ -124,7 +125,13 @@ export class Decoder {
|
|
|
124
125
|
// re-entrant execution, save the state and restore it after we do this decode
|
|
125
126
|
return saveState(() => {
|
|
126
127
|
clearSource()
|
|
127
|
-
|
|
128
|
+
try {
|
|
129
|
+
return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error && typeof error == 'object')
|
|
132
|
+
nestedDecodeErrors.add(error)
|
|
133
|
+
throw error
|
|
134
|
+
}
|
|
128
135
|
})
|
|
129
136
|
}
|
|
130
137
|
srcEnd = end > -1 ? end : source.length
|
|
@@ -204,15 +211,13 @@ export class Decoder {
|
|
|
204
211
|
export function getPosition() {
|
|
205
212
|
return position
|
|
206
213
|
}
|
|
214
|
+
const nestedDecodeErrors = new WeakSet()
|
|
207
215
|
export function checkedRead() {
|
|
208
216
|
try {
|
|
209
217
|
let result = read()
|
|
210
218
|
if (bundledStrings) {
|
|
211
|
-
if (position >= bundledStrings.postBundlePosition)
|
|
212
|
-
|
|
213
|
-
error.incomplete = true;
|
|
214
|
-
throw error
|
|
215
|
-
}
|
|
219
|
+
if (position >= bundledStrings.postBundlePosition)
|
|
220
|
+
throw new Error('Unexpected bundle position')
|
|
216
221
|
// bundled strings to skip past
|
|
217
222
|
position = bundledStrings.postBundlePosition;
|
|
218
223
|
bundledStrings = null;
|
|
@@ -236,20 +241,36 @@ export function checkedRead() {
|
|
|
236
241
|
return result
|
|
237
242
|
} catch(error) {
|
|
238
243
|
clearSource()
|
|
239
|
-
if (
|
|
244
|
+
if (nestedDecodeErrors.has(error)) {
|
|
245
|
+
// from a nested decode of embedded data (by an extension), which was complete in this source
|
|
246
|
+
if (error.incomplete)
|
|
247
|
+
error.incomplete = false
|
|
248
|
+
} else if (error instanceof RangeError && position + 8 > srcEnd ||
|
|
249
|
+
error && typeof error.message == 'string' && error.message.startsWith('Unexpected end of buffer')) {
|
|
250
|
+
// a DataView read past the end of the source; these read at most 8 bytes and precede advancing position.
|
|
251
|
+
// Any other RangeError (such as a stack overflow from deeply nested input) is fatal.
|
|
240
252
|
error.incomplete = true
|
|
241
253
|
}
|
|
242
254
|
throw error
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
|
|
258
|
+
function endOfCBORError() {
|
|
259
|
+
let error = new Error('Unexpected end of CBOR data')
|
|
260
|
+
error.incomplete = true
|
|
261
|
+
return error
|
|
262
|
+
}
|
|
263
|
+
|
|
246
264
|
export function read() {
|
|
265
|
+
// compared as < so that a NaN position (from a corrupt length) also throws instead of reading undefined
|
|
266
|
+
if (!(position < srcEnd)) throw endOfCBORError()
|
|
247
267
|
let token = src[position++]
|
|
248
268
|
let majorType = token >> 5
|
|
249
269
|
token = token & 0x1f
|
|
250
270
|
if (token > 0x17) {
|
|
251
271
|
switch (token) {
|
|
252
272
|
case 0x18:
|
|
273
|
+
if (position >= srcEnd) throw endOfCBORError()
|
|
253
274
|
token = src[position++]
|
|
254
275
|
break
|
|
255
276
|
case 0x19:
|
|
@@ -312,13 +333,13 @@ export function read() {
|
|
|
312
333
|
let i = 0;
|
|
313
334
|
if (currentDecoder.keyMap) {
|
|
314
335
|
while((key = read()) != STOP_CODE) {
|
|
315
|
-
if (i++ >=
|
|
336
|
+
if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
|
|
316
337
|
object[safeKey(currentDecoder.decodeKey(key))] = read()
|
|
317
338
|
}
|
|
318
339
|
}
|
|
319
340
|
else {
|
|
320
341
|
while ((key = read()) != STOP_CODE) {
|
|
321
|
-
if (i++ >=
|
|
342
|
+
if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
|
|
322
343
|
object[safeKey(key)] = read()
|
|
323
344
|
}
|
|
324
345
|
}
|
|
@@ -364,11 +385,13 @@ export function read() {
|
|
|
364
385
|
case 1: // negative int
|
|
365
386
|
return ~token
|
|
366
387
|
case 2: // buffer
|
|
388
|
+
if (token > srcEnd - position) throw endOfCBORError()
|
|
367
389
|
return readBin(token)
|
|
368
390
|
case 3: // string
|
|
369
|
-
if (srcStringEnd >= position) {
|
|
391
|
+
if (srcStringEnd >= position) { // already extracted, which only covers complete strings
|
|
370
392
|
return srcString.slice(position - srcStringStart, (position += token) - srcStringStart)
|
|
371
393
|
}
|
|
394
|
+
if (token > srcEnd - position) throw endOfCBORError()
|
|
372
395
|
if (srcStringEnd == 0 && srcEnd < 140 && token < 32) {
|
|
373
396
|
// for small blocks, avoiding the overhead of the extract call is helpful
|
|
374
397
|
let string = token < 16 ? shortStringInJS(token) : longStringInJS(token)
|
|
@@ -377,15 +400,19 @@ export function read() {
|
|
|
377
400
|
}
|
|
378
401
|
return readFixedString(token)
|
|
379
402
|
case 4: // array
|
|
380
|
-
if (token
|
|
403
|
+
if (token > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
|
|
404
|
+
// every element occupies at least one byte, so a length beyond what remains in the source can
|
|
405
|
+
// never be satisfied; check before allocating so a tiny header can not force a huge allocation
|
|
406
|
+
if (token > srcEnd - position) throw endOfCBORError()
|
|
381
407
|
let array = new Array(token)
|
|
382
408
|
//if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
|
|
383
409
|
//else
|
|
384
410
|
for (let i = 0; i < token; i++) array[i] = read()
|
|
385
411
|
return array
|
|
386
412
|
case 5: // map
|
|
387
|
-
if (token >= maxMapSize) throw new Error(`Map size exceeds ${maxArraySize}`)
|
|
388
413
|
if (currentDecoder.mapsAsObjects) {
|
|
414
|
+
if (token > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
|
|
415
|
+
if (token > (srcEnd - position) / 2) throw endOfCBORError() // each entry needs at least two bytes
|
|
389
416
|
let object = {}
|
|
390
417
|
if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read()
|
|
391
418
|
else for (let i = 0; i < token; i++) object[safeKey(read())] = read()
|
|
@@ -395,6 +422,8 @@ export function read() {
|
|
|
395
422
|
currentDecoder.mapsAsObjects = true
|
|
396
423
|
restoreMapsAsObject = false
|
|
397
424
|
}
|
|
425
|
+
if (token > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
|
|
426
|
+
if (token > (srcEnd - position) / 2) throw endOfCBORError()
|
|
398
427
|
let map = new Map()
|
|
399
428
|
if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()),read())
|
|
400
429
|
else for (let i = 0; i < token; i++) map.set(read(), read())
|
|
@@ -476,11 +505,7 @@ export function read() {
|
|
|
476
505
|
throw new Error('Unknown token ' + token)
|
|
477
506
|
}
|
|
478
507
|
default: // negative int
|
|
479
|
-
if (isNaN(token))
|
|
480
|
-
let error = new Error('Unexpected end of CBOR data')
|
|
481
|
-
error.incomplete = true
|
|
482
|
-
throw error
|
|
483
|
-
}
|
|
508
|
+
if (isNaN(token)) throw endOfCBORError()
|
|
484
509
|
throw new Error('Unknown CBOR token ' + token)
|
|
485
510
|
}
|
|
486
511
|
}
|
|
@@ -870,6 +895,7 @@ function readKey() {
|
|
|
870
895
|
if (length >= 0x60 && length < 0x78) {
|
|
871
896
|
// fixstr, potentially use key cache
|
|
872
897
|
length = length - 0x60
|
|
898
|
+
if (length > srcEnd - position) throw endOfCBORError()
|
|
873
899
|
if (srcStringEnd >= position) // if it has been extracted, must use it (and faster anyway)
|
|
874
900
|
return srcString.slice(position - srcStringStart, (position += length) - srcStringStart)
|
|
875
901
|
else if (!(srcStringEnd == 0 && srcEnd < 180))
|
|
@@ -1005,18 +1031,12 @@ currentExtensions[27] = (data) => { // http://cbor.schmorp.de/generic-object
|
|
|
1005
1031
|
return (glbl[data[0]] || Error)(data[1], data[2])
|
|
1006
1032
|
}
|
|
1007
1033
|
const packedTable = (read) => {
|
|
1008
|
-
if (
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
error.incomplete = true
|
|
1012
|
-
throw error
|
|
1013
|
-
}
|
|
1034
|
+
if (!(position < srcEnd)) throw endOfCBORError()
|
|
1035
|
+
if (src[position++] != 0x84)
|
|
1036
|
+
throw new Error('Packed values structure must be followed by a 4 element array')
|
|
1014
1037
|
let newPackedValues = read() // packed values
|
|
1015
|
-
if (!newPackedValues || !newPackedValues.length)
|
|
1016
|
-
|
|
1017
|
-
error.incomplete = true
|
|
1018
|
-
throw error
|
|
1019
|
-
}
|
|
1038
|
+
if (!Array.isArray(newPackedValues) || !newPackedValues.length)
|
|
1039
|
+
throw new Error('Packed values structure must be followed by a 4 element array')
|
|
1020
1040
|
packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues
|
|
1021
1041
|
packedValues.prefixes = read()
|
|
1022
1042
|
packedValues.suffixes = read()
|
|
@@ -1034,10 +1054,7 @@ currentExtensions[PACKED_REFERENCE_TAG_ID] = (data) => { // packed reference
|
|
|
1034
1054
|
}
|
|
1035
1055
|
if (typeof data == 'number')
|
|
1036
1056
|
return packedValues[16 + (data >= 0 ? 2 * data : (-2 * data - 1))]
|
|
1037
|
-
|
|
1038
|
-
if (data === undefined)
|
|
1039
|
-
error.incomplete = true
|
|
1040
|
-
throw error
|
|
1057
|
+
throw new Error('No support for non-integer packed references yet')
|
|
1041
1058
|
}
|
|
1042
1059
|
|
|
1043
1060
|
// The following code is an incomplete implementation of http://cbor.schmorp.de/stringref
|
|
@@ -1107,9 +1124,19 @@ currentExtensions[258] = (array) => new Set(array); // https://github.com/input-
|
|
|
1107
1124
|
(currentExtensions[259] = (read) => {
|
|
1108
1125
|
// https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec
|
|
1109
1126
|
// for decoding as a standard Map
|
|
1110
|
-
|
|
1111
|
-
|
|
1127
|
+
let decoder = currentDecoder
|
|
1128
|
+
if (decoder.mapsAsObjects) {
|
|
1129
|
+
decoder.mapsAsObjects = false
|
|
1112
1130
|
restoreMapsAsObject = true
|
|
1131
|
+
try {
|
|
1132
|
+
return read()
|
|
1133
|
+
} finally {
|
|
1134
|
+
// normally restored by the map header, but not if the tagged value is not a map or the read fails
|
|
1135
|
+
if (restoreMapsAsObject) {
|
|
1136
|
+
decoder.mapsAsObjects = true
|
|
1137
|
+
restoreMapsAsObject = false
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1113
1140
|
}
|
|
1114
1141
|
return read()
|
|
1115
1142
|
}).handlesRead = true
|
|
@@ -1220,17 +1247,21 @@ function readBundleExt() {
|
|
|
1220
1247
|
}
|
|
1221
1248
|
|
|
1222
1249
|
function readJustLength() {
|
|
1250
|
+
if (!(position < srcEnd)) throw endOfCBORError()
|
|
1223
1251
|
let token = src[position++] & 0x1f
|
|
1224
1252
|
if (token > 0x17) {
|
|
1225
1253
|
switch (token) {
|
|
1226
1254
|
case 0x18:
|
|
1255
|
+
if (position >= srcEnd) throw endOfCBORError()
|
|
1227
1256
|
token = src[position++]
|
|
1228
1257
|
break
|
|
1229
1258
|
case 0x19:
|
|
1259
|
+
if (position + 2 > srcEnd) throw endOfCBORError()
|
|
1230
1260
|
token = dataView.getUint16(position)
|
|
1231
1261
|
position += 2
|
|
1232
1262
|
break
|
|
1233
1263
|
case 0x1a:
|
|
1264
|
+
if (position + 4 > srcEnd) throw endOfCBORError()
|
|
1234
1265
|
token = dataView.getUint32(position)
|
|
1235
1266
|
position += 4
|
|
1236
1267
|
break
|
|
@@ -1272,22 +1303,27 @@ function saveState(callback) {
|
|
|
1272
1303
|
let savedStructures = currentStructures
|
|
1273
1304
|
let savedDecoder = currentDecoder
|
|
1274
1305
|
let savedSequentialMode = sequentialMode
|
|
1275
|
-
let
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1306
|
+
let savedRestoreMapsAsObject = restoreMapsAsObject
|
|
1307
|
+
restoreMapsAsObject = false
|
|
1308
|
+
try {
|
|
1309
|
+
return callback()
|
|
1310
|
+
} finally {
|
|
1311
|
+
srcEnd = savedSrcEnd
|
|
1312
|
+
position = savedPosition
|
|
1313
|
+
stringPosition = savedStringPosition
|
|
1314
|
+
srcStringStart = savedSrcStringStart
|
|
1315
|
+
srcStringEnd = savedSrcStringEnd
|
|
1316
|
+
srcString = savedSrcString
|
|
1317
|
+
strings = savedStrings
|
|
1318
|
+
referenceMap = savedReferenceMap
|
|
1319
|
+
bundledStrings = savedBundledStrings
|
|
1320
|
+
src = savedSrc
|
|
1321
|
+
sequentialMode = savedSequentialMode
|
|
1322
|
+
currentStructures = savedStructures
|
|
1323
|
+
currentDecoder = savedDecoder
|
|
1324
|
+
restoreMapsAsObject = savedRestoreMapsAsObject
|
|
1325
|
+
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength)
|
|
1326
|
+
}
|
|
1291
1327
|
}
|
|
1292
1328
|
export function clearSource() {
|
|
1293
1329
|
src = null
|
|
@@ -1303,6 +1339,7 @@ export function setSizeLimits(limits) {
|
|
|
1303
1339
|
if (limits.maxMapSize) maxMapSize = limits.maxMapSize;
|
|
1304
1340
|
if (limits.maxArraySize) maxArraySize = limits.maxArraySize;
|
|
1305
1341
|
if (limits.maxObjectSize) maxObjectSize = limits.maxObjectSize;
|
|
1342
|
+
objectSizeLimit = Math.min(maxObjectSize, maxMapSize)
|
|
1306
1343
|
}
|
|
1307
1344
|
|
|
1308
1345
|
export const mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
|
package/dist/decode-no-eval.cjs
CHANGED
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
let maxArraySize = 112810000; // This is the maximum array size in V8. We would potentially detect and set it higher
|
|
22
22
|
// for JSC, but this is pretty large and should be sufficient for most use cases
|
|
23
23
|
let maxMapSize = 16810000; // JavaScript has a fixed maximum map size of about 16710000, but JS itself enforces this,
|
|
24
|
-
//
|
|
24
|
+
// so we don't need to
|
|
25
|
+
|
|
26
|
+
let maxObjectSize = 16710000; // It takes over a minute to create this many keys in an object, so this is probably a
|
|
27
|
+
// reasonable choice.
|
|
28
|
+
let objectSizeLimit = maxObjectSize; // objects are limited by both maxObjectSize and maxMapSize
|
|
25
29
|
let strings = EMPTY_ARRAY;
|
|
26
30
|
let stringPosition = 0;
|
|
27
31
|
let currentDecoder = {};
|
|
@@ -125,7 +129,13 @@
|
|
|
125
129
|
// re-entrant execution, save the state and restore it after we do this decode
|
|
126
130
|
return saveState(() => {
|
|
127
131
|
clearSource();
|
|
128
|
-
|
|
132
|
+
try {
|
|
133
|
+
return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error && typeof error == 'object')
|
|
136
|
+
nestedDecodeErrors.add(error);
|
|
137
|
+
throw error
|
|
138
|
+
}
|
|
129
139
|
})
|
|
130
140
|
}
|
|
131
141
|
srcEnd = end > -1 ? end : source.length;
|
|
@@ -205,15 +215,13 @@
|
|
|
205
215
|
function getPosition() {
|
|
206
216
|
return position
|
|
207
217
|
}
|
|
218
|
+
const nestedDecodeErrors = new WeakSet();
|
|
208
219
|
function checkedRead() {
|
|
209
220
|
try {
|
|
210
221
|
let result = read();
|
|
211
222
|
if (bundledStrings) {
|
|
212
|
-
if (position >= bundledStrings.postBundlePosition)
|
|
213
|
-
|
|
214
|
-
error.incomplete = true;
|
|
215
|
-
throw error
|
|
216
|
-
}
|
|
223
|
+
if (position >= bundledStrings.postBundlePosition)
|
|
224
|
+
throw new Error('Unexpected bundle position')
|
|
217
225
|
// bundled strings to skip past
|
|
218
226
|
position = bundledStrings.postBundlePosition;
|
|
219
227
|
bundledStrings = null;
|
|
@@ -237,20 +245,36 @@
|
|
|
237
245
|
return result
|
|
238
246
|
} catch(error) {
|
|
239
247
|
clearSource();
|
|
240
|
-
if (
|
|
248
|
+
if (nestedDecodeErrors.has(error)) {
|
|
249
|
+
// from a nested decode of embedded data (by an extension), which was complete in this source
|
|
250
|
+
if (error.incomplete)
|
|
251
|
+
error.incomplete = false;
|
|
252
|
+
} else if (error instanceof RangeError && position + 8 > srcEnd ||
|
|
253
|
+
error && typeof error.message == 'string' && error.message.startsWith('Unexpected end of buffer')) {
|
|
254
|
+
// a DataView read past the end of the source; these read at most 8 bytes and precede advancing position.
|
|
255
|
+
// Any other RangeError (such as a stack overflow from deeply nested input) is fatal.
|
|
241
256
|
error.incomplete = true;
|
|
242
257
|
}
|
|
243
258
|
throw error
|
|
244
259
|
}
|
|
245
260
|
}
|
|
246
261
|
|
|
262
|
+
function endOfCBORError() {
|
|
263
|
+
let error = new Error('Unexpected end of CBOR data');
|
|
264
|
+
error.incomplete = true;
|
|
265
|
+
return error
|
|
266
|
+
}
|
|
267
|
+
|
|
247
268
|
function read() {
|
|
269
|
+
// compared as < so that a NaN position (from a corrupt length) also throws instead of reading undefined
|
|
270
|
+
if (!(position < srcEnd)) throw endOfCBORError()
|
|
248
271
|
let token = src[position++];
|
|
249
272
|
let majorType = token >> 5;
|
|
250
273
|
token = token & 0x1f;
|
|
251
274
|
if (token > 0x17) {
|
|
252
275
|
switch (token) {
|
|
253
276
|
case 0x18:
|
|
277
|
+
if (position >= srcEnd) throw endOfCBORError()
|
|
254
278
|
token = src[position++];
|
|
255
279
|
break
|
|
256
280
|
case 0x19:
|
|
@@ -313,13 +337,13 @@
|
|
|
313
337
|
let i = 0;
|
|
314
338
|
if (currentDecoder.keyMap) {
|
|
315
339
|
while((key = read()) != STOP_CODE) {
|
|
316
|
-
if (i++ >=
|
|
340
|
+
if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
|
|
317
341
|
object[safeKey(currentDecoder.decodeKey(key))] = read();
|
|
318
342
|
}
|
|
319
343
|
}
|
|
320
344
|
else {
|
|
321
345
|
while ((key = read()) != STOP_CODE) {
|
|
322
|
-
if (i++ >=
|
|
346
|
+
if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
|
|
323
347
|
object[safeKey(key)] = read();
|
|
324
348
|
}
|
|
325
349
|
}
|
|
@@ -365,11 +389,13 @@
|
|
|
365
389
|
case 1: // negative int
|
|
366
390
|
return ~token
|
|
367
391
|
case 2: // buffer
|
|
392
|
+
if (token > srcEnd - position) throw endOfCBORError()
|
|
368
393
|
return readBin(token)
|
|
369
394
|
case 3: // string
|
|
370
|
-
if (srcStringEnd >= position) {
|
|
395
|
+
if (srcStringEnd >= position) { // already extracted, which only covers complete strings
|
|
371
396
|
return srcString.slice(position - srcStringStart, (position += token) - srcStringStart)
|
|
372
397
|
}
|
|
398
|
+
if (token > srcEnd - position) throw endOfCBORError()
|
|
373
399
|
if (srcStringEnd == 0 && srcEnd < 140 && token < 32) {
|
|
374
400
|
// for small blocks, avoiding the overhead of the extract call is helpful
|
|
375
401
|
let string = token < 16 ? shortStringInJS(token) : longStringInJS(token);
|
|
@@ -378,15 +404,19 @@
|
|
|
378
404
|
}
|
|
379
405
|
return readFixedString(token)
|
|
380
406
|
case 4: // array
|
|
381
|
-
if (token
|
|
407
|
+
if (token > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
|
|
408
|
+
// every element occupies at least one byte, so a length beyond what remains in the source can
|
|
409
|
+
// never be satisfied; check before allocating so a tiny header can not force a huge allocation
|
|
410
|
+
if (token > srcEnd - position) throw endOfCBORError()
|
|
382
411
|
let array = new Array(token);
|
|
383
412
|
//if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
|
|
384
413
|
//else
|
|
385
414
|
for (let i = 0; i < token; i++) array[i] = read();
|
|
386
415
|
return array
|
|
387
416
|
case 5: // map
|
|
388
|
-
if (token >= maxMapSize) throw new Error(`Map size exceeds ${maxArraySize}`)
|
|
389
417
|
if (currentDecoder.mapsAsObjects) {
|
|
418
|
+
if (token > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
|
|
419
|
+
if (token > (srcEnd - position) / 2) throw endOfCBORError() // each entry needs at least two bytes
|
|
390
420
|
let object = {};
|
|
391
421
|
if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read();
|
|
392
422
|
else for (let i = 0; i < token; i++) object[safeKey(read())] = read();
|
|
@@ -396,6 +426,8 @@
|
|
|
396
426
|
currentDecoder.mapsAsObjects = true;
|
|
397
427
|
restoreMapsAsObject = false;
|
|
398
428
|
}
|
|
429
|
+
if (token > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
|
|
430
|
+
if (token > (srcEnd - position) / 2) throw endOfCBORError()
|
|
399
431
|
let map = new Map();
|
|
400
432
|
if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()),read());
|
|
401
433
|
else for (let i = 0; i < token; i++) map.set(read(), read());
|
|
@@ -477,11 +509,7 @@
|
|
|
477
509
|
throw new Error('Unknown token ' + token)
|
|
478
510
|
}
|
|
479
511
|
default: // negative int
|
|
480
|
-
if (isNaN(token))
|
|
481
|
-
let error = new Error('Unexpected end of CBOR data');
|
|
482
|
-
error.incomplete = true;
|
|
483
|
-
throw error
|
|
484
|
-
}
|
|
512
|
+
if (isNaN(token)) throw endOfCBORError()
|
|
485
513
|
throw new Error('Unknown CBOR token ' + token)
|
|
486
514
|
}
|
|
487
515
|
}
|
|
@@ -930,18 +958,12 @@
|
|
|
930
958
|
return (glbl[data[0]] || Error)(data[1], data[2])
|
|
931
959
|
};
|
|
932
960
|
const packedTable = (read) => {
|
|
933
|
-
if (
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
error.incomplete = true;
|
|
937
|
-
throw error
|
|
938
|
-
}
|
|
961
|
+
if (!(position < srcEnd)) throw endOfCBORError()
|
|
962
|
+
if (src[position++] != 0x84)
|
|
963
|
+
throw new Error('Packed values structure must be followed by a 4 element array')
|
|
939
964
|
let newPackedValues = read(); // packed values
|
|
940
|
-
if (!newPackedValues || !newPackedValues.length)
|
|
941
|
-
|
|
942
|
-
error.incomplete = true;
|
|
943
|
-
throw error
|
|
944
|
-
}
|
|
965
|
+
if (!Array.isArray(newPackedValues) || !newPackedValues.length)
|
|
966
|
+
throw new Error('Packed values structure must be followed by a 4 element array')
|
|
945
967
|
packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues;
|
|
946
968
|
packedValues.prefixes = read();
|
|
947
969
|
packedValues.suffixes = read();
|
|
@@ -959,10 +981,7 @@
|
|
|
959
981
|
}
|
|
960
982
|
if (typeof data == 'number')
|
|
961
983
|
return packedValues[16 + (data >= 0 ? 2 * data : (-2 * data - 1))]
|
|
962
|
-
|
|
963
|
-
if (data === undefined)
|
|
964
|
-
error.incomplete = true;
|
|
965
|
-
throw error
|
|
984
|
+
throw new Error('No support for non-integer packed references yet')
|
|
966
985
|
};
|
|
967
986
|
|
|
968
987
|
// The following code is an incomplete implementation of http://cbor.schmorp.de/stringref
|
|
@@ -1032,9 +1051,19 @@
|
|
|
1032
1051
|
(currentExtensions[259] = (read) => {
|
|
1033
1052
|
// https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec
|
|
1034
1053
|
// for decoding as a standard Map
|
|
1035
|
-
|
|
1036
|
-
|
|
1054
|
+
let decoder = currentDecoder;
|
|
1055
|
+
if (decoder.mapsAsObjects) {
|
|
1056
|
+
decoder.mapsAsObjects = false;
|
|
1037
1057
|
restoreMapsAsObject = true;
|
|
1058
|
+
try {
|
|
1059
|
+
return read()
|
|
1060
|
+
} finally {
|
|
1061
|
+
// normally restored by the map header, but not if the tagged value is not a map or the read fails
|
|
1062
|
+
if (restoreMapsAsObject) {
|
|
1063
|
+
decoder.mapsAsObjects = true;
|
|
1064
|
+
restoreMapsAsObject = false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1038
1067
|
}
|
|
1039
1068
|
return read()
|
|
1040
1069
|
}).handlesRead = true;
|
|
@@ -1145,17 +1174,21 @@
|
|
|
1145
1174
|
}
|
|
1146
1175
|
|
|
1147
1176
|
function readJustLength() {
|
|
1177
|
+
if (!(position < srcEnd)) throw endOfCBORError()
|
|
1148
1178
|
let token = src[position++] & 0x1f;
|
|
1149
1179
|
if (token > 0x17) {
|
|
1150
1180
|
switch (token) {
|
|
1151
1181
|
case 0x18:
|
|
1182
|
+
if (position >= srcEnd) throw endOfCBORError()
|
|
1152
1183
|
token = src[position++];
|
|
1153
1184
|
break
|
|
1154
1185
|
case 0x19:
|
|
1186
|
+
if (position + 2 > srcEnd) throw endOfCBORError()
|
|
1155
1187
|
token = dataView.getUint16(position);
|
|
1156
1188
|
position += 2;
|
|
1157
1189
|
break
|
|
1158
1190
|
case 0x1a:
|
|
1191
|
+
if (position + 4 > srcEnd) throw endOfCBORError()
|
|
1159
1192
|
token = dataView.getUint32(position);
|
|
1160
1193
|
position += 4;
|
|
1161
1194
|
break
|
|
@@ -1197,22 +1230,27 @@
|
|
|
1197
1230
|
let savedStructures = currentStructures;
|
|
1198
1231
|
let savedDecoder = currentDecoder;
|
|
1199
1232
|
let savedSequentialMode = sequentialMode;
|
|
1200
|
-
let
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1233
|
+
let savedRestoreMapsAsObject = restoreMapsAsObject;
|
|
1234
|
+
restoreMapsAsObject = false;
|
|
1235
|
+
try {
|
|
1236
|
+
return callback()
|
|
1237
|
+
} finally {
|
|
1238
|
+
srcEnd = savedSrcEnd;
|
|
1239
|
+
position = savedPosition;
|
|
1240
|
+
stringPosition = savedStringPosition;
|
|
1241
|
+
srcStringStart = savedSrcStringStart;
|
|
1242
|
+
srcStringEnd = savedSrcStringEnd;
|
|
1243
|
+
srcString = savedSrcString;
|
|
1244
|
+
strings = savedStrings;
|
|
1245
|
+
referenceMap = savedReferenceMap;
|
|
1246
|
+
bundledStrings = savedBundledStrings;
|
|
1247
|
+
src = savedSrc;
|
|
1248
|
+
sequentialMode = savedSequentialMode;
|
|
1249
|
+
currentStructures = savedStructures;
|
|
1250
|
+
currentDecoder = savedDecoder;
|
|
1251
|
+
restoreMapsAsObject = savedRestoreMapsAsObject;
|
|
1252
|
+
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
|
|
1253
|
+
}
|
|
1216
1254
|
}
|
|
1217
1255
|
function clearSource() {
|
|
1218
1256
|
src = null;
|
|
@@ -1227,7 +1265,8 @@
|
|
|
1227
1265
|
function setSizeLimits(limits) {
|
|
1228
1266
|
if (limits.maxMapSize) maxMapSize = limits.maxMapSize;
|
|
1229
1267
|
if (limits.maxArraySize) maxArraySize = limits.maxArraySize;
|
|
1230
|
-
if (limits.maxObjectSize) limits.maxObjectSize;
|
|
1268
|
+
if (limits.maxObjectSize) maxObjectSize = limits.maxObjectSize;
|
|
1269
|
+
objectSizeLimit = Math.min(maxObjectSize, maxMapSize);
|
|
1231
1270
|
}
|
|
1232
1271
|
|
|
1233
1272
|
const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
|