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.
@@ -17,7 +17,6 @@
17
17
  const RECORD_INLINE_ID = 0xdfff; // temporary first-come first-serve tag // proposed tag: 0x7265 // 're'
18
18
  const BUNDLED_STRINGS_ID = 0xdff9;
19
19
  const PACKED_REFERENCE_TAG_ID = 6;
20
- const STOP_CODE = {};
21
20
  let maxArraySize = 112810000; // This is the maximum array size in V8. We would potentially detect and set it higher
22
21
  // for JSC, but this is pretty large and should be sufficient for most use cases
23
22
  let maxMapSize = 16810000; // JavaScript has a fixed maximum map size of about 16710000, but JS itself enforces this,
@@ -129,16 +128,11 @@
129
128
  // re-entrant execution, save the state and restore it after we do this decode
130
129
  return saveState(() => {
131
130
  clearSource();
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
- }
131
+ return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
139
132
  })
140
133
  }
141
- srcEnd = end > -1 ? end : source.length;
134
+ // past the view, the JS string readers would decode the missing bytes as zeros instead of failing as truncated
135
+ srcEnd = end > -1 && end < source.length ? end : source.length;
142
136
  position = 0;
143
137
  stringPosition = 0;
144
138
  srcStringEnd = 0;
@@ -178,18 +172,28 @@
178
172
  return checkedRead()
179
173
  }
180
174
  decodeMultiple(source, forEach) {
175
+ if (src) {
176
+ // Keep the complete nested sequence inside its own decoder state.
177
+ return saveState(() => {
178
+ clearSource();
179
+ return this ? this.decodeMultiple(source, forEach) : defaultDecoder.decodeMultiple(source, forEach)
180
+ })
181
+ }
181
182
  let values, lastPosition = 0;
182
183
  try {
183
184
  let size = source.length;
184
185
  sequentialMode = true;
185
186
  let value = this ? this.decode(source, size) : defaultDecoder.decode(source, size);
186
187
  if (forEach) {
188
+ let end = position;
187
189
  if (forEach(value) === false) {
188
190
  return
189
191
  }
190
- while(position < size) {
191
- lastPosition = position;
192
- if (forEach(checkedRead()) === false) {
192
+ while(end < size) {
193
+ lastPosition = end;
194
+ value = checkedRead();
195
+ end = position;
196
+ if (forEach(value) === false) {
193
197
  return
194
198
  }
195
199
  }
@@ -203,8 +207,10 @@
203
207
  return values
204
208
  }
205
209
  } catch(error) {
206
- error.lastPosition = lastPosition;
207
- error.values = values;
210
+ if (error && (typeof error == 'object' || typeof error == 'function')) {
211
+ error.lastPosition = lastPosition;
212
+ error.values = values;
213
+ }
208
214
  throw error
209
215
  } finally {
210
216
  sequentialMode = false;
@@ -216,8 +222,10 @@
216
222
  return position
217
223
  }
218
224
  const nestedDecodeErrors = new WeakSet();
225
+ let arrayAllocationBudget;
219
226
  function checkedRead() {
220
227
  try {
228
+ arrayAllocationBudget = srcEnd - position;
221
229
  let result = read();
222
230
  if (bundledStrings) {
223
231
  if (position >= bundledStrings.postBundlePosition)
@@ -259,9 +267,21 @@
259
267
  }
260
268
  }
261
269
 
262
- function endOfCBORError() {
270
+ function readBreak() {
271
+ if (position < srcEnd && src[position] === 0xff) {
272
+ position++;
273
+ return true
274
+ }
275
+ return false
276
+ }
277
+ // neededEnd, from a length check, is a position that the source must extend to before the item can decode. It is a
278
+ // lower bound for any completion of the source: every byte read before the check was within the source, so a longer
279
+ // source reaches the same check with the same length. Streams wait for it instead of decoding on every chunk.
280
+ function endOfCBORError(neededEnd) {
263
281
  let error = new Error('Unexpected end of CBOR data');
264
282
  error.incomplete = true;
283
+ if (neededEnd)
284
+ error.neededEnd = neededEnd;
265
285
  return error
266
286
  }
267
287
 
@@ -313,6 +333,9 @@
313
333
  } else if (currentDecoder.int64AsNumber) {
314
334
  token = dataView.getUint32(position) * 0x100000000;
315
335
  token += dataView.getUint32(position + 4);
336
+ position += 8;
337
+ if (majorType === 1) return -1 - token
338
+ break
316
339
  } else token = dataView.getBigUint64(position);
317
340
  position += 8;
318
341
  break
@@ -324,10 +347,10 @@
324
347
  throw new Error('Indefinite length not supported for byte or text strings')
325
348
  case 4: // array
326
349
  let array = [];
327
- let value, i = 0;
328
- while ((value = read()) != STOP_CODE) {
350
+ let i = 0;
351
+ while (!readBreak()) {
329
352
  if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
330
- array[i++] = value;
353
+ array[i++] = read();
331
354
  }
332
355
  return majorType == 4 ? array : majorType == 3 ? array.join('') : Buffer.concat(array)
333
356
  case 5: // map
@@ -336,13 +359,15 @@
336
359
  let object = {};
337
360
  let i = 0;
338
361
  if (currentDecoder.keyMap) {
339
- while((key = read()) != STOP_CODE) {
362
+ while (!readBreak()) {
363
+ key = read();
340
364
  if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
341
365
  object[safeKey(currentDecoder.decodeKey(key))] = read();
342
366
  }
343
367
  }
344
368
  else {
345
- while ((key = read()) != STOP_CODE) {
369
+ while (!readBreak()) {
370
+ key = read();
346
371
  if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
347
372
  object[safeKey(key)] = read();
348
373
  }
@@ -356,7 +381,8 @@
356
381
  let map = new Map();
357
382
  if (currentDecoder.keyMap) {
358
383
  let i = 0;
359
- while((key = read()) != STOP_CODE) {
384
+ while (!readBreak()) {
385
+ key = read();
360
386
  if (i++ >= maxMapSize) {
361
387
  throw new Error(`Map size exceeds ${maxMapSize}`);
362
388
  }
@@ -365,7 +391,8 @@
365
391
  }
366
392
  else {
367
393
  let i = 0;
368
- while ((key = read()) != STOP_CODE) {
394
+ while (!readBreak()) {
395
+ key = read();
369
396
  if (i++ >= maxMapSize) {
370
397
  throw new Error(`Map size exceeds ${maxMapSize}`);
371
398
  }
@@ -375,7 +402,8 @@
375
402
  return map
376
403
  }
377
404
  case 7:
378
- return STOP_CODE
405
+ // readBreak consumes every break that ends an indefinite-length item
406
+ throw new Error('Unexpected break stop code outside of an indefinite-length item')
379
407
  default:
380
408
  throw new Error('Invalid major type for indefinite length ' + majorType)
381
409
  }
@@ -389,13 +417,13 @@
389
417
  case 1: // negative int
390
418
  return ~token
391
419
  case 2: // buffer
392
- if (token > srcEnd - position) throw endOfCBORError()
420
+ if (token > srcEnd - position) throw endOfCBORError(position + token)
393
421
  return readBin(token)
394
422
  case 3: // string
395
423
  if (srcStringEnd >= position) { // already extracted, which only covers complete strings
396
424
  return srcString.slice(position - srcStringStart, (position += token) - srcStringStart)
397
425
  }
398
- if (token > srcEnd - position) throw endOfCBORError()
426
+ if (token > srcEnd - position) throw endOfCBORError(position + token)
399
427
  if (srcStringEnd == 0 && srcEnd < 140 && token < 32) {
400
428
  // for small blocks, avoiding the overhead of the extract call is helpful
401
429
  let string = token < 16 ? shortStringInJS(token) : longStringInJS(token);
@@ -407,7 +435,9 @@
407
435
  if (token > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
408
436
  // every element occupies at least one byte, so a length beyond what remains in the source can
409
437
  // never be satisfied; check before allocating so a tiny header can not force a huge allocation
410
- if (token > srcEnd - position) throw endOfCBORError()
438
+ // nested arrays each pass the check above yet still preallocate, so also spend from a
439
+ // per-decode budget of the remaining bytes; the summed lengths of any valid tree stay under it
440
+ if (token > srcEnd - position || (arrayAllocationBudget -= token) < 0) throw endOfCBORError(position + token)
411
441
  let array = new Array(token);
412
442
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
413
443
  //else
@@ -416,7 +446,7 @@
416
446
  case 5: // map
417
447
  if (currentDecoder.mapsAsObjects) {
418
448
  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
449
+ if (token > (srcEnd - position) / 2) throw endOfCBORError(position + 2 * token) // each entry needs at least two bytes
420
450
  let object = {};
421
451
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read();
422
452
  else for (let i = 0; i < token; i++) object[safeKey(read())] = read();
@@ -427,7 +457,7 @@
427
457
  restoreMapsAsObject = false;
428
458
  }
429
459
  if (token > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
430
- if (token > (srcEnd - position) / 2) throw endOfCBORError()
460
+ if (token > (srcEnd - position) / 2) throw endOfCBORError(position + 2 * token)
431
461
  let map = new Map();
432
462
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()),read());
433
463
  else for (let i = 0; i < token; i++) map.set(read(), read());
@@ -943,6 +973,7 @@
943
973
  }
944
974
  return object
945
975
  };
976
+ const legacyRecordDefinition = currentExtensions[LEGACY_RECORD_INLINE_ID];
946
977
  currentExtensions[14] = (value) => {
947
978
  if (bundledStrings)
948
979
  return bundledStrings[0].slice(bundledStrings.position0, bundledStrings.position0 += value)
@@ -1215,6 +1246,9 @@
1215
1246
  }
1216
1247
 
1217
1248
  function saveState(callback) {
1249
+ let savedFragmentEnd = fragmentEnd;
1250
+ let savedFragmentReader = fragmentReadString;
1251
+ let savedReadFixedString = readFixedString;
1218
1252
  let savedSrcEnd = srcEnd;
1219
1253
  let savedPosition = position;
1220
1254
  let savedStringPosition = stringPosition;
@@ -1224,6 +1258,7 @@
1224
1258
  let savedStrings = strings;
1225
1259
  let savedReferenceMap = referenceMap;
1226
1260
  let savedBundledStrings = bundledStrings;
1261
+ let savedPackedValues = packedValues;
1227
1262
 
1228
1263
  // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
1229
1264
  let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
@@ -1231,10 +1266,23 @@
1231
1266
  let savedDecoder = currentDecoder;
1232
1267
  let savedSequentialMode = sequentialMode;
1233
1268
  let savedRestoreMapsAsObject = restoreMapsAsObject;
1269
+ let savedArrayAllocationBudget = arrayAllocationBudget;
1270
+ fragmentEnd = 0;
1271
+ if (fragmentReadString) readFixedString = fragmentReadString;
1272
+ fragmentReadString = null;
1234
1273
  restoreMapsAsObject = false;
1235
1274
  try {
1236
1275
  return callback()
1276
+ } catch (error) {
1277
+ // the callback decodes its own source (a shared structures blob, or an extension's embedded data), so a
1278
+ // truncation there cannot be completed by more of the outer input; checkedRead() clears the flag
1279
+ if (error && (typeof error == 'object' || typeof error == 'function'))
1280
+ nestedDecodeErrors.add(error);
1281
+ throw error
1237
1282
  } finally {
1283
+ fragmentEnd = savedFragmentEnd;
1284
+ fragmentReadString = savedFragmentReader;
1285
+ readFixedString = savedReadFixedString;
1238
1286
  srcEnd = savedSrcEnd;
1239
1287
  position = savedPosition;
1240
1288
  stringPosition = savedStringPosition;
@@ -1244,11 +1292,13 @@
1244
1292
  strings = savedStrings;
1245
1293
  referenceMap = savedReferenceMap;
1246
1294
  bundledStrings = savedBundledStrings;
1295
+ packedValues = savedPackedValues;
1247
1296
  src = savedSrc;
1248
1297
  sequentialMode = savedSequentialMode;
1249
1298
  currentStructures = savedStructures;
1250
1299
  currentDecoder = savedDecoder;
1251
1300
  restoreMapsAsObject = savedRestoreMapsAsObject;
1301
+ arrayAllocationBudget = savedArrayAllocationBudget;
1252
1302
  dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
1253
1303
  }
1254
1304
  }
@@ -1258,8 +1308,87 @@
1258
1308
  currentStructures = null;
1259
1309
  }
1260
1310
 
1311
+ const registeredExtensions = new Map();
1312
+ let fragmentEnd = 0;
1313
+ let fragmentReadString;
1314
+
1315
+ const builtinScanTags = new Map([[LEGACY_RECORD_INLINE_ID, legacyRecordDefinition], [259, currentExtensions[259]]]);
1316
+ function isBuiltinScanTag(tag) {
1317
+ return builtinScanTags.has(tag) && currentExtensions[tag] === builtinScanTags.get(tag)
1318
+ }
1319
+
1320
+ function getScanTag(tag, decoder) {
1321
+ return tag >= BUNDLED_STRINGS_ID && decoder.structures && decoder.structures[tag & 0x1fff]
1322
+ }
1323
+
1324
+ function checkScanContainer(major, length, mapsAsObjects) {
1325
+ if (major === 4) {
1326
+ if (length > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
1327
+ } else if (mapsAsObjects) {
1328
+ if (length > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
1329
+ } else if (length > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
1330
+ }
1331
+
1332
+ function decodeScanned(decoder, source, end) {
1333
+ const boundStrings = exports.isNativeAccelerationEnabled && end < source.length;
1334
+ if (!registeredExtensions.size && !boundStrings) return decoder.decodeMultiple(source)
1335
+ const savedEnd = fragmentEnd;
1336
+ const savedReader = readFixedString;
1337
+ const savedFragmentReader = fragmentReadString;
1338
+ const originals = [];
1339
+ fragmentEnd = end;
1340
+ fragmentReadString = savedReader;
1341
+ try {
1342
+ if (boundStrings) readFixedString = (length) => {
1343
+ if (!fragmentEnd || position >= fragmentEnd) return savedReader(length)
1344
+ const savedSrcEnd = srcEnd;
1345
+ srcEnd = fragmentEnd;
1346
+ try { return savedReader(length) } finally { srcEnd = savedSrcEnd; }
1347
+ };
1348
+ for (const [tag, extension] of registeredExtensions) {
1349
+ if (!extension.handlesRead) continue
1350
+ const original = currentExtensions[tag];
1351
+ const guarded = (read) => {
1352
+ if (!fragmentEnd || position >= fragmentEnd) return extension(read)
1353
+ let calls = 0, framingError;
1354
+ const value = extension(() => {
1355
+ if (calls++) {
1356
+ framingError = new Error('Tag ' + tag + ': fragmented decoding cannot frame multi-item handlesRead extensions');
1357
+ throw framingError
1358
+ }
1359
+ return read()
1360
+ });
1361
+ if (framingError) throw framingError
1362
+ return value
1363
+ };
1364
+ guarded.handlesRead = true;
1365
+ originals.push([tag, original, guarded]);
1366
+ currentExtensions[tag] = guarded;
1367
+ }
1368
+ return decoder.decodeMultiple(source)
1369
+ } finally {
1370
+ fragmentEnd = savedEnd;
1371
+ fragmentReadString = savedFragmentReader;
1372
+ readFixedString = savedReader;
1373
+ for (const [tag, original, guarded] of originals) {
1374
+ if (currentExtensions[tag] === guarded) currentExtensions[tag] = original;
1375
+ }
1376
+ }
1377
+ }
1378
+
1379
+ // Internal streaming hooks; not part of the public decoder API.
1380
+ const _stream = {
1381
+ getTag: getScanTag,
1382
+ isBuiltinTag: isBuiltinScanTag,
1383
+ checkContainer: checkScanContainer,
1384
+ decode: decodeScanned,
1385
+ get readThreshold() { return inlineObjectReadThreshold }
1386
+ };
1387
+
1261
1388
  function addExtension(extension) {
1262
1389
  currentExtensions[extension.tag] = extension.decode;
1390
+ if (typeof extension.decode === 'function') registeredExtensions.set(extension.tag, extension.decode);
1391
+ else registeredExtensions.delete(extension.tag);
1263
1392
  }
1264
1393
 
1265
1394
  function setSizeLimits(limits) {
@@ -1291,6 +1420,7 @@
1291
1420
  exports.Decoder = Decoder;
1292
1421
  exports.FLOAT32_OPTIONS = FLOAT32_OPTIONS;
1293
1422
  exports.Tag = Tag;
1423
+ exports._stream = _stream;
1294
1424
  exports.addExtension = addExtension;
1295
1425
  exports.checkedRead = checkedRead;
1296
1426
  exports.clearSource = clearSource;