cbor-x 1.6.6 → 1.6.8

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 CHANGED
@@ -241,8 +241,12 @@ setSizeLimits({
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
+
244
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.
245
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
+
246
250
  ## Performance
247
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):
248
252
 
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; // This is the maximum number of keys in a Map. It takes over a minute to create this
23
- // many keys in an object, so also probably a reasonable choice there.
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
- return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
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,15 @@ export class Decoder {
204
211
  export function getPosition() {
205
212
  return position
206
213
  }
214
+ const nestedDecodeErrors = new WeakSet()
215
+ let arrayAllocationBudget
207
216
  export function checkedRead() {
208
217
  try {
218
+ arrayAllocationBudget = srcEnd - position
209
219
  let result = read()
210
220
  if (bundledStrings) {
211
- if (position >= bundledStrings.postBundlePosition) {
212
- let error = new Error('Unexpected bundle position');
213
- error.incomplete = true;
214
- throw error
215
- }
221
+ if (position >= bundledStrings.postBundlePosition)
222
+ throw new Error('Unexpected bundle position')
216
223
  // bundled strings to skip past
217
224
  position = bundledStrings.postBundlePosition;
218
225
  bundledStrings = null;
@@ -236,7 +243,14 @@ export function checkedRead() {
236
243
  return result
237
244
  } catch(error) {
238
245
  clearSource()
239
- if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer')) {
246
+ if (nestedDecodeErrors.has(error)) {
247
+ // from a nested decode of embedded data (by an extension), which was complete in this source
248
+ if (error.incomplete)
249
+ error.incomplete = false
250
+ } else if (error instanceof RangeError && position + 8 > srcEnd ||
251
+ error && typeof error.message == 'string' && error.message.startsWith('Unexpected end of buffer')) {
252
+ // a DataView read past the end of the source; these read at most 8 bytes and precede advancing position.
253
+ // Any other RangeError (such as a stack overflow from deeply nested input) is fatal.
240
254
  error.incomplete = true
241
255
  }
242
256
  throw error
@@ -321,13 +335,13 @@ export function read() {
321
335
  let i = 0;
322
336
  if (currentDecoder.keyMap) {
323
337
  while((key = read()) != STOP_CODE) {
324
- if (i++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
338
+ if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
325
339
  object[safeKey(currentDecoder.decodeKey(key))] = read()
326
340
  }
327
341
  }
328
342
  else {
329
343
  while ((key = read()) != STOP_CODE) {
330
- if (i++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
344
+ if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
331
345
  object[safeKey(key)] = read()
332
346
  }
333
347
  }
@@ -373,11 +387,13 @@ export function read() {
373
387
  case 1: // negative int
374
388
  return ~token
375
389
  case 2: // buffer
390
+ if (token > srcEnd - position) throw endOfCBORError()
376
391
  return readBin(token)
377
392
  case 3: // string
378
- if (srcStringEnd >= position) {
393
+ if (srcStringEnd >= position) { // already extracted, which only covers complete strings
379
394
  return srcString.slice(position - srcStringStart, (position += token) - srcStringStart)
380
395
  }
396
+ if (token > srcEnd - position) throw endOfCBORError()
381
397
  if (srcStringEnd == 0 && srcEnd < 140 && token < 32) {
382
398
  // for small blocks, avoiding the overhead of the extract call is helpful
383
399
  let string = token < 16 ? shortStringInJS(token) : longStringInJS(token)
@@ -386,19 +402,21 @@ export function read() {
386
402
  }
387
403
  return readFixedString(token)
388
404
  case 4: // array
389
- if (token >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
405
+ if (token > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
390
406
  // every element occupies at least one byte, so a length beyond what remains in the source can
391
407
  // never be satisfied; check before allocating so a tiny header can not force a huge allocation
392
- if (token > srcEnd - position) throw endOfCBORError()
408
+ // nested arrays each pass the check above yet still preallocate, so also spend from a
409
+ // per-decode budget of the remaining bytes; the summed lengths of any valid tree stay under it
410
+ if (token > srcEnd - position || (arrayAllocationBudget -= token) < 0) throw endOfCBORError()
393
411
  let array = new Array(token)
394
412
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
395
413
  //else
396
414
  for (let i = 0; i < token; i++) array[i] = read()
397
415
  return array
398
416
  case 5: // map
399
- if (token >= maxMapSize) throw new Error(`Map size exceeds ${maxArraySize}`)
400
- if (token > (srcEnd - position) / 2) throw endOfCBORError() // each entry needs at least two bytes
401
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
402
420
  let object = {}
403
421
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read()
404
422
  else for (let i = 0; i < token; i++) object[safeKey(read())] = read()
@@ -408,6 +426,8 @@ export function read() {
408
426
  currentDecoder.mapsAsObjects = true
409
427
  restoreMapsAsObject = false
410
428
  }
429
+ if (token > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
430
+ if (token > (srcEnd - position) / 2) throw endOfCBORError()
411
431
  let map = new Map()
412
432
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()),read())
413
433
  else for (let i = 0; i < token; i++) map.set(read(), read())
@@ -879,6 +899,7 @@ function readKey() {
879
899
  if (length >= 0x60 && length < 0x78) {
880
900
  // fixstr, potentially use key cache
881
901
  length = length - 0x60
902
+ if (length > srcEnd - position) throw endOfCBORError()
882
903
  if (srcStringEnd >= position) // if it has been extracted, must use it (and faster anyway)
883
904
  return srcString.slice(position - srcStringStart, (position += length) - srcStringStart)
884
905
  else if (!(srcStringEnd == 0 && srcEnd < 180))
@@ -1014,18 +1035,12 @@ currentExtensions[27] = (data) => { // http://cbor.schmorp.de/generic-object
1014
1035
  return (glbl[data[0]] || Error)(data[1], data[2])
1015
1036
  }
1016
1037
  const packedTable = (read) => {
1017
- if (src[position++] != 0x84) {
1018
- let error = new Error('Packed values structure must be followed by a 4 element array')
1019
- if (src.length < position)
1020
- error.incomplete = true
1021
- throw error
1022
- }
1038
+ if (!(position < srcEnd)) throw endOfCBORError()
1039
+ if (src[position++] != 0x84)
1040
+ throw new Error('Packed values structure must be followed by a 4 element array')
1023
1041
  let newPackedValues = read() // packed values
1024
- if (!newPackedValues || !newPackedValues.length) {
1025
- let error = new Error('Packed values structure must be followed by a 4 element array')
1026
- error.incomplete = true
1027
- throw error
1028
- }
1042
+ if (!Array.isArray(newPackedValues) || !newPackedValues.length)
1043
+ throw new Error('Packed values structure must be followed by a 4 element array')
1029
1044
  packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues
1030
1045
  packedValues.prefixes = read()
1031
1046
  packedValues.suffixes = read()
@@ -1043,10 +1058,7 @@ currentExtensions[PACKED_REFERENCE_TAG_ID] = (data) => { // packed reference
1043
1058
  }
1044
1059
  if (typeof data == 'number')
1045
1060
  return packedValues[16 + (data >= 0 ? 2 * data : (-2 * data - 1))]
1046
- let error = new Error('No support for non-integer packed references yet')
1047
- if (data === undefined)
1048
- error.incomplete = true
1049
- throw error
1061
+ throw new Error('No support for non-integer packed references yet')
1050
1062
  }
1051
1063
 
1052
1064
  // The following code is an incomplete implementation of http://cbor.schmorp.de/stringref
@@ -1116,9 +1128,19 @@ currentExtensions[258] = (array) => new Set(array); // https://github.com/input-
1116
1128
  (currentExtensions[259] = (read) => {
1117
1129
  // https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec
1118
1130
  // for decoding as a standard Map
1119
- if (currentDecoder.mapsAsObjects) {
1120
- currentDecoder.mapsAsObjects = false
1131
+ let decoder = currentDecoder
1132
+ if (decoder.mapsAsObjects) {
1133
+ decoder.mapsAsObjects = false
1121
1134
  restoreMapsAsObject = true
1135
+ try {
1136
+ return read()
1137
+ } finally {
1138
+ // normally restored by the map header, but not if the tagged value is not a map or the read fails
1139
+ if (restoreMapsAsObject) {
1140
+ decoder.mapsAsObjects = true
1141
+ restoreMapsAsObject = false
1142
+ }
1143
+ }
1122
1144
  }
1123
1145
  return read()
1124
1146
  }).handlesRead = true
@@ -1238,10 +1260,12 @@ function readJustLength() {
1238
1260
  token = src[position++]
1239
1261
  break
1240
1262
  case 0x19:
1263
+ if (position + 2 > srcEnd) throw endOfCBORError()
1241
1264
  token = dataView.getUint16(position)
1242
1265
  position += 2
1243
1266
  break
1244
1267
  case 0x1a:
1268
+ if (position + 4 > srcEnd) throw endOfCBORError()
1245
1269
  token = dataView.getUint32(position)
1246
1270
  position += 4
1247
1271
  break
@@ -1283,22 +1307,29 @@ function saveState(callback) {
1283
1307
  let savedStructures = currentStructures
1284
1308
  let savedDecoder = currentDecoder
1285
1309
  let savedSequentialMode = sequentialMode
1286
- let value = callback()
1287
- srcEnd = savedSrcEnd
1288
- position = savedPosition
1289
- stringPosition = savedStringPosition
1290
- srcStringStart = savedSrcStringStart
1291
- srcStringEnd = savedSrcStringEnd
1292
- srcString = savedSrcString
1293
- strings = savedStrings
1294
- referenceMap = savedReferenceMap
1295
- bundledStrings = savedBundledStrings
1296
- src = savedSrc
1297
- sequentialMode = savedSequentialMode
1298
- currentStructures = savedStructures
1299
- currentDecoder = savedDecoder
1300
- dataView = new DataView(src.buffer, src.byteOffset, src.byteLength)
1301
- return value
1310
+ let savedRestoreMapsAsObject = restoreMapsAsObject
1311
+ let savedArrayAllocationBudget = arrayAllocationBudget
1312
+ restoreMapsAsObject = false
1313
+ try {
1314
+ return callback()
1315
+ } finally {
1316
+ srcEnd = savedSrcEnd
1317
+ position = savedPosition
1318
+ stringPosition = savedStringPosition
1319
+ srcStringStart = savedSrcStringStart
1320
+ srcStringEnd = savedSrcStringEnd
1321
+ srcString = savedSrcString
1322
+ strings = savedStrings
1323
+ referenceMap = savedReferenceMap
1324
+ bundledStrings = savedBundledStrings
1325
+ src = savedSrc
1326
+ sequentialMode = savedSequentialMode
1327
+ currentStructures = savedStructures
1328
+ currentDecoder = savedDecoder
1329
+ restoreMapsAsObject = savedRestoreMapsAsObject
1330
+ arrayAllocationBudget = savedArrayAllocationBudget
1331
+ dataView = new DataView(src.buffer, src.byteOffset, src.byteLength)
1332
+ }
1302
1333
  }
1303
1334
  export function clearSource() {
1304
1335
  src = null
@@ -1314,6 +1345,7 @@ export function setSizeLimits(limits) {
1314
1345
  if (limits.maxMapSize) maxMapSize = limits.maxMapSize;
1315
1346
  if (limits.maxArraySize) maxArraySize = limits.maxArraySize;
1316
1347
  if (limits.maxObjectSize) maxObjectSize = limits.maxObjectSize;
1348
+ objectSizeLimit = Math.min(maxObjectSize, maxMapSize)
1317
1349
  }
1318
1350
 
1319
1351
  export const mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
@@ -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
- // many keys in an object, so also probably a reasonable choice there.
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
- return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
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,15 @@
205
215
  function getPosition() {
206
216
  return position
207
217
  }
218
+ const nestedDecodeErrors = new WeakSet();
219
+ let arrayAllocationBudget;
208
220
  function checkedRead() {
209
221
  try {
222
+ arrayAllocationBudget = srcEnd - position;
210
223
  let result = read();
211
224
  if (bundledStrings) {
212
- if (position >= bundledStrings.postBundlePosition) {
213
- let error = new Error('Unexpected bundle position');
214
- error.incomplete = true;
215
- throw error
216
- }
225
+ if (position >= bundledStrings.postBundlePosition)
226
+ throw new Error('Unexpected bundle position')
217
227
  // bundled strings to skip past
218
228
  position = bundledStrings.postBundlePosition;
219
229
  bundledStrings = null;
@@ -237,7 +247,14 @@
237
247
  return result
238
248
  } catch(error) {
239
249
  clearSource();
240
- if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer')) {
250
+ if (nestedDecodeErrors.has(error)) {
251
+ // from a nested decode of embedded data (by an extension), which was complete in this source
252
+ if (error.incomplete)
253
+ error.incomplete = false;
254
+ } else if (error instanceof RangeError && position + 8 > srcEnd ||
255
+ error && typeof error.message == 'string' && error.message.startsWith('Unexpected end of buffer')) {
256
+ // a DataView read past the end of the source; these read at most 8 bytes and precede advancing position.
257
+ // Any other RangeError (such as a stack overflow from deeply nested input) is fatal.
241
258
  error.incomplete = true;
242
259
  }
243
260
  throw error
@@ -322,13 +339,13 @@
322
339
  let i = 0;
323
340
  if (currentDecoder.keyMap) {
324
341
  while((key = read()) != STOP_CODE) {
325
- if (i++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
342
+ if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
326
343
  object[safeKey(currentDecoder.decodeKey(key))] = read();
327
344
  }
328
345
  }
329
346
  else {
330
347
  while ((key = read()) != STOP_CODE) {
331
- if (i++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
348
+ if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
332
349
  object[safeKey(key)] = read();
333
350
  }
334
351
  }
@@ -374,11 +391,13 @@
374
391
  case 1: // negative int
375
392
  return ~token
376
393
  case 2: // buffer
394
+ if (token > srcEnd - position) throw endOfCBORError()
377
395
  return readBin(token)
378
396
  case 3: // string
379
- if (srcStringEnd >= position) {
397
+ if (srcStringEnd >= position) { // already extracted, which only covers complete strings
380
398
  return srcString.slice(position - srcStringStart, (position += token) - srcStringStart)
381
399
  }
400
+ if (token > srcEnd - position) throw endOfCBORError()
382
401
  if (srcStringEnd == 0 && srcEnd < 140 && token < 32) {
383
402
  // for small blocks, avoiding the overhead of the extract call is helpful
384
403
  let string = token < 16 ? shortStringInJS(token) : longStringInJS(token);
@@ -387,19 +406,21 @@
387
406
  }
388
407
  return readFixedString(token)
389
408
  case 4: // array
390
- if (token >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
409
+ if (token > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
391
410
  // every element occupies at least one byte, so a length beyond what remains in the source can
392
411
  // never be satisfied; check before allocating so a tiny header can not force a huge allocation
393
- if (token > srcEnd - position) throw endOfCBORError()
412
+ // nested arrays each pass the check above yet still preallocate, so also spend from a
413
+ // per-decode budget of the remaining bytes; the summed lengths of any valid tree stay under it
414
+ if (token > srcEnd - position || (arrayAllocationBudget -= token) < 0) throw endOfCBORError()
394
415
  let array = new Array(token);
395
416
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
396
417
  //else
397
418
  for (let i = 0; i < token; i++) array[i] = read();
398
419
  return array
399
420
  case 5: // map
400
- if (token >= maxMapSize) throw new Error(`Map size exceeds ${maxArraySize}`)
401
- if (token > (srcEnd - position) / 2) throw endOfCBORError() // each entry needs at least two bytes
402
421
  if (currentDecoder.mapsAsObjects) {
422
+ if (token > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
423
+ if (token > (srcEnd - position) / 2) throw endOfCBORError() // each entry needs at least two bytes
403
424
  let object = {};
404
425
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read();
405
426
  else for (let i = 0; i < token; i++) object[safeKey(read())] = read();
@@ -409,6 +430,8 @@
409
430
  currentDecoder.mapsAsObjects = true;
410
431
  restoreMapsAsObject = false;
411
432
  }
433
+ if (token > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
434
+ if (token > (srcEnd - position) / 2) throw endOfCBORError()
412
435
  let map = new Map();
413
436
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()),read());
414
437
  else for (let i = 0; i < token; i++) map.set(read(), read());
@@ -939,18 +962,12 @@
939
962
  return (glbl[data[0]] || Error)(data[1], data[2])
940
963
  };
941
964
  const packedTable = (read) => {
942
- if (src[position++] != 0x84) {
943
- let error = new Error('Packed values structure must be followed by a 4 element array');
944
- if (src.length < position)
945
- error.incomplete = true;
946
- throw error
947
- }
965
+ if (!(position < srcEnd)) throw endOfCBORError()
966
+ if (src[position++] != 0x84)
967
+ throw new Error('Packed values structure must be followed by a 4 element array')
948
968
  let newPackedValues = read(); // packed values
949
- if (!newPackedValues || !newPackedValues.length) {
950
- let error = new Error('Packed values structure must be followed by a 4 element array');
951
- error.incomplete = true;
952
- throw error
953
- }
969
+ if (!Array.isArray(newPackedValues) || !newPackedValues.length)
970
+ throw new Error('Packed values structure must be followed by a 4 element array')
954
971
  packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues;
955
972
  packedValues.prefixes = read();
956
973
  packedValues.suffixes = read();
@@ -968,10 +985,7 @@
968
985
  }
969
986
  if (typeof data == 'number')
970
987
  return packedValues[16 + (data >= 0 ? 2 * data : (-2 * data - 1))]
971
- let error = new Error('No support for non-integer packed references yet');
972
- if (data === undefined)
973
- error.incomplete = true;
974
- throw error
988
+ throw new Error('No support for non-integer packed references yet')
975
989
  };
976
990
 
977
991
  // The following code is an incomplete implementation of http://cbor.schmorp.de/stringref
@@ -1041,9 +1055,19 @@
1041
1055
  (currentExtensions[259] = (read) => {
1042
1056
  // https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec
1043
1057
  // for decoding as a standard Map
1044
- if (currentDecoder.mapsAsObjects) {
1045
- currentDecoder.mapsAsObjects = false;
1058
+ let decoder = currentDecoder;
1059
+ if (decoder.mapsAsObjects) {
1060
+ decoder.mapsAsObjects = false;
1046
1061
  restoreMapsAsObject = true;
1062
+ try {
1063
+ return read()
1064
+ } finally {
1065
+ // normally restored by the map header, but not if the tagged value is not a map or the read fails
1066
+ if (restoreMapsAsObject) {
1067
+ decoder.mapsAsObjects = true;
1068
+ restoreMapsAsObject = false;
1069
+ }
1070
+ }
1047
1071
  }
1048
1072
  return read()
1049
1073
  }).handlesRead = true;
@@ -1163,10 +1187,12 @@
1163
1187
  token = src[position++];
1164
1188
  break
1165
1189
  case 0x19:
1190
+ if (position + 2 > srcEnd) throw endOfCBORError()
1166
1191
  token = dataView.getUint16(position);
1167
1192
  position += 2;
1168
1193
  break
1169
1194
  case 0x1a:
1195
+ if (position + 4 > srcEnd) throw endOfCBORError()
1170
1196
  token = dataView.getUint32(position);
1171
1197
  position += 4;
1172
1198
  break
@@ -1208,22 +1234,29 @@
1208
1234
  let savedStructures = currentStructures;
1209
1235
  let savedDecoder = currentDecoder;
1210
1236
  let savedSequentialMode = sequentialMode;
1211
- let value = callback();
1212
- srcEnd = savedSrcEnd;
1213
- position = savedPosition;
1214
- stringPosition = savedStringPosition;
1215
- srcStringStart = savedSrcStringStart;
1216
- srcStringEnd = savedSrcStringEnd;
1217
- srcString = savedSrcString;
1218
- strings = savedStrings;
1219
- referenceMap = savedReferenceMap;
1220
- bundledStrings = savedBundledStrings;
1221
- src = savedSrc;
1222
- sequentialMode = savedSequentialMode;
1223
- currentStructures = savedStructures;
1224
- currentDecoder = savedDecoder;
1225
- dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
1226
- return value
1237
+ let savedRestoreMapsAsObject = restoreMapsAsObject;
1238
+ let savedArrayAllocationBudget = arrayAllocationBudget;
1239
+ restoreMapsAsObject = false;
1240
+ try {
1241
+ return callback()
1242
+ } finally {
1243
+ srcEnd = savedSrcEnd;
1244
+ position = savedPosition;
1245
+ stringPosition = savedStringPosition;
1246
+ srcStringStart = savedSrcStringStart;
1247
+ srcStringEnd = savedSrcStringEnd;
1248
+ srcString = savedSrcString;
1249
+ strings = savedStrings;
1250
+ referenceMap = savedReferenceMap;
1251
+ bundledStrings = savedBundledStrings;
1252
+ src = savedSrc;
1253
+ sequentialMode = savedSequentialMode;
1254
+ currentStructures = savedStructures;
1255
+ currentDecoder = savedDecoder;
1256
+ restoreMapsAsObject = savedRestoreMapsAsObject;
1257
+ arrayAllocationBudget = savedArrayAllocationBudget;
1258
+ dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
1259
+ }
1227
1260
  }
1228
1261
  function clearSource() {
1229
1262
  src = null;
@@ -1238,7 +1271,8 @@
1238
1271
  function setSizeLimits(limits) {
1239
1272
  if (limits.maxMapSize) maxMapSize = limits.maxMapSize;
1240
1273
  if (limits.maxArraySize) maxArraySize = limits.maxArraySize;
1241
- if (limits.maxObjectSize) limits.maxObjectSize;
1274
+ if (limits.maxObjectSize) maxObjectSize = limits.maxObjectSize;
1275
+ objectSizeLimit = Math.min(maxObjectSize, maxMapSize);
1242
1276
  }
1243
1277
 
1244
1278
  const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding