"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.deserialize = void 0; var buffer_1 = require("buffer"); var binary_1 = require("../binary"); var code_1 = require("../code"); var constants = require("../constants"); var db_ref_1 = require("../db_ref"); var decimal128_1 = require("../decimal128"); var double_1 = require("../double"); var error_1 = require("../error"); var int_32_1 = require("../int_32"); var long_1 = require("../long"); var max_key_1 = require("../max_key"); var min_key_1 = require("../min_key"); var objectid_1 = require("../objectid"); var regexp_1 = require("../regexp"); var symbol_1 = require("../symbol"); var timestamp_1 = require("../timestamp"); var validate_utf8_1 = require("../validate_utf8"); // Internal long versions var JS_INT_MAX_LONG = long_1.Long.fromNumber(constants.JS_INT_MAX); var JS_INT_MIN_LONG = long_1.Long.fromNumber(constants.JS_INT_MIN); var functionCache = {}; function deserialize(buffer, options, isArray) { options = options == null ? {} : options; var index = options && options.index ? options.index : 0; // Read the document size var size = buffer[index] | (buffer[index + 1] << 8) | (buffer[index + 2] << 16) | (buffer[index + 3] << 24); if (size < 5) { throw new error_1.BSONError("bson size must be >= 5, is " + size); } if (options.allowObjectSmallerThanBufferSize && buffer.length < size) { throw new error_1.BSONError("buffer length " + buffer.length + " must be >= bson size " + size); } if (!options.allowObjectSmallerThanBufferSize && buffer.length !== size) { throw new error_1.BSONError("buffer length " + buffer.length + " must === bson size " + size); } if (size + index > buffer.byteLength) { throw new error_1.BSONError("(bson size " + size + " + options.index " + index + " must be <= buffer length " + buffer.byteLength + ")"); } // Illegal end value if (buffer[index + size - 1] !== 0) { throw new error_1.BSONError("One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00"); } // Start deserializtion return deserializeObject(buffer, index, options, isArray); } exports.deserialize = deserialize; var allowedDBRefKeys = /^\$ref$|^\$id$|^\$db$/; function deserializeObject(buffer, index, options, isArray) { if (isArray === void 0) { isArray = false; } var evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions']; var cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions']; var fieldsAsRaw = options['fieldsAsRaw'] == null ? null : options['fieldsAsRaw']; // Return raw bson buffer instead of parsing it var raw = options['raw'] == null ? false : options['raw']; // Return BSONRegExp objects instead of native regular expressions var bsonRegExp = typeof options['bsonRegExp'] === 'boolean' ? options['bsonRegExp'] : false; // Controls the promotion of values vs wrapper classes var promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers']; var promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs']; var promoteValues = options['promoteValues'] == null ? true : options['promoteValues']; // Ensures default validation option if none given var validation = options.validation == null ? { utf8: true } : options.validation; // Shows if global utf-8 validation is enabled or disabled var globalUTFValidation = true; // Reflects utf-8 validation setting regardless of global or specific key validation var validationSetting; // Set of keys either to enable or disable validation on var utf8KeysSet = new Set(); // Check for boolean uniformity and empty validation option var utf8ValidatedKeys = validation.utf8; if (typeof utf8ValidatedKeys === 'boolean') { validationSetting = utf8ValidatedKeys; } else { globalUTFValidation = false; var utf8ValidationValues = Object.keys(utf8ValidatedKeys).map(function (key) { return utf8ValidatedKeys[key]; }); if (utf8ValidationValues.length === 0) { throw new error_1.BSONError('UTF-8 validation setting cannot be empty'); } if (typeof utf8ValidationValues[0] !== 'boolean') { throw new error_1.BSONError('Invalid UTF-8 validation option, must specify boolean values'); } validationSetting = utf8ValidationValues[0]; // Ensures boolean uniformity in utf-8 validation (all true or all false) if (!utf8ValidationValues.every(function (item) { return item === validationSetting; })) { throw new error_1.BSONError('Invalid UTF-8 validation option - keys must be all true or all false'); } } // Add keys to set that will either be validated or not based on validationSetting if (!globalUTFValidation) { for (var _i = 0, _a = Object.keys(utf8ValidatedKeys); _i < _a.length; _i++) { var key = _a[_i]; utf8KeysSet.add(key); } } // Set the start index var startIndex = index; // Validate that we have at least 4 bytes of buffer if (buffer.length < 5) throw new error_1.BSONError('corrupt bson message < 5 bytes long'); // 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_1.BSONError('corrupt bson message'); // Create holding object var object = isArray ? [] : {}; // Used for arrays to skip having to perform utf8 decoding var arrayIndex = 0; var done = false; var isPossibleDBRef = isArray ? false : null; // While we have more left data left keep parsing while (!done) { // Read the type var elementType = buffer[index++]; // If we get a zero it's the last byte, exit if (elementType === 0) break; // 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.byteLength) throw new error_1.BSONError('Bad BSON Document: illegal CString'); // Represents the key var name = isArray ? arrayIndex++ : buffer.toString('utf8', index, i); // shouldValidateKey is true if the key should be validated, false otherwise var shouldValidateKey = true; if (globalUTFValidation || utf8KeysSet.has(name)) { shouldValidateKey = validationSetting; } else { shouldValidateKey = !validationSetting; } if (isPossibleDBRef !== false && name[0] === '$') { isPossibleDBRef = allowedDBRefKeys.test(name); } var value = void 0; index = i + 1; if (elementType === constants.BSON_DATA_STRING) { 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_1.BSONError('bad string length in bson'); } value = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey); index = index + stringSize; } else if (elementType === constants.BSON_DATA_OID) { var oid = buffer_1.Buffer.alloc(12); buffer.copy(oid, 0, index, index + 12); value = new objectid_1.ObjectId(oid); index = index + 12; } else if (elementType === constants.BSON_DATA_INT && promoteValues === false) { value = new int_32_1.Int32(buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24)); } else if (elementType === constants.BSON_DATA_INT) { value = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); } else if (elementType === constants.BSON_DATA_NUMBER && promoteValues === false) { value = new double_1.Double(buffer.readDoubleLE(index)); index = index + 8; } else if (elementType === constants.BSON_DATA_NUMBER) { value = buffer.readDoubleLE(index); index = index + 8; } else if (elementType === constants.BSON_DATA_DATE) { 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); value = new Date(new long_1.Long(lowBits, highBits).toNumber()); } else if (elementType === constants.BSON_DATA_BOOLEAN) { if (buffer[index] !== 0 && buffer[index] !== 1) throw new error_1.BSONError('illegal boolean type value'); value = buffer[index++] === 1; } else if (elementType === constants.BSON_DATA_OBJECT) { var _index = index; var objectSize = buffer[index] | (buffer[index + 1] << 8) | (buffer[index + 2] << 16) | (buffer[index + 3] << 24); if (objectSize <= 0 || objectSize > buffer.length - index) throw new error_1.BSONError('bad embedded document length in bson'); // We have a raw value if (raw) { value = buffer.slice(index, index + objectSize); } else { var objectOptions = options; if (!globalUTFValidation) { objectOptions = __assign(__assign({}, options), { validation: { utf8: shouldValidateKey } }); } value = deserializeObject(buffer, _index, objectOptions, false); } index = index + objectSize; } else if (elementType === constants.BSON_DATA_ARRAY) { var _index = index; var objectSize = buffer[index] | (buffer[index + 1] << 8) | (buffer[index + 2] << 16) | (buffer[index + 3] << 24); var arrayOptions = options; // Stop index var stopIndex = index + objectSize; // All elements of array to be returned as raw bson if (fieldsAsRaw && fieldsAsRaw[name]) { arrayOptions = {}; for (var n in options) { arrayOptions[n] = options[n]; } arrayOptions['raw'] = true; } if (!globalUTFValidation) { arrayOptions = __assign(__assign({}, arrayOptions), { validation: { utf8: shouldValidateKey } }); } value = deserializeObject(buffer, _index, arrayOptions, true); index = index + objectSize; if (buffer[index - 1] !== 0) throw new error_1.BSONError('invalid array terminator byte'); if (index !== stopIndex) throw new error_1.BSONError('corrupted array bson'); } else if (elementType === constants.BSON_DATA_UNDEFINED) { value = undefined; } else if (elementType === constants.BSON_DATA_NULL) { value = null; } else if (elementType === constants.BSON_DATA_LONG) { // Unpack the low and high bits 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); var long = new long_1.Long(lowBits, highBits); // Promote the long if possible if (promoteLongs && promoteValues === true) { value = long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG) ? long.toNumber() : long; } else { value = long; } } else if (elementType === constants.BSON_DATA_DECIMAL128) { // Buffer to contain the decimal bytes var bytes = buffer_1.Buffer.alloc(16); // Copy the next 16 bytes into the bytes buffer buffer.copy(bytes, 0, index, index + 16); // Update index index = index + 16; // Assign the new Decimal128 value var decimal128 = new decimal128_1.Decimal128(bytes); // If we have an alternative mapper use that if ('toObject' in decimal128 && typeof decimal128.toObject === 'function') { value = decimal128.toObject(); } else { value = decimal128; } } else if (elementType === constants.BSON_DATA_BINARY) { var binarySize = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); var totalBinarySize = binarySize; var subType = buffer[index++]; // Did we have a negative binary size, throw if (binarySize < 0) throw new error_1.BSONError('Negative binary type element size found'); // Is the length longer than the document if (binarySize > buffer.byteLength) throw new error_1.BSONError('Binary type size larger than document size'); // Decode as raw Buffer object if options specifies it if (buffer['slice'] != null) { // If we have subtype 2 skip the 4 bytes for the size if (subType === binary_1.Binary.SUBTYPE_BYTE_ARRAY) { binarySize = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); if (binarySize < 0) throw new error_1.BSONError('Negative binary type element size found for subtype 0x02'); if (binarySize > totalBinarySize - 4) throw new error_1.BSONError('Binary type with subtype 0x02 contains too long binary size'); if (binarySize < totalBinarySize - 4) throw new error_1.BSONError('Binary type with subtype 0x02 contains too short binary size'); } if (promoteBuffers && promoteValues) { value = buffer.slice(index, index + binarySize); } else { value = new binary_1.Binary(buffer.slice(index, index + binarySize), subType); } } else { var _buffer = buffer_1.Buffer.alloc(binarySize); // If we have subtype 2 skip the 4 bytes for the size if (subType === binary_1.Binary.SUBTYPE_BYTE_ARRAY) { binarySize = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); if (binarySize < 0) throw new error_1.BSONError('Negative binary type element size found for subtype 0x02'); if (binarySize > totalBinarySize - 4) throw new error_1.BSONError('Binary type with subtype 0x02 contains too long binary size'); if (binarySize < totalBinarySize - 4) throw new error_1.BSONError('Binary type with subtype 0x02 contains too short binary size'); } // Copy the data for (i = 0; i < binarySize; i++) { _buffer[i] = buffer[index + i]; } if (promoteBuffers && promoteValues) { value = _buffer; } else { value = new binary_1.Binary(_buffer, subType); } } // Update the index index = index + binarySize; } else if (elementType === constants.BSON_DATA_REGEXP && bsonRegExp === false) { // Get the start search index 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_1.BSONError('Bad BSON Document: illegal CString'); // Return the C string var source = buffer.toString('utf8', index, i); // Create the regexp index = i + 1; // Get the start search index 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_1.BSONError('Bad BSON Document: illegal CString'); // Return the C string var regExpOptions = buffer.toString('utf8', index, i); index = i + 1; // For each option add the corresponding one for javascript var optionsArray = new Array(regExpOptions.length); // Parse options for (i = 0; i < regExpOptions.length; i++) { switch (regExpOptions[i]) { case 'm': optionsArray[i] = 'm'; break; case 's': optionsArray[i] = 'g'; break; case 'i': optionsArray[i] = 'i'; break; } } value = new RegExp(source, optionsArray.join('')); } else if (elementType === constants.BSON_DATA_REGEXP && bsonRegExp === true) { // Get the start search index 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_1.BSONError('Bad BSON Document: illegal CString'); // Return the C string var source = buffer.toString('utf8', index, i); index = i + 1; // Get the start search index 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_1.BSONError('Bad BSON Document: illegal CString'); // Return the C string var regExpOptions = buffer.toString('utf8', index, i); index = i + 1; // Set the object value = new regexp_1.BSONRegExp(source, regExpOptions); } else if (elementType === constants.BSON_DATA_SYMBOL) { 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_1.BSONError('bad string length in bson'); } var symbol = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey); value = promoteValues ? symbol : new symbol_1.BSONSymbol(symbol); index = index + stringSize; } else if (elementType === constants.BSON_DATA_TIMESTAMP) { 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); value = new timestamp_1.Timestamp(lowBits, highBits); } else if (elementType === constants.BSON_DATA_MIN_KEY) { value = new min_key_1.MinKey(); } else if (elementType === constants.BSON_DATA_MAX_KEY) { value = new max_key_1.MaxKey(); } else if (elementType === constants.BSON_DATA_CODE) { 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_1.BSONError('bad string length in bson'); } var functionString = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey); // If we are evaluating the functions if (evalFunctions) { // If we have cache enabled let's look for the md5 of the function in the cache if (cacheFunctions) { // Got to do this to avoid V8 deoptimizing the call due to finding eval value = isolateEval(functionString, functionCache, object); } else { value = isolateEval(functionString); } } else { value = new code_1.Code(functionString); } // Update parse index position index = index + stringSize; } else if (elementType === constants.BSON_DATA_CODE_W_SCOPE) { var totalSize = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); // Element cannot be shorter than totalSize + stringSize + documentSize + terminator if (totalSize < 4 + 4 + 4 + 1) { throw new error_1.BSONError('code_w_scope total size shorter minimum expected length'); } // Get the code string size var stringSize = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); // Check if we have a valid string if (stringSize <= 0 || stringSize > buffer.length - index || buffer[index + stringSize - 1] !== 0) { throw new error_1.BSONError('bad string length in bson'); } // Javascript function var functionString = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey); // Update parse index position index = index + stringSize; // Parse the element var _index = index; // Decode the size of the object document var objectSize = buffer[index] | (buffer[index + 1] << 8) | (buffer[index + 2] << 16) | (buffer[index + 3] << 24); // Decode the scope object var scopeObject = deserializeObject(buffer, _index, options, false); // Adjust the index index = index + objectSize; // Check if field length is too short if (totalSize < 4 + 4 + objectSize + stringSize) { throw new error_1.BSONError('code_w_scope total size is too short, truncating scope'); } // Check if totalSize field is too long if (totalSize > 4 + 4 + objectSize + stringSize) { throw new error_1.BSONError('code_w_scope total size is too long, clips outer document'); } // If we are evaluating the functions if (evalFunctions) { // If we have cache enabled let's look for the md5 of the function in the cache if (cacheFunctions) { // Got to do this to avoid V8 deoptimizing the call due to finding eval value = isolateEval(functionString, functionCache, object); } else { value = isolateEval(functionString); } value.scope = scopeObject; } else { value = new code_1.Code(functionString, scopeObject); } } else if (elementType === constants.BSON_DATA_DBPOINTER) { // Get the code string size var stringSize = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); // Check if we have a valid string if (stringSize <= 0 || stringSize > buffer.length - index || buffer[index + stringSize - 1] !== 0) throw new error_1.BSONError('bad string length in bson'); // Namespace if (validation != null && validation.utf8) { if (!validate_utf8_1.validateUtf8(buffer, index, index + stringSize - 1)) { throw new error_1.BSONError('Invalid UTF-8 string in BSON document'); } } var namespace = buffer.toString('utf8', index, index + stringSize - 1); // Update parse index position index = index + stringSize; // Read the oid var oidBuffer = buffer_1.Buffer.alloc(12); buffer.copy(oidBuffer, 0, index, index + 12); var oid = new objectid_1.ObjectId(oidBuffer); // Update the index index = index + 12; // Upgrade to DBRef type value = new db_ref_1.DBRef(namespace, oid); } else { throw new error_1.BSONError('Detected unknown BSON type ' + elementType.toString(16) + ' for fieldname "' + name + '"'); } if (name === '__proto__') { Object.defineProperty(object, name, { value: value, writable: true, enumerable: true, configurable: true }); } else { object[name] = value; } } // Check if the deserialization was against a valid array/object if (size !== index - startIndex) { if (isArray) throw new error_1.BSONError('corrupt array bson'); throw new error_1.BSONError('corrupt object bson'); } // if we did not find "$ref", "$id", "$db", or found an extraneous $key, don't make a DBRef if (!isPossibleDBRef) return object; if (db_ref_1.isDBRefLike(object)) { var copy = Object.assign({}, object); delete copy.$ref; delete copy.$id; delete copy.$db; return new db_ref_1.DBRef(object.$ref, object.$id, object.$db, copy); } return object; } /** * Ensure eval is isolated, store the result in functionCache. * * @internal */ function isolateEval(functionString, functionCache, object) { if (!functionCache) return new Function(functionString); // Check for cache hit, eval if missing and return cached function if (functionCache[functionString] == null) { functionCache[functionString] = new Function(functionString); } // Set the object return functionCache[functionString].bind(object); } function getValidatedString(buffer, start, end, shouldValidateUtf8) { var value = buffer.toString('utf8', start, end); // if utf8 validation is on, do the check if (shouldValidateUtf8) { for (var i = 0; i < value.length; i++) { if (value.charCodeAt(i) === 0xfffd) { if (!validate_utf8_1.validateUtf8(buffer, start, end)) { throw new error_1.BSONError('Invalid UTF-8 string in BSON document'); } break; } } } return value; } //# sourceMappingURL=deserializer.js.map