3 var readIEEE754 = require('../float_parser').readIEEE754,
4 f = require('util').format,
5 Long = require('../long').Long,
6 Double = require('../double').Double,
7 Timestamp = require('../timestamp').Timestamp,
8 ObjectID = require('../objectid').ObjectID,
9 Symbol = require('../symbol').Symbol,
10 Code = require('../code').Code,
11 MinKey = require('../min_key').MinKey,
12 MaxKey = require('../max_key').MaxKey,
13 Decimal128 = require('../decimal128'),
14 Int32 = require('../int_32'),
15 DBRef = require('../db_ref').DBRef,
16 BSONRegExp = require('../regexp').BSONRegExp,
17 Binary = require('../binary').Binary;
19 var deserialize = function(buffer, options, isArray) {
20 options = options == null ? {} : options;
21 var index = options && options.index ? options.index : 0;
22 // Read the document size
23 var size = buffer[index] | buffer[index+1] << 8 | buffer[index+2] << 16 | buffer[index+3] << 24;
25 // Ensure buffer is valid size
26 if(size < 5 || buffer.length < size || (size + index) > buffer.length) {
27 throw new Error("corrupt bson message");
31 if(buffer[index + size - 1] != 0) {
32 throw new Error("One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00");
35 // Start deserializtion
36 return deserializeObject(buffer, index, options, isArray);
39 var deserializeObject = function(buffer, index, options, isArray) {
40 var evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions'];
41 var cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions'];
42 var cacheFunctionsCrc32 = options['cacheFunctionsCrc32'] == null ? false : options['cacheFunctionsCrc32'];
43 var fieldsAsRaw = options['fieldsAsRaw'] == null ? null : options['fieldsAsRaw'];
45 // Return raw bson buffer instead of parsing it
46 var raw = options['raw'] == null ? false : options['raw'];
48 // Return BSONRegExp objects instead of native regular expressions
49 var bsonRegExp = typeof options['bsonRegExp'] == 'boolean' ? options['bsonRegExp'] : false;
51 // Controls the promotion of values vs wrapper classes
52 var promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers'];
53 var promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs'];
54 var promoteValues = options['promoteValues'] == null ? true : options['promoteValues'];
56 // Set the start index
57 var startIndex = index;
59 // Validate that we have at least 4 bytes of buffer
60 if(buffer.length < 5) throw new Error("corrupt bson message < 5 bytes long");
62 // Read the document size
63 var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
65 // Ensure buffer is valid size
66 if(size < 5 || size > buffer.length) throw new Error("corrupt bson message");
68 // Create holding object
69 var object = isArray ? [] : {};
70 // Used for arrays to skip having to perform utf8 decoding
73 // While we have more left data left keep parsing
76 var elementType = buffer[index++];
77 // If we get a zero it's the last byte, exit
78 if(elementType == 0) {
82 // Get the start search index
84 // Locate the end of the c string
85 while(buffer[i] !== 0x00 && i < buffer.length) {
89 // If are at the end of the buffer there is a problem with the document
90 if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString")
91 var name = isArray ? arrayIndex++ : buffer.toString('utf8', index, i);
95 if(elementType == BSON.BSON_DATA_STRING) {
96 var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
97 if(stringSize <= 0 || stringSize > (buffer.length - index) || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson");
98 object[name] = buffer.toString('utf8', index, index + stringSize - 1);
99 index = index + stringSize;
100 } else if(elementType == BSON.BSON_DATA_OID) {
101 var oid = new Buffer(12);
102 buffer.copy(oid, 0, index, index + 12);
103 object[name] = new ObjectID(oid);
105 } else if(elementType == BSON.BSON_DATA_INT && promoteValues == false) {
106 object[name] = new Int32(buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24);
107 } else if(elementType == BSON.BSON_DATA_INT) {
108 object[name] = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
109 } else if(elementType == BSON.BSON_DATA_NUMBER && promoteValues == false) {
110 object[name] = new Double(buffer.readDoubleLE(index));
112 } else if(elementType == BSON.BSON_DATA_NUMBER) {
113 object[name] = buffer.readDoubleLE(index);
115 } else if(elementType == BSON.BSON_DATA_DATE) {
116 var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
117 var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
118 object[name] = new Date(new Long(lowBits, highBits).toNumber());
119 } else if(elementType == BSON.BSON_DATA_BOOLEAN) {
120 if(buffer[index] != 0 && buffer[index] != 1) throw new Error('illegal boolean type value');
121 object[name] = buffer[index++] == 1;
122 } else if(elementType == BSON.BSON_DATA_OBJECT) {
124 var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24;
125 if(objectSize <= 0 || objectSize > (buffer.length - index)) throw new Error("bad embedded document length in bson");
127 // We have a raw value
129 object[name] = buffer.slice(index, index + objectSize);
131 object[name] = deserializeObject(buffer, _index, options, false);
134 index = index + objectSize;
135 } else if(elementType == BSON.BSON_DATA_ARRAY) {
137 var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24;
138 var arrayOptions = options;
141 var stopIndex = index + objectSize;
143 // All elements of array to be returned as raw bson
144 if(fieldsAsRaw && fieldsAsRaw[name]) {
146 for(var n in options) arrayOptions[n] = options[n];
147 arrayOptions['raw'] = true;
150 object[name] = deserializeObject(buffer, _index, arrayOptions, true);
151 index = index + objectSize;
153 if(buffer[index - 1] != 0) throw new Error('invalid array terminator byte');
154 if(index != stopIndex) throw new Error('corrupted array bson');
155 } else if(elementType == BSON.BSON_DATA_UNDEFINED) {
156 object[name] = undefined;
157 } else if(elementType == BSON.BSON_DATA_NULL) {
159 } else if(elementType == BSON.BSON_DATA_LONG) {
160 // Unpack the low and high bits
161 var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
162 var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
163 var long = new Long(lowBits, highBits);
164 // Promote the long if possible
165 if(promoteLongs && promoteValues == true) {
166 object[name] = long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG) ? long.toNumber() : long;
170 } else if(elementType == BSON.BSON_DATA_DECIMAL128) {
171 // Buffer to contain the decimal bytes
172 var bytes = new Buffer(16);
173 // Copy the next 16 bytes into the bytes buffer
174 buffer.copy(bytes, 0, index, index + 16);
177 // Assign the new Decimal128 value
178 var decimal128 = new Decimal128(bytes);
179 // If we have an alternative mapper use that
180 object[name] = decimal128.toObject ? decimal128.toObject() : decimal128;
181 } else if(elementType == BSON.BSON_DATA_BINARY) {
182 var binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
183 var totalBinarySize = binarySize;
184 var subType = buffer[index++];
186 // Did we have a negative binary size, throw
187 if(binarySize < 0) throw new Error('Negative binary type element size found');
189 // Is the length longer than the document
190 if(binarySize > buffer.length) throw new Error('Binary type size larger than document size');
192 // Decode as raw Buffer object if options specifies it
193 if(buffer['slice'] != null) {
194 // If we have subtype 2 skip the 4 bytes for the size
195 if(subType == Binary.SUBTYPE_BYTE_ARRAY) {
196 binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
197 if(binarySize < 0) throw new Error('Negative binary type element size found for subtype 0x02');
198 if(binarySize > (totalBinarySize - 4)) throw new Error('Binary type with subtype 0x02 contains to long binary size');
199 if(binarySize < (totalBinarySize - 4)) throw new Error('Binary type with subtype 0x02 contains to short binary size');
202 if(promoteBuffers && promoteValues) {
203 object[name] = buffer.slice(index, index + binarySize);
205 object[name] = new Binary(buffer.slice(index, index + binarySize), subType);
208 var _buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(binarySize)) : new Array(binarySize);
209 // If we have subtype 2 skip the 4 bytes for the size
210 if(subType == Binary.SUBTYPE_BYTE_ARRAY) {
211 binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
212 if(binarySize < 0) throw new Error('Negative binary type element size found for subtype 0x02');
213 if(binarySize > (totalBinarySize - 4)) throw new Error('Binary type with subtype 0x02 contains to long binary size');
214 if(binarySize < (totalBinarySize - 4)) throw new Error('Binary type with subtype 0x02 contains to short binary size');
218 for(var i = 0; i < binarySize; i++) {
219 _buffer[i] = buffer[index + i];
222 if(promoteBuffers && promoteValues) {
223 object[name] = _buffer;
225 object[name] = new Binary(_buffer, subType);
230 index = index + binarySize;
231 } else if(elementType == BSON.BSON_DATA_REGEXP && bsonRegExp == false) {
232 // Get the start search index
234 // Locate the end of the c string
235 while(buffer[i] !== 0x00 && i < buffer.length) {
238 // If are at the end of the buffer there is a problem with the document
239 if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString")
240 // Return the C string
241 var source = buffer.toString('utf8', index, i);
245 // Get the start search index
247 // Locate the end of the c string
248 while(buffer[i] !== 0x00 && i < buffer.length) {
251 // If are at the end of the buffer there is a problem with the document
252 if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString")
253 // Return the C string
254 var regExpOptions = buffer.toString('utf8', index, i);
257 // For each option add the corresponding one for javascript
258 var optionsArray = new Array(regExpOptions.length);
261 for(var i = 0; i < regExpOptions.length; i++) {
262 switch(regExpOptions[i]) {
264 optionsArray[i] = 'm';
267 optionsArray[i] = 'g';
270 optionsArray[i] = 'i';
275 object[name] = new RegExp(source, optionsArray.join(''));
276 } else if(elementType == BSON.BSON_DATA_REGEXP && bsonRegExp == true) {
277 // Get the start search index
279 // Locate the end of the c string
280 while(buffer[i] !== 0x00 && i < buffer.length) {
283 // If are at the end of the buffer there is a problem with the document
284 if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString")
285 // Return the C string
286 var source = buffer.toString('utf8', index, i);
289 // Get the start search index
291 // Locate the end of the c string
292 while(buffer[i] !== 0x00 && i < buffer.length) {
295 // If are at the end of the buffer there is a problem with the document
296 if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString")
297 // Return the C string
298 var regExpOptions = buffer.toString('utf8', index, i);
302 object[name] = new BSONRegExp(source, regExpOptions);
303 } else if(elementType == BSON.BSON_DATA_SYMBOL) {
304 var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
305 if(stringSize <= 0 || stringSize > (buffer.length - index) || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson");
306 object[name] = new Symbol(buffer.toString('utf8', index, index + stringSize - 1));
307 index = index + stringSize;
308 } else if(elementType == BSON.BSON_DATA_TIMESTAMP) {
309 var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
310 var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
311 object[name] = new Timestamp(lowBits, highBits);
312 } else if(elementType == BSON.BSON_DATA_MIN_KEY) {
313 object[name] = new MinKey();
314 } else if(elementType == BSON.BSON_DATA_MAX_KEY) {
315 object[name] = new MaxKey();
316 } else if(elementType == BSON.BSON_DATA_CODE) {
317 var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
318 if(stringSize <= 0 || stringSize > (buffer.length - index) || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson");
319 var functionString = buffer.toString('utf8', index, index + stringSize - 1);
321 // If we are evaluating the functions
324 // If we have cache enabled let's look for the md5 of the function in the cache
326 var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString;
327 // Got to do this to avoid V8 deoptimizing the call due to finding eval
328 object[name] = isolateEvalWithHash(functionCache, hash, functionString, object);
330 object[name] = isolateEval(functionString);
333 object[name] = new Code(functionString);
336 // Update parse index position
337 index = index + stringSize;
338 } else if(elementType == BSON.BSON_DATA_CODE_W_SCOPE) {
339 var totalSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
341 // Element cannot be shorter than totalSize + stringSize + documentSize + terminator
342 if(totalSize < (4 + 4 + 4 + 1)) {
343 throw new Error("code_w_scope total size shorter minimum expected length");
346 // Get the code string size
347 var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
348 // Check if we have a valid string
349 if(stringSize <= 0 || stringSize > (buffer.length - index) || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson");
351 // Javascript function
352 var functionString = buffer.toString('utf8', index, index + stringSize - 1);
353 // Update parse index position
354 index = index + stringSize;
357 // Decode the size of the object document
358 var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24;
359 // Decode the scope object
360 var scopeObject = deserializeObject(buffer, _index, options, false);
362 index = index + objectSize;
364 // Check if field length is to short
365 if(totalSize < (4 + 4 + objectSize + stringSize)) {
366 throw new Error('code_w_scope total size is to short, truncating scope');
369 // Check if totalSize field is to long
370 if(totalSize > (4 + 4 + objectSize + stringSize)) {
371 throw new Error('code_w_scope total size is to long, clips outer document');
374 // If we are evaluating the functions
376 // Contains the value we are going to set
378 // If we have cache enabled let's look for the md5 of the function in the cache
380 var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString;
381 // Got to do this to avoid V8 deoptimizing the call due to finding eval
382 object[name] = isolateEvalWithHash(functionCache, hash, functionString, object);
384 object[name] = isolateEval(functionString);
387 object[name].scope = scopeObject;
389 object[name] = new Code(functionString, scopeObject);
391 } else if(elementType == BSON_DATA_DBPOINTER) {
392 // Get the code string size
393 var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
394 // Check if we have a valid string
395 if(stringSize <= 0 || stringSize > (buffer.length - index) || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson");
397 var namespace = buffer.toString('utf8', index, index + stringSize - 1);
398 // Update parse index position
399 index = index + stringSize;
402 var oidBuffer = new Buffer(12);
403 buffer.copy(oidBuffer, 0, index, index + 12);
404 var oid = new ObjectID(oidBuffer);
409 // Split the namespace
410 var parts = namespace.split('.');
411 var db = parts.shift();
412 var collection = parts.join('.');
413 // Upgrade to DBRef type
414 object[name] = new DBRef(collection, oid, db);
416 throw new Error("Detected unknown BSON type " + elementType.toString(16) + " for fieldname \"" + name + "\", are you using the latest BSON parser");
420 // Check if the deserialization was against a valid array/object
421 if(size != (index - startIndex)) {
422 if(isArray) throw new Error('corrupt array bson');
423 throw new Error('corrupt object bson');
426 // Check if we have a db ref object
427 if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']);
432 * Ensure eval is isolated.
437 var isolateEvalWithHash = function(functionCache, hash, functionString, object) {
438 // Contains the value we are going to set
441 // Check for cache hit, eval if missing and return cached function
442 if(functionCache[hash] == null) {
443 eval("value = " + functionString);
444 functionCache[hash] = value;
447 return functionCache[hash].bind(object);
451 * Ensure eval is isolated.
456 var isolateEval = function(functionString) {
457 // Contains the value we are going to set
460 eval("value = " + functionString);
467 * Contains the function cache if we have that enable to allow for avoiding the eval step on each deserialization, comparison is by md5
472 var functionCache = BSON.functionCache = {};
477 * @classconstant BSON_DATA_NUMBER
479 BSON.BSON_DATA_NUMBER = 1;
483 * @classconstant BSON_DATA_STRING
485 BSON.BSON_DATA_STRING = 2;
489 * @classconstant BSON_DATA_OBJECT
491 BSON.BSON_DATA_OBJECT = 3;
495 * @classconstant BSON_DATA_ARRAY
497 BSON.BSON_DATA_ARRAY = 4;
501 * @classconstant BSON_DATA_BINARY
503 BSON.BSON_DATA_BINARY = 5;
507 * @classconstant BSON_DATA_UNDEFINED
509 BSON.BSON_DATA_UNDEFINED = 6;
513 * @classconstant BSON_DATA_OID
515 BSON.BSON_DATA_OID = 7;
519 * @classconstant BSON_DATA_BOOLEAN
521 BSON.BSON_DATA_BOOLEAN = 8;
525 * @classconstant BSON_DATA_DATE
527 BSON.BSON_DATA_DATE = 9;
531 * @classconstant BSON_DATA_NULL
533 BSON.BSON_DATA_NULL = 10;
537 * @classconstant BSON_DATA_REGEXP
539 BSON.BSON_DATA_REGEXP = 11;
543 * @classconstant BSON_DATA_DBPOINTER
545 BSON.BSON_DATA_DBPOINTER = 12;
549 * @classconstant BSON_DATA_CODE
551 BSON.BSON_DATA_CODE = 13;
555 * @classconstant BSON_DATA_SYMBOL
557 BSON.BSON_DATA_SYMBOL = 14;
559 * Code with Scope BSON Type
561 * @classconstant BSON_DATA_CODE_W_SCOPE
563 BSON.BSON_DATA_CODE_W_SCOPE = 15;
565 * 32 bit Integer BSON Type
567 * @classconstant BSON_DATA_INT
569 BSON.BSON_DATA_INT = 16;
571 * Timestamp BSON Type
573 * @classconstant BSON_DATA_TIMESTAMP
575 BSON.BSON_DATA_TIMESTAMP = 17;
579 * @classconstant BSON_DATA_LONG
581 BSON.BSON_DATA_LONG = 18;
585 * @classconstant BSON_DATA_DECIMAL128
587 BSON.BSON_DATA_DECIMAL128 = 19;
591 * @classconstant BSON_DATA_MIN_KEY
593 BSON.BSON_DATA_MIN_KEY = 0xff;
597 * @classconstant BSON_DATA_MAX_KEY
599 BSON.BSON_DATA_MAX_KEY = 0x7f;
602 * Binary Default Type
604 * @classconstant BSON_BINARY_SUBTYPE_DEFAULT
606 BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0;
608 * Binary Function Type
610 * @classconstant BSON_BINARY_SUBTYPE_FUNCTION
612 BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1;
614 * Binary Byte Array Type
616 * @classconstant BSON_BINARY_SUBTYPE_BYTE_ARRAY
618 BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2;
622 * @classconstant BSON_BINARY_SUBTYPE_UUID
624 BSON.BSON_BINARY_SUBTYPE_UUID = 3;
628 * @classconstant BSON_BINARY_SUBTYPE_MD5
630 BSON.BSON_BINARY_SUBTYPE_MD5 = 4;
632 * Binary User Defined Type
634 * @classconstant BSON_BINARY_SUBTYPE_USER_DEFINED
636 BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128;
639 BSON.BSON_INT32_MAX = 0x7FFFFFFF;
640 BSON.BSON_INT32_MIN = -0x80000000;
642 BSON.BSON_INT64_MAX = Math.pow(2, 63) - 1;
643 BSON.BSON_INT64_MIN = -Math.pow(2, 63);
645 // JS MAX PRECISE VALUES
646 BSON.JS_INT_MAX = 0x20000000000000; // Any integer up to 2^53 can be precisely represented by a double.
647 BSON.JS_INT_MIN = -0x20000000000000; // Any integer down to -2^53 can be precisely represented by a double.
649 // Internal long versions
650 var JS_INT_MAX_LONG = Long.fromNumber(0x20000000000000); // Any integer up to 2^53 can be precisely represented by a double.
651 var JS_INT_MIN_LONG = Long.fromNumber(-0x20000000000000); // Any integer down to -2^53 can be precisely represented by a double.
653 module.exports = deserialize