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 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 setMaxLimits export. Each of the properties are optional (only provide
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 { setMaxLimits } from 'cbor-x';
237
- setMaxLimits({
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, setMaxLimits, MAX_LIMITS_OPTIONS } from '.'
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; // 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,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
- let error = new Error('Unexpected bundle position');
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 (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer')) {
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++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
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++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
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 >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
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 (src[position++] != 0x84) {
1009
- let error = new Error('Packed values structure must be followed by a 4 element array')
1010
- if (src.length < position)
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
- let error = new Error('Packed values structure must be followed by a 4 element array')
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
- let error = new Error('No support for non-integer packed references yet')
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
- if (currentDecoder.mapsAsObjects) {
1111
- currentDecoder.mapsAsObjects = false
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 value = callback()
1276
- srcEnd = savedSrcEnd
1277
- position = savedPosition
1278
- stringPosition = savedStringPosition
1279
- srcStringStart = savedSrcStringStart
1280
- srcStringEnd = savedSrcStringEnd
1281
- srcString = savedSrcString
1282
- strings = savedStrings
1283
- referenceMap = savedReferenceMap
1284
- bundledStrings = savedBundledStrings
1285
- src = savedSrc
1286
- sequentialMode = savedSequentialMode
1287
- currentStructures = savedStructures
1288
- currentDecoder = savedDecoder
1289
- dataView = new DataView(src.buffer, src.byteOffset, src.byteLength)
1290
- return value
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
@@ -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,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
- let error = new Error('Unexpected bundle position');
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 (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer')) {
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++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
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++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
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 >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
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 (src[position++] != 0x84) {
934
- let error = new Error('Packed values structure must be followed by a 4 element array');
935
- if (src.length < position)
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
- let error = new Error('Packed values structure must be followed by a 4 element array');
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
- let error = new Error('No support for non-integer packed references yet');
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
- if (currentDecoder.mapsAsObjects) {
1036
- currentDecoder.mapsAsObjects = false;
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 value = callback();
1201
- srcEnd = savedSrcEnd;
1202
- position = savedPosition;
1203
- stringPosition = savedStringPosition;
1204
- srcStringStart = savedSrcStringStart;
1205
- srcStringEnd = savedSrcStringEnd;
1206
- srcString = savedSrcString;
1207
- strings = savedStrings;
1208
- referenceMap = savedReferenceMap;
1209
- bundledStrings = savedBundledStrings;
1210
- src = savedSrc;
1211
- sequentialMode = savedSequentialMode;
1212
- currentStructures = savedStructures;
1213
- currentDecoder = savedDecoder;
1214
- dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
1215
- return value
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