|
|
import { Binary, UUID } from '../binary'; |
|
|
import type { Document } from '../bson'; |
|
|
import { Code } from '../code'; |
|
|
import * as constants from '../constants'; |
|
|
import { DBRef, type DBRefLike, isDBRefLike } from '../db_ref'; |
|
|
import { Decimal128 } from '../decimal128'; |
|
|
import { Double } from '../double'; |
|
|
import { BSONError } from '../error'; |
|
|
import { Int32 } from '../int_32'; |
|
|
import { Long } from '../long'; |
|
|
import { MaxKey } from '../max_key'; |
|
|
import { MinKey } from '../min_key'; |
|
|
import { ObjectId } from '../objectid'; |
|
|
import { BSONRegExp } from '../regexp'; |
|
|
import { BSONSymbol } from '../symbol'; |
|
|
import { Timestamp } from '../timestamp'; |
|
|
import { BSONDataView, ByteUtils } from '../utils/byte_utils'; |
|
|
import { validateUtf8 } from '../validate_utf8'; |
|
|
|
|
|
|
|
|
export interface DeserializeOptions { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useBigInt64?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
promoteLongs?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
promoteBuffers?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
promoteValues?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fieldsAsRaw?: Document; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bsonRegExp?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
allowObjectSmallerThanBufferSize?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index?: number; |
|
|
|
|
|
raw?: boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
validation?: { utf8: boolean | Record<string, true> | Record<string, false> }; |
|
|
} |
|
|
|
|
|
|
|
|
const JS_INT_MAX_LONG = Long.fromNumber(constants.JS_INT_MAX); |
|
|
const JS_INT_MIN_LONG = Long.fromNumber(constants.JS_INT_MIN); |
|
|
|
|
|
export function internalDeserialize( |
|
|
buffer: Uint8Array, |
|
|
options: DeserializeOptions, |
|
|
isArray?: boolean |
|
|
): Document { |
|
|
options = options == null ? {} : options; |
|
|
const index = options && options.index ? options.index : 0; |
|
|
|
|
|
const size = |
|
|
buffer[index] | |
|
|
(buffer[index + 1] << 8) | |
|
|
(buffer[index + 2] << 16) | |
|
|
(buffer[index + 3] << 24); |
|
|
|
|
|
if (size < 5) { |
|
|
throw new BSONError(`bson size must be >= 5, is ${size}`); |
|
|
} |
|
|
|
|
|
if (options.allowObjectSmallerThanBufferSize && buffer.length < size) { |
|
|
throw new BSONError(`buffer length ${buffer.length} must be >= bson size ${size}`); |
|
|
} |
|
|
|
|
|
if (!options.allowObjectSmallerThanBufferSize && buffer.length !== size) { |
|
|
throw new BSONError(`buffer length ${buffer.length} must === bson size ${size}`); |
|
|
} |
|
|
|
|
|
if (size + index > buffer.byteLength) { |
|
|
throw new BSONError( |
|
|
`(bson size ${size} + options.index ${index} must be <= buffer length ${buffer.byteLength})` |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
if (buffer[index + size - 1] !== 0) { |
|
|
throw new BSONError( |
|
|
"One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00" |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
return deserializeObject(buffer, index, options, isArray); |
|
|
} |
|
|
|
|
|
const allowedDBRefKeys = /^\$ref$|^\$id$|^\$db$/; |
|
|
|
|
|
function deserializeObject( |
|
|
buffer: Uint8Array, |
|
|
index: number, |
|
|
options: DeserializeOptions, |
|
|
isArray = false |
|
|
) { |
|
|
const fieldsAsRaw = options['fieldsAsRaw'] == null ? null : options['fieldsAsRaw']; |
|
|
|
|
|
|
|
|
const raw = options['raw'] == null ? false : options['raw']; |
|
|
|
|
|
|
|
|
const bsonRegExp = typeof options['bsonRegExp'] === 'boolean' ? options['bsonRegExp'] : false; |
|
|
|
|
|
|
|
|
const promoteBuffers = options.promoteBuffers ?? false; |
|
|
const promoteLongs = options.promoteLongs ?? true; |
|
|
const promoteValues = options.promoteValues ?? true; |
|
|
const useBigInt64 = options.useBigInt64 ?? false; |
|
|
|
|
|
if (useBigInt64 && !promoteValues) { |
|
|
throw new BSONError('Must either request bigint or Long for int64 deserialization'); |
|
|
} |
|
|
|
|
|
if (useBigInt64 && !promoteLongs) { |
|
|
throw new BSONError('Must either request bigint or Long for int64 deserialization'); |
|
|
} |
|
|
|
|
|
|
|
|
const validation = options.validation == null ? { utf8: true } : options.validation; |
|
|
|
|
|
|
|
|
let globalUTFValidation = true; |
|
|
|
|
|
let validationSetting: boolean; |
|
|
|
|
|
const utf8KeysSet = new Set(); |
|
|
|
|
|
|
|
|
const utf8ValidatedKeys = validation.utf8; |
|
|
if (typeof utf8ValidatedKeys === 'boolean') { |
|
|
validationSetting = utf8ValidatedKeys; |
|
|
} else { |
|
|
globalUTFValidation = false; |
|
|
const utf8ValidationValues = Object.keys(utf8ValidatedKeys).map(function (key) { |
|
|
return utf8ValidatedKeys[key]; |
|
|
}); |
|
|
if (utf8ValidationValues.length === 0) { |
|
|
throw new BSONError('UTF-8 validation setting cannot be empty'); |
|
|
} |
|
|
if (typeof utf8ValidationValues[0] !== 'boolean') { |
|
|
throw new BSONError('Invalid UTF-8 validation option, must specify boolean values'); |
|
|
} |
|
|
validationSetting = utf8ValidationValues[0]; |
|
|
|
|
|
if (!utf8ValidationValues.every(item => item === validationSetting)) { |
|
|
throw new BSONError('Invalid UTF-8 validation option - keys must be all true or all false'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!globalUTFValidation) { |
|
|
for (const key of Object.keys(utf8ValidatedKeys)) { |
|
|
utf8KeysSet.add(key); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const startIndex = index; |
|
|
|
|
|
|
|
|
if (buffer.length < 5) throw new BSONError('corrupt bson message < 5 bytes long'); |
|
|
|
|
|
|
|
|
const size = |
|
|
buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); |
|
|
|
|
|
|
|
|
if (size < 5 || size > buffer.length) throw new BSONError('corrupt bson message'); |
|
|
|
|
|
|
|
|
const object: Document = isArray ? [] : {}; |
|
|
|
|
|
let arrayIndex = 0; |
|
|
const done = false; |
|
|
|
|
|
let isPossibleDBRef = isArray ? false : null; |
|
|
|
|
|
|
|
|
const dataview = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); |
|
|
while (!done) { |
|
|
|
|
|
const elementType = buffer[index++]; |
|
|
|
|
|
|
|
|
if (elementType === 0) break; |
|
|
|
|
|
|
|
|
let i = index; |
|
|
|
|
|
while (buffer[i] !== 0x00 && i < buffer.length) { |
|
|
i++; |
|
|
} |
|
|
|
|
|
|
|
|
if (i >= buffer.byteLength) throw new BSONError('Bad BSON Document: illegal CString'); |
|
|
|
|
|
|
|
|
const name = isArray ? arrayIndex++ : ByteUtils.toUTF8(buffer, index, i); |
|
|
|
|
|
|
|
|
let shouldValidateKey = true; |
|
|
if (globalUTFValidation || utf8KeysSet.has(name)) { |
|
|
shouldValidateKey = validationSetting; |
|
|
} else { |
|
|
shouldValidateKey = !validationSetting; |
|
|
} |
|
|
|
|
|
if (isPossibleDBRef !== false && (name as string)[0] === '$') { |
|
|
isPossibleDBRef = allowedDBRefKeys.test(name as string); |
|
|
} |
|
|
let value; |
|
|
|
|
|
index = i + 1; |
|
|
|
|
|
if (elementType === constants.BSON_DATA_STRING) { |
|
|
const 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 BSONError('bad string length in bson'); |
|
|
} |
|
|
value = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey); |
|
|
index = index + stringSize; |
|
|
} else if (elementType === constants.BSON_DATA_OID) { |
|
|
const oid = ByteUtils.allocate(12); |
|
|
oid.set(buffer.subarray(index, index + 12)); |
|
|
value = new ObjectId(oid); |
|
|
index = index + 12; |
|
|
} else if (elementType === constants.BSON_DATA_INT && promoteValues === false) { |
|
|
value = new 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(dataview.getFloat64(index, true)); |
|
|
index = index + 8; |
|
|
} else if (elementType === constants.BSON_DATA_NUMBER) { |
|
|
value = dataview.getFloat64(index, true); |
|
|
index = index + 8; |
|
|
} else if (elementType === constants.BSON_DATA_DATE) { |
|
|
const lowBits = |
|
|
buffer[index++] | |
|
|
(buffer[index++] << 8) | |
|
|
(buffer[index++] << 16) | |
|
|
(buffer[index++] << 24); |
|
|
const highBits = |
|
|
buffer[index++] | |
|
|
(buffer[index++] << 8) | |
|
|
(buffer[index++] << 16) | |
|
|
(buffer[index++] << 24); |
|
|
value = new Date(new Long(lowBits, highBits).toNumber()); |
|
|
} else if (elementType === constants.BSON_DATA_BOOLEAN) { |
|
|
if (buffer[index] !== 0 && buffer[index] !== 1) |
|
|
throw new BSONError('illegal boolean type value'); |
|
|
value = buffer[index++] === 1; |
|
|
} else if (elementType === constants.BSON_DATA_OBJECT) { |
|
|
const _index = index; |
|
|
const objectSize = |
|
|
buffer[index] | |
|
|
(buffer[index + 1] << 8) | |
|
|
(buffer[index + 2] << 16) | |
|
|
(buffer[index + 3] << 24); |
|
|
if (objectSize <= 0 || objectSize > buffer.length - index) |
|
|
throw new BSONError('bad embedded document length in bson'); |
|
|
|
|
|
|
|
|
if (raw) { |
|
|
value = buffer.slice(index, index + objectSize); |
|
|
} else { |
|
|
let objectOptions = options; |
|
|
if (!globalUTFValidation) { |
|
|
objectOptions = { ...options, validation: { utf8: shouldValidateKey } }; |
|
|
} |
|
|
value = deserializeObject(buffer, _index, objectOptions, false); |
|
|
} |
|
|
|
|
|
index = index + objectSize; |
|
|
} else if (elementType === constants.BSON_DATA_ARRAY) { |
|
|
const _index = index; |
|
|
const objectSize = |
|
|
buffer[index] | |
|
|
(buffer[index + 1] << 8) | |
|
|
(buffer[index + 2] << 16) | |
|
|
(buffer[index + 3] << 24); |
|
|
let arrayOptions: DeserializeOptions = options; |
|
|
|
|
|
|
|
|
const stopIndex = index + objectSize; |
|
|
|
|
|
|
|
|
if (fieldsAsRaw && fieldsAsRaw[name]) { |
|
|
arrayOptions = { ...options, raw: true }; |
|
|
} |
|
|
|
|
|
if (!globalUTFValidation) { |
|
|
arrayOptions = { ...arrayOptions, validation: { utf8: shouldValidateKey } }; |
|
|
} |
|
|
value = deserializeObject(buffer, _index, arrayOptions, true); |
|
|
index = index + objectSize; |
|
|
|
|
|
if (buffer[index - 1] !== 0) throw new BSONError('invalid array terminator byte'); |
|
|
if (index !== stopIndex) throw new 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) { |
|
|
|
|
|
const dataview = BSONDataView.fromUint8Array(buffer.subarray(index, index + 8)); |
|
|
|
|
|
const lowBits = |
|
|
buffer[index++] | |
|
|
(buffer[index++] << 8) | |
|
|
(buffer[index++] << 16) | |
|
|
(buffer[index++] << 24); |
|
|
const highBits = |
|
|
buffer[index++] | |
|
|
(buffer[index++] << 8) | |
|
|
(buffer[index++] << 16) | |
|
|
(buffer[index++] << 24); |
|
|
const long = new Long(lowBits, highBits); |
|
|
if (useBigInt64) { |
|
|
value = dataview.getBigInt64(0, true); |
|
|
} else 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) { |
|
|
|
|
|
const bytes = ByteUtils.allocate(16); |
|
|
|
|
|
bytes.set(buffer.subarray(index, index + 16), 0); |
|
|
|
|
|
index = index + 16; |
|
|
|
|
|
value = new Decimal128(bytes); |
|
|
} else if (elementType === constants.BSON_DATA_BINARY) { |
|
|
let binarySize = |
|
|
buffer[index++] | |
|
|
(buffer[index++] << 8) | |
|
|
(buffer[index++] << 16) | |
|
|
(buffer[index++] << 24); |
|
|
const totalBinarySize = binarySize; |
|
|
const subType = buffer[index++]; |
|
|
|
|
|
|
|
|
if (binarySize < 0) throw new BSONError('Negative binary type element size found'); |
|
|
|
|
|
|
|
|
if (binarySize > buffer.byteLength) |
|
|
throw new BSONError('Binary type size larger than document size'); |
|
|
|
|
|
|
|
|
if (buffer['slice'] != null) { |
|
|
|
|
|
if (subType === Binary.SUBTYPE_BYTE_ARRAY) { |
|
|
binarySize = |
|
|
buffer[index++] | |
|
|
(buffer[index++] << 8) | |
|
|
(buffer[index++] << 16) | |
|
|
(buffer[index++] << 24); |
|
|
if (binarySize < 0) |
|
|
throw new BSONError('Negative binary type element size found for subtype 0x02'); |
|
|
if (binarySize > totalBinarySize - 4) |
|
|
throw new BSONError('Binary type with subtype 0x02 contains too long binary size'); |
|
|
if (binarySize < totalBinarySize - 4) |
|
|
throw new BSONError('Binary type with subtype 0x02 contains too short binary size'); |
|
|
} |
|
|
|
|
|
if (promoteBuffers && promoteValues) { |
|
|
value = ByteUtils.toLocalBufferType(buffer.slice(index, index + binarySize)); |
|
|
} else { |
|
|
value = new Binary(buffer.slice(index, index + binarySize), subType); |
|
|
if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) { |
|
|
value = value.toUUID(); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
const _buffer = ByteUtils.allocate(binarySize); |
|
|
|
|
|
if (subType === Binary.SUBTYPE_BYTE_ARRAY) { |
|
|
binarySize = |
|
|
buffer[index++] | |
|
|
(buffer[index++] << 8) | |
|
|
(buffer[index++] << 16) | |
|
|
(buffer[index++] << 24); |
|
|
if (binarySize < 0) |
|
|
throw new BSONError('Negative binary type element size found for subtype 0x02'); |
|
|
if (binarySize > totalBinarySize - 4) |
|
|
throw new BSONError('Binary type with subtype 0x02 contains too long binary size'); |
|
|
if (binarySize < totalBinarySize - 4) |
|
|
throw new BSONError('Binary type with subtype 0x02 contains too short binary size'); |
|
|
} |
|
|
|
|
|
|
|
|
for (i = 0; i < binarySize; i++) { |
|
|
_buffer[i] = buffer[index + i]; |
|
|
} |
|
|
|
|
|
if (promoteBuffers && promoteValues) { |
|
|
value = _buffer; |
|
|
} else { |
|
|
value = new Binary(buffer.slice(index, index + binarySize), subType); |
|
|
if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW && UUID.isValid(value)) { |
|
|
value = value.toUUID(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
index = index + binarySize; |
|
|
} else if (elementType === constants.BSON_DATA_REGEXP && bsonRegExp === false) { |
|
|
|
|
|
i = index; |
|
|
|
|
|
while (buffer[i] !== 0x00 && i < buffer.length) { |
|
|
i++; |
|
|
} |
|
|
|
|
|
if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); |
|
|
|
|
|
const source = ByteUtils.toUTF8(buffer, index, i); |
|
|
|
|
|
index = i + 1; |
|
|
|
|
|
|
|
|
i = index; |
|
|
|
|
|
while (buffer[i] !== 0x00 && i < buffer.length) { |
|
|
i++; |
|
|
} |
|
|
|
|
|
if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); |
|
|
|
|
|
const regExpOptions = ByteUtils.toUTF8(buffer, index, i); |
|
|
index = i + 1; |
|
|
|
|
|
|
|
|
const optionsArray = new Array(regExpOptions.length); |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
i = index; |
|
|
|
|
|
while (buffer[i] !== 0x00 && i < buffer.length) { |
|
|
i++; |
|
|
} |
|
|
|
|
|
if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); |
|
|
|
|
|
const source = ByteUtils.toUTF8(buffer, index, i); |
|
|
index = i + 1; |
|
|
|
|
|
|
|
|
i = index; |
|
|
|
|
|
while (buffer[i] !== 0x00 && i < buffer.length) { |
|
|
i++; |
|
|
} |
|
|
|
|
|
if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); |
|
|
|
|
|
const regExpOptions = ByteUtils.toUTF8(buffer, index, i); |
|
|
index = i + 1; |
|
|
|
|
|
|
|
|
value = new BSONRegExp(source, regExpOptions); |
|
|
} else if (elementType === constants.BSON_DATA_SYMBOL) { |
|
|
const 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 BSONError('bad string length in bson'); |
|
|
} |
|
|
const symbol = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey); |
|
|
value = promoteValues ? symbol : new BSONSymbol(symbol); |
|
|
index = index + stringSize; |
|
|
} else if (elementType === constants.BSON_DATA_TIMESTAMP) { |
|
|
|
|
|
|
|
|
|
|
|
const i = |
|
|
buffer[index++] + |
|
|
buffer[index++] * (1 << 8) + |
|
|
buffer[index++] * (1 << 16) + |
|
|
buffer[index++] * (1 << 24); |
|
|
const t = |
|
|
buffer[index++] + |
|
|
buffer[index++] * (1 << 8) + |
|
|
buffer[index++] * (1 << 16) + |
|
|
buffer[index++] * (1 << 24); |
|
|
|
|
|
value = new Timestamp({ i, t }); |
|
|
} else if (elementType === constants.BSON_DATA_MIN_KEY) { |
|
|
value = new MinKey(); |
|
|
} else if (elementType === constants.BSON_DATA_MAX_KEY) { |
|
|
value = new MaxKey(); |
|
|
} else if (elementType === constants.BSON_DATA_CODE) { |
|
|
const 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 BSONError('bad string length in bson'); |
|
|
} |
|
|
const functionString = getValidatedString( |
|
|
buffer, |
|
|
index, |
|
|
index + stringSize - 1, |
|
|
shouldValidateKey |
|
|
); |
|
|
|
|
|
value = new Code(functionString); |
|
|
|
|
|
|
|
|
index = index + stringSize; |
|
|
} else if (elementType === constants.BSON_DATA_CODE_W_SCOPE) { |
|
|
const totalSize = |
|
|
buffer[index++] | |
|
|
(buffer[index++] << 8) | |
|
|
(buffer[index++] << 16) | |
|
|
(buffer[index++] << 24); |
|
|
|
|
|
|
|
|
if (totalSize < 4 + 4 + 4 + 1) { |
|
|
throw new BSONError('code_w_scope total size shorter minimum expected length'); |
|
|
} |
|
|
|
|
|
|
|
|
const 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 BSONError('bad string length in bson'); |
|
|
} |
|
|
|
|
|
|
|
|
const functionString = getValidatedString( |
|
|
buffer, |
|
|
index, |
|
|
index + stringSize - 1, |
|
|
shouldValidateKey |
|
|
); |
|
|
|
|
|
index = index + stringSize; |
|
|
|
|
|
const _index = index; |
|
|
|
|
|
const objectSize = |
|
|
buffer[index] | |
|
|
(buffer[index + 1] << 8) | |
|
|
(buffer[index + 2] << 16) | |
|
|
(buffer[index + 3] << 24); |
|
|
|
|
|
const scopeObject = deserializeObject(buffer, _index, options, false); |
|
|
|
|
|
index = index + objectSize; |
|
|
|
|
|
|
|
|
if (totalSize < 4 + 4 + objectSize + stringSize) { |
|
|
throw new BSONError('code_w_scope total size is too short, truncating scope'); |
|
|
} |
|
|
|
|
|
|
|
|
if (totalSize > 4 + 4 + objectSize + stringSize) { |
|
|
throw new BSONError('code_w_scope total size is too long, clips outer document'); |
|
|
} |
|
|
|
|
|
value = new Code(functionString, scopeObject); |
|
|
} else if (elementType === constants.BSON_DATA_DBPOINTER) { |
|
|
|
|
|
const 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 BSONError('bad string length in bson'); |
|
|
|
|
|
if (validation != null && validation.utf8) { |
|
|
if (!validateUtf8(buffer, index, index + stringSize - 1)) { |
|
|
throw new BSONError('Invalid UTF-8 string in BSON document'); |
|
|
} |
|
|
} |
|
|
const namespace = ByteUtils.toUTF8(buffer, index, index + stringSize - 1); |
|
|
// Update parse index position |
|
|
index = index + stringSize; |
|
|
|
|
|
// Read the oid |
|
|
const oidBuffer = ByteUtils.allocate(12); |
|
|
oidBuffer.set(buffer.subarray(index, index + 12), 0); |
|
|
const oid = new ObjectId(oidBuffer); |
|
|
|
|
|
// Update the index |
|
|
index = index + 12; |
|
|
|
|
|
// Upgrade to DBRef type |
|
|
value = new DBRef(namespace, oid); |
|
|
} else { |
|
|
throw new BSONError( |
|
|
`Detected unknown BSON type ${elementType.toString(16)} for fieldname "${name}"` |
|
|
); |
|
|
} |
|
|
if (name === '__proto__') { |
|
|
Object.defineProperty(object, name, { |
|
|
value, |
|
|
writable: true, |
|
|
enumerable: true, |
|
|
configurable: true |
|
|
}); |
|
|
} else { |
|
|
object[name] = value; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (size !== index - startIndex) { |
|
|
if (isArray) throw new BSONError('corrupt array bson'); |
|
|
throw new BSONError('corrupt object bson'); |
|
|
} |
|
|
|
|
|
|
|
|
if (!isPossibleDBRef) return object; |
|
|
|
|
|
if (isDBRefLike(object)) { |
|
|
const copy = Object.assign({}, object) as Partial<DBRefLike>; |
|
|
delete copy.$ref; |
|
|
delete copy.$id; |
|
|
delete copy.$db; |
|
|
return new DBRef(object.$ref, object.$id, object.$db, copy); |
|
|
} |
|
|
|
|
|
return object; |
|
|
} |
|
|
|
|
|
function getValidatedString( |
|
|
buffer: Uint8Array, |
|
|
start: number, |
|
|
end: number, |
|
|
shouldValidateUtf8: boolean |
|
|
) { |
|
|
const value = ByteUtils.toUTF8(buffer, start, end); |
|
|
|
|
|
if (shouldValidateUtf8) { |
|
|
for (let i = 0; i < value.length; i++) { |
|
|
if (value.charCodeAt(i) === 0xfffd) { |
|
|
if (!validateUtf8(buffer, start, end)) { |
|
|
throw new BSONError('Invalid UTF-8 string in BSON document'); |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
return value; |
|
|
} |
|
|
|