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.
- package/HISTORY.md +48 -1
- package/README.md +7 -9
- package/bower.json +1 -1
- package/bson.d.ts +986 -0
- package/dist/bson.browser.esm.js +7811 -5011
- package/dist/bson.browser.esm.js.map +1 -0
- package/dist/bson.browser.umd.js +7862 -5099
- package/dist/bson.browser.umd.js.map +1 -0
- package/dist/bson.bundle.js +8723 -9228
- package/dist/bson.bundle.js.map +1 -0
- package/dist/bson.esm.js +5728 -4951
- package/dist/bson.esm.js.map +1 -0
- package/etc/prepare.js +19 -0
- package/lib/binary.js +218 -398
- package/lib/binary.js.map +1 -0
- package/lib/bson.js +201 -240
- package/lib/bson.js.map +1 -0
- package/lib/code.js +41 -41
- package/lib/code.js.map +1 -0
- package/lib/constants.js +78 -203
- package/lib/constants.js.map +1 -0
- package/lib/db_ref.js +88 -81
- package/lib/db_ref.js.map +1 -0
- package/lib/decimal128.js +667 -777
- package/lib/decimal128.js.map +1 -0
- package/lib/double.js +68 -70
- package/lib/double.js.map +1 -0
- package/lib/ensure_buffer.js +22 -18
- package/lib/ensure_buffer.js.map +1 -0
- package/lib/extended_json.js +310 -321
- package/lib/extended_json.js.map +1 -0
- package/lib/float_parser.js +98 -104
- package/lib/float_parser.js.map +1 -0
- package/lib/int_32.js +50 -49
- package/lib/int_32.js.map +1 -0
- package/lib/long.js +881 -16
- package/lib/long.js.map +1 -0
- package/lib/map.js +130 -122
- package/lib/map.js.map +1 -0
- package/lib/max_key.js +28 -25
- package/lib/max_key.js.map +1 -0
- package/lib/min_key.js +28 -25
- package/lib/min_key.js.map +1 -0
- package/lib/objectid.js +300 -410
- package/lib/objectid.js.map +1 -0
- package/lib/parser/calculate_size.js +188 -224
- package/lib/parser/calculate_size.js.map +1 -0
- package/lib/parser/deserializer.js +545 -621
- package/lib/parser/deserializer.js.map +1 -0
- package/lib/parser/serializer.js +798 -923
- package/lib/parser/serializer.js.map +1 -0
- package/lib/parser/utils.js +92 -30
- package/lib/parser/utils.js.map +1 -0
- package/lib/regexp.js +61 -74
- package/lib/regexp.js.map +1 -0
- package/lib/symbol.js +45 -58
- package/lib/symbol.js.map +1 -0
- package/lib/timestamp.js +91 -95
- package/lib/timestamp.js.map +1 -0
- package/lib/uuid.js +48 -0
- package/lib/uuid.js.map +1 -0
- package/lib/validate_utf8.js +41 -42
- package/lib/validate_utf8.js.map +1 -0
- package/package.json +53 -31
- package/src/binary.ts +272 -0
- package/src/bson.ts +326 -0
- package/src/code.ts +61 -0
- package/src/constants.ts +104 -0
- package/src/db_ref.ts +119 -0
- package/src/decimal128.ts +803 -0
- package/src/double.ts +87 -0
- package/src/ensure_buffer.ts +26 -0
- package/src/extended_json.ts +395 -0
- package/src/float_parser.ts +152 -0
- package/src/int_32.ts +66 -0
- package/src/long.ts +1002 -0
- package/src/map.ts +139 -0
- package/src/max_key.ts +37 -0
- package/src/min_key.ts +37 -0
- package/src/objectid.ts +379 -0
- package/src/parser/calculate_size.ts +230 -0
- package/src/parser/deserializer.ts +655 -0
- package/src/parser/serializer.ts +1069 -0
- package/src/parser/utils.ts +93 -0
- package/src/regexp.ts +94 -0
- package/src/symbol.ts +59 -0
- package/src/timestamp.ts +108 -0
- package/src/uuid.ts +57 -0
- package/src/validate_utf8.ts +47 -0
- 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
|
+
}
|