cbor-x 1.5.8 → 1.6.0

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
@@ -228,6 +228,18 @@ Note, that the performance is decreased with decimal rounding by about 20-25%, a
228
228
 
229
229
  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.
230
230
 
231
+ ### Setting Size Limits
232
+ 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
+ the properties you want to change), for example (with the defaults):
234
+ ```JavaScript
235
+ import { setMaxLimits } from 'cbor-x';
236
+ setMaxLimits({
237
+ maxArraySize: 112810000,
238
+ maxMapSize: 16810000,
239
+ maxObjectSize : 16710000
240
+ });
241
+ ```
242
+
231
243
  ## Performance
232
244
  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):
233
245
 
package/decode.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { decode, decodeMultiple, Decoder, addExtension, clearSource,roundFloat32, isNativeAccelerationEnabled,
2
- Extension, Options, FLOAT32_OPTIONS} from '.'
2
+ Extension, Options, FLOAT32_OPTIONS, setMaxLimits, MAX_LIMITS_OPTIONS } from '.'
package/decode.js CHANGED
@@ -14,6 +14,13 @@ const BUNDLED_STRINGS_ID = 0xdff9
14
14
  const PACKED_TABLE_TAG_ID = 51
15
15
  const PACKED_REFERENCE_TAG_ID = 6
16
16
  const STOP_CODE = {}
17
+ let maxArraySize = 112810000 // This is the maximum array size in V8. We would potentially detect and set it higher
18
+ // for JSC, but this is pretty large and should be sufficient for most use cases
19
+ let maxMapSize = 16810000 // JavaScript has a fixed maximum map size of about 16710000, but JS itself enforces this,
20
+ // so we don't need to
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.
17
24
  let strings = EMPTY_ARRAY
18
25
  let stringPosition = 0
19
26
  let currentDecoder = {}
@@ -294,6 +301,7 @@ export function read() {
294
301
  let array = []
295
302
  let value, i = 0
296
303
  while ((value = read()) != STOP_CODE) {
304
+ if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
297
305
  array[i++] = value
298
306
  }
299
307
  return majorType == 4 ? array : majorType == 3 ? array.join('') : Buffer.concat(array)
@@ -301,8 +309,19 @@ export function read() {
301
309
  let key
302
310
  if (currentDecoder.mapsAsObjects) {
303
311
  let object = {}
304
- if (currentDecoder.keyMap) while((key = read()) != STOP_CODE) object[safeKey(currentDecoder.decodeKey(key))] = read()
305
- else while ((key = read()) != STOP_CODE) object[safeKey(key)] = read()
312
+ let i = 0;
313
+ if (currentDecoder.keyMap) {
314
+ while((key = read()) != STOP_CODE) {
315
+ if (i++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
316
+ object[safeKey(currentDecoder.decodeKey(key))] = read()
317
+ }
318
+ }
319
+ else {
320
+ while ((key = read()) != STOP_CODE) {
321
+ if (i++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
322
+ object[safeKey(key)] = read()
323
+ }
324
+ }
306
325
  return object
307
326
  } else {
308
327
  if (restoreMapsAsObject) {
@@ -310,8 +329,24 @@ export function read() {
310
329
  restoreMapsAsObject = false
311
330
  }
312
331
  let map = new Map()
313
- if (currentDecoder.keyMap) while((key = read()) != STOP_CODE) map.set(currentDecoder.decodeKey(key), read())
314
- else while ((key = read()) != STOP_CODE) map.set(key, read())
332
+ if (currentDecoder.keyMap) {
333
+ let i = 0;
334
+ while((key = read()) != STOP_CODE) {
335
+ if (i++ >= maxMapSize) {
336
+ throw new Error(`Map size exceeds ${maxMapSize}`);
337
+ }
338
+ map.set(currentDecoder.decodeKey(key), read())
339
+ }
340
+ }
341
+ else {
342
+ let i = 0;
343
+ while ((key = read()) != STOP_CODE) {
344
+ if (i++ >= maxMapSize) {
345
+ throw new Error(`Map size exceeds ${maxMapSize}`);
346
+ }
347
+ map.set(key, read())
348
+ }
349
+ }
315
350
  return map
316
351
  }
317
352
  case 7:
@@ -342,12 +377,14 @@ export function read() {
342
377
  }
343
378
  return readFixedString(token)
344
379
  case 4: // array
380
+ if (token >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
345
381
  let array = new Array(token)
346
382
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
347
383
  //else
348
384
  for (let i = 0; i < token; i++) array[i] = read()
349
385
  return array
350
386
  case 5: // map
387
+ if (token >= maxMapSize) throw new Error(`Map size exceeds ${maxArraySize}`)
351
388
  if (currentDecoder.mapsAsObjects) {
352
389
  let object = {}
353
390
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read()
@@ -449,6 +486,7 @@ export function read() {
449
486
  }
450
487
  const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/
451
488
  function createStructureReader(structure) {
489
+ if (!structure) throw new Error('Structure is required in record definition');
452
490
  function readObject() {
453
491
  // get the array size from the header
454
492
  let length = src[position++]
@@ -504,7 +542,8 @@ function createStructureReader(structure) {
504
542
  function safeKey(key) {
505
543
  // protect against prototype pollution
506
544
  if (typeof key === 'string') return key === '__proto__' ? '__proto_' : key
507
- if (typeof key !== 'object') return key.toString()
545
+ if (typeof key === 'number' || typeof key === 'boolean' || typeof key === 'bigint') return key.toString();
546
+ if (key == null) return key + '';
508
547
  // protect against expensive (DoS) string conversions
509
548
  throw new Error('Invalid property name type ' + typeof key);
510
549
  }
@@ -885,7 +924,7 @@ currentExtensions[2] = (buffer) => {
885
924
  // bigint extension
886
925
  let value = BigInt(0)
887
926
  for (let i = 0, l = buffer.byteLength; i < l; i++) {
888
- value = BigInt(buffer[i]) + value << BigInt(8)
927
+ value = BigInt(buffer[i]) + (value << BigInt(8))
889
928
  }
890
929
  return value
891
930
  }
@@ -1000,6 +1039,7 @@ currentExtensions[28] = (read) => {
1000
1039
  referenceMap.id = 0
1001
1040
  }
1002
1041
  let id = referenceMap.id++
1042
+ let startingPosition = position
1003
1043
  let token = src[position]
1004
1044
  let target
1005
1045
  // TODO: handle Maps, Sets, and other types that can cycle; this is complicated, because you potentially need to read
@@ -1012,8 +1052,20 @@ currentExtensions[28] = (read) => {
1012
1052
  let refEntry = { target } // a placeholder object
1013
1053
  referenceMap.set(id, refEntry)
1014
1054
  let targetProperties = read() // read the next value as the target object to id
1015
- if (refEntry.used) // there is a cycle, so we have to assign properties to original target
1055
+ if (refEntry.used) {// there is a cycle, so we have to assign properties to original target
1056
+ if (Object.getPrototypeOf(target) !== Object.getPrototypeOf(targetProperties)) {
1057
+ // this means that the returned target does not match the targetProperties, so we need rerun the read to
1058
+ // have the correctly create instance be assigned as a reference, then we do the copy the properties back to the
1059
+ // target
1060
+ // reset the position so that the read can be repeated
1061
+ position = startingPosition
1062
+ // the returned instance is our new target for references
1063
+ target = targetProperties
1064
+ referenceMap.set(id, { target })
1065
+ targetProperties = read()
1066
+ }
1016
1067
  return Object.assign(target, targetProperties)
1068
+ }
1017
1069
  refEntry.target = targetProperties // the placeholder wasn't used, replace with the deserialized one
1018
1070
  return targetProperties // no cycle, can just use the returned read object
1019
1071
  }
@@ -1095,7 +1147,7 @@ function registerTypedArray(TypedArray, tag) {
1095
1147
  for (let littleEndian = 0; littleEndian < 2; littleEndian++) {
1096
1148
  if (!littleEndian && bytesPerElement == 1)
1097
1149
  continue
1098
- let sizeShift = bytesPerElement == 2 ? 1 : bytesPerElement == 4 ? 2 : 3
1150
+ let sizeShift = bytesPerElement == 2 ? 1 : bytesPerElement == 4 ? 2 : bytesPerElement == 8 ? 3 : 0
1099
1151
  currentExtensions[littleEndian ? tag : (tag - 4)] = (bytesPerElement == 1 || littleEndian == isLittleEndianMachine) ? (buffer) => {
1100
1152
  if (!TypedArray)
1101
1153
  throw new Error('Could not find typed array for code ' + tag)
@@ -1105,7 +1157,7 @@ function registerTypedArray(TypedArray, tag) {
1105
1157
  bytesPerElement === 2 && !(buffer.byteOffset & 1) ||
1106
1158
  bytesPerElement === 4 && !(buffer.byteOffset & 3) ||
1107
1159
  bytesPerElement === 8 && !(buffer.byteOffset & 7))
1108
- return new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength);
1160
+ return new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength >> sizeShift);
1109
1161
  }
1110
1162
  // we have to slice/copy here to get a new ArrayBuffer, if we are not word/byte aligned
1111
1163
  return new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer)
@@ -1222,6 +1274,12 @@ export function addExtension(extension) {
1222
1274
  currentExtensions[extension.tag] = extension.decode
1223
1275
  }
1224
1276
 
1277
+ export function setSizeLimits(limits) {
1278
+ if (limits.maxMapSize) maxMapSize = limits.maxMapSize;
1279
+ if (limits.maxArraySize) maxArraySize = limits.maxArraySize;
1280
+ if (limits.maxObjectSize) maxObjectSize = limits.maxObjectSize;
1281
+ }
1282
+
1225
1283
  export const mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
1226
1284
  for (let i = 0; i < 256; i++) {
1227
1285
  mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103))
@@ -18,6 +18,10 @@
18
18
  const BUNDLED_STRINGS_ID = 0xdff9;
19
19
  const PACKED_REFERENCE_TAG_ID = 6;
20
20
  const STOP_CODE = {};
21
+ let maxArraySize = 112810000; // This is the maximum array size in V8. We would potentially detect and set it higher
22
+ // for JSC, but this is pretty large and should be sufficient for most use cases
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.
21
25
  let strings = EMPTY_ARRAY;
22
26
  let stringPosition = 0;
23
27
  let currentDecoder = {};
@@ -298,6 +302,7 @@
298
302
  let array = [];
299
303
  let value, i = 0;
300
304
  while ((value = read()) != STOP_CODE) {
305
+ if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
301
306
  array[i++] = value;
302
307
  }
303
308
  return majorType == 4 ? array : majorType == 3 ? array.join('') : Buffer.concat(array)
@@ -305,8 +310,19 @@
305
310
  let key;
306
311
  if (currentDecoder.mapsAsObjects) {
307
312
  let object = {};
308
- if (currentDecoder.keyMap) while((key = read()) != STOP_CODE) object[safeKey(currentDecoder.decodeKey(key))] = read();
309
- else while ((key = read()) != STOP_CODE) object[safeKey(key)] = read();
313
+ let i = 0;
314
+ if (currentDecoder.keyMap) {
315
+ while((key = read()) != STOP_CODE) {
316
+ if (i++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
317
+ object[safeKey(currentDecoder.decodeKey(key))] = read();
318
+ }
319
+ }
320
+ else {
321
+ while ((key = read()) != STOP_CODE) {
322
+ if (i++ >= maxMapSize) throw new Error(`Property count exceeds ${maxMapSize}`)
323
+ object[safeKey(key)] = read();
324
+ }
325
+ }
310
326
  return object
311
327
  } else {
312
328
  if (restoreMapsAsObject) {
@@ -314,8 +330,24 @@
314
330
  restoreMapsAsObject = false;
315
331
  }
316
332
  let map = new Map();
317
- if (currentDecoder.keyMap) while((key = read()) != STOP_CODE) map.set(currentDecoder.decodeKey(key), read());
318
- else while ((key = read()) != STOP_CODE) map.set(key, read());
333
+ if (currentDecoder.keyMap) {
334
+ let i = 0;
335
+ while((key = read()) != STOP_CODE) {
336
+ if (i++ >= maxMapSize) {
337
+ throw new Error(`Map size exceeds ${maxMapSize}`);
338
+ }
339
+ map.set(currentDecoder.decodeKey(key), read());
340
+ }
341
+ }
342
+ else {
343
+ let i = 0;
344
+ while ((key = read()) != STOP_CODE) {
345
+ if (i++ >= maxMapSize) {
346
+ throw new Error(`Map size exceeds ${maxMapSize}`);
347
+ }
348
+ map.set(key, read());
349
+ }
350
+ }
319
351
  return map
320
352
  }
321
353
  case 7:
@@ -346,12 +378,14 @@
346
378
  }
347
379
  return readFixedString(token)
348
380
  case 4: // array
381
+ if (token >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
349
382
  let array = new Array(token);
350
383
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
351
384
  //else
352
385
  for (let i = 0; i < token; i++) array[i] = read();
353
386
  return array
354
387
  case 5: // map
388
+ if (token >= maxMapSize) throw new Error(`Map size exceeds ${maxArraySize}`)
355
389
  if (currentDecoder.mapsAsObjects) {
356
390
  let object = {};
357
391
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read();
@@ -453,6 +487,7 @@
453
487
  }
454
488
  const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/;
455
489
  function createStructureReader(structure) {
490
+ if (!structure) throw new Error('Structure is required in record definition');
456
491
  function readObject() {
457
492
  // get the array size from the header
458
493
  let length = src[position++];
@@ -508,7 +543,8 @@
508
543
  function safeKey(key) {
509
544
  // protect against prototype pollution
510
545
  if (typeof key === 'string') return key === '__proto__' ? '__proto_' : key
511
- if (typeof key !== 'object') return key.toString()
546
+ if (typeof key === 'number' || typeof key === 'boolean' || typeof key === 'bigint') return key.toString();
547
+ if (key == null) return key + '';
512
548
  // protect against expensive (DoS) string conversions
513
549
  throw new Error('Invalid property name type ' + typeof key);
514
550
  }
@@ -813,7 +849,7 @@
813
849
  // bigint extension
814
850
  let value = BigInt(0);
815
851
  for (let i = 0, l = buffer.byteLength; i < l; i++) {
816
- value = BigInt(buffer[i]) + value << BigInt(8);
852
+ value = BigInt(buffer[i]) + (value << BigInt(8));
817
853
  }
818
854
  return value
819
855
  };
@@ -928,6 +964,7 @@
928
964
  referenceMap.id = 0;
929
965
  }
930
966
  let id = referenceMap.id++;
967
+ let startingPosition = position;
931
968
  let token = src[position];
932
969
  let target;
933
970
  // TODO: handle Maps, Sets, and other types that can cycle; this is complicated, because you potentially need to read
@@ -940,8 +977,20 @@
940
977
  let refEntry = { target }; // a placeholder object
941
978
  referenceMap.set(id, refEntry);
942
979
  let targetProperties = read(); // read the next value as the target object to id
943
- if (refEntry.used) // there is a cycle, so we have to assign properties to original target
980
+ if (refEntry.used) {// there is a cycle, so we have to assign properties to original target
981
+ if (Object.getPrototypeOf(target) !== Object.getPrototypeOf(targetProperties)) {
982
+ // this means that the returned target does not match the targetProperties, so we need rerun the read to
983
+ // have the correctly create instance be assigned as a reference, then we do the copy the properties back to the
984
+ // target
985
+ // reset the position so that the read can be repeated
986
+ position = startingPosition;
987
+ // the returned instance is our new target for references
988
+ target = targetProperties;
989
+ referenceMap.set(id, { target });
990
+ targetProperties = read();
991
+ }
944
992
  return Object.assign(target, targetProperties)
993
+ }
945
994
  refEntry.target = targetProperties; // the placeholder wasn't used, replace with the deserialized one
946
995
  return targetProperties // no cycle, can just use the returned read object
947
996
  };
@@ -1023,7 +1072,7 @@
1023
1072
  for (let littleEndian = 0; littleEndian < 2; littleEndian++) {
1024
1073
  if (!littleEndian && bytesPerElement == 1)
1025
1074
  continue
1026
- let sizeShift = bytesPerElement == 2 ? 1 : bytesPerElement == 4 ? 2 : 3;
1075
+ let sizeShift = bytesPerElement == 2 ? 1 : bytesPerElement == 4 ? 2 : bytesPerElement == 8 ? 3 : 0;
1027
1076
  currentExtensions[littleEndian ? tag : (tag - 4)] = (bytesPerElement == 1 || littleEndian == isLittleEndianMachine) ? (buffer) => {
1028
1077
  if (!TypedArray)
1029
1078
  throw new Error('Could not find typed array for code ' + tag)
@@ -1033,7 +1082,7 @@
1033
1082
  bytesPerElement === 2 && !(buffer.byteOffset & 1) ||
1034
1083
  bytesPerElement === 4 && !(buffer.byteOffset & 3) ||
1035
1084
  bytesPerElement === 8 && !(buffer.byteOffset & 7))
1036
- return new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength);
1085
+ return new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength >> sizeShift);
1037
1086
  }
1038
1087
  // we have to slice/copy here to get a new ArrayBuffer, if we are not word/byte aligned
1039
1088
  return new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer)
@@ -1150,6 +1199,12 @@
1150
1199
  currentExtensions[extension.tag] = extension.decode;
1151
1200
  }
1152
1201
 
1202
+ function setSizeLimits(limits) {
1203
+ if (limits.maxMapSize) maxMapSize = limits.maxMapSize;
1204
+ if (limits.maxArraySize) maxArraySize = limits.maxArraySize;
1205
+ if (limits.maxObjectSize) limits.maxObjectSize;
1206
+ }
1207
+
1153
1208
  const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
1154
1209
  for (let i = 0; i < 256; i++) {
1155
1210
  mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
@@ -1182,6 +1237,7 @@
1182
1237
  exports.read = read;
1183
1238
  exports.roundFloat32 = roundFloat32;
1184
1239
  exports.setExtractor = setExtractor;
1240
+ exports.setSizeLimits = setSizeLimits;
1185
1241
  exports.typedArrays = typedArrays;
1186
1242
 
1187
1243
  }));