bson 4.5.2 → 4.6.1

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 (56) hide show
  1. package/bower.json +1 -1
  2. package/bson.d.ts +39 -3
  3. package/dist/bson.browser.esm.js +320 -187
  4. package/dist/bson.browser.esm.js.map +1 -1
  5. package/dist/bson.browser.umd.js +323 -186
  6. package/dist/bson.browser.umd.js.map +1 -1
  7. package/dist/bson.bundle.js +323 -186
  8. package/dist/bson.bundle.js.map +1 -1
  9. package/dist/bson.esm.js +320 -187
  10. package/dist/bson.esm.js.map +1 -1
  11. package/lib/binary.js +11 -6
  12. package/lib/binary.js.map +1 -1
  13. package/lib/bson.js +11 -3
  14. package/lib/bson.js.map +1 -1
  15. package/lib/constants.js +5 -1
  16. package/lib/constants.js.map +1 -1
  17. package/lib/decimal128.js +13 -5
  18. package/lib/decimal128.js.map +1 -1
  19. package/lib/ensure_buffer.js +3 -2
  20. package/lib/ensure_buffer.js.map +1 -1
  21. package/lib/error.js +55 -0
  22. package/lib/error.js.map +1 -0
  23. package/lib/extended_json.js +11 -5
  24. package/lib/extended_json.js.map +1 -1
  25. package/lib/int_32.js +1 -1
  26. package/lib/int_32.js.map +1 -1
  27. package/lib/objectid.js +42 -47
  28. package/lib/objectid.js.map +1 -1
  29. package/lib/parser/calculate_size.js +2 -2
  30. package/lib/parser/calculate_size.js.map +1 -1
  31. package/lib/parser/deserializer.js +131 -53
  32. package/lib/parser/deserializer.js.map +1 -1
  33. package/lib/parser/serializer.js +16 -20
  34. package/lib/parser/serializer.js.map +1 -1
  35. package/lib/regexp.js +9 -2
  36. package/lib/regexp.js.map +1 -1
  37. package/lib/uuid.js +2 -1
  38. package/lib/uuid.js.map +1 -1
  39. package/lib/uuid_utils.js +2 -1
  40. package/lib/uuid_utils.js.map +1 -1
  41. package/package.json +4 -2
  42. package/src/binary.ts +11 -6
  43. package/src/bson.ts +7 -1
  44. package/src/constants.ts +6 -0
  45. package/src/decimal128.ts +12 -5
  46. package/src/ensure_buffer.ts +3 -2
  47. package/src/error.ts +23 -0
  48. package/src/extended_json.ts +13 -5
  49. package/src/int_32.ts +1 -1
  50. package/src/objectid.ts +44 -62
  51. package/src/parser/calculate_size.ts +2 -2
  52. package/src/parser/deserializer.ts +159 -57
  53. package/src/parser/serializer.ts +16 -17
  54. package/src/regexp.ts +14 -2
  55. package/src/uuid.ts +2 -1
  56. package/src/uuid_utils.ts +2 -1
package/src/objectid.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import { ensureBuffer } from './ensure_buffer';
3
+ import { BSONTypeError } from './error';
3
4
  import { deprecate, isUint8Array, randomBytes } from './parser/utils';
4
5
 
5
6
  // Regular expression that checks for hex value
@@ -30,7 +31,7 @@ export class ObjectId {
30
31
  _bsontype!: 'ObjectId';
31
32
 
32
33
  /** @internal */
33
- static index = ~~(Math.random() * 0xffffff);
34
+ static index = Math.floor(Math.random() * 0xffffff);
34
35
 
35
36
  static cacheHexString: boolean;
36
37
 
@@ -42,54 +43,54 @@ export class ObjectId {
42
43
  /**
43
44
  * Create an ObjectId type
44
45
  *
45
- * @param id - Can be a 24 character hex string, 12 byte binary Buffer, or a number.
46
+ * @param inputId - Can be a 24 character hex string, 12 byte binary Buffer, or a number.
46
47
  */
47
- constructor(id?: string | Buffer | number | ObjectIdLike | ObjectId) {
48
- if (!(this instanceof ObjectId)) return new ObjectId(id);
49
-
50
- // Duck-typing to support ObjectId from different npm packages
51
- if (id instanceof ObjectId) {
52
- this[kId] = id.id;
53
- this.__id = id.__id;
54
- }
55
-
56
- if (typeof id === 'object' && id && 'id' in id) {
57
- if ('toHexString' in id && typeof id.toHexString === 'function') {
58
- this[kId] = Buffer.from(id.toHexString(), 'hex');
48
+ constructor(inputId?: string | number | ObjectId | ObjectIdLike | Buffer | Uint8Array) {
49
+ if (!(this instanceof ObjectId)) return new ObjectId(inputId);
50
+
51
+ // workingId is set based on type of input and whether valid id exists for the input
52
+ let workingId;
53
+ if (typeof inputId === 'object' && inputId && 'id' in inputId) {
54
+ if (typeof inputId.id !== 'string' && !ArrayBuffer.isView(inputId.id)) {
55
+ throw new BSONTypeError(
56
+ 'Argument passed in must have an id that is of type string or Buffer'
57
+ );
58
+ }
59
+ if ('toHexString' in inputId && typeof inputId.toHexString === 'function') {
60
+ workingId = Buffer.from(inputId.toHexString(), 'hex');
59
61
  } else {
60
- this[kId] = typeof id.id === 'string' ? Buffer.from(id.id) : id.id;
62
+ workingId = inputId.id;
61
63
  }
64
+ } else {
65
+ workingId = inputId;
62
66
  }
63
67
 
64
- // The most common use case (blank id, new objectId instance)
65
- if (id == null || typeof id === 'number') {
68
+ // the following cases use workingId to construct an ObjectId
69
+ if (workingId == null || typeof workingId === 'number') {
70
+ // The most common use case (blank id, new objectId instance)
66
71
  // Generate a new id
67
- this[kId] = ObjectId.generate(typeof id === 'number' ? id : undefined);
68
- // If we are caching the hex string
69
- if (ObjectId.cacheHexString) {
70
- this.__id = this.id.toString('hex');
71
- }
72
- }
73
-
74
- if (ArrayBuffer.isView(id) && id.byteLength === 12) {
75
- this[kId] = ensureBuffer(id);
76
- }
77
-
78
- if (typeof id === 'string') {
79
- if (id.length === 12) {
80
- const bytes = Buffer.from(id);
72
+ this[kId] = ObjectId.generate(typeof workingId === 'number' ? workingId : undefined);
73
+ } else if (ArrayBuffer.isView(workingId) && workingId.byteLength === 12) {
74
+ this[kId] = ensureBuffer(workingId);
75
+ } else if (typeof workingId === 'string') {
76
+ if (workingId.length === 12) {
77
+ const bytes = Buffer.from(workingId);
81
78
  if (bytes.byteLength === 12) {
82
79
  this[kId] = bytes;
80
+ } else {
81
+ throw new BSONTypeError('Argument passed in must be a string of 12 bytes');
83
82
  }
84
- } else if (id.length === 24 && checkForHexRegExp.test(id)) {
85
- this[kId] = Buffer.from(id, 'hex');
83
+ } else if (workingId.length === 24 && checkForHexRegExp.test(workingId)) {
84
+ this[kId] = Buffer.from(workingId, 'hex');
86
85
  } else {
87
- throw new TypeError(
88
- 'Argument passed in must be a Buffer or string of 12 bytes or a string of 24 hex characters'
86
+ throw new BSONTypeError(
87
+ 'Argument passed in must be a string of 12 bytes or a string of 24 hex characters'
89
88
  );
90
89
  }
90
+ } else {
91
+ throw new BSONTypeError('Argument passed in does not match the accepted types');
91
92
  }
92
-
93
+ // If we are caching the hex string
93
94
  if (ObjectId.cacheHexString) {
94
95
  this.__id = this.id.toString('hex');
95
96
  }
@@ -155,7 +156,7 @@ export class ObjectId {
155
156
  */
156
157
  static generate(time?: number): Buffer {
157
158
  if ('number' !== typeof time) {
158
- time = ~~(Date.now() / 1000);
159
+ time = Math.floor(Date.now() / 1000);
159
160
  }
160
161
 
161
162
  const inc = ObjectId.getInc();
@@ -276,7 +277,7 @@ export class ObjectId {
276
277
  static createFromHexString(hexString: string): ObjectId {
277
278
  // Throw an error if it's not a valid setup
278
279
  if (typeof hexString === 'undefined' || (hexString != null && hexString.length !== 24)) {
279
- throw new TypeError(
280
+ throw new BSONTypeError(
280
281
  'Argument passed in must be a single String of 12 bytes or a string of 24 hex characters'
281
282
  );
282
283
  }
@@ -289,34 +290,15 @@ export class ObjectId {
289
290
  *
290
291
  * @param id - ObjectId instance to validate.
291
292
  */
292
- static isValid(id: number | string | ObjectId | Uint8Array | ObjectIdLike): boolean {
293
+ static isValid(id: string | number | ObjectId | ObjectIdLike | Buffer | Uint8Array): boolean {
293
294
  if (id == null) return false;
294
295
 
295
- if (typeof id === 'number') {
296
+ try {
297
+ new ObjectId(id);
296
298
  return true;
299
+ } catch {
300
+ return false;
297
301
  }
298
-
299
- if (typeof id === 'string') {
300
- return id.length === 12 || (id.length === 24 && checkForHexRegExp.test(id));
301
- }
302
-
303
- if (id instanceof ObjectId) {
304
- return true;
305
- }
306
-
307
- if (isUint8Array(id) && id.length === 12) {
308
- return true;
309
- }
310
-
311
- // Duck-Typing detection of ObjectId like objects
312
- if (typeof id === 'object' && 'toHexString' in id && typeof id.toHexString === 'function') {
313
- if (typeof id.id === 'string') {
314
- return id.id.length === 12;
315
- }
316
- return id.toHexString().length === 24 && checkForHexRegExp.test(id.id.toString('hex'));
317
- }
318
-
319
- return false;
320
302
  }
321
303
 
322
304
  /** @internal */
@@ -24,7 +24,7 @@ export function calculateObjectSize(
24
24
  } else {
25
25
  // If we have toBSON defined, override the current object
26
26
 
27
- if (object.toBSON) {
27
+ if (typeof object?.toBSON === 'function') {
28
28
  object = object.toBSON();
29
29
  }
30
30
 
@@ -47,7 +47,7 @@ function calculateElement(
47
47
  ignoreUndefined = false
48
48
  ) {
49
49
  // If we have toBSON defined, override the current object
50
- if (value && value.toBSON) {
50
+ if (typeof value?.toBSON === 'function') {
51
51
  value = value.toBSON();
52
52
  }
53
53
 
@@ -6,6 +6,7 @@ import * as constants from '../constants';
6
6
  import { DBRef, DBRefLike, isDBRefLike } from '../db_ref';
7
7
  import { Decimal128 } from '../decimal128';
8
8
  import { Double } from '../double';
9
+ import { BSONError } from '../error';
9
10
  import { Int32 } from '../int_32';
10
11
  import { Long } from '../long';
11
12
  import { MaxKey } from '../max_key';
@@ -44,6 +45,22 @@ export interface DeserializeOptions {
44
45
  index?: number;
45
46
 
46
47
  raw?: boolean;
48
+ /** Allows for opt-out utf-8 validation for all keys or
49
+ * specified keys. Must be all true or all false.
50
+ *
51
+ * @example
52
+ * ```js
53
+ * // disables validation on all keys
54
+ * validation: { utf8: false }
55
+ *
56
+ * // enables validation only on specified keys a, b, and c
57
+ * validation: { utf8: { a: true, b: true, c: true } }
58
+ *
59
+ * // disables validation only on specified keys a, b
60
+ * validation: { utf8: { a: false, b: false } }
61
+ * ```
62
+ */
63
+ validation?: { utf8: boolean | Record<string, true> | Record<string, false> };
47
64
  }
48
65
 
49
66
  // Internal long versions
@@ -67,26 +84,28 @@ export function deserialize(
67
84
  (buffer[index + 3] << 24);
68
85
 
69
86
  if (size < 5) {
70
- throw new Error(`bson size must be >= 5, is ${size}`);
87
+ throw new BSONError(`bson size must be >= 5, is ${size}`);
71
88
  }
72
89
 
73
90
  if (options.allowObjectSmallerThanBufferSize && buffer.length < size) {
74
- throw new Error(`buffer length ${buffer.length} must be >= bson size ${size}`);
91
+ throw new BSONError(`buffer length ${buffer.length} must be >= bson size ${size}`);
75
92
  }
76
93
 
77
94
  if (!options.allowObjectSmallerThanBufferSize && buffer.length !== size) {
78
- throw new Error(`buffer length ${buffer.length} must === bson size ${size}`);
95
+ throw new BSONError(`buffer length ${buffer.length} must === bson size ${size}`);
79
96
  }
80
97
 
81
98
  if (size + index > buffer.byteLength) {
82
- throw new Error(
99
+ throw new BSONError(
83
100
  `(bson size ${size} + options.index ${index} must be <= buffer length ${buffer.byteLength})`
84
101
  );
85
102
  }
86
103
 
87
104
  // Illegal end value
88
105
  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");
106
+ throw new BSONError(
107
+ "One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00"
108
+ );
90
109
  }
91
110
 
92
111
  // Start deserializtion
@@ -117,18 +136,57 @@ function deserializeObject(
117
136
  const promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs'];
118
137
  const promoteValues = options['promoteValues'] == null ? true : options['promoteValues'];
119
138
 
139
+ // Ensures default validation option if none given
140
+ const validation = options.validation == null ? { utf8: true } : options.validation;
141
+
142
+ // Shows if global utf-8 validation is enabled or disabled
143
+ let globalUTFValidation = true;
144
+ // Reflects utf-8 validation setting regardless of global or specific key validation
145
+ let validationSetting: boolean;
146
+ // Set of keys either to enable or disable validation on
147
+ const utf8KeysSet = new Set();
148
+
149
+ // Check for boolean uniformity and empty validation option
150
+ const utf8ValidatedKeys = validation.utf8;
151
+ if (typeof utf8ValidatedKeys === 'boolean') {
152
+ validationSetting = utf8ValidatedKeys;
153
+ } else {
154
+ globalUTFValidation = false;
155
+ const utf8ValidationValues = Object.keys(utf8ValidatedKeys).map(function (key) {
156
+ return utf8ValidatedKeys[key];
157
+ });
158
+ if (utf8ValidationValues.length === 0) {
159
+ throw new BSONError('UTF-8 validation setting cannot be empty');
160
+ }
161
+ if (typeof utf8ValidationValues[0] !== 'boolean') {
162
+ throw new BSONError('Invalid UTF-8 validation option, must specify boolean values');
163
+ }
164
+ validationSetting = utf8ValidationValues[0];
165
+ // Ensures boolean uniformity in utf-8 validation (all true or all false)
166
+ if (!utf8ValidationValues.every(item => item === validationSetting)) {
167
+ throw new BSONError('Invalid UTF-8 validation option - keys must be all true or all false');
168
+ }
169
+ }
170
+
171
+ // Add keys to set that will either be validated or not based on validationSetting
172
+ if (!globalUTFValidation) {
173
+ for (const key of Object.keys(utf8ValidatedKeys)) {
174
+ utf8KeysSet.add(key);
175
+ }
176
+ }
177
+
120
178
  // Set the start index
121
179
  const startIndex = index;
122
180
 
123
181
  // Validate that we have at least 4 bytes of buffer
124
- if (buffer.length < 5) throw new Error('corrupt bson message < 5 bytes long');
182
+ if (buffer.length < 5) throw new BSONError('corrupt bson message < 5 bytes long');
125
183
 
126
184
  // Read the document size
127
185
  const size =
128
186
  buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);
129
187
 
130
188
  // Ensure buffer is valid size
131
- if (size < 5 || size > buffer.length) throw new Error('corrupt bson message');
189
+ if (size < 5 || size > buffer.length) throw new BSONError('corrupt bson message');
132
190
 
133
191
  // Create holding object
134
192
  const object: Document = isArray ? [] : {};
@@ -154,8 +212,19 @@ function deserializeObject(
154
212
  }
155
213
 
156
214
  // If are at the end of the buffer there is a problem with the document
157
- if (i >= buffer.byteLength) throw new Error('Bad BSON Document: illegal CString');
215
+ if (i >= buffer.byteLength) throw new BSONError('Bad BSON Document: illegal CString');
216
+
217
+ // Represents the key
158
218
  const name = isArray ? arrayIndex++ : buffer.toString('utf8', index, i);
219
+
220
+ // shouldValidateKey is true if the key should be validated, false otherwise
221
+ let shouldValidateKey = true;
222
+ if (globalUTFValidation || utf8KeysSet.has(name)) {
223
+ shouldValidateKey = validationSetting;
224
+ } else {
225
+ shouldValidateKey = !validationSetting;
226
+ }
227
+
159
228
  if (isPossibleDBRef !== false && (name as string)[0] === '$') {
160
229
  isPossibleDBRef = allowedDBRefKeys.test(name as string);
161
230
  }
@@ -173,20 +242,10 @@ function deserializeObject(
173
242
  stringSize <= 0 ||
174
243
  stringSize > buffer.length - index ||
175
244
  buffer[index + stringSize - 1] !== 0
176
- )
177
- throw new Error('bad string length in bson');
178
-
179
- value = buffer.toString('utf8', index, index + stringSize - 1);
180
-
181
- for (let i = 0; i < value.length; i++) {
182
- if (value.charCodeAt(i) === 0xfffd) {
183
- if (!validateUtf8(buffer, index, index + stringSize - 1)) {
184
- throw new Error('Invalid UTF-8 string in BSON document');
185
- }
186
- break;
187
- }
245
+ ) {
246
+ throw new BSONError('bad string length in bson');
188
247
  }
189
-
248
+ value = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey);
190
249
  index = index + stringSize;
191
250
  } else if (elementType === constants.BSON_DATA_OID) {
192
251
  const oid = Buffer.alloc(12);
@@ -222,7 +281,8 @@ function deserializeObject(
222
281
  (buffer[index++] << 24);
223
282
  value = new Date(new Long(lowBits, highBits).toNumber());
224
283
  } else if (elementType === constants.BSON_DATA_BOOLEAN) {
225
- if (buffer[index] !== 0 && buffer[index] !== 1) throw new Error('illegal boolean type value');
284
+ if (buffer[index] !== 0 && buffer[index] !== 1)
285
+ throw new BSONError('illegal boolean type value');
226
286
  value = buffer[index++] === 1;
227
287
  } else if (elementType === constants.BSON_DATA_OBJECT) {
228
288
  const _index = index;
@@ -232,13 +292,17 @@ function deserializeObject(
232
292
  (buffer[index + 2] << 16) |
233
293
  (buffer[index + 3] << 24);
234
294
  if (objectSize <= 0 || objectSize > buffer.length - index)
235
- throw new Error('bad embedded document length in bson');
295
+ throw new BSONError('bad embedded document length in bson');
236
296
 
237
297
  // We have a raw value
238
298
  if (raw) {
239
299
  value = buffer.slice(index, index + objectSize);
240
300
  } else {
241
- value = deserializeObject(buffer, _index, options, false);
301
+ let objectOptions = options;
302
+ if (!globalUTFValidation) {
303
+ objectOptions = { ...options, validation: { utf8: shouldValidateKey } };
304
+ }
305
+ value = deserializeObject(buffer, _index, objectOptions, false);
242
306
  }
243
307
 
244
308
  index = index + objectSize;
@@ -266,12 +330,14 @@ function deserializeObject(
266
330
  }
267
331
  arrayOptions['raw'] = true;
268
332
  }
269
-
333
+ if (!globalUTFValidation) {
334
+ arrayOptions = { ...arrayOptions, validation: { utf8: shouldValidateKey } };
335
+ }
270
336
  value = deserializeObject(buffer, _index, arrayOptions, true);
271
337
  index = index + objectSize;
272
338
 
273
- if (buffer[index - 1] !== 0) throw new Error('invalid array terminator byte');
274
- if (index !== stopIndex) throw new Error('corrupted array bson');
339
+ if (buffer[index - 1] !== 0) throw new BSONError('invalid array terminator byte');
340
+ if (index !== stopIndex) throw new BSONError('corrupted array bson');
275
341
  } else if (elementType === constants.BSON_DATA_UNDEFINED) {
276
342
  value = undefined;
277
343
  } else if (elementType === constants.BSON_DATA_NULL) {
@@ -323,11 +389,11 @@ function deserializeObject(
323
389
  const subType = buffer[index++];
324
390
 
325
391
  // Did we have a negative binary size, throw
326
- if (binarySize < 0) throw new Error('Negative binary type element size found');
392
+ if (binarySize < 0) throw new BSONError('Negative binary type element size found');
327
393
 
328
394
  // Is the length longer than the document
329
395
  if (binarySize > buffer.byteLength)
330
- throw new Error('Binary type size larger than document size');
396
+ throw new BSONError('Binary type size larger than document size');
331
397
 
332
398
  // Decode as raw Buffer object if options specifies it
333
399
  if (buffer['slice'] != null) {
@@ -339,11 +405,11 @@ function deserializeObject(
339
405
  (buffer[index++] << 16) |
340
406
  (buffer[index++] << 24);
341
407
  if (binarySize < 0)
342
- throw new Error('Negative binary type element size found for subtype 0x02');
408
+ throw new BSONError('Negative binary type element size found for subtype 0x02');
343
409
  if (binarySize > totalBinarySize - 4)
344
- throw new Error('Binary type with subtype 0x02 contains too long binary size');
410
+ throw new BSONError('Binary type with subtype 0x02 contains too long binary size');
345
411
  if (binarySize < totalBinarySize - 4)
346
- throw new Error('Binary type with subtype 0x02 contains too short binary size');
412
+ throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
347
413
  }
348
414
 
349
415
  if (promoteBuffers && promoteValues) {
@@ -361,11 +427,11 @@ function deserializeObject(
361
427
  (buffer[index++] << 16) |
362
428
  (buffer[index++] << 24);
363
429
  if (binarySize < 0)
364
- throw new Error('Negative binary type element size found for subtype 0x02');
430
+ throw new BSONError('Negative binary type element size found for subtype 0x02');
365
431
  if (binarySize > totalBinarySize - 4)
366
- throw new Error('Binary type with subtype 0x02 contains too long binary size');
432
+ throw new BSONError('Binary type with subtype 0x02 contains too long binary size');
367
433
  if (binarySize < totalBinarySize - 4)
368
- throw new Error('Binary type with subtype 0x02 contains too short binary size');
434
+ throw new BSONError('Binary type with subtype 0x02 contains too short binary size');
369
435
  }
370
436
 
371
437
  // Copy the data
@@ -390,7 +456,7 @@ function deserializeObject(
390
456
  i++;
391
457
  }
392
458
  // If are at the end of the buffer there is a problem with the document
393
- if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString');
459
+ if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');
394
460
  // Return the C string
395
461
  const source = buffer.toString('utf8', index, i);
396
462
  // Create the regexp
@@ -403,7 +469,7 @@ function deserializeObject(
403
469
  i++;
404
470
  }
405
471
  // If are at the end of the buffer there is a problem with the document
406
- if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString');
472
+ if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');
407
473
  // Return the C string
408
474
  const regExpOptions = buffer.toString('utf8', index, i);
409
475
  index = i + 1;
@@ -435,7 +501,7 @@ function deserializeObject(
435
501
  i++;
436
502
  }
437
503
  // If are at the end of the buffer there is a problem with the document
438
- if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString');
504
+ if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');
439
505
  // Return the C string
440
506
  const source = buffer.toString('utf8', index, i);
441
507
  index = i + 1;
@@ -447,7 +513,7 @@ function deserializeObject(
447
513
  i++;
448
514
  }
449
515
  // If are at the end of the buffer there is a problem with the document
450
- if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString');
516
+ if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString');
451
517
  // Return the C string
452
518
  const regExpOptions = buffer.toString('utf8', index, i);
453
519
  index = i + 1;
@@ -464,9 +530,10 @@ function deserializeObject(
464
530
  stringSize <= 0 ||
465
531
  stringSize > buffer.length - index ||
466
532
  buffer[index + stringSize - 1] !== 0
467
- )
468
- throw new Error('bad string length in bson');
469
- const symbol = buffer.toString('utf8', index, index + stringSize - 1);
533
+ ) {
534
+ throw new BSONError('bad string length in bson');
535
+ }
536
+ const symbol = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey);
470
537
  value = promoteValues ? symbol : new BSONSymbol(symbol);
471
538
  index = index + stringSize;
472
539
  } else if (elementType === constants.BSON_DATA_TIMESTAMP) {
@@ -496,9 +563,15 @@ function deserializeObject(
496
563
  stringSize <= 0 ||
497
564
  stringSize > buffer.length - index ||
498
565
  buffer[index + stringSize - 1] !== 0
499
- )
500
- throw new Error('bad string length in bson');
501
- const functionString = buffer.toString('utf8', index, index + stringSize - 1);
566
+ ) {
567
+ throw new BSONError('bad string length in bson');
568
+ }
569
+ const functionString = getValidatedString(
570
+ buffer,
571
+ index,
572
+ index + stringSize - 1,
573
+ shouldValidateKey
574
+ );
502
575
 
503
576
  // If we are evaluating the functions
504
577
  if (evalFunctions) {
@@ -524,7 +597,7 @@ function deserializeObject(
524
597
 
525
598
  // Element cannot be shorter than totalSize + stringSize + documentSize + terminator
526
599
  if (totalSize < 4 + 4 + 4 + 1) {
527
- throw new Error('code_w_scope total size shorter minimum expected length');
600
+ throw new BSONError('code_w_scope total size shorter minimum expected length');
528
601
  }
529
602
 
530
603
  // Get the code string size
@@ -538,11 +611,17 @@ function deserializeObject(
538
611
  stringSize <= 0 ||
539
612
  stringSize > buffer.length - index ||
540
613
  buffer[index + stringSize - 1] !== 0
541
- )
542
- throw new Error('bad string length in bson');
614
+ ) {
615
+ throw new BSONError('bad string length in bson');
616
+ }
543
617
 
544
618
  // Javascript function
545
- const functionString = buffer.toString('utf8', index, index + stringSize - 1);
619
+ const functionString = getValidatedString(
620
+ buffer,
621
+ index,
622
+ index + stringSize - 1,
623
+ shouldValidateKey
624
+ );
546
625
  // Update parse index position
547
626
  index = index + stringSize;
548
627
  // Parse the element
@@ -560,12 +639,12 @@ function deserializeObject(
560
639
 
561
640
  // Check if field length is too short
562
641
  if (totalSize < 4 + 4 + objectSize + stringSize) {
563
- throw new Error('code_w_scope total size is too short, truncating scope');
642
+ throw new BSONError('code_w_scope total size is too short, truncating scope');
564
643
  }
565
644
 
566
645
  // Check if totalSize field is too long
567
646
  if (totalSize > 4 + 4 + objectSize + stringSize) {
568
- throw new Error('code_w_scope total size is too long, clips outer document');
647
+ throw new BSONError('code_w_scope total size is too long, clips outer document');
569
648
  }
570
649
 
571
650
  // If we are evaluating the functions
@@ -595,10 +674,12 @@ function deserializeObject(
595
674
  stringSize > buffer.length - index ||
596
675
  buffer[index + stringSize - 1] !== 0
597
676
  )
598
- throw new Error('bad string length in bson');
677
+ throw new BSONError('bad string length in bson');
599
678
  // Namespace
600
- if (!validateUtf8(buffer, index, index + stringSize - 1)) {
601
- throw new Error('Invalid UTF-8 string in BSON document');
679
+ if (validation != null && validation.utf8) {
680
+ if (!validateUtf8(buffer, index, index + stringSize - 1)) {
681
+ throw new BSONError('Invalid UTF-8 string in BSON document');
682
+ }
602
683
  }
603
684
  const namespace = buffer.toString('utf8', index, index + stringSize - 1);
604
685
  // Update parse index position
@@ -615,7 +696,7 @@ function deserializeObject(
615
696
  // Upgrade to DBRef type
616
697
  value = new DBRef(namespace, oid);
617
698
  } else {
618
- throw new Error(
699
+ throw new BSONError(
619
700
  'Detected unknown BSON type ' + elementType.toString(16) + ' for fieldname "' + name + '"'
620
701
  );
621
702
  }
@@ -633,8 +714,8 @@ function deserializeObject(
633
714
 
634
715
  // Check if the deserialization was against a valid array/object
635
716
  if (size !== index - startIndex) {
636
- if (isArray) throw new Error('corrupt array bson');
637
- throw new Error('corrupt object bson');
717
+ if (isArray) throw new BSONError('corrupt array bson');
718
+ throw new BSONError('corrupt object bson');
638
719
  }
639
720
 
640
721
  // if we did not find "$ref", "$id", "$db", or found an extraneous $key, don't make a DBRef
@@ -670,3 +751,24 @@ function isolateEval(
670
751
  // Set the object
671
752
  return functionCache[functionString].bind(object);
672
753
  }
754
+
755
+ function getValidatedString(
756
+ buffer: Buffer,
757
+ start: number,
758
+ end: number,
759
+ shouldValidateUtf8: boolean
760
+ ) {
761
+ const value = buffer.toString('utf8', start, end);
762
+ // if utf8 validation is on, do the check
763
+ if (shouldValidateUtf8) {
764
+ for (let i = 0; i < value.length; i++) {
765
+ if (value.charCodeAt(i) === 0xfffd) {
766
+ if (!validateUtf8(buffer, start, end)) {
767
+ throw new BSONError('Invalid UTF-8 string in BSON document');
768
+ }
769
+ break;
770
+ }
771
+ }
772
+ }
773
+ return value;
774
+ }