cbor-x 1.6.7 → 1.6.9

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/dist/test.js CHANGED
@@ -15,7 +15,6 @@
15
15
  const RECORD_INLINE_ID = 0xdfff; // temporary first-come first-serve tag // proposed tag: 0x7265 // 're'
16
16
  const BUNDLED_STRINGS_ID = 0xdff9;
17
17
  const PACKED_REFERENCE_TAG_ID = 6;
18
- const STOP_CODE = {};
19
18
  let maxArraySize = 112810000; // This is the maximum array size in V8. We would potentially detect and set it higher
20
19
  // for JSC, but this is pretty large and should be sufficient for most use cases
21
20
  let maxMapSize = 16810000; // JavaScript has a fixed maximum map size of about 16710000, but JS itself enforces this,
@@ -126,16 +125,11 @@
126
125
  // re-entrant execution, save the state and restore it after we do this decode
127
126
  return saveState(() => {
128
127
  clearSource();
129
- try {
130
- return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
131
- } catch (error) {
132
- if (error && typeof error == 'object')
133
- nestedDecodeErrors.add(error);
134
- throw error
135
- }
128
+ return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
136
129
  })
137
130
  }
138
- srcEnd = end > -1 ? end : source.length;
131
+ // past the view, the JS string readers would decode the missing bytes as zeros instead of failing as truncated
132
+ srcEnd = end > -1 && end < source.length ? end : source.length;
139
133
  position$1 = 0;
140
134
  stringPosition = 0;
141
135
  srcStringEnd = 0;
@@ -175,18 +169,28 @@
175
169
  return checkedRead()
176
170
  }
177
171
  decodeMultiple(source, forEach) {
172
+ if (src) {
173
+ // Keep the complete nested sequence inside its own decoder state.
174
+ return saveState(() => {
175
+ clearSource();
176
+ return this ? this.decodeMultiple(source, forEach) : defaultDecoder.decodeMultiple(source, forEach)
177
+ })
178
+ }
178
179
  let values, lastPosition = 0;
179
180
  try {
180
181
  let size = source.length;
181
182
  sequentialMode = true;
182
183
  let value = this ? this.decode(source, size) : defaultDecoder.decode(source, size);
183
184
  if (forEach) {
185
+ let end = position$1;
184
186
  if (forEach(value) === false) {
185
187
  return
186
188
  }
187
- while(position$1 < size) {
188
- lastPosition = position$1;
189
- if (forEach(checkedRead()) === false) {
189
+ while(end < size) {
190
+ lastPosition = end;
191
+ value = checkedRead();
192
+ end = position$1;
193
+ if (forEach(value) === false) {
190
194
  return
191
195
  }
192
196
  }
@@ -200,8 +204,10 @@
200
204
  return values
201
205
  }
202
206
  } catch(error) {
203
- error.lastPosition = lastPosition;
204
- error.values = values;
207
+ if (error && (typeof error == 'object' || typeof error == 'function')) {
208
+ error.lastPosition = lastPosition;
209
+ error.values = values;
210
+ }
205
211
  throw error
206
212
  } finally {
207
213
  sequentialMode = false;
@@ -210,8 +216,10 @@
210
216
  }
211
217
  }
212
218
  const nestedDecodeErrors = new WeakSet();
219
+ let arrayAllocationBudget;
213
220
  function checkedRead() {
214
221
  try {
222
+ arrayAllocationBudget = srcEnd - position$1;
215
223
  let result = read();
216
224
  if (bundledStrings$1) {
217
225
  if (position$1 >= bundledStrings$1.postBundlePosition)
@@ -253,9 +261,21 @@
253
261
  }
254
262
  }
255
263
 
256
- function endOfCBORError() {
264
+ function readBreak() {
265
+ if (position$1 < srcEnd && src[position$1] === 0xff) {
266
+ position$1++;
267
+ return true
268
+ }
269
+ return false
270
+ }
271
+ // neededEnd, from a length check, is a position that the source must extend to before the item can decode. It is a
272
+ // lower bound for any completion of the source: every byte read before the check was within the source, so a longer
273
+ // source reaches the same check with the same length. Streams wait for it instead of decoding on every chunk.
274
+ function endOfCBORError(neededEnd) {
257
275
  let error = new Error('Unexpected end of CBOR data');
258
276
  error.incomplete = true;
277
+ if (neededEnd)
278
+ error.neededEnd = neededEnd;
259
279
  return error
260
280
  }
261
281
 
@@ -307,6 +327,9 @@
307
327
  } else if (currentDecoder.int64AsNumber) {
308
328
  token = dataView.getUint32(position$1) * 0x100000000;
309
329
  token += dataView.getUint32(position$1 + 4);
330
+ position$1 += 8;
331
+ if (majorType === 1) return -1 - token
332
+ break
310
333
  } else token = dataView.getBigUint64(position$1);
311
334
  position$1 += 8;
312
335
  break
@@ -318,10 +341,10 @@
318
341
  throw new Error('Indefinite length not supported for byte or text strings')
319
342
  case 4: // array
320
343
  let array = [];
321
- let value, i = 0;
322
- while ((value = read()) != STOP_CODE) {
344
+ let i = 0;
345
+ while (!readBreak()) {
323
346
  if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
324
- array[i++] = value;
347
+ array[i++] = read();
325
348
  }
326
349
  return majorType == 4 ? array : majorType == 3 ? array.join('') : Buffer.concat(array)
327
350
  case 5: // map
@@ -330,13 +353,15 @@
330
353
  let object = {};
331
354
  let i = 0;
332
355
  if (currentDecoder.keyMap) {
333
- while((key = read()) != STOP_CODE) {
356
+ while (!readBreak()) {
357
+ key = read();
334
358
  if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
335
359
  object[safeKey(currentDecoder.decodeKey(key))] = read();
336
360
  }
337
361
  }
338
362
  else {
339
- while ((key = read()) != STOP_CODE) {
363
+ while (!readBreak()) {
364
+ key = read();
340
365
  if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
341
366
  object[safeKey(key)] = read();
342
367
  }
@@ -350,7 +375,8 @@
350
375
  let map = new Map();
351
376
  if (currentDecoder.keyMap) {
352
377
  let i = 0;
353
- while((key = read()) != STOP_CODE) {
378
+ while (!readBreak()) {
379
+ key = read();
354
380
  if (i++ >= maxMapSize) {
355
381
  throw new Error(`Map size exceeds ${maxMapSize}`);
356
382
  }
@@ -359,7 +385,8 @@
359
385
  }
360
386
  else {
361
387
  let i = 0;
362
- while ((key = read()) != STOP_CODE) {
388
+ while (!readBreak()) {
389
+ key = read();
363
390
  if (i++ >= maxMapSize) {
364
391
  throw new Error(`Map size exceeds ${maxMapSize}`);
365
392
  }
@@ -369,7 +396,8 @@
369
396
  return map
370
397
  }
371
398
  case 7:
372
- return STOP_CODE
399
+ // readBreak consumes every break that ends an indefinite-length item
400
+ throw new Error('Unexpected break stop code outside of an indefinite-length item')
373
401
  default:
374
402
  throw new Error('Invalid major type for indefinite length ' + majorType)
375
403
  }
@@ -383,13 +411,13 @@
383
411
  case 1: // negative int
384
412
  return ~token
385
413
  case 2: // buffer
386
- if (token > srcEnd - position$1) throw endOfCBORError()
414
+ if (token > srcEnd - position$1) throw endOfCBORError(position$1 + token)
387
415
  return readBin(token)
388
416
  case 3: // string
389
417
  if (srcStringEnd >= position$1) { // already extracted, which only covers complete strings
390
418
  return srcString.slice(position$1 - srcStringStart, (position$1 += token) - srcStringStart)
391
419
  }
392
- if (token > srcEnd - position$1) throw endOfCBORError()
420
+ if (token > srcEnd - position$1) throw endOfCBORError(position$1 + token)
393
421
  if (srcStringEnd == 0 && srcEnd < 140 && token < 32) {
394
422
  // for small blocks, avoiding the overhead of the extract call is helpful
395
423
  let string = token < 16 ? shortStringInJS(token) : longStringInJS(token);
@@ -401,7 +429,9 @@
401
429
  if (token > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
402
430
  // every element occupies at least one byte, so a length beyond what remains in the source can
403
431
  // never be satisfied; check before allocating so a tiny header can not force a huge allocation
404
- if (token > srcEnd - position$1) throw endOfCBORError()
432
+ // nested arrays each pass the check above yet still preallocate, so also spend from a
433
+ // per-decode budget of the remaining bytes; the summed lengths of any valid tree stay under it
434
+ if (token > srcEnd - position$1 || (arrayAllocationBudget -= token) < 0) throw endOfCBORError(position$1 + token)
405
435
  let array = new Array(token);
406
436
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
407
437
  //else
@@ -410,7 +440,7 @@
410
440
  case 5: // map
411
441
  if (currentDecoder.mapsAsObjects) {
412
442
  if (token > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
413
- if (token > (srcEnd - position$1) / 2) throw endOfCBORError() // each entry needs at least two bytes
443
+ if (token > (srcEnd - position$1) / 2) throw endOfCBORError(position$1 + 2 * token) // each entry needs at least two bytes
414
444
  let object = {};
415
445
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read();
416
446
  else for (let i = 0; i < token; i++) object[safeKey(read())] = read();
@@ -421,7 +451,7 @@
421
451
  restoreMapsAsObject = false;
422
452
  }
423
453
  if (token > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
424
- if (token > (srcEnd - position$1) / 2) throw endOfCBORError()
454
+ if (token > (srcEnd - position$1) / 2) throw endOfCBORError(position$1 + 2 * token)
425
455
  let map = new Map();
426
456
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()),read());
427
457
  else for (let i = 0; i < token; i++) map.set(read(), read());
@@ -934,6 +964,7 @@
934
964
  }
935
965
  return object
936
966
  };
967
+ const legacyRecordDefinition = currentExtensions[LEGACY_RECORD_INLINE_ID];
937
968
  currentExtensions[14] = (value) => {
938
969
  if (bundledStrings$1)
939
970
  return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value)
@@ -1206,6 +1237,8 @@
1206
1237
  }
1207
1238
 
1208
1239
  function saveState(callback) {
1240
+ let savedFragmentReader = fragmentReadString;
1241
+ let savedReadFixedString = readFixedString;
1209
1242
  let savedSrcEnd = srcEnd;
1210
1243
  let savedPosition = position$1;
1211
1244
  let savedStringPosition = stringPosition;
@@ -1215,6 +1248,7 @@
1215
1248
  let savedStrings = strings;
1216
1249
  let savedReferenceMap = referenceMap;
1217
1250
  let savedBundledStrings = bundledStrings$1;
1251
+ let savedPackedValues = packedValues;
1218
1252
 
1219
1253
  // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
1220
1254
  let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
@@ -1222,10 +1256,21 @@
1222
1256
  let savedDecoder = currentDecoder;
1223
1257
  let savedSequentialMode = sequentialMode;
1224
1258
  let savedRestoreMapsAsObject = restoreMapsAsObject;
1259
+ let savedArrayAllocationBudget = arrayAllocationBudget;
1260
+ if (fragmentReadString) readFixedString = fragmentReadString;
1261
+ fragmentReadString = null;
1225
1262
  restoreMapsAsObject = false;
1226
1263
  try {
1227
1264
  return callback()
1265
+ } catch (error) {
1266
+ // the callback decodes its own source (a shared structures blob, or an extension's embedded data), so a
1267
+ // truncation there cannot be completed by more of the outer input; checkedRead() clears the flag
1268
+ if (error && (typeof error == 'object' || typeof error == 'function'))
1269
+ nestedDecodeErrors.add(error);
1270
+ throw error
1228
1271
  } finally {
1272
+ fragmentReadString = savedFragmentReader;
1273
+ readFixedString = savedReadFixedString;
1229
1274
  srcEnd = savedSrcEnd;
1230
1275
  position$1 = savedPosition;
1231
1276
  stringPosition = savedStringPosition;
@@ -1235,11 +1280,13 @@
1235
1280
  strings = savedStrings;
1236
1281
  referenceMap = savedReferenceMap;
1237
1282
  bundledStrings$1 = savedBundledStrings;
1283
+ packedValues = savedPackedValues;
1238
1284
  src = savedSrc;
1239
1285
  sequentialMode = savedSequentialMode;
1240
1286
  currentStructures = savedStructures;
1241
1287
  currentDecoder = savedDecoder;
1242
1288
  restoreMapsAsObject = savedRestoreMapsAsObject;
1289
+ arrayAllocationBudget = savedArrayAllocationBudget;
1243
1290
  dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
1244
1291
  }
1245
1292
  }
@@ -1249,8 +1296,15 @@
1249
1296
  currentStructures = null;
1250
1297
  }
1251
1298
 
1299
+ const registeredExtensions = new Map();
1300
+ let fragmentReadString;
1301
+
1302
+ new Map([[LEGACY_RECORD_INLINE_ID, legacyRecordDefinition], [259, currentExtensions[259]]]);
1303
+
1252
1304
  function addExtension$2(extension) {
1253
1305
  currentExtensions[extension.tag] = extension.decode;
1306
+ if (typeof extension.decode === 'function') registeredExtensions.set(extension.tag, extension.decode);
1307
+ else registeredExtensions.delete(extension.tag);
1254
1308
  }
1255
1309
 
1256
1310
  const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
@@ -1838,6 +1892,11 @@
1838
1892
  return encode(json)
1839
1893
  }
1840
1894
 
1895
+ // not in extensions, so class instances that reach writeObject do not pay another instanceof
1896
+ if (constructor === DataView) {
1897
+ writeBuffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength), makeRoom);
1898
+ return
1899
+ }
1841
1900
  // no extension found, write as a plain object
1842
1901
  writeObject(value);
1843
1902
  }
@@ -1931,8 +1990,22 @@
1931
1990
  size++;
1932
1991
  }
1933
1992
  }
1934
- target[objectOffset++ + start] = size >> 8;
1935
- target[objectOffset + start] = size & 0xff;
1993
+ if (size < 0x10000) {
1994
+ // fits in a map-16 (uint16) count: back-patch the reserved 2 bytes
1995
+ target[objectOffset++ + start] = size >> 8;
1996
+ target[objectOffset + start] = size & 0xff;
1997
+ } else {
1998
+ // too many entries for a map-16 count: widen the already-written header
1999
+ // to map-32, mirroring writeArrayHeader's 0x9a/setUint32 path. Shift the
2000
+ // encoded entries 2 bytes forward to make room for the wider (uint32) count.
2001
+ if (position + 2 > safeEnd)
2002
+ makeRoom(position + 2);
2003
+ let headerPosition = objectOffset + start;
2004
+ target.copyWithin(headerPosition + 4, headerPosition + 2, position);
2005
+ position += 2;
2006
+ target[headerPosition - 1] = 0xba; // map 32
2007
+ targetView.setUint32(headerPosition, size);
2008
+ }
1936
2009
  } :
1937
2010
  (object, skipValues) => {
1938
2011
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
@@ -2671,6 +2744,15 @@
2671
2744
  assert.throws(() => decode$1(Buffer.from('7a10000000', 'hex')));
2672
2745
  });
2673
2746
 
2747
+ test('break stop code outside indefinite-length item', function() {
2748
+ assert.throws(() => decode$1(Buffer.from('ff', 'hex')));
2749
+ assert.throws(() => decode$1(Buffer.from('81ff', 'hex')));
2750
+ assert.throws(() => decode$1(Buffer.from('a16161ff', 'hex')));
2751
+ assert.throws(() => decode$1(Buffer.from('c0ff', 'hex')));
2752
+ assert.deepEqual(decode$1(Buffer.from('9f01ff', 'hex')), [1]);
2753
+ assert.deepEqual(decode$1(Buffer.from('bf616101ff', 'hex')), { a: 1 });
2754
+ });
2755
+
2674
2756
  test('overlong encoding', function() {
2675
2757
  const payload = Buffer.from([0x62, 0xc0, 0xaf]);
2676
2758
  const result = decode$1(payload);
@@ -3010,6 +3092,24 @@
3010
3092
  var decoded = decode(encoded);
3011
3093
  assert.equal(decoded.length, 0);
3012
3094
  });
3095
+ test('fixed map size overflow (map-16 to map-32)', function() {
3096
+ this.timeout(120000);
3097
+ // issue #131: the default encode() serializes objects with a back-patched
3098
+ // map-16 (uint16) header, so an object with >= 65536 keys overflowed the
3099
+ // count and the decoder stopped early with "Data read, but end of buffer not
3100
+ // reached". Verify the round-trip across the map-16 -> map-32 boundary.
3101
+ function roundTripObjectWith(count) {
3102
+ var data = {};
3103
+ for (var i = 0; i < count; i++)
3104
+ data['k' + i] = i;
3105
+ var decoded = decode(encode(data));
3106
+ assert.equal(Object.keys(decoded).length, count);
3107
+ assert.deepEqual(decoded, data);
3108
+ }
3109
+ roundTripObjectWith(65535); // map-16 path, must stay unchanged
3110
+ roundTripObjectWith(65536); // first size that needs map-32
3111
+ roundTripObjectWith(70000); // well past the 16-bit boundary
3112
+ });
3013
3113
 
3014
3114
  test('random strings', function(){
3015
3115
  var data = [];
@@ -3136,6 +3236,14 @@
3136
3236
  var deserialized = encoder.decode(serialized);
3137
3237
  assert.equal(deserialized, data);
3138
3238
  });
3239
+ test('negative int64 as number', function() {
3240
+ let encoded = Buffer.from('3b0000000e96848f5f', 'hex');
3241
+ assert.equal(new Decoder().decode(encoded), -62654812000n);
3242
+
3243
+ let decoder = new Decoder({ int64AsNumber: true });
3244
+ assert.equal(decoder.decode(encoded), -62654812000);
3245
+ assert.equal(decoder.decode(Buffer.from('3b001ffffffffffffe', 'hex')), Number.MIN_SAFE_INTEGER);
3246
+ });
3139
3247
  test('bigint to float', function() {
3140
3248
  var data = {
3141
3249
  a: 325283295382932843n
@@ -3180,6 +3288,21 @@
3180
3288
  assert.equal(decoded[2], 5);
3181
3289
  assert.equal(decoded.byteLength, 3);
3182
3290
  });
3291
+ test('encode DataView', function() {
3292
+ let bytes = new Uint8Array(40).map((_, i) => i);
3293
+ assert.equal(Buffer.from(encode(new DataView(bytes.buffer, 3, 4))).toString('hex'), '4403040506');
3294
+ assert.equal(Buffer.from(encode(new DataView(bytes.buffer, 5, 30))).toString('hex'),
3295
+ '581e' + Buffer.from(bytes.subarray(5, 35)).toString('hex'));
3296
+ assert.equal(Buffer.from(encode(new DataView(bytes.buffer, 0, 0))).toString('hex'), '40');
3297
+ assert.deepEqual(decode(encode({ v: new DataView(bytes.buffer, 1, 2) })), { v: Buffer.from([1, 2]) });
3298
+ assert.equal(Buffer.from(encode(bytes.buffer.slice(1, 4))).toString('hex'), '43010203');
3299
+ assert.equal(Buffer.from(encode(bytes.subarray(1, 4))).toString('hex'), 'd84043010203');
3300
+ assert.equal(Buffer.from(encode(Buffer.from(bytes.subarray(1, 4)))).toString('hex'), '43010203');
3301
+ assert.equal(Buffer.from(new Encoder({ tagUint8Array: false }).encode(bytes.subarray(1, 4))).toString('hex'), '43010203');
3302
+ let uint16Tag = new Uint8Array(new Uint16Array([1]).buffer)[0] == 1 ? '45' : '41';
3303
+ assert.equal(Buffer.from(encode(new Uint16Array(bytes.buffer, 2, 2))).toString('hex'), 'd8' + uint16Tag + '4402030405');
3304
+ assert.equal(Buffer.from(encode(new Uint8ClampedArray(bytes.buffer, 2, 2))).toString('hex'), 'd844420203');
3305
+ });
3183
3306
 
3184
3307
  test('iterator/indefinite length array', function(){
3185
3308
  class NotArray {