@@ -45,6 +45,22 @@ export interface DeserializeOptions {
45
45
index ?: number ;
46
46
47
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 > } ;
48
64
}
49
65
50
66
// Internal long versions
@@ -120,6 +136,45 @@ function deserializeObject(
120
136
const promoteLongs = options [ 'promoteLongs' ] == null ? true : options [ 'promoteLongs' ] ;
121
137
const promoteValues = options [ 'promoteValues' ] == null ? true : options [ 'promoteValues' ] ;
122
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
+
123
178
// Set the start index
124
179
const startIndex = index ;
125
180
@@ -158,7 +213,18 @@ function deserializeObject(
158
213
159
214
// If are at the end of the buffer there is a problem with the document
160
215
if ( i >= buffer . byteLength ) throw new BSONError ( 'Bad BSON Document: illegal CString' ) ;
216
+
217
+ // Represents the key
161
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
+
162
228
if ( isPossibleDBRef !== false && ( name as string ) [ 0 ] === '$' ) {
163
229
isPossibleDBRef = allowedDBRefKeys . test ( name as string ) ;
164
230
}
@@ -179,9 +245,7 @@ function deserializeObject(
179
245
) {
180
246
throw new BSONError ( 'bad string length in bson' ) ;
181
247
}
182
-
183
- value = getValidatedString ( buffer , index , index + stringSize - 1 ) ;
184
-
248
+ value = getValidatedString ( buffer , index , index + stringSize - 1 , shouldValidateKey ) ;
185
249
index = index + stringSize ;
186
250
} else if ( elementType === constants . BSON_DATA_OID ) {
187
251
const oid = Buffer . alloc ( 12 ) ;
@@ -234,7 +298,11 @@ function deserializeObject(
234
298
if ( raw ) {
235
299
value = buffer . slice ( index , index + objectSize ) ;
236
300
} else {
237
- 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 ) ;
238
306
}
239
307
240
308
index = index + objectSize ;
@@ -262,7 +330,9 @@ function deserializeObject(
262
330
}
263
331
arrayOptions [ 'raw' ] = true ;
264
332
}
265
-
333
+ if ( ! globalUTFValidation ) {
334
+ arrayOptions = { ...arrayOptions , validation : { utf8 : shouldValidateKey } } ;
335
+ }
266
336
value = deserializeObject ( buffer , _index , arrayOptions , true ) ;
267
337
index = index + objectSize ;
268
338
@@ -463,7 +533,7 @@ function deserializeObject(
463
533
) {
464
534
throw new BSONError ( 'bad string length in bson' ) ;
465
535
}
466
- const symbol = getValidatedString ( buffer , index , index + stringSize - 1 ) ;
536
+ const symbol = getValidatedString ( buffer , index , index + stringSize - 1 , shouldValidateKey ) ;
467
537
value = promoteValues ? symbol : new BSONSymbol ( symbol ) ;
468
538
index = index + stringSize ;
469
539
} else if ( elementType === constants . BSON_DATA_TIMESTAMP ) {
@@ -496,7 +566,12 @@ function deserializeObject(
496
566
) {
497
567
throw new BSONError ( 'bad string length in bson' ) ;
498
568
}
499
- const functionString = getValidatedString ( buffer , index , index + stringSize - 1 ) ;
569
+ const functionString = getValidatedString (
570
+ buffer ,
571
+ index ,
572
+ index + stringSize - 1 ,
573
+ shouldValidateKey
574
+ ) ;
500
575
501
576
// If we are evaluating the functions
502
577
if ( evalFunctions ) {
@@ -541,7 +616,12 @@ function deserializeObject(
541
616
}
542
617
543
618
// Javascript function
544
- const functionString = getValidatedString ( buffer , index , index + stringSize - 1 ) ;
619
+ const functionString = getValidatedString (
620
+ buffer ,
621
+ index ,
622
+ index + stringSize - 1 ,
623
+ shouldValidateKey
624
+ ) ;
545
625
// Update parse index position
546
626
index = index + stringSize ;
547
627
// Parse the element
@@ -596,8 +676,10 @@ function deserializeObject(
596
676
)
597
677
throw new BSONError ( 'bad string length in bson' ) ;
598
678
// Namespace
599
- if ( ! validateUtf8 ( buffer , index , index + stringSize - 1 ) ) {
600
- throw new BSONError ( '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
+ }
601
683
}
602
684
const namespace = buffer . toString ( 'utf8' , index , index + stringSize - 1 ) ;
603
685
// Update parse index position
@@ -670,14 +752,22 @@ function isolateEval(
670
752
return functionCache [ functionString ] . bind ( object ) ;
671
753
}
672
754
673
- function getValidatedString ( buffer : Buffer , start : number , end : number ) {
755
+ function getValidatedString (
756
+ buffer : Buffer ,
757
+ start : number ,
758
+ end : number ,
759
+ shouldValidateUtf8 : boolean
760
+ ) {
674
761
const value = buffer . toString ( 'utf8' , start , end ) ;
675
- for ( let i = 0 ; i < value . length ; i ++ ) {
676
- if ( value . charCodeAt ( i ) === 0xfffd ) {
677
- if ( ! validateUtf8 ( buffer , start , end ) ) {
678
- throw new BSONError ( 'Invalid UTF-8 string in BSON document' ) ;
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 ;
679
770
}
680
- break ;
681
771
}
682
772
}
683
773
return value ;
0 commit comments