"use strict" var writeIEEE754 = require('../float_parser').writeIEEE754, readIEEE754 = require('../float_parser').readIEEE754, f = require('util').format, Long = require('../long').Long, Double = require('../double').Double, Timestamp = require('../timestamp').Timestamp, ObjectID = require('../objectid').ObjectID, Symbol = require('../symbol').Symbol, Code = require('../code').Code, MinKey = require('../min_key').MinKey, MaxKey = require('../max_key').MaxKey, DBRef = require('../db_ref').DBRef, BSONRegExp = require('../regexp').BSONRegExp, Binary = require('../binary').Binary; var BSON = {}; /** * Contains the function cache if we have that enable to allow for avoiding the eval step on each deserialization, comparison is by md5 * * @ignore * @api private */ var functionCache = BSON.functionCache = {}; /** * Number BSON Type * * @classconstant BSON_DATA_NUMBER **/ BSON.BSON_DATA_NUMBER = 1; /** * String BSON Type * * @classconstant BSON_DATA_STRING **/ BSON.BSON_DATA_STRING = 2; /** * Object BSON Type * * @classconstant BSON_DATA_OBJECT **/ BSON.BSON_DATA_OBJECT = 3; /** * Array BSON Type * * @classconstant BSON_DATA_ARRAY **/ BSON.BSON_DATA_ARRAY = 4; /** * Binary BSON Type * * @classconstant BSON_DATA_BINARY **/ BSON.BSON_DATA_BINARY = 5; /** * ObjectID BSON Type * * @classconstant BSON_DATA_OID **/ BSON.BSON_DATA_OID = 7; /** * Boolean BSON Type * * @classconstant BSON_DATA_BOOLEAN **/ BSON.BSON_DATA_BOOLEAN = 8; /** * Date BSON Type * * @classconstant BSON_DATA_DATE **/ BSON.BSON_DATA_DATE = 9; /** * null BSON Type * * @classconstant BSON_DATA_NULL **/ BSON.BSON_DATA_NULL = 10; /** * RegExp BSON Type * * @classconstant BSON_DATA_REGEXP **/ BSON.BSON_DATA_REGEXP = 11; /** * Code BSON Type * * @classconstant BSON_DATA_CODE **/ BSON.BSON_DATA_CODE = 13; /** * Symbol BSON Type * * @classconstant BSON_DATA_SYMBOL **/ BSON.BSON_DATA_SYMBOL = 14; /** * Code with Scope BSON Type * * @classconstant BSON_DATA_CODE_W_SCOPE **/ BSON.BSON_DATA_CODE_W_SCOPE = 15; /** * 32 bit Integer BSON Type * * @classconstant BSON_DATA_INT **/ BSON.BSON_DATA_INT = 16; /** * Timestamp BSON Type * * @classconstant BSON_DATA_TIMESTAMP **/ BSON.BSON_DATA_TIMESTAMP = 17; /** * Long BSON Type * * @classconstant BSON_DATA_LONG **/ BSON.BSON_DATA_LONG = 18; /** * MinKey BSON Type * * @classconstant BSON_DATA_MIN_KEY **/ BSON.BSON_DATA_MIN_KEY = 0xff; /** * MaxKey BSON Type * * @classconstant BSON_DATA_MAX_KEY **/ BSON.BSON_DATA_MAX_KEY = 0x7f; /** * Binary Default Type * * @classconstant BSON_BINARY_SUBTYPE_DEFAULT **/ BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0; /** * Binary Function Type * * @classconstant BSON_BINARY_SUBTYPE_FUNCTION **/ BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1; /** * Binary Byte Array Type * * @classconstant BSON_BINARY_SUBTYPE_BYTE_ARRAY **/ BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; /** * Binary UUID Type * * @classconstant BSON_BINARY_SUBTYPE_UUID **/ BSON.BSON_BINARY_SUBTYPE_UUID = 3; /** * Binary MD5 Type * * @classconstant BSON_BINARY_SUBTYPE_MD5 **/ BSON.BSON_BINARY_SUBTYPE_MD5 = 4; /** * Binary User Defined Type * * @classconstant BSON_BINARY_SUBTYPE_USER_DEFINED **/ BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; // BSON MAX VALUES BSON.BSON_INT32_MAX = 0x7FFFFFFF; BSON.BSON_INT32_MIN = -0x80000000; BSON.BSON_INT64_MAX = Math.pow(2, 63) - 1; BSON.BSON_INT64_MIN = -Math.pow(2, 63); // JS MAX PRECISE VALUES BSON.JS_INT_MAX = 0x20000000000000; // Any integer up to 2^53 can be precisely represented by a double. BSON.JS_INT_MIN = -0x20000000000000; // Any integer down to -2^53 can be precisely represented by a double. // Internal long versions var JS_INT_MAX_LONG = Long.fromNumber(0x20000000000000); // Any integer up to 2^53 can be precisely represented by a double. var JS_INT_MIN_LONG = Long.fromNumber(-0x20000000000000); // Any integer down to -2^53 can be precisely represented by a double. var deserialize = function(buffer, options, isArray) { var index = 0; // Read the document size var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; // Ensure buffer is valid size if(size < 5 || buffer.length < size) { throw new Error("corrupt bson message"); } // Illegal end value if(buffer[size - 1] != 0) { throw new Error("One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00"); } // Start deserializtion return deserializeObject(buffer, options, isArray); } // // Reads in a C style string // var readCStyleStringSpecial = function(buffer, index) { // // Get the start search index // var i = index; // // Locate the end of the c string // while(buffer[i] !== 0x00 && i < buffer.length) { // i++ // } // // If are at the end of the buffer there is a problem with the document // if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString") // // Grab utf8 encoded string // var string = buffer.toString('utf8', index, i); // // Update index position // index = i + 1; // // Return string // return {s: string, i: index}; // } // Reads in a C style string var readCStyleStringSpecial = function(buffer, index) { // Get the start search index var i = index; // Locate the end of the c string while(buffer[i] !== 0x00 && i < buffer.length) { i++ } // If are at the end of the buffer there is a problem with the document if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString") // Grab utf8 encoded string return buffer.toString('utf8', index, i); } var DeserializationMethods = {} DeserializationMethods[BSON.BSON_DATA_OID] = function(name, object, buffer, index) { var string = buffer.toString('binary', index, index + 12); object[name] = new ObjectID(string); return index + 12; } DeserializationMethods[BSON.BSON_DATA_NUMBER] = function(name, object, buffer, index) { object[name] = buffer.readDoubleLE(index); return index + 8; } DeserializationMethods[BSON.BSON_DATA_INT] = function(name, object, buffer, index) { object[name] = buffer.readInt32LE(index); return index + 4; } DeserializationMethods[BSON.BSON_DATA_TIMESTAMP] = function(name, object, buffer, index) { var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; object[name] = new Timestamp(lowBits, highBits); return index; } DeserializationMethods[BSON.BSON_DATA_STRING] = function(name, object, buffer, index) { var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; if(stringSize <= 0 || stringSize > (buffer.length - index) || buffer[index + stringSize - 1] != 0) throw new Error("bad string length in bson"); object[name] = buffer.toString('utf8', index, index + stringSize - 1); return index + stringSize; } DeserializationMethods[BSON.BSON_DATA_BOOLEAN] = function(name, object, buffer, index) { object[name] = buffer[index++] == 1; return index; } var deserializeObject = function(buffer, options, isArray) { // Options options = options == null ? {} : options; var evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions']; var cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions']; var cacheFunctionsCrc32 = options['cacheFunctionsCrc32'] == null ? false : options['cacheFunctionsCrc32']; var promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs']; var fieldsAsRaw = options['fieldsAsRaw'] == null ? {} : options['fieldsAsRaw']; // Return BSONRegExp objects instead of native regular expressions var bsonRegExp = typeof options['bsonRegExp'] == 'boolean' ? options['bsonRegExp'] : false; var promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers']; // Validate that we have at least 4 bytes of buffer if(buffer.length < 5) throw new Error("corrupt bson message < 5 bytes long"); // Set up index var index = typeof options['index'] == 'number' ? options['index'] : 0; // Read the document size var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; // Ensure buffer is valid size if(size < 5 || size > buffer.length) throw new Error("corrupt bson message"); // Create holding object var object = isArray ? [] : {}; // While we have more left data left keep parsing while(true) { // Read the type var elementType = buffer[index++]; // If we get a zero it's the last byte, exit if(elementType == 0) break; var name = readCStyleStringSpecial(buffer, index); index = index + name.length + 1; // console.log("----------- 0 " + elementType + " :: " + name) index = DeserializationMethods[elementType](name, object, buffer, index); // console.log('--------------- 1') } // Check if we have a db ref object if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']); // Return the final objects return object; } /** * Ensure eval is isolated. * * @ignore * @api private */ var isolateEvalWithHash = function(functionCache, hash, functionString, object) { // Contains the value we are going to set var value = null; // Check for cache hit, eval if missing and return cached function if(functionCache[hash] == null) { eval("value = " + functionString); functionCache[hash] = value; } // Set the object return functionCache[hash].bind(object); } /** * Ensure eval is isolated. * * @ignore * @api private */ var isolateEval = function(functionString) { // Contains the value we are going to set var value = null; // Eval the function eval("value = " + functionString); return value; } module.exports = deserialize