@@ 14305-14695 (lines=391) @@ | ||
14302 | return deserializeObject(buffer, index, options, isArray); |
|
14303 | }; |
|
14304 | ||
14305 | var deserializeObject = function (buffer, index, options, isArray) { |
|
14306 | var evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions']; |
|
14307 | var cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions']; |
|
14308 | var cacheFunctionsCrc32 = options['cacheFunctionsCrc32'] == null ? false : options['cacheFunctionsCrc32']; |
|
14309 | var fieldsAsRaw = options['fieldsAsRaw'] == null ? null : options['fieldsAsRaw']; |
|
14310 | ||
14311 | // Return raw bson buffer instead of parsing it |
|
14312 | var raw = options['raw'] == null ? false : options['raw']; |
|
14313 | ||
14314 | // Return BSONRegExp objects instead of native regular expressions |
|
14315 | var bsonRegExp = typeof options['bsonRegExp'] == 'boolean' ? options['bsonRegExp'] : false; |
|
14316 | ||
14317 | // Controls the promotion of values vs wrapper classes |
|
14318 | var promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers']; |
|
14319 | var promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs']; |
|
14320 | var promoteValues = options['promoteValues'] == null ? true : options['promoteValues']; |
|
14321 | ||
14322 | // Set the start index |
|
14323 | var startIndex = index; |
|
14324 | ||
14325 | // Validate that we have at least 4 bytes of buffer |
|
14326 | if (buffer.length < 5) throw new Error("corrupt bson message < 5 bytes long"); |
|
14327 | ||
14328 | // Read the document size |
|
14329 | var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14330 | ||
14331 | // Ensure buffer is valid size |
|
14332 | if (size < 5 || size > buffer.length) throw new Error("corrupt bson message"); |
|
14333 | ||
14334 | // Create holding object |
|
14335 | var object = isArray ? [] : {}; |
|
14336 | // Used for arrays to skip having to perform utf8 decoding |
|
14337 | var arrayIndex = 0; |
|
14338 | ||
14339 | // While we have more left data left keep parsing |
|
14340 | while (true) { |
|
14341 | // Read the type |
|
14342 | var elementType = buffer[index++]; |
|
14343 | // If we get a zero it's the last byte, exit |
|
14344 | if (elementType == 0) { |
|
14345 | break; |
|
14346 | } |
|
14347 | ||
14348 | // Get the start search index |
|
14349 | var i = index; |
|
14350 | // Locate the end of the c string |
|
14351 | while (buffer[i] !== 0x00 && i < buffer.length) { |
|
14352 | i++; |
|
14353 | } |
|
14354 | ||
14355 | // If are at the end of the buffer there is a problem with the document |
|
14356 | if (i >= buffer.length) throw new Error("Bad BSON Document: illegal CString"); |
|
14357 | var name = isArray ? arrayIndex++ : buffer.toString('utf8', index, i); |
|
14358 | ||
14359 | index = i + 1; |
|
14360 | ||
14361 | if (elementType == BSON.BSON_DATA_STRING) { |
|
14362 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14363 | if (stringSize <= 0 || stringSize > buffer.length - index || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson"); |
|
14364 | object[name] = buffer.toString('utf8', index, index + stringSize - 1); |
|
14365 | index = index + stringSize; |
|
14366 | } else if (elementType == BSON.BSON_DATA_OID) { |
|
14367 | var oid = new Buffer(12); |
|
14368 | buffer.copy(oid, 0, index, index + 12); |
|
14369 | object[name] = new ObjectID(oid); |
|
14370 | index = index + 12; |
|
14371 | } else if (elementType == BSON.BSON_DATA_INT && promoteValues == false) { |
|
14372 | object[name] = new Int32(buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24); |
|
14373 | } else if (elementType == BSON.BSON_DATA_INT) { |
|
14374 | object[name] = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14375 | } else if (elementType == BSON.BSON_DATA_NUMBER && promoteValues == false) { |
|
14376 | object[name] = new Double(buffer.readDoubleLE(index)); |
|
14377 | index = index + 8; |
|
14378 | } else if (elementType == BSON.BSON_DATA_NUMBER) { |
|
14379 | object[name] = buffer.readDoubleLE(index); |
|
14380 | index = index + 8; |
|
14381 | } else if (elementType == BSON.BSON_DATA_DATE) { |
|
14382 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14383 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14384 | object[name] = new Date(new Long(lowBits, highBits).toNumber()); |
|
14385 | } else if (elementType == BSON.BSON_DATA_BOOLEAN) { |
|
14386 | if (buffer[index] != 0 && buffer[index] != 1) throw new Error('illegal boolean type value'); |
|
14387 | object[name] = buffer[index++] == 1; |
|
14388 | } else if (elementType == BSON.BSON_DATA_OBJECT) { |
|
14389 | var _index = index; |
|
14390 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
|
14391 | if (objectSize <= 0 || objectSize > buffer.length - index) throw new Error("bad embedded document length in bson"); |
|
14392 | ||
14393 | // We have a raw value |
|
14394 | if (raw) { |
|
14395 | object[name] = buffer.slice(index, index + objectSize); |
|
14396 | } else { |
|
14397 | object[name] = deserializeObject(buffer, _index, options, false); |
|
14398 | } |
|
14399 | ||
14400 | index = index + objectSize; |
|
14401 | } else if (elementType == BSON.BSON_DATA_ARRAY) { |
|
14402 | var _index = index; |
|
14403 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
|
14404 | var arrayOptions = options; |
|
14405 | ||
14406 | // Stop index |
|
14407 | var stopIndex = index + objectSize; |
|
14408 | ||
14409 | // All elements of array to be returned as raw bson |
|
14410 | if (fieldsAsRaw && fieldsAsRaw[name]) { |
|
14411 | arrayOptions = {}; |
|
14412 | for (var n in options) arrayOptions[n] = options[n]; |
|
14413 | arrayOptions['raw'] = true; |
|
14414 | } |
|
14415 | ||
14416 | object[name] = deserializeObject(buffer, _index, arrayOptions, true); |
|
14417 | index = index + objectSize; |
|
14418 | ||
14419 | if (buffer[index - 1] != 0) throw new Error('invalid array terminator byte'); |
|
14420 | if (index != stopIndex) throw new Error('corrupted array bson'); |
|
14421 | } else if (elementType == BSON.BSON_DATA_UNDEFINED) { |
|
14422 | object[name] = undefined; |
|
14423 | } else if (elementType == BSON.BSON_DATA_NULL) { |
|
14424 | object[name] = null; |
|
14425 | } else if (elementType == BSON.BSON_DATA_LONG) { |
|
14426 | // Unpack the low and high bits |
|
14427 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14428 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14429 | var long = new Long(lowBits, highBits); |
|
14430 | // Promote the long if possible |
|
14431 | if (promoteLongs && promoteValues == true) { |
|
14432 | object[name] = long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG) ? long.toNumber() : long; |
|
14433 | } else { |
|
14434 | object[name] = long; |
|
14435 | } |
|
14436 | } else if (elementType == BSON.BSON_DATA_DECIMAL128) { |
|
14437 | // Buffer to contain the decimal bytes |
|
14438 | var bytes = new Buffer(16); |
|
14439 | // Copy the next 16 bytes into the bytes buffer |
|
14440 | buffer.copy(bytes, 0, index, index + 16); |
|
14441 | // Update index |
|
14442 | index = index + 16; |
|
14443 | // Assign the new Decimal128 value |
|
14444 | var decimal128 = new Decimal128(bytes); |
|
14445 | // If we have an alternative mapper use that |
|
14446 | object[name] = decimal128.toObject ? decimal128.toObject() : decimal128; |
|
14447 | } else if (elementType == BSON.BSON_DATA_BINARY) { |
|
14448 | var binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14449 | var totalBinarySize = binarySize; |
|
14450 | var subType = buffer[index++]; |
|
14451 | ||
14452 | // Did we have a negative binary size, throw |
|
14453 | if (binarySize < 0) throw new Error('Negative binary type element size found'); |
|
14454 | ||
14455 | // Is the length longer than the document |
|
14456 | if (binarySize > buffer.length) throw new Error('Binary type size larger than document size'); |
|
14457 | ||
14458 | // Decode as raw Buffer object if options specifies it |
|
14459 | if (buffer['slice'] != null) { |
|
14460 | // If we have subtype 2 skip the 4 bytes for the size |
|
14461 | if (subType == Binary.SUBTYPE_BYTE_ARRAY) { |
|
14462 | binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14463 | if (binarySize < 0) throw new Error('Negative binary type element size found for subtype 0x02'); |
|
14464 | if (binarySize > totalBinarySize - 4) throw new Error('Binary type with subtype 0x02 contains to long binary size'); |
|
14465 | if (binarySize < totalBinarySize - 4) throw new Error('Binary type with subtype 0x02 contains to short binary size'); |
|
14466 | } |
|
14467 | ||
14468 | if (promoteBuffers && promoteValues) { |
|
14469 | object[name] = buffer.slice(index, index + binarySize); |
|
14470 | } else { |
|
14471 | object[name] = new Binary(buffer.slice(index, index + binarySize), subType); |
|
14472 | } |
|
14473 | } else { |
|
14474 | var _buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(binarySize)) : new Array(binarySize); |
|
14475 | // If we have subtype 2 skip the 4 bytes for the size |
|
14476 | if (subType == Binary.SUBTYPE_BYTE_ARRAY) { |
|
14477 | binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14478 | if (binarySize < 0) throw new Error('Negative binary type element size found for subtype 0x02'); |
|
14479 | if (binarySize > totalBinarySize - 4) throw new Error('Binary type with subtype 0x02 contains to long binary size'); |
|
14480 | if (binarySize < totalBinarySize - 4) throw new Error('Binary type with subtype 0x02 contains to short binary size'); |
|
14481 | } |
|
14482 | ||
14483 | // Copy the data |
|
14484 | for (var i = 0; i < binarySize; i++) { |
|
14485 | _buffer[i] = buffer[index + i]; |
|
14486 | } |
|
14487 | ||
14488 | if (promoteBuffers && promoteValues) { |
|
14489 | object[name] = _buffer; |
|
14490 | } else { |
|
14491 | object[name] = new Binary(_buffer, subType); |
|
14492 | } |
|
14493 | } |
|
14494 | ||
14495 | // Update the index |
|
14496 | index = index + binarySize; |
|
14497 | } else if (elementType == BSON.BSON_DATA_REGEXP && bsonRegExp == false) { |
|
14498 | // Get the start search index |
|
14499 | var i = index; |
|
14500 | // Locate the end of the c string |
|
14501 | while (buffer[i] !== 0x00 && i < buffer.length) { |
|
14502 | i++; |
|
14503 | } |
|
14504 | // If are at the end of the buffer there is a problem with the document |
|
14505 | if (i >= buffer.length) throw new Error("Bad BSON Document: illegal CString"); |
|
14506 | // Return the C string |
|
14507 | var source = buffer.toString('utf8', index, i); |
|
14508 | // Create the regexp |
|
14509 | index = i + 1; |
|
14510 | ||
14511 | // Get the start search index |
|
14512 | var i = index; |
|
14513 | // Locate the end of the c string |
|
14514 | while (buffer[i] !== 0x00 && i < buffer.length) { |
|
14515 | i++; |
|
14516 | } |
|
14517 | // If are at the end of the buffer there is a problem with the document |
|
14518 | if (i >= buffer.length) throw new Error("Bad BSON Document: illegal CString"); |
|
14519 | // Return the C string |
|
14520 | var regExpOptions = buffer.toString('utf8', index, i); |
|
14521 | index = i + 1; |
|
14522 | ||
14523 | // For each option add the corresponding one for javascript |
|
14524 | var optionsArray = new Array(regExpOptions.length); |
|
14525 | ||
14526 | // Parse options |
|
14527 | for (var i = 0; i < regExpOptions.length; i++) { |
|
14528 | switch (regExpOptions[i]) { |
|
14529 | case 'm': |
|
14530 | optionsArray[i] = 'm'; |
|
14531 | break; |
|
14532 | case 's': |
|
14533 | optionsArray[i] = 'g'; |
|
14534 | break; |
|
14535 | case 'i': |
|
14536 | optionsArray[i] = 'i'; |
|
14537 | break; |
|
14538 | } |
|
14539 | } |
|
14540 | ||
14541 | object[name] = new RegExp(source, optionsArray.join('')); |
|
14542 | } else if (elementType == BSON.BSON_DATA_REGEXP && bsonRegExp == true) { |
|
14543 | // Get the start search index |
|
14544 | var i = index; |
|
14545 | // Locate the end of the c string |
|
14546 | while (buffer[i] !== 0x00 && i < buffer.length) { |
|
14547 | i++; |
|
14548 | } |
|
14549 | // If are at the end of the buffer there is a problem with the document |
|
14550 | if (i >= buffer.length) throw new Error("Bad BSON Document: illegal CString"); |
|
14551 | // Return the C string |
|
14552 | var source = buffer.toString('utf8', index, i); |
|
14553 | index = i + 1; |
|
14554 | ||
14555 | // Get the start search index |
|
14556 | var i = index; |
|
14557 | // Locate the end of the c string |
|
14558 | while (buffer[i] !== 0x00 && i < buffer.length) { |
|
14559 | i++; |
|
14560 | } |
|
14561 | // If are at the end of the buffer there is a problem with the document |
|
14562 | if (i >= buffer.length) throw new Error("Bad BSON Document: illegal CString"); |
|
14563 | // Return the C string |
|
14564 | var regExpOptions = buffer.toString('utf8', index, i); |
|
14565 | index = i + 1; |
|
14566 | ||
14567 | // Set the object |
|
14568 | object[name] = new BSONRegExp(source, regExpOptions); |
|
14569 | } else if (elementType == BSON.BSON_DATA_SYMBOL) { |
|
14570 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14571 | if (stringSize <= 0 || stringSize > buffer.length - index || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson"); |
|
14572 | object[name] = new Symbol(buffer.toString('utf8', index, index + stringSize - 1)); |
|
14573 | index = index + stringSize; |
|
14574 | } else if (elementType == BSON.BSON_DATA_TIMESTAMP) { |
|
14575 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14576 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14577 | object[name] = new Timestamp(lowBits, highBits); |
|
14578 | } else if (elementType == BSON.BSON_DATA_MIN_KEY) { |
|
14579 | object[name] = new MinKey(); |
|
14580 | } else if (elementType == BSON.BSON_DATA_MAX_KEY) { |
|
14581 | object[name] = new MaxKey(); |
|
14582 | } else if (elementType == BSON.BSON_DATA_CODE) { |
|
14583 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14584 | if (stringSize <= 0 || stringSize > buffer.length - index || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson"); |
|
14585 | var functionString = buffer.toString('utf8', index, index + stringSize - 1); |
|
14586 | ||
14587 | // If we are evaluating the functions |
|
14588 | if (evalFunctions) { |
|
14589 | var value = null; |
|
14590 | // If we have cache enabled let's look for the md5 of the function in the cache |
|
14591 | if (cacheFunctions) { |
|
14592 | var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString; |
|
14593 | // Got to do this to avoid V8 deoptimizing the call due to finding eval |
|
14594 | object[name] = isolateEvalWithHash(functionCache, hash, functionString, object); |
|
14595 | } else { |
|
14596 | object[name] = isolateEval(functionString); |
|
14597 | } |
|
14598 | } else { |
|
14599 | object[name] = new Code(functionString); |
|
14600 | } |
|
14601 | ||
14602 | // Update parse index position |
|
14603 | index = index + stringSize; |
|
14604 | } else if (elementType == BSON.BSON_DATA_CODE_W_SCOPE) { |
|
14605 | var totalSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14606 | ||
14607 | // Element cannot be shorter than totalSize + stringSize + documentSize + terminator |
|
14608 | if (totalSize < 4 + 4 + 4 + 1) { |
|
14609 | throw new Error("code_w_scope total size shorter minimum expected length"); |
|
14610 | } |
|
14611 | ||
14612 | // Get the code string size |
|
14613 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14614 | // Check if we have a valid string |
|
14615 | if (stringSize <= 0 || stringSize > buffer.length - index || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson"); |
|
14616 | ||
14617 | // Javascript function |
|
14618 | var functionString = buffer.toString('utf8', index, index + stringSize - 1); |
|
14619 | // Update parse index position |
|
14620 | index = index + stringSize; |
|
14621 | // Parse the element |
|
14622 | var _index = index; |
|
14623 | // Decode the size of the object document |
|
14624 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
|
14625 | // Decode the scope object |
|
14626 | var scopeObject = deserializeObject(buffer, _index, options, false); |
|
14627 | // Adjust the index |
|
14628 | index = index + objectSize; |
|
14629 | ||
14630 | // Check if field length is to short |
|
14631 | if (totalSize < 4 + 4 + objectSize + stringSize) { |
|
14632 | throw new Error('code_w_scope total size is to short, truncating scope'); |
|
14633 | } |
|
14634 | ||
14635 | // Check if totalSize field is to long |
|
14636 | if (totalSize > 4 + 4 + objectSize + stringSize) { |
|
14637 | throw new Error('code_w_scope total size is to long, clips outer document'); |
|
14638 | } |
|
14639 | ||
14640 | // If we are evaluating the functions |
|
14641 | if (evalFunctions) { |
|
14642 | // Contains the value we are going to set |
|
14643 | var value = null; |
|
14644 | // If we have cache enabled let's look for the md5 of the function in the cache |
|
14645 | if (cacheFunctions) { |
|
14646 | var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString; |
|
14647 | // Got to do this to avoid V8 deoptimizing the call due to finding eval |
|
14648 | object[name] = isolateEvalWithHash(functionCache, hash, functionString, object); |
|
14649 | } else { |
|
14650 | object[name] = isolateEval(functionString); |
|
14651 | } |
|
14652 | ||
14653 | object[name].scope = scopeObject; |
|
14654 | } else { |
|
14655 | object[name] = new Code(functionString, scopeObject); |
|
14656 | } |
|
14657 | } else if (elementType == BSON.BSON_DATA_DBPOINTER) { |
|
14658 | // Get the code string size |
|
14659 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
14660 | // Check if we have a valid string |
|
14661 | if (stringSize <= 0 || stringSize > buffer.length - index || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson"); |
|
14662 | // Namespace |
|
14663 | var namespace = buffer.toString('utf8', index, index + stringSize - 1); |
|
14664 | // Update parse index position |
|
14665 | index = index + stringSize; |
|
14666 | ||
14667 | // Read the oid |
|
14668 | var oidBuffer = new Buffer(12); |
|
14669 | buffer.copy(oidBuffer, 0, index, index + 12); |
|
14670 | var oid = new ObjectID(oidBuffer); |
|
14671 | ||
14672 | // Update the index |
|
14673 | index = index + 12; |
|
14674 | ||
14675 | // Split the namespace |
|
14676 | var parts = namespace.split('.'); |
|
14677 | var db = parts.shift(); |
|
14678 | var collection = parts.join('.'); |
|
14679 | // Upgrade to DBRef type |
|
14680 | object[name] = new DBRef(collection, oid, db); |
|
14681 | } else { |
|
14682 | throw new Error("Detected unknown BSON type " + elementType.toString(16) + " for fieldname \"" + name + "\", are you using the latest BSON parser"); |
|
14683 | } |
|
14684 | } |
|
14685 | ||
14686 | // Check if the deserialization was against a valid array/object |
|
14687 | if (size != index - startIndex) { |
|
14688 | if (isArray) throw new Error('corrupt array bson'); |
|
14689 | throw new Error('corrupt object bson'); |
|
14690 | } |
|
14691 | ||
14692 | // Check if we have a db ref object |
|
14693 | if (object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']); |
|
14694 | return object; |
|
14695 | }; |
|
14696 | ||
14697 | /** |
|
14698 | * Ensure eval is isolated. |
@@ 39-429 (lines=391) @@ | ||
36 | return deserializeObject(buffer, index, options, isArray); |
|
37 | } |
|
38 | ||
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']; |
|
44 | ||
45 | // Return raw bson buffer instead of parsing it |
|
46 | var raw = options['raw'] == null ? false : options['raw']; |
|
47 | ||
48 | // Return BSONRegExp objects instead of native regular expressions |
|
49 | var bsonRegExp = typeof options['bsonRegExp'] == 'boolean' ? options['bsonRegExp'] : false; |
|
50 | ||
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']; |
|
55 | ||
56 | // Set the start index |
|
57 | var startIndex = index; |
|
58 | ||
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"); |
|
61 | ||
62 | // Read the document size |
|
63 | var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
|
64 | ||
65 | // Ensure buffer is valid size |
|
66 | if(size < 5 || size > buffer.length) throw new Error("corrupt bson message"); |
|
67 | ||
68 | // Create holding object |
|
69 | var object = isArray ? [] : {}; |
|
70 | // Used for arrays to skip having to perform utf8 decoding |
|
71 | var arrayIndex = 0; |
|
72 | ||
73 | // While we have more left data left keep parsing |
|
74 | while(true) { |
|
75 | // Read the type |
|
76 | var elementType = buffer[index++]; |
|
77 | // If we get a zero it's the last byte, exit |
|
78 | if(elementType == 0) { |
|
79 | break; |
|
80 | } |
|
81 | ||
82 | // Get the start search index |
|
83 | var i = index; |
|
84 | // Locate the end of the c string |
|
85 | while(buffer[i] !== 0x00 && i < buffer.length) { |
|
86 | i++ |
|
87 | } |
|
88 | ||
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); |
|
92 | ||
93 | index = i + 1; |
|
94 | ||
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); |
|
104 | index = index + 12; |
|
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)); |
|
111 | index = index + 8; |
|
112 | } else if(elementType == BSON.BSON_DATA_NUMBER) { |
|
113 | object[name] = buffer.readDoubleLE(index); |
|
114 | index = index + 8; |
|
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) { |
|
123 | var _index = index; |
|
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"); |
|
126 | ||
127 | // We have a raw value |
|
128 | if(raw) { |
|
129 | object[name] = buffer.slice(index, index + objectSize); |
|
130 | } else { |
|
131 | object[name] = deserializeObject(buffer, _index, options, false); |
|
132 | } |
|
133 | ||
134 | index = index + objectSize; |
|
135 | } else if(elementType == BSON.BSON_DATA_ARRAY) { |
|
136 | var _index = index; |
|
137 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
|
138 | var arrayOptions = options; |
|
139 | ||
140 | // Stop index |
|
141 | var stopIndex = index + objectSize; |
|
142 | ||
143 | // All elements of array to be returned as raw bson |
|
144 | if(fieldsAsRaw && fieldsAsRaw[name]) { |
|
145 | arrayOptions = {}; |
|
146 | for(var n in options) arrayOptions[n] = options[n]; |
|
147 | arrayOptions['raw'] = true; |
|
148 | } |
|
149 | ||
150 | object[name] = deserializeObject(buffer, _index, arrayOptions, true); |
|
151 | index = index + objectSize; |
|
152 | ||
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) { |
|
158 | object[name] = 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; |
|
167 | } else { |
|
168 | object[name] = long; |
|
169 | } |
|
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); |
|
175 | // Update index |
|
176 | 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++]; |
|
185 | ||
186 | // Did we have a negative binary size, throw |
|
187 | if(binarySize < 0) throw new Error('Negative binary type element size found'); |
|
188 | ||
189 | // Is the length longer than the document |
|
190 | if(binarySize > buffer.length) throw new Error('Binary type size larger than document size'); |
|
191 | ||
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'); |
|
200 | } |
|
201 | ||
202 | if(promoteBuffers && promoteValues) { |
|
203 | object[name] = buffer.slice(index, index + binarySize); |
|
204 | } else { |
|
205 | object[name] = new Binary(buffer.slice(index, index + binarySize), subType); |
|
206 | } |
|
207 | } else { |
|
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'); |
|
215 | } |
|
216 | ||
217 | // Copy the data |
|
218 | for(var i = 0; i < binarySize; i++) { |
|
219 | _buffer[i] = buffer[index + i]; |
|
220 | } |
|
221 | ||
222 | if(promoteBuffers && promoteValues) { |
|
223 | object[name] = _buffer; |
|
224 | } else { |
|
225 | object[name] = new Binary(_buffer, subType); |
|
226 | } |
|
227 | } |
|
228 | ||
229 | // Update the index |
|
230 | index = index + binarySize; |
|
231 | } else if(elementType == BSON.BSON_DATA_REGEXP && bsonRegExp == false) { |
|
232 | // Get the start search index |
|
233 | var i = index; |
|
234 | // Locate the end of the c string |
|
235 | while(buffer[i] !== 0x00 && i < buffer.length) { |
|
236 | i++ |
|
237 | } |
|
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); |
|
242 | // Create the regexp |
|
243 | index = i + 1; |
|
244 | ||
245 | // Get the start search index |
|
246 | var i = index; |
|
247 | // Locate the end of the c string |
|
248 | while(buffer[i] !== 0x00 && i < buffer.length) { |
|
249 | i++ |
|
250 | } |
|
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); |
|
255 | index = i + 1; |
|
256 | ||
257 | // For each option add the corresponding one for javascript |
|
258 | var optionsArray = new Array(regExpOptions.length); |
|
259 | ||
260 | // Parse options |
|
261 | for(var i = 0; i < regExpOptions.length; i++) { |
|
262 | switch(regExpOptions[i]) { |
|
263 | case 'm': |
|
264 | optionsArray[i] = 'm'; |
|
265 | break; |
|
266 | case 's': |
|
267 | optionsArray[i] = 'g'; |
|
268 | break; |
|
269 | case 'i': |
|
270 | optionsArray[i] = 'i'; |
|
271 | break; |
|
272 | } |
|
273 | } |
|
274 | ||
275 | object[name] = new RegExp(source, optionsArray.join('')); |
|
276 | } else if(elementType == BSON.BSON_DATA_REGEXP && bsonRegExp == true) { |
|
277 | // Get the start search index |
|
278 | var i = index; |
|
279 | // Locate the end of the c string |
|
280 | while(buffer[i] !== 0x00 && i < buffer.length) { |
|
281 | i++ |
|
282 | } |
|
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); |
|
287 | index = i + 1; |
|
288 | ||
289 | // Get the start search index |
|
290 | var i = index; |
|
291 | // Locate the end of the c string |
|
292 | while(buffer[i] !== 0x00 && i < buffer.length) { |
|
293 | i++ |
|
294 | } |
|
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); |
|
299 | index = i + 1; |
|
300 | ||
301 | // Set the object |
|
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); |
|
320 | ||
321 | // If we are evaluating the functions |
|
322 | if(evalFunctions) { |
|
323 | var value = null; |
|
324 | // If we have cache enabled let's look for the md5 of the function in the cache |
|
325 | if(cacheFunctions) { |
|
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); |
|
329 | } else { |
|
330 | object[name] = isolateEval(functionString); |
|
331 | } |
|
332 | } else { |
|
333 | object[name] = new Code(functionString); |
|
334 | } |
|
335 | ||
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; |
|
340 | ||
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"); |
|
344 | } |
|
345 | ||
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"); |
|
350 | ||
351 | // Javascript function |
|
352 | var functionString = buffer.toString('utf8', index, index + stringSize - 1); |
|
353 | // Update parse index position |
|
354 | index = index + stringSize; |
|
355 | // Parse the element |
|
356 | var _index = index; |
|
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); |
|
361 | // Adjust the index |
|
362 | index = index + objectSize; |
|
363 | ||
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'); |
|
367 | } |
|
368 | ||
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'); |
|
372 | } |
|
373 | ||
374 | // If we are evaluating the functions |
|
375 | if(evalFunctions) { |
|
376 | // Contains the value we are going to set |
|
377 | var value = null; |
|
378 | // If we have cache enabled let's look for the md5 of the function in the cache |
|
379 | if(cacheFunctions) { |
|
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); |
|
383 | } else { |
|
384 | object[name] = isolateEval(functionString); |
|
385 | } |
|
386 | ||
387 | object[name].scope = scopeObject; |
|
388 | } else { |
|
389 | object[name] = new Code(functionString, scopeObject); |
|
390 | } |
|
391 | } else if(elementType == BSON.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"); |
|
396 | // Namespace |
|
397 | var namespace = buffer.toString('utf8', index, index + stringSize - 1); |
|
398 | // Update parse index position |
|
399 | index = index + stringSize; |
|
400 | ||
401 | // Read the oid |
|
402 | var oidBuffer = new Buffer(12); |
|
403 | buffer.copy(oidBuffer, 0, index, index + 12); |
|
404 | var oid = new ObjectID(oidBuffer); |
|
405 | ||
406 | // Update the index |
|
407 | index = index + 12; |
|
408 | ||
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); |
|
415 | } else { |
|
416 | throw new Error("Detected unknown BSON type " + elementType.toString(16) + " for fieldname \"" + name + "\", are you using the latest BSON parser"); |
|
417 | } |
|
418 | } |
|
419 | ||
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'); |
|
424 | } |
|
425 | ||
426 | // Check if we have a db ref object |
|
427 | if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']); |
|
428 | return object; |
|
429 | } |
|
430 | ||
431 | /** |
|
432 | * Ensure eval is isolated. |