mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
449 lines
19 KiB
449 lines
19 KiB
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.EjsonDecoder = void 0;
|
|
const values_1 = require("../bson/values");
|
|
const JsonDecoder_1 = require("../json/JsonDecoder");
|
|
const JsonDecoder_2 = require("../json/JsonDecoder");
|
|
class EjsonDecoder extends JsonDecoder_1.JsonDecoder {
|
|
constructor(options = {}) {
|
|
super();
|
|
this.options = options;
|
|
}
|
|
decodeFromString(json) {
|
|
const bytes = new TextEncoder().encode(json);
|
|
return this.decode(bytes);
|
|
}
|
|
readAny() {
|
|
this.skipWhitespace();
|
|
const reader = this.reader;
|
|
const uint8 = reader.uint8;
|
|
const char = uint8[reader.x];
|
|
switch (char) {
|
|
case 34:
|
|
return this.readStr();
|
|
case 91:
|
|
return this.readArr();
|
|
case 102:
|
|
return this.readFalse();
|
|
case 110:
|
|
return this.readNull();
|
|
case 116:
|
|
return this.readTrue();
|
|
case 123:
|
|
return this.readObjWithEjsonSupport();
|
|
default:
|
|
if ((char >= 48 && char <= 57) || char === 45)
|
|
return this.readNum();
|
|
throw new Error('Invalid JSON');
|
|
}
|
|
}
|
|
readArr() {
|
|
const reader = this.reader;
|
|
if (reader.u8() !== 0x5b)
|
|
throw new Error('Invalid JSON');
|
|
const arr = [];
|
|
const uint8 = reader.uint8;
|
|
let first = true;
|
|
while (true) {
|
|
this.skipWhitespace();
|
|
const char = uint8[reader.x];
|
|
if (char === 0x5d)
|
|
return reader.x++, arr;
|
|
if (char === 0x2c)
|
|
reader.x++;
|
|
else if (!first)
|
|
throw new Error('Invalid JSON');
|
|
this.skipWhitespace();
|
|
arr.push(this.readAny());
|
|
first = false;
|
|
}
|
|
}
|
|
readObjWithEjsonSupport() {
|
|
const reader = this.reader;
|
|
if (reader.u8() !== 0x7b)
|
|
throw new Error('Invalid JSON');
|
|
const obj = {};
|
|
const uint8 = reader.uint8;
|
|
let first = true;
|
|
while (true) {
|
|
this.skipWhitespace();
|
|
let char = uint8[reader.x];
|
|
if (char === 0x7d) {
|
|
reader.x++;
|
|
return this.transformEjsonObject(obj);
|
|
}
|
|
if (char === 0x2c)
|
|
reader.x++;
|
|
else if (!first)
|
|
throw new Error('Invalid JSON');
|
|
this.skipWhitespace();
|
|
char = uint8[reader.x++];
|
|
if (char !== 0x22)
|
|
throw new Error('Invalid JSON');
|
|
const key = (0, JsonDecoder_2.readKey)(reader);
|
|
if (key === '__proto__')
|
|
throw new Error('Invalid JSON');
|
|
this.skipWhitespace();
|
|
if (reader.u8() !== 0x3a)
|
|
throw new Error('Invalid JSON');
|
|
this.skipWhitespace();
|
|
obj[key] = this.readValue();
|
|
first = false;
|
|
}
|
|
}
|
|
readValue() {
|
|
this.skipWhitespace();
|
|
const reader = this.reader;
|
|
const uint8 = reader.uint8;
|
|
const char = uint8[reader.x];
|
|
switch (char) {
|
|
case 34:
|
|
return this.readStr();
|
|
case 91:
|
|
return this.readArr();
|
|
case 102:
|
|
return this.readFalse();
|
|
case 110:
|
|
return this.readNull();
|
|
case 116:
|
|
return this.readTrue();
|
|
case 123:
|
|
return this.readRawObj();
|
|
default:
|
|
if ((char >= 48 && char <= 57) || char === 45)
|
|
return this.readNum();
|
|
throw new Error('Invalid JSON');
|
|
}
|
|
}
|
|
readRawObj() {
|
|
const reader = this.reader;
|
|
if (reader.u8() !== 0x7b)
|
|
throw new Error('Invalid JSON');
|
|
const obj = {};
|
|
const uint8 = reader.uint8;
|
|
let first = true;
|
|
while (true) {
|
|
this.skipWhitespace();
|
|
let char = uint8[reader.x];
|
|
if (char === 0x7d) {
|
|
reader.x++;
|
|
return obj;
|
|
}
|
|
if (char === 0x2c)
|
|
reader.x++;
|
|
else if (!first)
|
|
throw new Error('Invalid JSON');
|
|
this.skipWhitespace();
|
|
char = uint8[reader.x++];
|
|
if (char !== 0x22)
|
|
throw new Error('Invalid JSON');
|
|
const key = (0, JsonDecoder_2.readKey)(reader);
|
|
if (key === '__proto__')
|
|
throw new Error('Invalid JSON');
|
|
this.skipWhitespace();
|
|
if (reader.u8() !== 0x3a)
|
|
throw new Error('Invalid JSON');
|
|
this.skipWhitespace();
|
|
obj[key] = this.readValue();
|
|
first = false;
|
|
}
|
|
}
|
|
transformEjsonObject(obj) {
|
|
const keys = Object.keys(obj);
|
|
const hasExactKeys = (expectedKeys) => {
|
|
if (keys.length !== expectedKeys.length)
|
|
return false;
|
|
return expectedKeys.every((key) => keys.includes(key));
|
|
};
|
|
const specialKeys = keys.filter((key) => key.startsWith('$'));
|
|
if (specialKeys.length > 0) {
|
|
if (specialKeys.includes('$oid')) {
|
|
if (!hasExactKeys(['$oid'])) {
|
|
throw new Error('Invalid ObjectId format: extra keys not allowed');
|
|
}
|
|
const oidStr = obj.$oid;
|
|
if (typeof oidStr === 'string' && /^[0-9a-fA-F]{24}$/.test(oidStr)) {
|
|
return this.parseObjectId(oidStr);
|
|
}
|
|
throw new Error('Invalid ObjectId format');
|
|
}
|
|
if (specialKeys.includes('$numberInt')) {
|
|
if (!hasExactKeys(['$numberInt'])) {
|
|
throw new Error('Invalid Int32 format: extra keys not allowed');
|
|
}
|
|
const intStr = obj.$numberInt;
|
|
if (typeof intStr === 'string') {
|
|
const value = parseInt(intStr, 10);
|
|
if (!isNaN(value) && value >= -2147483648 && value <= 2147483647) {
|
|
return new values_1.BsonInt32(value);
|
|
}
|
|
}
|
|
throw new Error('Invalid Int32 format');
|
|
}
|
|
if (specialKeys.includes('$numberLong')) {
|
|
if (!hasExactKeys(['$numberLong'])) {
|
|
throw new Error('Invalid Int64 format: extra keys not allowed');
|
|
}
|
|
const longStr = obj.$numberLong;
|
|
if (typeof longStr === 'string') {
|
|
const value = parseFloat(longStr);
|
|
if (!isNaN(value)) {
|
|
return new values_1.BsonInt64(value);
|
|
}
|
|
}
|
|
throw new Error('Invalid Int64 format');
|
|
}
|
|
if (specialKeys.includes('$numberDouble')) {
|
|
if (!hasExactKeys(['$numberDouble'])) {
|
|
throw new Error('Invalid Double format: extra keys not allowed');
|
|
}
|
|
const doubleStr = obj.$numberDouble;
|
|
if (typeof doubleStr === 'string') {
|
|
if (doubleStr === 'Infinity')
|
|
return new values_1.BsonFloat(Infinity);
|
|
if (doubleStr === '-Infinity')
|
|
return new values_1.BsonFloat(-Infinity);
|
|
if (doubleStr === 'NaN')
|
|
return new values_1.BsonFloat(NaN);
|
|
const value = parseFloat(doubleStr);
|
|
if (!isNaN(value)) {
|
|
return new values_1.BsonFloat(value);
|
|
}
|
|
}
|
|
throw new Error('Invalid Double format');
|
|
}
|
|
if (specialKeys.includes('$numberDecimal')) {
|
|
if (!hasExactKeys(['$numberDecimal'])) {
|
|
throw new Error('Invalid Decimal128 format: extra keys not allowed');
|
|
}
|
|
const decimalStr = obj.$numberDecimal;
|
|
if (typeof decimalStr === 'string') {
|
|
return new values_1.BsonDecimal128(new Uint8Array(16));
|
|
}
|
|
throw new Error('Invalid Decimal128 format');
|
|
}
|
|
if (specialKeys.includes('$binary')) {
|
|
if (!hasExactKeys(['$binary'])) {
|
|
throw new Error('Invalid Binary format: extra keys not allowed');
|
|
}
|
|
const binaryObj = obj.$binary;
|
|
if (typeof binaryObj === 'object' && binaryObj !== null) {
|
|
const binaryKeys = Object.keys(binaryObj);
|
|
if (binaryKeys.length === 2 && binaryKeys.includes('base64') && binaryKeys.includes('subType')) {
|
|
const base64 = binaryObj.base64;
|
|
const subType = binaryObj.subType;
|
|
if (typeof base64 === 'string' && typeof subType === 'string') {
|
|
const data = this.base64ToUint8Array(base64);
|
|
const subtype = parseInt(subType, 16);
|
|
return new values_1.BsonBinary(subtype, data);
|
|
}
|
|
}
|
|
}
|
|
throw new Error('Invalid Binary format');
|
|
}
|
|
if (specialKeys.includes('$uuid')) {
|
|
if (!hasExactKeys(['$uuid'])) {
|
|
throw new Error('Invalid UUID format: extra keys not allowed');
|
|
}
|
|
const uuidStr = obj.$uuid;
|
|
if (typeof uuidStr === 'string' && this.isValidUuid(uuidStr)) {
|
|
const data = this.uuidToBytes(uuidStr);
|
|
return new values_1.BsonBinary(4, data);
|
|
}
|
|
throw new Error('Invalid UUID format');
|
|
}
|
|
if (specialKeys.includes('$code') && !specialKeys.includes('$scope')) {
|
|
if (!hasExactKeys(['$code'])) {
|
|
throw new Error('Invalid Code format: extra keys not allowed');
|
|
}
|
|
const code = obj.$code;
|
|
if (typeof code === 'string') {
|
|
return new values_1.BsonJavascriptCode(code);
|
|
}
|
|
throw new Error('Invalid Code format');
|
|
}
|
|
if (specialKeys.includes('$code') && specialKeys.includes('$scope')) {
|
|
if (!hasExactKeys(['$code', '$scope'])) {
|
|
throw new Error('Invalid CodeWScope format: extra keys not allowed');
|
|
}
|
|
const code = obj.$code;
|
|
const scope = obj.$scope;
|
|
if (typeof code === 'string' && typeof scope === 'object' && scope !== null) {
|
|
return new values_1.BsonJavascriptCodeWithScope(code, this.transformEjsonObject(scope));
|
|
}
|
|
throw new Error('Invalid CodeWScope format');
|
|
}
|
|
if (specialKeys.includes('$symbol')) {
|
|
if (!hasExactKeys(['$symbol'])) {
|
|
throw new Error('Invalid Symbol format: extra keys not allowed');
|
|
}
|
|
const symbol = obj.$symbol;
|
|
if (typeof symbol === 'string') {
|
|
return new values_1.BsonSymbol(symbol);
|
|
}
|
|
throw new Error('Invalid Symbol format');
|
|
}
|
|
if (specialKeys.includes('$timestamp')) {
|
|
if (!hasExactKeys(['$timestamp'])) {
|
|
throw new Error('Invalid Timestamp format: extra keys not allowed');
|
|
}
|
|
const timestampObj = obj.$timestamp;
|
|
if (typeof timestampObj === 'object' && timestampObj !== null) {
|
|
const timestampKeys = Object.keys(timestampObj);
|
|
if (timestampKeys.length === 2 && timestampKeys.includes('t') && timestampKeys.includes('i')) {
|
|
const t = timestampObj.t;
|
|
const i = timestampObj.i;
|
|
if (typeof t === 'number' && typeof i === 'number' && t >= 0 && i >= 0) {
|
|
return new values_1.BsonTimestamp(i, t);
|
|
}
|
|
}
|
|
}
|
|
throw new Error('Invalid Timestamp format');
|
|
}
|
|
if (specialKeys.includes('$regularExpression')) {
|
|
if (!hasExactKeys(['$regularExpression'])) {
|
|
throw new Error('Invalid RegularExpression format: extra keys not allowed');
|
|
}
|
|
const regexObj = obj.$regularExpression;
|
|
if (typeof regexObj === 'object' && regexObj !== null) {
|
|
const regexKeys = Object.keys(regexObj);
|
|
if (regexKeys.length === 2 && regexKeys.includes('pattern') && regexKeys.includes('options')) {
|
|
const pattern = regexObj.pattern;
|
|
const options = regexObj.options;
|
|
if (typeof pattern === 'string' && typeof options === 'string') {
|
|
return new RegExp(pattern, options);
|
|
}
|
|
}
|
|
}
|
|
throw new Error('Invalid RegularExpression format');
|
|
}
|
|
if (specialKeys.includes('$dbPointer')) {
|
|
if (!hasExactKeys(['$dbPointer'])) {
|
|
throw new Error('Invalid DBPointer format: extra keys not allowed');
|
|
}
|
|
const dbPointerObj = obj.$dbPointer;
|
|
if (typeof dbPointerObj === 'object' && dbPointerObj !== null) {
|
|
const dbPointerKeys = Object.keys(dbPointerObj);
|
|
if (dbPointerKeys.length === 2 && dbPointerKeys.includes('$ref') && dbPointerKeys.includes('$id')) {
|
|
const ref = dbPointerObj.$ref;
|
|
const id = dbPointerObj.$id;
|
|
if (typeof ref === 'string' && id !== undefined) {
|
|
const transformedId = this.transformEjsonObject(id);
|
|
if (transformedId instanceof values_1.BsonObjectId) {
|
|
return new values_1.BsonDbPointer(ref, transformedId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
throw new Error('Invalid DBPointer format');
|
|
}
|
|
if (specialKeys.includes('$date')) {
|
|
if (!hasExactKeys(['$date'])) {
|
|
throw new Error('Invalid Date format: extra keys not allowed');
|
|
}
|
|
const dateValue = obj.$date;
|
|
if (typeof dateValue === 'string') {
|
|
const date = new Date(dateValue);
|
|
if (!isNaN(date.getTime())) {
|
|
return date;
|
|
}
|
|
}
|
|
else if (typeof dateValue === 'object' && dateValue !== null) {
|
|
const longObj = dateValue;
|
|
const longKeys = Object.keys(longObj);
|
|
if (longKeys.length === 1 && longKeys[0] === '$numberLong' && typeof longObj.$numberLong === 'string') {
|
|
const timestamp = parseFloat(longObj.$numberLong);
|
|
if (!isNaN(timestamp)) {
|
|
return new Date(timestamp);
|
|
}
|
|
}
|
|
}
|
|
throw new Error('Invalid Date format');
|
|
}
|
|
if (specialKeys.includes('$minKey')) {
|
|
if (!hasExactKeys(['$minKey'])) {
|
|
throw new Error('Invalid MinKey format: extra keys not allowed');
|
|
}
|
|
if (obj.$minKey === 1) {
|
|
return new values_1.BsonMinKey();
|
|
}
|
|
throw new Error('Invalid MinKey format');
|
|
}
|
|
if (specialKeys.includes('$maxKey')) {
|
|
if (!hasExactKeys(['$maxKey'])) {
|
|
throw new Error('Invalid MaxKey format: extra keys not allowed');
|
|
}
|
|
if (obj.$maxKey === 1) {
|
|
return new values_1.BsonMaxKey();
|
|
}
|
|
throw new Error('Invalid MaxKey format');
|
|
}
|
|
if (specialKeys.includes('$undefined')) {
|
|
if (!hasExactKeys(['$undefined'])) {
|
|
throw new Error('Invalid Undefined format: extra keys not allowed');
|
|
}
|
|
if (obj.$undefined === true) {
|
|
return undefined;
|
|
}
|
|
throw new Error('Invalid Undefined format');
|
|
}
|
|
}
|
|
if (keys.includes('$ref') && keys.includes('$id')) {
|
|
const ref = obj.$ref;
|
|
const id = this.transformEjsonObject(obj.$id);
|
|
const result = { $ref: ref, $id: id };
|
|
if (keys.includes('$db')) {
|
|
result.$db = obj.$db;
|
|
}
|
|
for (const key of keys) {
|
|
if (key !== '$ref' && key !== '$id' && key !== '$db') {
|
|
result[key] = this.transformEjsonObject(obj[key]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
const result = {};
|
|
for (const [key, val] of Object.entries(obj)) {
|
|
if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
|
|
result[key] = this.transformEjsonObject(val);
|
|
}
|
|
else if (Array.isArray(val)) {
|
|
result[key] = val.map((item) => typeof item === 'object' && item !== null && !Array.isArray(item)
|
|
? this.transformEjsonObject(item)
|
|
: item);
|
|
}
|
|
else {
|
|
result[key] = val;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
parseObjectId(hex) {
|
|
const timestamp = parseInt(hex.slice(0, 8), 16);
|
|
const process = parseInt(hex.slice(8, 18), 16);
|
|
const counter = parseInt(hex.slice(18, 24), 16);
|
|
return new values_1.BsonObjectId(timestamp, process, counter);
|
|
}
|
|
base64ToUint8Array(base64) {
|
|
const binary = atob(base64);
|
|
const bytes = new Uint8Array(binary.length);
|
|
for (let i = 0; i < binary.length; i++) {
|
|
bytes[i] = binary.charCodeAt(i);
|
|
}
|
|
return bytes;
|
|
}
|
|
isValidUuid(uuid) {
|
|
const uuidPattern = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
return uuidPattern.test(uuid);
|
|
}
|
|
uuidToBytes(uuid) {
|
|
const hex = uuid.replace(/-/g, '');
|
|
const bytes = new Uint8Array(16);
|
|
for (let i = 0; i < 16; i++) {
|
|
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
}
|
|
return bytes;
|
|
}
|
|
}
|
|
exports.EjsonDecoder = EjsonDecoder;
|
|
//# sourceMappingURL=EjsonDecoder.js.map
|