bson 4.1.0 → 4.2.3

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.
Files changed (90) hide show
  1. package/HISTORY.md +48 -1
  2. package/README.md +7 -9
  3. package/bower.json +1 -1
  4. package/bson.d.ts +986 -0
  5. package/dist/bson.browser.esm.js +7811 -5011
  6. package/dist/bson.browser.esm.js.map +1 -0
  7. package/dist/bson.browser.umd.js +7862 -5099
  8. package/dist/bson.browser.umd.js.map +1 -0
  9. package/dist/bson.bundle.js +8723 -9228
  10. package/dist/bson.bundle.js.map +1 -0
  11. package/dist/bson.esm.js +5728 -4951
  12. package/dist/bson.esm.js.map +1 -0
  13. package/etc/prepare.js +19 -0
  14. package/lib/binary.js +218 -398
  15. package/lib/binary.js.map +1 -0
  16. package/lib/bson.js +201 -240
  17. package/lib/bson.js.map +1 -0
  18. package/lib/code.js +41 -41
  19. package/lib/code.js.map +1 -0
  20. package/lib/constants.js +78 -203
  21. package/lib/constants.js.map +1 -0
  22. package/lib/db_ref.js +88 -81
  23. package/lib/db_ref.js.map +1 -0
  24. package/lib/decimal128.js +667 -777
  25. package/lib/decimal128.js.map +1 -0
  26. package/lib/double.js +68 -70
  27. package/lib/double.js.map +1 -0
  28. package/lib/ensure_buffer.js +22 -18
  29. package/lib/ensure_buffer.js.map +1 -0
  30. package/lib/extended_json.js +310 -321
  31. package/lib/extended_json.js.map +1 -0
  32. package/lib/float_parser.js +98 -104
  33. package/lib/float_parser.js.map +1 -0
  34. package/lib/int_32.js +50 -49
  35. package/lib/int_32.js.map +1 -0
  36. package/lib/long.js +881 -16
  37. package/lib/long.js.map +1 -0
  38. package/lib/map.js +130 -122
  39. package/lib/map.js.map +1 -0
  40. package/lib/max_key.js +28 -25
  41. package/lib/max_key.js.map +1 -0
  42. package/lib/min_key.js +28 -25
  43. package/lib/min_key.js.map +1 -0
  44. package/lib/objectid.js +300 -410
  45. package/lib/objectid.js.map +1 -0
  46. package/lib/parser/calculate_size.js +188 -224
  47. package/lib/parser/calculate_size.js.map +1 -0
  48. package/lib/parser/deserializer.js +545 -621
  49. package/lib/parser/deserializer.js.map +1 -0
  50. package/lib/parser/serializer.js +798 -923
  51. package/lib/parser/serializer.js.map +1 -0
  52. package/lib/parser/utils.js +92 -30
  53. package/lib/parser/utils.js.map +1 -0
  54. package/lib/regexp.js +61 -74
  55. package/lib/regexp.js.map +1 -0
  56. package/lib/symbol.js +45 -58
  57. package/lib/symbol.js.map +1 -0
  58. package/lib/timestamp.js +91 -95
  59. package/lib/timestamp.js.map +1 -0
  60. package/lib/uuid.js +48 -0
  61. package/lib/uuid.js.map +1 -0
  62. package/lib/validate_utf8.js +41 -42
  63. package/lib/validate_utf8.js.map +1 -0
  64. package/package.json +53 -31
  65. package/src/binary.ts +272 -0
  66. package/src/bson.ts +326 -0
  67. package/src/code.ts +61 -0
  68. package/src/constants.ts +104 -0
  69. package/src/db_ref.ts +119 -0
  70. package/src/decimal128.ts +803 -0
  71. package/src/double.ts +87 -0
  72. package/src/ensure_buffer.ts +26 -0
  73. package/src/extended_json.ts +395 -0
  74. package/src/float_parser.ts +152 -0
  75. package/src/int_32.ts +66 -0
  76. package/src/long.ts +1002 -0
  77. package/src/map.ts +139 -0
  78. package/src/max_key.ts +37 -0
  79. package/src/min_key.ts +37 -0
  80. package/src/objectid.ts +379 -0
  81. package/src/parser/calculate_size.ts +230 -0
  82. package/src/parser/deserializer.ts +655 -0
  83. package/src/parser/serializer.ts +1069 -0
  84. package/src/parser/utils.ts +93 -0
  85. package/src/regexp.ts +94 -0
  86. package/src/symbol.ts +59 -0
  87. package/src/timestamp.ts +108 -0
  88. package/src/uuid.ts +57 -0
  89. package/src/validate_utf8.ts +47 -0
  90. package/lib/fnv1a.js +0 -48
@@ -0,0 +1,655 @@
1
+ import { Buffer } from 'buffer';
2
+ import { Binary } from '../binary';
3
+ import type { Document } from '../bson';
4
+ import { Code } from '../code';
5
+ import * as constants from '../constants';
6
+ import { DBRef, DBRefLike, isDBRefLike } from '../db_ref';
7
+ import { Decimal128 } from '../decimal128';
8
+ import { Double } from '../double';
9
+ import { Int32 } from '../int_32';
10
+ import { Long } from '../long';
11
+ import { MaxKey } from '../max_key';
12
+ import { MinKey } from '../min_key';
13
+ import { ObjectId } from '../objectid';
14
+ import { BSONRegExp } from '../regexp';
15
+ import { BSONSymbol } from '../symbol';
16
+ import { Timestamp } from '../timestamp';
17
+ import { validateUtf8 } from '../validate_utf8';
18
+
19
+ /** @public */
20
+ export interface DeserializeOptions {
21
+ /** evaluate functions in the BSON document scoped to the object deserialized. */
22
+ evalFunctions?: boolean;
23
+ /** cache evaluated functions for reuse. */
24
+ cacheFunctions?: boolean;
25
+ /**
26
+ * use a crc32 code for caching, otherwise use the string of the function.
27
+ * @deprecated this option to use the crc32 function never worked as intended
28
+ * due to the fact that the crc32 function itself was never implemented.
29
+ * */
30
+ cacheFunctionsCrc32?: boolean;
31
+ /** when deserializing a Long will fit it into a Number if it's smaller than 53 bits */
32
+ promoteLongs?: boolean;
33
+ /** when deserializing a Binary will return it as a node.js Buffer instance. */
34
+ promoteBuffers?: boolean;
35
+ /** when deserializing will promote BSON values to their Node.js closest equivalent types. */
36
+ promoteValues?: boolean;
37
+ /** allow to specify if there what fields we wish to return as unserialized raw buffer. */
38
+ fieldsAsRaw?: Document;
39
+ /** return BSON regular expressions as BSONRegExp instances. */
40
+ bsonRegExp?: boolean;
41
+ /** allows the buffer to be larger than the parsed BSON object */
42
+ allowObjectSmallerThanBufferSize?: boolean;
43
+ /** Offset into buffer to begin reading document from */
44
+ index?: number;
45
+
46
+ raw?: boolean;
47
+ }
48
+
49
+ // Internal long versions
50
+ const JS_INT_MAX_LONG = Long.fromNumber(constants.JS_INT_MAX);
51
+ const JS_INT_MIN_LONG = Long.fromNumber(constants.JS_INT_MIN);
52
+
53
+ const functionCache: { [hash: string]: Function } = {};
54
+
55
+ export function deserialize(
56
+ buffer: Buffer,
57
+ options: DeserializeOptions,
58
+ isArray?: boolean
59
+ ): Document {
60
+ options = options == null ? {} : options;
61
+ const index = options && options.index ? options.index : 0;
62
+ // Read the document size
63
+ const size =
64
+ buffer[index] |
65
+ (buffer[index + 1] << 8) |
66
+ (buffer[index + 2] << 16) |
67
+ (buffer[index + 3] << 24);
68
+
69
+ if (size < 5) {
70
+ throw new Error(`bson size must be >= 5, is ${size}`);
71
+ }
72
+
73
+ if (options.allowObjectSmallerThanBufferSize && buffer.length < size) {
74
+ throw new Error(`buffer length ${buffer.length} must be >= bson size ${size}`);
75
+ }
76
+
77
+ if (!options.allowObjectSmallerThanBufferSize && buffer.length !== size) {
78
+ throw new Error(`buffer length ${buffer.length} must === bson size ${size}`);
79
+ }
80
+
81
+ if (size + index > buffer.byteLength) {
82
+ throw new Error(
83
+ `(bson size ${size} + options.index ${index} must be <= buffer length ${buffer.byteLength})`
84
+ );
85
+ }
86
+
87
+ // Illegal end value
88
+ if (buffer[index + size - 1] !== 0) {
89
+ throw new Error("One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00");
90
+ }
91
+
92
+ // Start deserializtion
93
+ return deserializeObject(buffer, index, options, isArray);
94
+ }
95
+
96
+ function deserializeObject(
97
+ buffer: Buffer,
98
+ index: number,
99
+ options: DeserializeOptions,
100
+ isArray = false
101
+ ) {
102
+ const evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions'];
103
+ const cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions'];
104
+
105
+ const fieldsAsRaw = options['fieldsAsRaw'] == null ? null : options['fieldsAsRaw'];
106
+
107
+ // Return raw bson buffer instead of parsing it
108
+ const raw = options['raw'] == null ? false : options['raw'];
109
+
110
+ // Return BSONRegExp objects instead of native regular expressions
111
+ const bsonRegExp = typeof options['bsonRegExp'] === 'boolean' ? options['bsonRegExp'] : false;
112
+
113
+ // Controls the promotion of values vs wrapper classes
114
+ const promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers'];
115
+ const promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs'];
116
+ const promoteValues = options['promoteValues'] == null ? true : options['promoteValues'];
117
+
118
+ // Set the start index
119
+ const startIndex = index;
120
+
121
+ // Validate that we have at least 4 bytes of buffer
122
+ if (buffer.length < 5) throw new Error('corrupt bson message < 5 bytes long');
123
+
124
+ // Read the document size
125
+ const size =
126
+ buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);
127
+
128
+ // Ensure buffer is valid size
129
+ if (size < 5 || size > buffer.length) throw new Error('corrupt bson message');
130
+
131
+ // Create holding object
132
+ const object: Document = isArray ? [] : {};
133
+ // Used for arrays to skip having to perform utf8 decoding
134
+ let arrayIndex = 0;
135
+ const done = false;
136
+
137
+ // While we have more left data left keep parsing
138
+ while (!done) {
139
+ // Read the type
140
+ const elementType = buffer[index++];
141
+
142
+ // If we get a zero it's the last byte, exit
143
+ if (elementType === 0) break;
144
+
145
+ // Get the start search index
146
+ let i = index;
147
+ // Locate the end of the c string
148
+ while (buffer[i] !== 0x00 && i < buffer.length) {
149
+ i++;
150
+ }
151
+
152
+ // If are at the end of the buffer there is a problem with the document
153
+ if (i >= buffer.byteLength) throw new Error('Bad BSON Document: illegal CString');
154
+ const name = isArray ? arrayIndex++ : buffer.toString('utf8', index, i);
155
+
156
+ index = i + 1;
157
+
158
+ if (elementType === constants.BSON_DATA_STRING) {
159
+ const stringSize =
160
+ buffer[index++] |
161
+ (buffer[index++] << 8) |
162
+ (buffer[index++] << 16) |
163
+ (buffer[index++] << 24);
164
+ if (
165
+ stringSize <= 0 ||
166
+ stringSize > buffer.length - index ||
167
+ buffer[index + stringSize - 1] !== 0
168
+ )
169
+ throw new Error('bad string length in bson');
170
+
171
+ if (!validateUtf8(buffer, index, index + stringSize - 1)) {
172
+ throw new Error('Invalid UTF-8 string in BSON document');
173
+ }
174
+
175
+ const s = buffer.toString('utf8', index, index + stringSize - 1);
176
+
177
+ object[name] = s;
178
+ index = index + stringSize;
179
+ } else if (elementType === constants.BSON_DATA_OID) {
180
+ const oid = Buffer.alloc(12);
181
+ buffer.copy(oid, 0, index, index + 12);
182
+ object[name] = new ObjectId(oid);
183
+ index = index + 12;
184
+ } else if (elementType === constants.BSON_DATA_INT && promoteValues === false) {
185
+ object[name] = new Int32(
186
+ buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24)
187
+ );
188
+ } else if (elementType === constants.BSON_DATA_INT) {
189
+ object[name] =
190
+ buffer[index++] |
191
+ (buffer[index++] << 8) |
192
+ (buffer[index++] << 16) |
193
+ (buffer[index++] << 24);
194
+ } else if (elementType === constants.BSON_DATA_NUMBER && promoteValues === false) {
195
+ object[name] = new Double(buffer.readDoubleLE(index));
196
+ index = index + 8;
197
+ } else if (elementType === constants.BSON_DATA_NUMBER) {
198
+ object[name] = buffer.readDoubleLE(index);
199
+ index = index + 8;
200
+ } else if (elementType === constants.BSON_DATA_DATE) {
201
+ const lowBits =
202
+ buffer[index++] |
203
+ (buffer[index++] << 8) |
204
+ (buffer[index++] << 16) |
205
+ (buffer[index++] << 24);
206
+ const highBits =
207
+ buffer[index++] |
208
+ (buffer[index++] << 8) |
209
+ (buffer[index++] << 16) |
210
+ (buffer[index++] << 24);
211
+ object[name] = new Date(new Long(lowBits, highBits).toNumber());
212
+ } else if (elementType === constants.BSON_DATA_BOOLEAN) {
213
+ if (buffer[index] !== 0 && buffer[index] !== 1) throw new Error('illegal boolean type value');
214
+ object[name] = buffer[index++] === 1;
215
+ } else if (elementType === constants.BSON_DATA_OBJECT) {
216
+ const _index = index;
217
+ const objectSize =
218
+ buffer[index] |
219
+ (buffer[index + 1] << 8) |
220
+ (buffer[index + 2] << 16) |
221
+ (buffer[index + 3] << 24);
222
+ if (objectSize <= 0 || objectSize > buffer.length - index)
223
+ throw new Error('bad embedded document length in bson');
224
+
225
+ // We have a raw value
226
+ if (raw) {
227
+ object[name] = buffer.slice(index, index + objectSize);
228
+ } else {
229
+ object[name] = deserializeObject(buffer, _index, options, false);
230
+ }
231
+
232
+ index = index + objectSize;
233
+ } else if (elementType === constants.BSON_DATA_ARRAY) {
234
+ const _index = index;
235
+ const objectSize =
236
+ buffer[index] |
237
+ (buffer[index + 1] << 8) |
238
+ (buffer[index + 2] << 16) |
239
+ (buffer[index + 3] << 24);
240
+ let arrayOptions = options;
241
+
242
+ // Stop index
243
+ const stopIndex = index + objectSize;
244
+
245
+ // All elements of array to be returned as raw bson
246
+ if (fieldsAsRaw && fieldsAsRaw[name]) {
247
+ arrayOptions = {};
248
+ for (const n in options) {
249
+ (arrayOptions as {
250
+ [key: string]: DeserializeOptions[keyof DeserializeOptions];
251
+ })[n] = options[n as keyof DeserializeOptions];
252
+ }
253
+ arrayOptions['raw'] = true;
254
+ }
255
+
256
+ object[name] = deserializeObject(buffer, _index, arrayOptions, true);
257
+ index = index + objectSize;
258
+
259
+ if (buffer[index - 1] !== 0) throw new Error('invalid array terminator byte');
260
+ if (index !== stopIndex) throw new Error('corrupted array bson');
261
+ } else if (elementType === constants.BSON_DATA_UNDEFINED) {
262
+ object[name] = undefined;
263
+ } else if (elementType === constants.BSON_DATA_NULL) {
264
+ object[name] = null;
265
+ } else if (elementType === constants.BSON_DATA_LONG) {
266
+ // Unpack the low and high bits
267
+ const lowBits =
268
+ buffer[index++] |
269
+ (buffer[index++] << 8) |
270
+ (buffer[index++] << 16) |
271
+ (buffer[index++] << 24);
272
+ const highBits =
273
+ buffer[index++] |
274
+ (buffer[index++] << 8) |
275
+ (buffer[index++] << 16) |
276
+ (buffer[index++] << 24);
277
+ const long = new Long(lowBits, highBits);
278
+ // Promote the long if possible
279
+ if (promoteLongs && promoteValues === true) {
280
+ object[name] =
281
+ long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG)
282
+ ? long.toNumber()
283
+ : long;
284
+ } else {
285
+ object[name] = long;
286
+ }
287
+ } else if (elementType === constants.BSON_DATA_DECIMAL128) {
288
+ // Buffer to contain the decimal bytes
289
+ const bytes = Buffer.alloc(16);
290
+ // Copy the next 16 bytes into the bytes buffer
291
+ buffer.copy(bytes, 0, index, index + 16);
292
+ // Update index
293
+ index = index + 16;
294
+ // Assign the new Decimal128 value
295
+ const decimal128 = new Decimal128(bytes) as Decimal128 | { toObject(): unknown };
296
+ // If we have an alternative mapper use that
297
+ if ('toObject' in decimal128 && typeof decimal128.toObject === 'function') {
298
+ object[name] = decimal128.toObject();
299
+ } else {
300
+ object[name] = decimal128;
301
+ }
302
+ } else if (elementType === constants.BSON_DATA_BINARY) {
303
+ let binarySize =
304
+ buffer[index++] |
305
+ (buffer[index++] << 8) |
306
+ (buffer[index++] << 16) |
307
+ (buffer[index++] << 24);
308
+ const totalBinarySize = binarySize;
309
+ const subType = buffer[index++];
310
+
311
+ // Did we have a negative binary size, throw
312
+ if (binarySize < 0) throw new Error('Negative binary type element size found');
313
+
314
+ // Is the length longer than the document
315
+ if (binarySize > buffer.byteLength)
316
+ throw new Error('Binary type size larger than document size');
317
+
318
+ // Decode as raw Buffer object if options specifies it
319
+ if (buffer['slice'] != null) {
320
+ // If we have subtype 2 skip the 4 bytes for the size
321
+ if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
322
+ binarySize =
323
+ buffer[index++] |
324
+ (buffer[index++] << 8) |
325
+ (buffer[index++] << 16) |
326
+ (buffer[index++] << 24);
327
+ if (binarySize < 0)
328
+ throw new Error('Negative binary type element size found for subtype 0x02');
329
+ if (binarySize > totalBinarySize - 4)
330
+ throw new Error('Binary type with subtype 0x02 contains too long binary size');
331
+ if (binarySize < totalBinarySize - 4)
332
+ throw new Error('Binary type with subtype 0x02 contains too short binary size');
333
+ }
334
+
335
+ if (promoteBuffers && promoteValues) {
336
+ object[name] = buffer.slice(index, index + binarySize);
337
+ } else {
338
+ object[name] = new Binary(buffer.slice(index, index + binarySize), subType);
339
+ }
340
+ } else {
341
+ const _buffer = Buffer.alloc(binarySize);
342
+ // If we have subtype 2 skip the 4 bytes for the size
343
+ if (subType === Binary.SUBTYPE_BYTE_ARRAY) {
344
+ binarySize =
345
+ buffer[index++] |
346
+ (buffer[index++] << 8) |
347
+ (buffer[index++] << 16) |
348
+ (buffer[index++] << 24);
349
+ if (binarySize < 0)
350
+ throw new Error('Negative binary type element size found for subtype 0x02');
351
+ if (binarySize > totalBinarySize - 4)
352
+ throw new Error('Binary type with subtype 0x02 contains too long binary size');
353
+ if (binarySize < totalBinarySize - 4)
354
+ throw new Error('Binary type with subtype 0x02 contains too short binary size');
355
+ }
356
+
357
+ // Copy the data
358
+ for (i = 0; i < binarySize; i++) {
359
+ _buffer[i] = buffer[index + i];
360
+ }
361
+
362
+ if (promoteBuffers && promoteValues) {
363
+ object[name] = _buffer;
364
+ } else {
365
+ object[name] = new Binary(_buffer, subType);
366
+ }
367
+ }
368
+
369
+ // Update the index
370
+ index = index + binarySize;
371
+ } else if (elementType === constants.BSON_DATA_REGEXP && bsonRegExp === false) {
372
+ // Get the start search index
373
+ i = index;
374
+ // Locate the end of the c string
375
+ while (buffer[i] !== 0x00 && i < buffer.length) {
376
+ i++;
377
+ }
378
+ // If are at the end of the buffer there is a problem with the document
379
+ if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString');
380
+ // Return the C string
381
+ const source = buffer.toString('utf8', index, i);
382
+ // Create the regexp
383
+ index = i + 1;
384
+
385
+ // Get the start search index
386
+ i = index;
387
+ // Locate the end of the c string
388
+ while (buffer[i] !== 0x00 && i < buffer.length) {
389
+ i++;
390
+ }
391
+ // If are at the end of the buffer there is a problem with the document
392
+ if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString');
393
+ // Return the C string
394
+ const regExpOptions = buffer.toString('utf8', index, i);
395
+ index = i + 1;
396
+
397
+ // For each option add the corresponding one for javascript
398
+ const optionsArray = new Array(regExpOptions.length);
399
+
400
+ // Parse options
401
+ for (i = 0; i < regExpOptions.length; i++) {
402
+ switch (regExpOptions[i]) {
403
+ case 'm':
404
+ optionsArray[i] = 'm';
405
+ break;
406
+ case 's':
407
+ optionsArray[i] = 'g';
408
+ break;
409
+ case 'i':
410
+ optionsArray[i] = 'i';
411
+ break;
412
+ }
413
+ }
414
+
415
+ object[name] = new RegExp(source, optionsArray.join(''));
416
+ } else if (elementType === constants.BSON_DATA_REGEXP && bsonRegExp === true) {
417
+ // Get the start search index
418
+ i = index;
419
+ // Locate the end of the c string
420
+ while (buffer[i] !== 0x00 && i < buffer.length) {
421
+ i++;
422
+ }
423
+ // If are at the end of the buffer there is a problem with the document
424
+ if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString');
425
+ // Return the C string
426
+ const source = buffer.toString('utf8', index, i);
427
+ index = i + 1;
428
+
429
+ // Get the start search index
430
+ i = index;
431
+ // Locate the end of the c string
432
+ while (buffer[i] !== 0x00 && i < buffer.length) {
433
+ i++;
434
+ }
435
+ // If are at the end of the buffer there is a problem with the document
436
+ if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString');
437
+ // Return the C string
438
+ const regExpOptions = buffer.toString('utf8', index, i);
439
+ index = i + 1;
440
+
441
+ // Set the object
442
+ object[name] = new BSONRegExp(source, regExpOptions);
443
+ } else if (elementType === constants.BSON_DATA_SYMBOL) {
444
+ const stringSize =
445
+ buffer[index++] |
446
+ (buffer[index++] << 8) |
447
+ (buffer[index++] << 16) |
448
+ (buffer[index++] << 24);
449
+ if (
450
+ stringSize <= 0 ||
451
+ stringSize > buffer.length - index ||
452
+ buffer[index + stringSize - 1] !== 0
453
+ )
454
+ throw new Error('bad string length in bson');
455
+ const symbol = buffer.toString('utf8', index, index + stringSize - 1);
456
+ object[name] = promoteValues ? symbol : new BSONSymbol(symbol);
457
+ index = index + stringSize;
458
+ } else if (elementType === constants.BSON_DATA_TIMESTAMP) {
459
+ const lowBits =
460
+ buffer[index++] |
461
+ (buffer[index++] << 8) |
462
+ (buffer[index++] << 16) |
463
+ (buffer[index++] << 24);
464
+ const highBits =
465
+ buffer[index++] |
466
+ (buffer[index++] << 8) |
467
+ (buffer[index++] << 16) |
468
+ (buffer[index++] << 24);
469
+
470
+ object[name] = new Timestamp(lowBits, highBits);
471
+ } else if (elementType === constants.BSON_DATA_MIN_KEY) {
472
+ object[name] = new MinKey();
473
+ } else if (elementType === constants.BSON_DATA_MAX_KEY) {
474
+ object[name] = new MaxKey();
475
+ } else if (elementType === constants.BSON_DATA_CODE) {
476
+ const stringSize =
477
+ buffer[index++] |
478
+ (buffer[index++] << 8) |
479
+ (buffer[index++] << 16) |
480
+ (buffer[index++] << 24);
481
+ if (
482
+ stringSize <= 0 ||
483
+ stringSize > buffer.length - index ||
484
+ buffer[index + stringSize - 1] !== 0
485
+ )
486
+ throw new Error('bad string length in bson');
487
+ const functionString = buffer.toString('utf8', index, index + stringSize - 1);
488
+
489
+ // If we are evaluating the functions
490
+ if (evalFunctions) {
491
+ // If we have cache enabled let's look for the md5 of the function in the cache
492
+ if (cacheFunctions) {
493
+ // Got to do this to avoid V8 deoptimizing the call due to finding eval
494
+ object[name] = isolateEval(functionString, functionCache, object);
495
+ } else {
496
+ object[name] = isolateEval(functionString);
497
+ }
498
+ } else {
499
+ object[name] = new Code(functionString);
500
+ }
501
+
502
+ // Update parse index position
503
+ index = index + stringSize;
504
+ } else if (elementType === constants.BSON_DATA_CODE_W_SCOPE) {
505
+ const totalSize =
506
+ buffer[index++] |
507
+ (buffer[index++] << 8) |
508
+ (buffer[index++] << 16) |
509
+ (buffer[index++] << 24);
510
+
511
+ // Element cannot be shorter than totalSize + stringSize + documentSize + terminator
512
+ if (totalSize < 4 + 4 + 4 + 1) {
513
+ throw new Error('code_w_scope total size shorter minimum expected length');
514
+ }
515
+
516
+ // Get the code string size
517
+ const stringSize =
518
+ buffer[index++] |
519
+ (buffer[index++] << 8) |
520
+ (buffer[index++] << 16) |
521
+ (buffer[index++] << 24);
522
+ // Check if we have a valid string
523
+ if (
524
+ stringSize <= 0 ||
525
+ stringSize > buffer.length - index ||
526
+ buffer[index + stringSize - 1] !== 0
527
+ )
528
+ throw new Error('bad string length in bson');
529
+
530
+ // Javascript function
531
+ const functionString = buffer.toString('utf8', index, index + stringSize - 1);
532
+ // Update parse index position
533
+ index = index + stringSize;
534
+ // Parse the element
535
+ const _index = index;
536
+ // Decode the size of the object document
537
+ const objectSize =
538
+ buffer[index] |
539
+ (buffer[index + 1] << 8) |
540
+ (buffer[index + 2] << 16) |
541
+ (buffer[index + 3] << 24);
542
+ // Decode the scope object
543
+ const scopeObject = deserializeObject(buffer, _index, options, false);
544
+ // Adjust the index
545
+ index = index + objectSize;
546
+
547
+ // Check if field length is too short
548
+ if (totalSize < 4 + 4 + objectSize + stringSize) {
549
+ throw new Error('code_w_scope total size is too short, truncating scope');
550
+ }
551
+
552
+ // Check if totalSize field is too long
553
+ if (totalSize > 4 + 4 + objectSize + stringSize) {
554
+ throw new Error('code_w_scope total size is too long, clips outer document');
555
+ }
556
+
557
+ // If we are evaluating the functions
558
+ if (evalFunctions) {
559
+ // If we have cache enabled let's look for the md5 of the function in the cache
560
+ if (cacheFunctions) {
561
+ // Got to do this to avoid V8 deoptimizing the call due to finding eval
562
+ object[name] = isolateEval(functionString, functionCache, object);
563
+ } else {
564
+ object[name] = isolateEval(functionString);
565
+ }
566
+
567
+ object[name].scope = scopeObject;
568
+ } else {
569
+ object[name] = new Code(functionString, scopeObject);
570
+ }
571
+ } else if (elementType === constants.BSON_DATA_DBPOINTER) {
572
+ // Get the code string size
573
+ const stringSize =
574
+ buffer[index++] |
575
+ (buffer[index++] << 8) |
576
+ (buffer[index++] << 16) |
577
+ (buffer[index++] << 24);
578
+ // Check if we have a valid string
579
+ if (
580
+ stringSize <= 0 ||
581
+ stringSize > buffer.length - index ||
582
+ buffer[index + stringSize - 1] !== 0
583
+ )
584
+ throw new Error('bad string length in bson');
585
+ // Namespace
586
+ if (!validateUtf8(buffer, index, index + stringSize - 1)) {
587
+ throw new Error('Invalid UTF-8 string in BSON document');
588
+ }
589
+ const namespace = buffer.toString('utf8', index, index + stringSize - 1);
590
+ // Update parse index position
591
+ index = index + stringSize;
592
+
593
+ // Read the oid
594
+ const oidBuffer = Buffer.alloc(12);
595
+ buffer.copy(oidBuffer, 0, index, index + 12);
596
+ const oid = new ObjectId(oidBuffer);
597
+
598
+ // Update the index
599
+ index = index + 12;
600
+
601
+ // Upgrade to DBRef type
602
+ object[name] = new DBRef(namespace, oid);
603
+ } else {
604
+ throw new Error(
605
+ 'Detected unknown BSON type ' + elementType.toString(16) + ' for fieldname "' + name + '"'
606
+ );
607
+ }
608
+ }
609
+
610
+ // Check if the deserialization was against a valid array/object
611
+ if (size !== index - startIndex) {
612
+ if (isArray) throw new Error('corrupt array bson');
613
+ throw new Error('corrupt object bson');
614
+ }
615
+
616
+ // check if object's $ keys are those of a DBRef
617
+ const dollarKeys = Object.keys(object).filter(k => k.startsWith('$'));
618
+ let valid = true;
619
+ dollarKeys.forEach(k => {
620
+ if (['$ref', '$id', '$db'].indexOf(k) === -1) valid = false;
621
+ });
622
+
623
+ // if a $key not in "$ref", "$id", "$db", don't make a DBRef
624
+ if (!valid) return object;
625
+
626
+ if (isDBRefLike(object)) {
627
+ const copy = Object.assign({}, object) as Partial<DBRefLike>;
628
+ delete copy.$ref;
629
+ delete copy.$id;
630
+ delete copy.$db;
631
+ return new DBRef(object.$ref, object.$id, object.$db, copy);
632
+ }
633
+
634
+ return object;
635
+ }
636
+
637
+ /**
638
+ * Ensure eval is isolated, store the result in functionCache.
639
+ *
640
+ * @internal
641
+ */
642
+ function isolateEval(
643
+ functionString: string,
644
+ functionCache?: { [hash: string]: Function },
645
+ object?: Document
646
+ ) {
647
+ if (!functionCache) return new Function(functionString);
648
+ // Check for cache hit, eval if missing and return cached function
649
+ if (functionCache[functionString] == null) {
650
+ functionCache[functionString] = new Function(functionString);
651
+ }
652
+
653
+ // Set the object
654
+ return functionCache[functionString].bind(object);
655
+ }