cbor-x 1.6.4 → 1.6.6

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,19 @@ 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
+ 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
+
244
246
  ## Performance
245
247
  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
248
 
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
@@ -243,13 +243,22 @@ export function checkedRead() {
243
243
  }
244
244
  }
245
245
 
246
+ function endOfCBORError() {
247
+ let error = new Error('Unexpected end of CBOR data')
248
+ error.incomplete = true
249
+ return error
250
+ }
251
+
246
252
  export function read() {
253
+ // compared as < so that a NaN position (from a corrupt length) also throws instead of reading undefined
254
+ if (!(position < srcEnd)) throw endOfCBORError()
247
255
  let token = src[position++]
248
256
  let majorType = token >> 5
249
257
  token = token & 0x1f
250
258
  if (token > 0x17) {
251
259
  switch (token) {
252
260
  case 0x18:
261
+ if (position >= srcEnd) throw endOfCBORError()
253
262
  token = src[position++]
254
263
  break
255
264
  case 0x19:
@@ -378,6 +387,9 @@ export function read() {
378
387
  return readFixedString(token)
379
388
  case 4: // array
380
389
  if (token >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
390
+ // every element occupies at least one byte, so a length beyond what remains in the source can
391
+ // never be satisfied; check before allocating so a tiny header can not force a huge allocation
392
+ if (token > srcEnd - position) throw endOfCBORError()
381
393
  let array = new Array(token)
382
394
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
383
395
  //else
@@ -385,6 +397,7 @@ export function read() {
385
397
  return array
386
398
  case 5: // map
387
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
388
401
  if (currentDecoder.mapsAsObjects) {
389
402
  let object = {}
390
403
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read()
@@ -476,11 +489,7 @@ export function read() {
476
489
  throw new Error('Unknown token ' + token)
477
490
  }
478
491
  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
- }
492
+ if (isNaN(token)) throw endOfCBORError()
484
493
  throw new Error('Unknown CBOR token ' + token)
485
494
  }
486
495
  }
@@ -610,43 +619,49 @@ function readStringJS(length) {
610
619
  units.push(byte1)
611
620
  } else if ((byte1 & 0xe0) === 0xc0) {
612
621
  // 2 bytes
613
- const byte2 = src[position++] & 0x3f
614
- const codePoint = ((byte1 & 0x1f) << 6) | byte2
615
- // Reject overlong encoding: 2-byte sequences must encode values >= 0x80
616
- if (codePoint < 0x80) {
617
- units.push(0xFFFD) // replacement character
622
+ if (byte1 < 0xc2 || position >= end || (src[position] & 0xc0) !== 0x80) {
623
+ units.push(0xFFFD)
618
624
  } else {
619
- units.push(codePoint)
625
+ const byte2 = src[position++] & 0x3f
626
+ units.push(((byte1 & 0x1f) << 6) | byte2)
620
627
  }
621
628
  } else if ((byte1 & 0xf0) === 0xe0) {
622
629
  // 3 bytes
623
- const byte2 = src[position++] & 0x3f
624
- const byte3 = src[position++] & 0x3f
625
- const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3
626
- // Reject overlong encoding: 3-byte sequences must encode values >= 0x800
627
- // Also reject surrogates (0xD800-0xDFFF)
628
- if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
629
- units.push(0xFFFD) // replacement character
630
+ const byte2 = position < end ? src[position] : 0
631
+ if (position >= end || (byte2 & 0xc0) !== 0x80 ||
632
+ (byte1 === 0xe0 && byte2 < 0xa0) || (byte1 === 0xed && byte2 >= 0xa0)) {
633
+ units.push(0xFFFD)
630
634
  } else {
631
- units.push(codePoint)
635
+ position++
636
+ if (position >= end || (src[position] & 0xc0) !== 0x80) {
637
+ units.push(0xFFFD)
638
+ } else {
639
+ const byte3 = src[position++] & 0x3f
640
+ units.push(((byte1 & 0x1f) << 12) | ((byte2 & 0x3f) << 6) | byte3)
641
+ }
632
642
  }
633
643
  } else if ((byte1 & 0xf8) === 0xf0) {
634
644
  // 4 bytes
635
- const byte2 = src[position++] & 0x3f
636
- const byte3 = src[position++] & 0x3f
637
- const byte4 = src[position++] & 0x3f
638
- let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4
639
- // Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
640
- // Also reject values > 0x10FFFF (maximum valid Unicode)
641
- if (unit < 0x10000 || unit > 0x10FFFF) {
642
- units.push(0xFFFD) // replacement character
643
- } else if (unit > 0xffff) {
644
- unit -= 0x10000
645
- units.push(((unit >>> 10) & 0x3ff) | 0xd800)
646
- unit = 0xdc00 | (unit & 0x3ff)
647
- units.push(unit)
645
+ const byte2 = position < end ? src[position] : 0
646
+ if (byte1 > 0xf4 || position >= end || (byte2 & 0xc0) !== 0x80 ||
647
+ (byte1 === 0xf0 && byte2 < 0x90) || (byte1 === 0xf4 && byte2 >= 0x90)) {
648
+ units.push(0xFFFD)
648
649
  } else {
649
- units.push(unit)
650
+ position++
651
+ if (position >= end || (src[position] & 0xc0) !== 0x80) {
652
+ units.push(0xFFFD)
653
+ } else {
654
+ const byte3 = src[position++] & 0x3f
655
+ if (position >= end || (src[position] & 0xc0) !== 0x80) {
656
+ units.push(0xFFFD)
657
+ } else {
658
+ const byte4 = src[position++] & 0x3f
659
+ let unit = ((byte1 & 0x07) << 0x12) | ((byte2 & 0x3f) << 0x0c) | (byte3 << 0x06) | byte4
660
+ unit -= 0x10000
661
+ units.push(((unit >>> 10) & 0x3ff) | 0xd800)
662
+ units.push(0xdc00 | (unit & 0x3ff))
663
+ }
664
+ }
650
665
  }
651
666
  } else {
652
667
  units.push(0xFFFD) // replacement character for invalid lead byte
@@ -1214,10 +1229,12 @@ function readBundleExt() {
1214
1229
  }
1215
1230
 
1216
1231
  function readJustLength() {
1232
+ if (!(position < srcEnd)) throw endOfCBORError()
1217
1233
  let token = src[position++] & 0x1f
1218
1234
  if (token > 0x17) {
1219
1235
  switch (token) {
1220
1236
  case 0x18:
1237
+ if (position >= srcEnd) throw endOfCBORError()
1221
1238
  token = src[position++]
1222
1239
  break
1223
1240
  case 0x19:
@@ -244,13 +244,22 @@
244
244
  }
245
245
  }
246
246
 
247
+ function endOfCBORError() {
248
+ let error = new Error('Unexpected end of CBOR data');
249
+ error.incomplete = true;
250
+ return error
251
+ }
252
+
247
253
  function read() {
254
+ // compared as < so that a NaN position (from a corrupt length) also throws instead of reading undefined
255
+ if (!(position < srcEnd)) throw endOfCBORError()
248
256
  let token = src[position++];
249
257
  let majorType = token >> 5;
250
258
  token = token & 0x1f;
251
259
  if (token > 0x17) {
252
260
  switch (token) {
253
261
  case 0x18:
262
+ if (position >= srcEnd) throw endOfCBORError()
254
263
  token = src[position++];
255
264
  break
256
265
  case 0x19:
@@ -379,6 +388,9 @@
379
388
  return readFixedString(token)
380
389
  case 4: // array
381
390
  if (token >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
391
+ // every element occupies at least one byte, so a length beyond what remains in the source can
392
+ // never be satisfied; check before allocating so a tiny header can not force a huge allocation
393
+ if (token > srcEnd - position) throw endOfCBORError()
382
394
  let array = new Array(token);
383
395
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
384
396
  //else
@@ -386,6 +398,7 @@
386
398
  return array
387
399
  case 5: // map
388
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
389
402
  if (currentDecoder.mapsAsObjects) {
390
403
  let object = {};
391
404
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read();
@@ -477,11 +490,7 @@
477
490
  throw new Error('Unknown token ' + token)
478
491
  }
479
492
  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
- }
493
+ if (isNaN(token)) throw endOfCBORError()
485
494
  throw new Error('Unknown CBOR token ' + token)
486
495
  }
487
496
  }
@@ -605,43 +614,49 @@
605
614
  units.push(byte1);
606
615
  } else if ((byte1 & 0xe0) === 0xc0) {
607
616
  // 2 bytes
608
- const byte2 = src[position++] & 0x3f;
609
- const codePoint = ((byte1 & 0x1f) << 6) | byte2;
610
- // Reject overlong encoding: 2-byte sequences must encode values >= 0x80
611
- if (codePoint < 0x80) {
612
- units.push(0xFFFD); // replacement character
617
+ if (byte1 < 0xc2 || position >= end || (src[position] & 0xc0) !== 0x80) {
618
+ units.push(0xFFFD);
613
619
  } else {
614
- units.push(codePoint);
620
+ const byte2 = src[position++] & 0x3f;
621
+ units.push(((byte1 & 0x1f) << 6) | byte2);
615
622
  }
616
623
  } else if ((byte1 & 0xf0) === 0xe0) {
617
624
  // 3 bytes
618
- const byte2 = src[position++] & 0x3f;
619
- const byte3 = src[position++] & 0x3f;
620
- const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
621
- // Reject overlong encoding: 3-byte sequences must encode values >= 0x800
622
- // Also reject surrogates (0xD800-0xDFFF)
623
- if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
624
- units.push(0xFFFD); // replacement character
625
+ const byte2 = position < end ? src[position] : 0;
626
+ if (position >= end || (byte2 & 0xc0) !== 0x80 ||
627
+ (byte1 === 0xe0 && byte2 < 0xa0) || (byte1 === 0xed && byte2 >= 0xa0)) {
628
+ units.push(0xFFFD);
625
629
  } else {
626
- units.push(codePoint);
630
+ position++;
631
+ if (position >= end || (src[position] & 0xc0) !== 0x80) {
632
+ units.push(0xFFFD);
633
+ } else {
634
+ const byte3 = src[position++] & 0x3f;
635
+ units.push(((byte1 & 0x1f) << 12) | ((byte2 & 0x3f) << 6) | byte3);
636
+ }
627
637
  }
628
638
  } else if ((byte1 & 0xf8) === 0xf0) {
629
639
  // 4 bytes
630
- const byte2 = src[position++] & 0x3f;
631
- const byte3 = src[position++] & 0x3f;
632
- const byte4 = src[position++] & 0x3f;
633
- let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
634
- // Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
635
- // Also reject values > 0x10FFFF (maximum valid Unicode)
636
- if (unit < 0x10000 || unit > 0x10FFFF) {
637
- units.push(0xFFFD); // replacement character
638
- } else if (unit > 0xffff) {
639
- unit -= 0x10000;
640
- units.push(((unit >>> 10) & 0x3ff) | 0xd800);
641
- unit = 0xdc00 | (unit & 0x3ff);
642
- units.push(unit);
640
+ const byte2 = position < end ? src[position] : 0;
641
+ if (byte1 > 0xf4 || position >= end || (byte2 & 0xc0) !== 0x80 ||
642
+ (byte1 === 0xf0 && byte2 < 0x90) || (byte1 === 0xf4 && byte2 >= 0x90)) {
643
+ units.push(0xFFFD);
643
644
  } else {
644
- units.push(unit);
645
+ position++;
646
+ if (position >= end || (src[position] & 0xc0) !== 0x80) {
647
+ units.push(0xFFFD);
648
+ } else {
649
+ const byte3 = src[position++] & 0x3f;
650
+ if (position >= end || (src[position] & 0xc0) !== 0x80) {
651
+ units.push(0xFFFD);
652
+ } else {
653
+ const byte4 = src[position++] & 0x3f;
654
+ let unit = ((byte1 & 0x07) << 0x12) | ((byte2 & 0x3f) << 0x0c) | (byte3 << 0x06) | byte4;
655
+ unit -= 0x10000;
656
+ units.push(((unit >>> 10) & 0x3ff) | 0xd800);
657
+ units.push(0xdc00 | (unit & 0x3ff));
658
+ }
659
+ }
645
660
  }
646
661
  } else {
647
662
  units.push(0xFFFD); // replacement character for invalid lead byte
@@ -1139,10 +1154,12 @@
1139
1154
  }
1140
1155
 
1141
1156
  function readJustLength() {
1157
+ if (!(position < srcEnd)) throw endOfCBORError()
1142
1158
  let token = src[position++] & 0x1f;
1143
1159
  if (token > 0x17) {
1144
1160
  switch (token) {
1145
1161
  case 0x18:
1162
+ if (position >= srcEnd) throw endOfCBORError()
1146
1163
  token = src[position++];
1147
1164
  break
1148
1165
  case 0x19: