bson 6.3.0 → 6.5.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/bson.d.ts +52 -3
- package/lib/bson.bundle.js +406 -249
- package/lib/bson.bundle.js.map +1 -1
- package/lib/bson.cjs +406 -249
- package/lib/bson.cjs.map +1 -1
- package/lib/bson.mjs +406 -250
- package/lib/bson.mjs.map +1 -1
- package/lib/bson.rn.cjs +407 -249
- package/lib/bson.rn.cjs.map +1 -1
- package/package.json +18 -18
- package/src/binary.ts +2 -0
- package/src/bson.ts +4 -6
- package/src/constants.ts +3 -0
- package/src/db_ref.ts +0 -1
- package/src/decimal128.ts +1 -1
- package/src/error.ts +22 -0
- package/src/objectid.ts +34 -15
- package/src/parser/deserializer.ts +75 -144
- package/src/parser/on_demand/index.ts +28 -0
- package/src/parser/on_demand/parse_to_elements.ts +174 -0
- package/src/parser/serializer.ts +41 -104
- package/src/utils/byte_utils.ts +2 -8
- package/src/utils/latin.ts +44 -1
- package/src/utils/node_byte_utils.ts +12 -6
- package/src/utils/number_utils.ts +165 -0
- package/src/utils/web_byte_utils.ts +10 -10
|
@@ -14,7 +14,8 @@ import { ObjectId } from '../objectid';
|
|
|
14
14
|
import { BSONRegExp } from '../regexp';
|
|
15
15
|
import { BSONSymbol } from '../symbol';
|
|
16
16
|
import { Timestamp } from '../timestamp';
|
|
17
|
-
import {
|
|
17
|
+
import { ByteUtils } from '../utils/byte_utils';
|
|
18
|
+
import { NumberUtils } from '../utils/number_utils';
|
|
18
19
|
import { validateUtf8 } from '../validate_utf8';
|
|
19
20
|
|
|
20
21
|
/** @public */
|
|
@@ -91,11 +92,7 @@ export function internalDeserialize(
|
|
|
91
92
|
options = options == null ? {} : options;
|
|
92
93
|
const index = options && options.index ? options.index : 0;
|
|
93
94
|
// Read the document size
|
|
94
|
-
const size =
|
|
95
|
-
buffer[index] |
|
|
96
|
-
(buffer[index + 1] << 8) |
|
|
97
|
-
(buffer[index + 2] << 16) |
|
|
98
|
-
(buffer[index + 3] << 24);
|
|
95
|
+
const size = NumberUtils.getInt32LE(buffer, index);
|
|
99
96
|
|
|
100
97
|
if (size < 5) {
|
|
101
98
|
throw new BSONError(`bson size must be >= 5, is ${size}`);
|
|
@@ -164,7 +161,7 @@ function deserializeObject(
|
|
|
164
161
|
// Reflects utf-8 validation setting regardless of global or specific key validation
|
|
165
162
|
let validationSetting: boolean;
|
|
166
163
|
// Set of keys either to enable or disable validation on
|
|
167
|
-
|
|
164
|
+
let utf8KeysSet;
|
|
168
165
|
|
|
169
166
|
// Check for boolean uniformity and empty validation option
|
|
170
167
|
const utf8ValidatedKeys = validation.utf8;
|
|
@@ -190,6 +187,8 @@ function deserializeObject(
|
|
|
190
187
|
|
|
191
188
|
// Add keys to set that will either be validated or not based on validationSetting
|
|
192
189
|
if (!globalUTFValidation) {
|
|
190
|
+
utf8KeysSet = new Set();
|
|
191
|
+
|
|
193
192
|
for (const key of Object.keys(utf8ValidatedKeys)) {
|
|
194
193
|
utf8KeysSet.add(key);
|
|
195
194
|
}
|
|
@@ -202,8 +201,8 @@ function deserializeObject(
|
|
|
202
201
|
if (buffer.length < 5) throw new BSONError('corrupt bson message < 5 bytes long');
|
|
203
202
|
|
|
204
203
|
// Read the document size
|
|
205
|
-
const size =
|
|
206
|
-
|
|
204
|
+
const size = NumberUtils.getInt32LE(buffer, index);
|
|
205
|
+
index += 4;
|
|
207
206
|
|
|
208
207
|
// Ensure buffer is valid size
|
|
209
208
|
if (size < 5 || size > buffer.length) throw new BSONError('corrupt bson message');
|
|
@@ -217,7 +216,6 @@ function deserializeObject(
|
|
|
217
216
|
let isPossibleDBRef = isArray ? false : null;
|
|
218
217
|
|
|
219
218
|
// While we have more left data left keep parsing
|
|
220
|
-
const dataview = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
221
219
|
while (!done) {
|
|
222
220
|
// Read the type
|
|
223
221
|
const elementType = buffer[index++];
|
|
@@ -240,7 +238,7 @@ function deserializeObject(
|
|
|
240
238
|
|
|
241
239
|
// shouldValidateKey is true if the key should be validated, false otherwise
|
|
242
240
|
let shouldValidateKey = true;
|
|
243
|
-
if (globalUTFValidation || utf8KeysSet
|
|
241
|
+
if (globalUTFValidation || utf8KeysSet?.has(name)) {
|
|
244
242
|
shouldValidateKey = validationSetting;
|
|
245
243
|
} else {
|
|
246
244
|
shouldValidateKey = !validationSetting;
|
|
@@ -254,11 +252,8 @@ function deserializeObject(
|
|
|
254
252
|
index = i + 1;
|
|
255
253
|
|
|
256
254
|
if (elementType === constants.BSON_DATA_STRING) {
|
|
257
|
-
const stringSize =
|
|
258
|
-
|
|
259
|
-
(buffer[index++] << 8) |
|
|
260
|
-
(buffer[index++] << 16) |
|
|
261
|
-
(buffer[index++] << 24);
|
|
255
|
+
const stringSize = NumberUtils.getInt32LE(buffer, index);
|
|
256
|
+
index += 4;
|
|
262
257
|
if (
|
|
263
258
|
stringSize <= 0 ||
|
|
264
259
|
stringSize > buffer.length - index ||
|
|
@@ -269,37 +264,25 @@ function deserializeObject(
|
|
|
269
264
|
value = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);
|
|
270
265
|
index = index + stringSize;
|
|
271
266
|
} else if (elementType === constants.BSON_DATA_OID) {
|
|
272
|
-
const oid = ByteUtils.
|
|
273
|
-
oid
|
|
267
|
+
const oid = ByteUtils.allocateUnsafe(12);
|
|
268
|
+
for (let i = 0; i < 12; i++) oid[i] = buffer[index + i];
|
|
274
269
|
value = new ObjectId(oid);
|
|
275
270
|
index = index + 12;
|
|
276
271
|
} else if (elementType === constants.BSON_DATA_INT && promoteValues === false) {
|
|
277
|
-
value = new Int32(
|
|
278
|
-
|
|
279
|
-
);
|
|
272
|
+
value = new Int32(NumberUtils.getInt32LE(buffer, index));
|
|
273
|
+
index += 4;
|
|
280
274
|
} else if (elementType === constants.BSON_DATA_INT) {
|
|
281
|
-
value =
|
|
282
|
-
|
|
283
|
-
(buffer[index++] << 8) |
|
|
284
|
-
(buffer[index++] << 16) |
|
|
285
|
-
(buffer[index++] << 24);
|
|
286
|
-
} else if (elementType === constants.BSON_DATA_NUMBER && promoteValues === false) {
|
|
287
|
-
value = new Double(dataview.getFloat64(index, true));
|
|
288
|
-
index = index + 8;
|
|
275
|
+
value = NumberUtils.getInt32LE(buffer, index);
|
|
276
|
+
index += 4;
|
|
289
277
|
} else if (elementType === constants.BSON_DATA_NUMBER) {
|
|
290
|
-
value =
|
|
291
|
-
index
|
|
278
|
+
value = NumberUtils.getFloat64LE(buffer, index);
|
|
279
|
+
index += 8;
|
|
280
|
+
if (promoteValues === false) value = new Double(value);
|
|
292
281
|
} else if (elementType === constants.BSON_DATA_DATE) {
|
|
293
|
-
const lowBits =
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
(buffer[index++] << 24);
|
|
298
|
-
const highBits =
|
|
299
|
-
buffer[index++] |
|
|
300
|
-
(buffer[index++] << 8) |
|
|
301
|
-
(buffer[index++] << 16) |
|
|
302
|
-
(buffer[index++] << 24);
|
|
282
|
+
const lowBits = NumberUtils.getInt32LE(buffer, index);
|
|
283
|
+
const highBits = NumberUtils.getInt32LE(buffer, index + 4);
|
|
284
|
+
index += 8;
|
|
285
|
+
|
|
303
286
|
value = new Date(new Long(lowBits, highBits).toNumber());
|
|
304
287
|
} else if (elementType === constants.BSON_DATA_BOOLEAN) {
|
|
305
288
|
if (buffer[index] !== 0 && buffer[index] !== 1)
|
|
@@ -307,11 +290,8 @@ function deserializeObject(
|
|
|
307
290
|
value = buffer[index++] === 1;
|
|
308
291
|
} else if (elementType === constants.BSON_DATA_OBJECT) {
|
|
309
292
|
const _index = index;
|
|
310
|
-
const objectSize =
|
|
311
|
-
|
|
312
|
-
(buffer[index + 1] << 8) |
|
|
313
|
-
(buffer[index + 2] << 16) |
|
|
314
|
-
(buffer[index + 3] << 24);
|
|
293
|
+
const objectSize = NumberUtils.getInt32LE(buffer, index);
|
|
294
|
+
|
|
315
295
|
if (objectSize <= 0 || objectSize > buffer.length - index)
|
|
316
296
|
throw new BSONError('bad embedded document length in bson');
|
|
317
297
|
|
|
@@ -329,11 +309,7 @@ function deserializeObject(
|
|
|
329
309
|
index = index + objectSize;
|
|
330
310
|
} else if (elementType === constants.BSON_DATA_ARRAY) {
|
|
331
311
|
const _index = index;
|
|
332
|
-
const objectSize =
|
|
333
|
-
buffer[index] |
|
|
334
|
-
(buffer[index + 1] << 8) |
|
|
335
|
-
(buffer[index + 2] << 16) |
|
|
336
|
-
(buffer[index + 3] << 24);
|
|
312
|
+
const objectSize = NumberUtils.getInt32LE(buffer, index);
|
|
337
313
|
let arrayOptions: DeserializeOptions = options;
|
|
338
314
|
|
|
339
315
|
// Stop index
|
|
@@ -357,46 +333,38 @@ function deserializeObject(
|
|
|
357
333
|
} else if (elementType === constants.BSON_DATA_NULL) {
|
|
358
334
|
value = null;
|
|
359
335
|
} else if (elementType === constants.BSON_DATA_LONG) {
|
|
360
|
-
// Unpack the low and high bits
|
|
361
|
-
const dataview = BSONDataView.fromUint8Array(buffer.subarray(index, index + 8));
|
|
362
|
-
|
|
363
|
-
const lowBits =
|
|
364
|
-
buffer[index++] |
|
|
365
|
-
(buffer[index++] << 8) |
|
|
366
|
-
(buffer[index++] << 16) |
|
|
367
|
-
(buffer[index++] << 24);
|
|
368
|
-
const highBits =
|
|
369
|
-
buffer[index++] |
|
|
370
|
-
(buffer[index++] << 8) |
|
|
371
|
-
(buffer[index++] << 16) |
|
|
372
|
-
(buffer[index++] << 24);
|
|
373
|
-
const long = new Long(lowBits, highBits);
|
|
374
336
|
if (useBigInt64) {
|
|
375
|
-
value =
|
|
376
|
-
|
|
377
|
-
// Promote the long if possible
|
|
378
|
-
value =
|
|
379
|
-
long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
|
|
380
|
-
? long.toNumber()
|
|
381
|
-
: long;
|
|
337
|
+
value = NumberUtils.getBigInt64LE(buffer, index);
|
|
338
|
+
index += 8;
|
|
382
339
|
} else {
|
|
383
|
-
|
|
340
|
+
// Unpack the low and high bits
|
|
341
|
+
const lowBits = NumberUtils.getInt32LE(buffer, index);
|
|
342
|
+
const highBits = NumberUtils.getInt32LE(buffer, index + 4);
|
|
343
|
+
index += 8;
|
|
344
|
+
|
|
345
|
+
const long = new Long(lowBits, highBits);
|
|
346
|
+
// Promote the long if possible
|
|
347
|
+
if (promoteLongs && promoteValues === true) {
|
|
348
|
+
value =
|
|
349
|
+
long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
|
|
350
|
+
? long.toNumber()
|
|
351
|
+
: long;
|
|
352
|
+
} else {
|
|
353
|
+
value = long;
|
|
354
|
+
}
|
|
384
355
|
}
|
|
385
356
|
} else if (elementType === constants.BSON_DATA_DECIMAL128) {
|
|
386
357
|
// Buffer to contain the decimal bytes
|
|
387
|
-
const bytes = ByteUtils.
|
|
358
|
+
const bytes = ByteUtils.allocateUnsafe(16);
|
|
388
359
|
// Copy the next 16 bytes into the bytes buffer
|
|
389
|
-
|
|
360
|
+
for (let i = 0; i < 16; i++) bytes[i] = buffer[index + i];
|
|
390
361
|
// Update index
|
|
391
362
|
index = index + 16;
|
|
392
363
|
// Assign the new Decimal128 value
|
|
393
364
|
value = new Decimal128(bytes);
|
|
394
365
|
} else if (elementType === constants.BSON_DATA_BINARY) {
|
|
395
|
-
let binarySize =
|
|
396
|
-
|
|
397
|
-
(buffer[index++] << 8) |
|
|
398
|
-
(buffer[index++] << 16) |
|
|
399
|
-
(buffer[index++] << 24);
|
|
366
|
+
let binarySize = NumberUtils.getInt32LE(buffer, index);
|
|
367
|
+
index += 4;
|
|
400
368
|
const totalBinarySize = binarySize;
|
|
401
369
|
const subType = buffer[index++];
|
|
402
370
|
|
|
@@ -411,11 +379,8 @@ function deserializeObject(
|
|
|
411
379
|
if (buffer['slice'] != null) {
|
|
412
380
|
// If we have subtype 2 skip the 4 bytes for the size
|
|
413
381
|
if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
|
|
414
|
-
binarySize =
|
|
415
|
-
|
|
416
|
-
(buffer[index++] << 8) |
|
|
417
|
-
(buffer[index++] << 16) |
|
|
418
|
-
(buffer[index++] << 24);
|
|
382
|
+
binarySize = NumberUtils.getInt32LE(buffer, index);
|
|
383
|
+
index += 4;
|
|
419
384
|
if (binarySize < 0)
|
|
420
385
|
throw new BSONError('Negative binary type element size found for subtype 0x02');
|
|
421
386
|
if (binarySize > totalBinarySize - 4)
|
|
@@ -433,14 +398,10 @@ function deserializeObject(
|
|
|
433
398
|
}
|
|
434
399
|
}
|
|
435
400
|
} else {
|
|
436
|
-
const _buffer = ByteUtils.allocate(binarySize);
|
|
437
401
|
// If we have subtype 2 skip the 4 bytes for the size
|
|
438
402
|
if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
|
|
439
|
-
binarySize =
|
|
440
|
-
|
|
441
|
-
(buffer[index++] << 8) |
|
|
442
|
-
(buffer[index++] << 16) |
|
|
443
|
-
(buffer[index++] << 24);
|
|
403
|
+
binarySize = NumberUtils.getInt32LE(buffer, index);
|
|
404
|
+
index += 4;
|
|
444
405
|
if (binarySize < 0)
|
|
445
406
|
throw new BSONError('Negative binary type element size found for subtype 0x02');
|
|
446
407
|
if (binarySize > totalBinarySize - 4)
|
|
@@ -449,13 +410,12 @@ function deserializeObject(
|
|
|
449
410
|
throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
|
|
450
411
|
}
|
|
451
412
|
|
|
452
|
-
// Copy the data
|
|
453
|
-
for (i = 0; i < binarySize; i++) {
|
|
454
|
-
_buffer[i] = buffer[index + i];
|
|
455
|
-
}
|
|
456
|
-
|
|
457
413
|
if (promoteBuffers && promoteValues) {
|
|
458
|
-
value =
|
|
414
|
+
value = ByteUtils.allocateUnsafe(binarySize);
|
|
415
|
+
// Copy the data
|
|
416
|
+
for (i = 0; i < binarySize; i++) {
|
|
417
|
+
value[i] = buffer[index + i];
|
|
418
|
+
}
|
|
459
419
|
} else {
|
|
460
420
|
value = new Binary(buffer.slice(index, index + binarySize), subType);
|
|
461
421
|
if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) {
|
|
@@ -539,11 +499,8 @@ function deserializeObject(
|
|
|
539
499
|
// Set the object
|
|
540
500
|
value = new BSONRegExp(source, regExpOptions);
|
|
541
501
|
} else if (elementType === constants.BSON_DATA_SYMBOL) {
|
|
542
|
-
const stringSize =
|
|
543
|
-
|
|
544
|
-
(buffer[index++] << 8) |
|
|
545
|
-
(buffer[index++] << 16) |
|
|
546
|
-
(buffer[index++] << 24);
|
|
502
|
+
const stringSize = NumberUtils.getInt32LE(buffer, index);
|
|
503
|
+
index += 4;
|
|
547
504
|
if (
|
|
548
505
|
stringSize <= 0 ||
|
|
549
506
|
stringSize > buffer.length - index ||
|
|
@@ -555,31 +512,18 @@ function deserializeObject(
|
|
|
555
512
|
value = promoteValues ? symbol : new BSONSymbol(symbol);
|
|
556
513
|
index = index + stringSize;
|
|
557
514
|
} else if (elementType === constants.BSON_DATA_TIMESTAMP) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
buffer[index++] * (1 << 8) +
|
|
564
|
-
buffer[index++] * (1 << 16) +
|
|
565
|
-
buffer[index++] * (1 << 24);
|
|
566
|
-
const t =
|
|
567
|
-
buffer[index++] +
|
|
568
|
-
buffer[index++] * (1 << 8) +
|
|
569
|
-
buffer[index++] * (1 << 16) +
|
|
570
|
-
buffer[index++] * (1 << 24);
|
|
571
|
-
|
|
572
|
-
value = new Timestamp({ i, t });
|
|
515
|
+
value = new Timestamp({
|
|
516
|
+
i: NumberUtils.getUint32LE(buffer, index),
|
|
517
|
+
t: NumberUtils.getUint32LE(buffer, index + 4)
|
|
518
|
+
});
|
|
519
|
+
index += 8;
|
|
573
520
|
} else if (elementType === constants.BSON_DATA_MIN_KEY) {
|
|
574
521
|
value = new MinKey();
|
|
575
522
|
} else if (elementType === constants.BSON_DATA_MAX_KEY) {
|
|
576
523
|
value = new MaxKey();
|
|
577
524
|
} else if (elementType === constants.BSON_DATA_CODE) {
|
|
578
|
-
const stringSize =
|
|
579
|
-
|
|
580
|
-
(buffer[index++] << 8) |
|
|
581
|
-
(buffer[index++] << 16) |
|
|
582
|
-
(buffer[index++] << 24);
|
|
525
|
+
const stringSize = NumberUtils.getInt32LE(buffer, index);
|
|
526
|
+
index += 4;
|
|
583
527
|
if (
|
|
584
528
|
stringSize <= 0 ||
|
|
585
529
|
stringSize > buffer.length - index ||
|
|
@@ -599,11 +543,8 @@ function deserializeObject(
|
|
|
599
543
|
// Update parse index position
|
|
600
544
|
index = index + stringSize;
|
|
601
545
|
} else if (elementType === constants.BSON_DATA_CODE_W_SCOPE) {
|
|
602
|
-
const totalSize =
|
|
603
|
-
|
|
604
|
-
(buffer[index++] << 8) |
|
|
605
|
-
(buffer[index++] << 16) |
|
|
606
|
-
(buffer[index++] << 24);
|
|
546
|
+
const totalSize = NumberUtils.getInt32LE(buffer, index);
|
|
547
|
+
index += 4;
|
|
607
548
|
|
|
608
549
|
// Element cannot be shorter than totalSize + stringSize + documentSize + terminator
|
|
609
550
|
if (totalSize < 4 + 4 + 4 + 1) {
|
|
@@ -611,11 +552,8 @@ function deserializeObject(
|
|
|
611
552
|
}
|
|
612
553
|
|
|
613
554
|
// Get the code string size
|
|
614
|
-
const stringSize =
|
|
615
|
-
|
|
616
|
-
(buffer[index++] << 8) |
|
|
617
|
-
(buffer[index++] << 16) |
|
|
618
|
-
(buffer[index++] << 24);
|
|
555
|
+
const stringSize = NumberUtils.getInt32LE(buffer, index);
|
|
556
|
+
index += 4;
|
|
619
557
|
// Check if we have a valid string
|
|
620
558
|
if (
|
|
621
559
|
stringSize <= 0 ||
|
|
@@ -637,11 +575,7 @@ function deserializeObject(
|
|
|
637
575
|
// Parse the element
|
|
638
576
|
const _index = index;
|
|
639
577
|
// Decode the size of the object document
|
|
640
|
-
const objectSize =
|
|
641
|
-
buffer[index] |
|
|
642
|
-
(buffer[index + 1] << 8) |
|
|
643
|
-
(buffer[index + 2] << 16) |
|
|
644
|
-
(buffer[index + 3] << 24);
|
|
578
|
+
const objectSize = NumberUtils.getInt32LE(buffer, index);
|
|
645
579
|
// Decode the scope object
|
|
646
580
|
const scopeObject = deserializeObject(buffer, _index, options, false);
|
|
647
581
|
// Adjust the index
|
|
@@ -660,11 +594,8 @@ function deserializeObject(
|
|
|
660
594
|
value = new Code(functionString, scopeObject);
|
|
661
595
|
} else if (elementType === constants.BSON_DATA_DBPOINTER) {
|
|
662
596
|
// Get the code string size
|
|
663
|
-
const stringSize =
|
|
664
|
-
|
|
665
|
-
(buffer[index++] << 8) |
|
|
666
|
-
(buffer[index++] << 16) |
|
|
667
|
-
(buffer[index++] << 24);
|
|
597
|
+
const stringSize = NumberUtils.getInt32LE(buffer, index);
|
|
598
|
+
index += 4;
|
|
668
599
|
// Check if we have a valid string
|
|
669
600
|
if (
|
|
670
601
|
stringSize <= 0 ||
|
|
@@ -683,8 +614,8 @@ function deserializeObject(
|
|
|
683
614
|
index = index + stringSize;
|
|
684
615
|
|
|
685
616
|
// Read the oid
|
|
686
|
-
const oidBuffer = ByteUtils.
|
|
687
|
-
|
|
617
|
+
const oidBuffer = ByteUtils.allocateUnsafe(12);
|
|
618
|
+
for (let i = 0; i < 12; i++) oidBuffer[i] = buffer[index + i];
|
|
688
619
|
const oid = new ObjectId(oidBuffer);
|
|
689
620
|
|
|
690
621
|
// Update the index
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type BSONError, BSONOffsetError } from '../../error';
|
|
2
|
+
import { type BSONElement, parseToElements } from './parse_to_elements';
|
|
3
|
+
/**
|
|
4
|
+
* @experimental
|
|
5
|
+
* @public
|
|
6
|
+
*
|
|
7
|
+
* A new set of BSON APIs that are currently experimental and not intended for production use.
|
|
8
|
+
*/
|
|
9
|
+
export type OnDemand = {
|
|
10
|
+
BSONOffsetError: {
|
|
11
|
+
new (message: string, offset: number): BSONOffsetError;
|
|
12
|
+
isBSONError(value: unknown): value is BSONError;
|
|
13
|
+
};
|
|
14
|
+
parseToElements: (this: void, bytes: Uint8Array, startOffset?: number) => Iterable<BSONElement>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @experimental
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
const onDemand: OnDemand = Object.create(null);
|
|
22
|
+
|
|
23
|
+
onDemand.parseToElements = parseToElements;
|
|
24
|
+
onDemand.BSONOffsetError = BSONOffsetError;
|
|
25
|
+
|
|
26
|
+
Object.freeze(onDemand);
|
|
27
|
+
|
|
28
|
+
export { onDemand };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
|
|
2
|
+
import { BSONOffsetError } from '../../error';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* - This enum is const so the code we produce will inline the numbers
|
|
9
|
+
* - `minKey` is set to 255 so unsigned comparisons succeed
|
|
10
|
+
* - Modify with caution, double check the bundle contains literals
|
|
11
|
+
*/
|
|
12
|
+
const enum t {
|
|
13
|
+
double = 1,
|
|
14
|
+
string = 2,
|
|
15
|
+
object = 3,
|
|
16
|
+
array = 4,
|
|
17
|
+
binData = 5,
|
|
18
|
+
undefined = 6,
|
|
19
|
+
objectId = 7,
|
|
20
|
+
bool = 8,
|
|
21
|
+
date = 9,
|
|
22
|
+
null = 10,
|
|
23
|
+
regex = 11,
|
|
24
|
+
dbPointer = 12,
|
|
25
|
+
javascript = 13,
|
|
26
|
+
symbol = 14,
|
|
27
|
+
javascriptWithScope = 15,
|
|
28
|
+
int = 16,
|
|
29
|
+
timestamp = 17,
|
|
30
|
+
long = 18,
|
|
31
|
+
decimal = 19,
|
|
32
|
+
minKey = 255,
|
|
33
|
+
maxKey = 127
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @public
|
|
38
|
+
* @experimental
|
|
39
|
+
*/
|
|
40
|
+
export type BSONElement = [
|
|
41
|
+
type: number,
|
|
42
|
+
nameOffset: number,
|
|
43
|
+
nameLength: number,
|
|
44
|
+
offset: number,
|
|
45
|
+
length: number
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/** Parses a int32 little-endian at offset, throws if it is negative */
|
|
49
|
+
function getSize(source: Uint8Array, offset: number): number {
|
|
50
|
+
if (source[offset + 3] > 127) {
|
|
51
|
+
throw new BSONOffsetError('BSON size cannot be negative', offset);
|
|
52
|
+
}
|
|
53
|
+
return (
|
|
54
|
+
source[offset] |
|
|
55
|
+
(source[offset + 1] << 8) |
|
|
56
|
+
(source[offset + 2] << 16) |
|
|
57
|
+
(source[offset + 3] << 24)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Searches for null terminator of a BSON element's value (Never the document null terminator)
|
|
63
|
+
* **Does not** bounds check since this should **ONLY** be used within parseToElements which has asserted that `bytes` ends with a `0x00`.
|
|
64
|
+
* So this will at most iterate to the document's terminator and error if that is the offset reached.
|
|
65
|
+
*/
|
|
66
|
+
function findNull(bytes: Uint8Array, offset: number): number {
|
|
67
|
+
let nullTerminatorOffset = offset;
|
|
68
|
+
|
|
69
|
+
for (; bytes[nullTerminatorOffset] !== 0x00; nullTerminatorOffset++);
|
|
70
|
+
|
|
71
|
+
if (nullTerminatorOffset === bytes.length - 1) {
|
|
72
|
+
// We reached the null terminator of the document, not a value's
|
|
73
|
+
throw new BSONOffsetError('Null terminator not found', offset);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return nullTerminatorOffset;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @public
|
|
81
|
+
* @experimental
|
|
82
|
+
*/
|
|
83
|
+
export function parseToElements(bytes: Uint8Array, startOffset = 0): Iterable<BSONElement> {
|
|
84
|
+
if (bytes.length < 5) {
|
|
85
|
+
throw new BSONOffsetError(
|
|
86
|
+
`Input must be at least 5 bytes, got ${bytes.length} bytes`,
|
|
87
|
+
startOffset
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const documentSize = getSize(bytes, startOffset);
|
|
92
|
+
|
|
93
|
+
if (documentSize > bytes.length - startOffset) {
|
|
94
|
+
throw new BSONOffsetError(
|
|
95
|
+
`Parsed documentSize (${documentSize} bytes) does not match input length (${bytes.length} bytes)`,
|
|
96
|
+
startOffset
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (bytes[startOffset + documentSize - 1] !== 0x00) {
|
|
101
|
+
throw new BSONOffsetError('BSON documents must end in 0x00', startOffset + documentSize);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const elements: BSONElement[] = [];
|
|
105
|
+
let offset = startOffset + 4;
|
|
106
|
+
|
|
107
|
+
while (offset <= documentSize + startOffset) {
|
|
108
|
+
const type = bytes[offset];
|
|
109
|
+
offset += 1;
|
|
110
|
+
|
|
111
|
+
if (type === 0) {
|
|
112
|
+
if (offset - startOffset !== documentSize) {
|
|
113
|
+
throw new BSONOffsetError(`Invalid 0x00 type byte`, offset);
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const nameOffset = offset;
|
|
119
|
+
const nameLength = findNull(bytes, offset) - nameOffset;
|
|
120
|
+
offset += nameLength + 1;
|
|
121
|
+
|
|
122
|
+
let length: number;
|
|
123
|
+
|
|
124
|
+
if (type === t.double || type === t.long || type === t.date || type === t.timestamp) {
|
|
125
|
+
length = 8;
|
|
126
|
+
} else if (type === t.int) {
|
|
127
|
+
length = 4;
|
|
128
|
+
} else if (type === t.objectId) {
|
|
129
|
+
length = 12;
|
|
130
|
+
} else if (type === t.decimal) {
|
|
131
|
+
length = 16;
|
|
132
|
+
} else if (type === t.bool) {
|
|
133
|
+
length = 1;
|
|
134
|
+
} else if (type === t.null || type === t.undefined || type === t.maxKey || type === t.minKey) {
|
|
135
|
+
length = 0;
|
|
136
|
+
}
|
|
137
|
+
// Needs a size calculation
|
|
138
|
+
else if (type === t.regex) {
|
|
139
|
+
length = findNull(bytes, findNull(bytes, offset) + 1) + 1 - offset;
|
|
140
|
+
} else if (type === t.object || type === t.array || type === t.javascriptWithScope) {
|
|
141
|
+
length = getSize(bytes, offset);
|
|
142
|
+
} else if (
|
|
143
|
+
type === t.string ||
|
|
144
|
+
type === t.binData ||
|
|
145
|
+
type === t.dbPointer ||
|
|
146
|
+
type === t.javascript ||
|
|
147
|
+
type === t.symbol
|
|
148
|
+
) {
|
|
149
|
+
length = getSize(bytes, offset) + 4;
|
|
150
|
+
if (type === t.binData) {
|
|
151
|
+
// binary subtype
|
|
152
|
+
length += 1;
|
|
153
|
+
}
|
|
154
|
+
if (type === t.dbPointer) {
|
|
155
|
+
// dbPointer's objectId
|
|
156
|
+
length += 12;
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
throw new BSONOffsetError(
|
|
160
|
+
`Invalid 0x${type.toString(16).padStart(2, '0')} type byte`,
|
|
161
|
+
offset
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (length > documentSize) {
|
|
166
|
+
throw new BSONOffsetError('value reports length larger than document', offset);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
elements.push([type, nameOffset, nameLength, offset, length]);
|
|
170
|
+
offset += length;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return elements;
|
|
174
|
+
}
|