From 22845613e1a6fd3bb7a6246583b29a5def8fdafa Mon Sep 17 00:00:00 2001 From: fraxken Date: Sun, 15 Jan 2023 22:15:21 +0100 Subject: [PATCH 1/2] fix: parsing-error because of unhandled syntax or null values --- examples/error.js | 594 +++++++++++++++++++++++++++++++++++ index.js | 8 +- package-lock.json | 14 +- package.json | 2 +- src/obfuscators/index.js | 6 +- test/fixtures/regress/jsx.js | 8 + test/module.spec.js | 10 +- test/regress.spec.js | 8 + 8 files changed, 638 insertions(+), 12 deletions(-) create mode 100644 examples/error.js create mode 100644 test/fixtures/regress/jsx.js diff --git a/examples/error.js b/examples/error.js new file mode 100644 index 00000000..15bf162e --- /dev/null +++ b/examples/error.js @@ -0,0 +1,594 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AnyItem = exports.Item = void 0; +const converter_1 = require("./aws/converter"); +const internal_1 = require("./aws/ddb/internal"); +const utils_1 = require("./utils"); +const Error_1 = require("./Error"); +const Internal_1 = require("./Internal"); +const { internalProperties } = Internal_1.default.General; +const dynamooseUndefined = Internal_1.default.Public.undefined; +const dynamooseAny = Internal_1.default.Public.any; +const Populate_1 = require("./Populate"); +const InternalPropertiesClass_1 = require("./InternalPropertiesClass"); +const Error_2 = require("./Error"); +// Item represents an item in a Model that is either pending (not saved) or saved +class Item extends InternalPropertiesClass_1.InternalPropertiesClass { + /** + * Create a new item. + * @param model Internal property. Not used publicly. + * @param object The object for the item. + * @param settings The settings for the item. + */ + constructor(model, object, settings) { + super(); + const itemObject = Item.isDynamoObject(object) ? Item.fromDynamo(object) : object; + Object.keys(itemObject).forEach((key) => this[key] = itemObject[key]); + this.setInternalProperties(internalProperties, { + "originalObject": utils_1.default.deep_copy(itemObject), + "originalSettings": Object.assign({}, settings), + model, + "storedInDynamo": settings.type === "fromDynamo" + }); + } + static objectToDynamo(object, settings = { "type": "object" }) { + if (object === undefined) { + return undefined; + } + const options = settings.type === "value" ? undefined : { "removeUndefinedValues": true }; + return (settings.type === "value" ? (0, converter_1.default)().convertToAttr : (0, converter_1.default)().marshall)(object, options); + } + static fromDynamo(object) { + const result = (0, converter_1.default)().unmarshall(object); + utils_1.default.object.entries(result).forEach(([key, value]) => { + if (value instanceof Uint8Array) { + utils_1.default.object.set(result, key, Buffer.from(value)); + } + }); + return result; + } + // This function will return null if it's unknown if it is a Dynamo object (ex. empty object). It will return true if it is a Dynamo object and false if it's not. + static isDynamoObject(object, recursive) { + function isValid(value) { + if (typeof value === "undefined" || value === null) { + return false; + } + const keys = Object.keys(value); + const key = keys[0]; + const nestedResult = typeof value[key] === "object" && !(value[key] instanceof Buffer) && !(value[key] instanceof Uint8Array) ? Array.isArray(value[key]) ? value[key].every((value) => Item.isDynamoObject(value, true)) : Item.isDynamoObject(value[key]) : true; + const { Schema } = require("./Schema"); + const attributeType = Schema.attributeTypes.findDynamoDBType(key); + return typeof value === "object" && keys.length === 1 && attributeType && (nestedResult || Object.keys(value[key]).length === 0 || attributeType.isSet); + } + const keys = Object.keys(object); + const values = Object.values(object); + if (keys.length === 0) { + return null; + } + else { + return recursive ? isValid(object) : values.every((value) => isValid(value)); + } + } + // This function handles actions that should take place before every response (get, scan, query, batchGet, etc.) + async prepareForResponse() { + if (this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.populate) { + return this.populate({ "properties": this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.populate }); + } + return this; + } + /** + * This function returns the original item that was received from DynamoDB. This function will return a JSON object that represents the original item. In the event no item has been retrieved from DynamoDB `null` will be returned. + * + * ```js + * const user = await User.get(1); + * console.log(user); // {"id": 1, "name": "Bob"} + * user.name = "Tim"; + * + * console.log(user); // {"id": 1, "name": "Tim"} + * console.log(user.original()); // {"id": 1, "name": "Bob"} + * ``` + * @returns Object | null + */ + original() { + return this.getInternalProperties(internalProperties).originalSettings.type === "fromDynamo" ? this.getInternalProperties(internalProperties).originalObject : null; + } + /** + * This function returns a JSON object representation of the item. This is most commonly used when comparing a item to an object you receive elsewhere without worrying about prototypes. + * + * ```js + * const user = new User({"id": 1, "name": "Tim"}); + * + * console.log(user); // Item {"id": 1, "name": "Tim"} + * console.log(user.toJSON()); // {"id": 1, "name": "Tim"} + * ``` + * + * Due to the fact that a item instance is based on an object it is rare that you will have to use this function since you can access all the properties of the item directly. For example, both of the results will yield the same output. + * + * ```js + * const user = new User({"id": 1, "name": "Tim"}); + * + * console.log(user.id); // 1 + * console.log(user.toJSON().id); // 1 + * ``` + * @returns Object + */ + toJSON() { + return utils_1.default.dynamoose.itemToJSON.bind(this)(); + } + /** + * This method will return a promise containing an object of the item that includes default values for any undefined values in the item. + * + * ```js + * const schema = new Schema({ + * "id": String, + * "data": { + * "type": String, + * "default": "Hello World" + * } + * }); + * const User = dynamoose.model("User", schema); + * const user = new User({"id": 1}); + * console.log(await user.withDefaults()); // {"id": 1, "data": "Hello World"} + * ``` + * @returns Promise + */ + async withDefaults() { + return Item.objectFromSchema(utils_1.default.deep_copy(this.toJSON()), this.getInternalProperties(internalProperties).model, { + "typeCheck": false, + "defaults": true, + "type": "toDynamo" + }); + } + // Serializer + serialize(nameOrOptions) { + return this.getInternalProperties(internalProperties).model.serializer.getInternalProperties(internalProperties).serialize(this, nameOrOptions); + } + delete(callback) { + const hashKey = this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).getHashKey(); + const rangeKey = this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).getRangeKey(); + const key = { [hashKey]: this[hashKey] }; + if (rangeKey) { + key[rangeKey] = this[rangeKey]; + } + return this.getInternalProperties(internalProperties).model.delete(key, callback); + } + save(settings, callback) { + if (typeof settings !== "object" && typeof settings !== "undefined") { + callback = settings; + settings = {}; + } + if (typeof settings === "undefined") { + settings = {}; + } + const table = this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).table(); + let savedItem; + const localSettings = settings; + const paramsPromise = this.toDynamo({ "defaults": true, "validate": true, "required": true, "enum": true, "forceDefault": true, "combine": true, "saveUnknown": true, "customTypesDynamo": true, "updateTimestamps": true, "modifiers": ["set"], "mapAttributes": true }).then(async (item) => { + savedItem = item; + let putItemObj = { + "Item": item, + "TableName": table.getInternalProperties(internalProperties).name + }; + if (localSettings.condition) { + putItemObj = Object.assign(Object.assign({}, putItemObj), await localSettings.condition.getInternalProperties(internalProperties).requestObject(this.getInternalProperties(internalProperties).model)); + } + if (localSettings.overwrite === false) { + const conditionExpression = "attribute_not_exists(#__hash_key)"; + putItemObj.ConditionExpression = putItemObj.ConditionExpression ? `(${putItemObj.ConditionExpression}) AND (${conditionExpression})` : conditionExpression; + putItemObj.ExpressionAttributeNames = Object.assign(Object.assign({}, putItemObj.ExpressionAttributeNames || {}), { "#__hash_key": this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).getHashKey() }); + } + return putItemObj; + }); + if (settings.return === "request") { + if (callback) { + const localCallback = callback; + paramsPromise.then((result) => localCallback(null, result)); + return; + } + else { + return paramsPromise; + } + } + const promise = Promise.all([paramsPromise, table.getInternalProperties(internalProperties).pendingTaskPromise()]).then((promises) => { + const [putItemObj] = promises; + return (0, internal_1.default)(table.getInternalProperties(internalProperties).instance, "putItem", putItemObj); + }); + if (callback) { + const localCallback = callback; + promise.then(() => { + this.getInternalProperties(internalProperties).storedInDynamo = true; + const returnItem = new (this.getInternalProperties(internalProperties).model).Item(savedItem); + returnItem.getInternalProperties(internalProperties).storedInDynamo = true; + localCallback(null, returnItem); + }).catch((error) => callback(error)); + } + else { + return (async () => { + await promise; + this.getInternalProperties(internalProperties).storedInDynamo = true; + const returnItem = new (this.getInternalProperties(internalProperties).model).Item(savedItem); + returnItem.getInternalProperties(internalProperties).storedInDynamo = true; + return returnItem; + })(); + } + } + populate(...args) { + return Populate_1.PopulateItem.bind(this)(...args); + } +} +exports.Item = Item; +class AnyItem extends Item { +} +exports.AnyItem = AnyItem; +// This function will mutate the object passed in to run any actions to conform to the schema that cannot be achieved through non mutating methods in Item.objectFromSchema (setting timestamps, etc.) +Item.prepareForObjectFromSchema = async function (object, model, settings) { + if (settings.updateTimestamps) { + const schema = model.getInternalProperties(internalProperties).schemaForObject(object); + if (schema.getInternalProperties(internalProperties).settings.timestamps && settings.type === "toDynamo") { + const date = new Date(); + const timeResult = (prop) => { + const typeDetails = schema.getAttributeTypeDetails(prop.name); + if (Array.isArray(typeDetails)) { + throw new Error_2.default.InvalidType(`Not allowed to use an array of types for the timestamps attribute "${prop.name}".`); + } + switch (typeDetails.typeSettings.storage) { + case "iso": return date.toISOString(); + case "seconds": return Math.floor(date.getTime() / 1000); + default: return date.getTime(); + } + }; + const timestampProperties = schema.getInternalProperties(internalProperties).getTimestampAttributes(); + const createdAtProperties = timestampProperties.filter((val) => val.type === "createdAt"); + const updatedAtProperties = timestampProperties.filter((val) => val.type === "updatedAt"); + if (object.getInternalProperties && object.getInternalProperties(internalProperties) && !object.getInternalProperties(internalProperties).storedInDynamo && (typeof settings.updateTimestamps === "boolean" || settings.updateTimestamps.createdAt)) { + createdAtProperties.forEach((prop) => { + utils_1.default.object.set(object, prop.name, timeResult(prop)); + }); + } + if (typeof settings.updateTimestamps === "boolean" || settings.updateTimestamps.updatedAt) { + updatedAtProperties.forEach((prop) => { + utils_1.default.object.set(object, prop.name, timeResult(prop)); + }); + } + } + } + return object; +}; +// This function will return a list of attributes combining both the schema attributes with the item attributes. This also takes into account all attributes that could exist (ex. properties in sets that don't exist in item), adding the indexes for each item in the item set. +// https://stackoverflow.com/a/59928314/894067 +Item.attributesWithSchema = async function (item, model) { + const schema = model.getInternalProperties(internalProperties).schemaForObject(item); + const attributes = schema.attributes(); + // build a tree out of schema attributes + const root = {}; + attributes.forEach((attribute) => { + let node = root; + attribute.split(".").forEach((part) => { + node[part] = node[part] || {}; + node = node[part]; + }); + }); + // explore the tree + function traverse(node, treeNode, outPath, callback) { + callback(outPath); + if (Object.keys(treeNode).length === 0) { // a leaf + return; + } + Object.keys(treeNode).forEach((attr) => { + if (attr === "0") { + // We check for empty objects here (added `typeof node === "object" && Object.keys(node).length == 0`, see PR https://github.com/dynamoose/dynamoose/pull/1034) to handle the use case of 2d arrays, or arrays within arrays. `node` in that case will be an empty object. + if (!node || node.length == 0 || typeof node === "object" && Object.keys(node).length == 0) { + node = [{}]; // fake the path for arrays + } + node.forEach((a, index) => { + outPath.push(index); + traverse(node[index], treeNode[attr], outPath, callback); + outPath.pop(); + }); + } + else { + if (!node) { + node = {}; // fake the path for properties + } + outPath.push(attr); + traverse(node[attr], treeNode[attr], outPath, callback); + outPath.pop(); + } + }); + } + const out = []; + traverse(item, root, [], (val) => out.push(val.join("."))); + const result = out.slice(1); + return result; +}; +// This function will return an object that conforms to the schema (removing any properties that don't exist, using default values, etc.) & throws an error if there is a type mismatch. +Item.objectFromSchema = async function (object, model, settings = { "type": "toDynamo" }) { + if (settings.checkExpiredItem && model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.expires && (model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.expires.items || {}).returnExpired === false && object[model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.expires.attribute] && object[model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.expires.attribute] * 1000 < Date.now()) { + return undefined; + } + let returnObject = utils_1.default.deep_copy(object); + const schema = settings.schema || model.getInternalProperties(internalProperties).schemaForObject(returnObject); + const schemaAttributes = schema.attributes(returnObject); + function mapAttributes(type) { + if (settings.mapAttributes && settings.type === type) { + const schemaInternalProperties = schema.getInternalProperties(internalProperties); + const mappedAttributesObject = type === "toDynamo" ? schemaInternalProperties.getMapSettingObject() : schema.attributes().reduce((obj, attribute) => { + const mapValues = schemaInternalProperties.getMapSettingValuesForKey(attribute); + if (mapValues && mapValues.length > 0) { + const defaultMapAttribute = schema.getInternalProperties(internalProperties).getDefaultMapAttribute(attribute); + if (defaultMapAttribute) { + if (defaultMapAttribute !== attribute) { + obj[attribute] = defaultMapAttribute; + } + } + else { + obj[attribute] = mapValues[0]; + } + } + return obj; + }, {}); + Object.entries(mappedAttributesObject).forEach(([oldKey, newKey]) => { + if (returnObject[oldKey] !== undefined && returnObject[newKey] !== undefined) { + throw new Error_2.default.InvalidParameter(`Cannot map attribute ${oldKey} to ${newKey} because both are defined`); + } + if (returnObject[oldKey] !== undefined) { + returnObject[newKey] = returnObject[oldKey]; + delete returnObject[oldKey]; + } + }); + } + } + // Map Attributes toDynamo + mapAttributes("toDynamo"); + // Type check + const typeIndexOptionMap = schema.getTypePaths(returnObject, settings); + if (settings.typeCheck === undefined || settings.typeCheck === true) { + const validParents = []; // This array is used to allow for set contents to not be type checked + const keysToDelete = []; + const checkTypeFunction = (item) => { + const [key, value] = item; + if (validParents.find((parent) => key.startsWith(parent.key) && (parent.infinite || key.split(".").length === parent.key.split(".").length + 1))) { + return; + } + const genericKey = key.replace(/\.\d+/gu, ".0"); // This is a key replacing all list numbers with 0 to standardize things like checking if it exists in the schema + const existsInSchema = schemaAttributes.includes(genericKey); + if (existsInSchema) { + const { isValidType, matchedTypeDetails, typeDetailsArray } = utils_1.default.dynamoose.getValueTypeCheckResult(schema, value, genericKey, settings, { "standardKey": true, typeIndexOptionMap }); + if (!isValidType) { + throw new Error_1.default.TypeMismatch(`Expected ${key} to be of type ${typeDetailsArray.map((detail) => detail.dynamicName ? detail.dynamicName() : detail.name.toLowerCase()).join(", ")}, instead found type ${utils_1.default.type_name(value, typeDetailsArray)}.`); + } + else if (matchedTypeDetails.isSet || matchedTypeDetails.name.toLowerCase() === "model" || (matchedTypeDetails.name === "Object" || matchedTypeDetails.name === "Array") && schema.getAttributeSettingValue("schema", genericKey) === dynamooseAny) { + validParents.push({ key, "infinite": true }); + } + else if ( /*typeDetails.dynamodbType === "M" || */matchedTypeDetails.dynamodbType === "L") { + // The code below is an optimization for large array types to speed up the process of not having to check the type for every element but only the ones that are different + value.forEach((subValue, index, array) => { + if (index === 0 || typeof subValue !== typeof array[0]) { + checkTypeFunction([`${key}.${index}`, subValue]); + } + else if (keysToDelete.includes(`${key}.0`) && typeof subValue === typeof array[0]) { + keysToDelete.push(`${key}.${index}`); + } + }); + validParents.push({ key }); + } + } + else { + // Check saveUnknown + if (!settings.saveUnknown || !utils_1.default.dynamoose.wildcard_allowed_check(schema.getSettingValue("saveUnknown"), key)) { + keysToDelete.push(key); + } + } + }; + utils_1.default.object.entries(returnObject).filter((item) => item[1] !== undefined && item[1] !== dynamooseUndefined).map(checkTypeFunction); + keysToDelete.reverse().forEach((key) => utils_1.default.object.delete(returnObject, key)); + } + if (settings.defaults || settings.forceDefault) { + await Promise.all((await Item.attributesWithSchema(returnObject, model)).map(async (key) => { + const value = utils_1.default.object.get(returnObject, key); + if (value === dynamooseUndefined) { + utils_1.default.object.set(returnObject, key, undefined); + } + else { + const defaultValue = await schema.defaultCheck(key, value, settings); + const isDefaultValueUndefined = Array.isArray(defaultValue) ? defaultValue.some((defaultValue) => typeof defaultValue === "undefined" || defaultValue === null) : typeof defaultValue === "undefined" || defaultValue === null; + const parentKey = utils_1.default.parentKey(key); + const parentValue = parentKey.length === 0 ? returnObject : utils_1.default.object.get(returnObject, parentKey); + if (!isDefaultValueUndefined) { + const { isValidType, typeDetailsArray } = utils_1.default.dynamoose.getValueTypeCheckResult(schema, defaultValue, key, settings, { typeIndexOptionMap }); + if (!isValidType) { + throw new Error_1.default.TypeMismatch(`Expected ${key} to be of type ${typeDetailsArray.map((detail) => detail.dynamicName ? detail.dynamicName() : detail.name.toLowerCase()).join(", ")}, instead found type ${typeof defaultValue}.`); + } + else if (typeof parentValue !== "undefined" && parentValue !== null) { + utils_1.default.object.set(returnObject, key, defaultValue); + } + } + } + })); + } + // Custom Types + if (settings.customTypesDynamo) { + (await Item.attributesWithSchema(returnObject, model)).map((key) => { + const value = utils_1.default.object.get(returnObject, key); + const isValueUndefined = typeof value === "undefined" || value === null; + if (!isValueUndefined) { + const typeDetails = utils_1.default.dynamoose.getValueTypeCheckResult(schema, value, key, settings, { typeIndexOptionMap }).matchedTypeDetails; + const { customType } = typeDetails; + const { "type": typeInfo } = typeDetails.isOfType(value); + const isCorrectTypeAlready = typeInfo === (settings.type === "toDynamo" ? "underlying" : "main"); + if (customType && customType.functions[settings.type] && !isCorrectTypeAlready) { + const customValue = customType.functions[settings.type](value); + utils_1.default.object.set(returnObject, key, customValue); + } + } + }); + } + // DynamoDB Type Handler (ex. converting sets to correct value for toDynamo & fromDynamo) + utils_1.default.object.entries(returnObject).filter((item) => typeof item[1] === "object").forEach((item) => { + const [key, value] = item; + let typeDetails; + try { + typeDetails = utils_1.default.dynamoose.getValueTypeCheckResult(schema, value, key, settings, { typeIndexOptionMap }).matchedTypeDetails; + } + catch (e) { + const { Schema } = require("./Schema"); + typeDetails = Schema.attributeTypes.findTypeForValue(value, settings.type, settings); + } + if (typeDetails && typeDetails[settings.type]) { + utils_1.default.object.set(returnObject, key, typeDetails[settings.type](value)); + } + }); + if (settings.combine) { + schemaAttributes.map((key) => { + try { + const typeDetails = schema.getAttributeTypeDetails(key); + return { + key, + "type": typeDetails + }; + } + catch (e) { } // eslint-disable-line no-empty + }).filter((item) => { + return Array.isArray(item.type) ? item.type.some((type) => type.name === "Combine") : item.type.name === "Combine"; + }).map((obj) => { + if (obj && Array.isArray(obj.type)) { + throw new Error_1.default.InvalidParameter("Combine type is not allowed to be used with multiple types."); + } + return obj; + }).forEach((item) => { + const { key, type } = item; + const value = type.typeSettings.attributes.map((attribute) => utils_1.default.object.get(returnObject, attribute)).filter((value) => typeof value !== "undefined" && value !== null).join(type.typeSettings.separator); + utils_1.default.object.set(returnObject, key, value); + }); + } + if (settings.modifiers) { + await Promise.all(settings.modifiers.map(async (modifier) => { + await Promise.all((await Item.attributesWithSchema(returnObject, model)).map(async (key) => { + const value = utils_1.default.object.get(returnObject, key); + const modifierFunction = await schema.getAttributeSettingValue(modifier, key, { "returnFunction": true, typeIndexOptionMap }); + const modifierFunctionExists = Array.isArray(modifierFunction) ? modifierFunction.some((val) => Boolean(val)) : Boolean(modifierFunction); + const isValueUndefined = typeof value === "undefined" || value === null; + if (modifierFunctionExists && !isValueUndefined) { + const oldValue = object.original ? utils_1.default.object.get(object.original(), key) : undefined; + utils_1.default.object.set(returnObject, key, await modifierFunction(value, oldValue)); + } + })); + const schemaModifier = schema.getInternalProperties(internalProperties).settings[modifier]; + if (schemaModifier) { + returnObject = await schemaModifier(returnObject); + } + })); + } + if (settings.validate) { + await Promise.all((await Item.attributesWithSchema(returnObject, model)).map(async (key) => { + const value = utils_1.default.object.get(returnObject, key); + const isValueUndefined = typeof value === "undefined" || value === null; + if (!isValueUndefined) { + const validator = await schema.getAttributeSettingValue("validate", key, { "returnFunction": true, typeIndexOptionMap }); + if (validator) { + let result; + if (validator instanceof RegExp) { + if (typeof value === "string") { + result = validator.test(value); + } + else { + throw new Error_1.default.ValidationError(`Trying to pass in ${typeof value} to a RegExp validator for key: ${key}.`); + } + } + else { + result = typeof validator === "function" ? await validator(value) : validator === value; + } + if (!result) { + throw new Error_1.default.ValidationError(`${key} with a value of ${value} had a validation error when trying to save the item`); + } + } + } + })); + const schemaValidator = schema.getInternalProperties(internalProperties).settings.validate; + if (schemaValidator) { + const result = await schemaValidator(returnObject); + if (!result) { + throw new Error_1.default.ValidationError(`${JSON.stringify(returnObject)} had a schema validation error when trying to save the item.`); + } + } + } + if (settings.required) { + let attributesToCheck = await Item.attributesWithSchema(returnObject, model); + if (settings.required === "nested") { + attributesToCheck = attributesToCheck.filter((attribute) => utils_1.default.object.keys(returnObject).find((key) => attribute === key || attribute.startsWith(key + "."))); + } + await Promise.all(attributesToCheck.map(async (key) => { + const check = async () => { + const value = utils_1.default.object.get(returnObject, key); + await schema.requiredCheck(key, value); + }; + const keyParts = key.split("."); + const parentKey = keyParts.slice(0, -1).join("."); + if (parentKey) { + const parentValue = utils_1.default.object.get(returnObject, parentKey); + const isParentValueUndefined = typeof parentValue === "undefined" || parentValue === null; + if (!isParentValueUndefined) { + await check(); + } + } + else { + await check(); + } + })); + } + if (settings.enum) { + await Promise.all((await Item.attributesWithSchema(returnObject, model)).map(async (key) => { + const value = utils_1.default.object.get(returnObject, key); + const isValueUndefined = typeof value === "undefined" || value === null; + if (!isValueUndefined) { + const enumArray = await schema.getAttributeSettingValue("enum", key, { "returnFunction": false, typeIndexOptionMap }); + if (enumArray && !enumArray.includes(value)) { + throw new Error_1.default.ValidationError(`${key} must equal ${JSON.stringify(enumArray)}, but is set to ${value}`); + } + } + })); + } + // Map Attributes fromDynamo + mapAttributes("fromDynamo"); + return Object.assign({}, returnObject); +}; +Item.prototype.toDynamo = async function (settings = {}) { + const newSettings = Object.assign(Object.assign({}, settings), { "type": "toDynamo" }); + await Item.prepareForObjectFromSchema(this, this.getInternalProperties(internalProperties).model, newSettings); + const object = await Item.objectFromSchema(this, this.getInternalProperties(internalProperties).model, newSettings); + return Item.objectToDynamo(object); +}; +// This function will modify the item to conform to the Schema +Item.prototype.conformToSchema = async function (settings = { "type": "fromDynamo" }) { + let item = this; + if (settings.type === "fromDynamo") { + item = await this.prepareForResponse(); + } + const model = item.getInternalProperties(internalProperties).model; + await Item.prepareForObjectFromSchema(item, model, settings); + const expectedObject = await Item.objectFromSchema(item, model, settings); + if (!expectedObject) { + return expectedObject; + } + const expectedKeys = Object.keys(expectedObject); + if (settings.mapAttributes) { + const schema = model.getInternalProperties(internalProperties).schemaForObject(expectedObject); + const schemaInternalProperties = schema.getInternalProperties(internalProperties); + const mapSettingObject = schemaInternalProperties.getMapSettingObject(); + for (const key in mapSettingObject) { + const expectedObjectValue = utils_1.default.object.get(expectedObject, key); + if (expectedObjectValue) { + utils_1.default.object.set(this, key, expectedObjectValue); + } + } + } + for (const key in item) { + if (!expectedKeys.includes(key)) { + delete this[key]; + } + else if (this[key] !== expectedObject[key]) { + this[key] = expectedObject[key]; + } + } + return this; +}; diff --git a/index.js b/index.js index 0c2b19de..41e54362 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,8 @@ import { warnings } from "./src/warnings.js"; const kMeriyahDefaultOptions = { next: true, loc: true, - raw: true + raw: true, + jsx: true }; export function runASTAnalysis(str, options = Object.create(null)) { @@ -97,7 +98,10 @@ function parseScriptExtended(strToAnalyze, isEcmaScriptModule) { return body; } catch (error) { - if (error.name === "SyntaxError" && error.description.includes("The import keyword")) { + if (error.name === "SyntaxError" && ( + error.description.includes("The import keyword") || + error.description.includes("The export keyword") + )) { const { body } = meriyah.parseScript(strToAnalyze, { ...kMeriyahDefaultOptions, module: true }); diff --git a/package-lock.json b/package-lock.json index 02a82532..c51ef457 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "6.0.0", "license": "MIT", "dependencies": { - "@nodesecure/estree-ast-utils": "^1.3.0", + "@nodesecure/estree-ast-utils": "^1.3.1", "@nodesecure/sec-literal": "^1.2.0", "estree-walker": "^3.0.1", "is-minified-code": "^2.0.0", @@ -572,9 +572,9 @@ } }, "node_modules/@nodesecure/estree-ast-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@nodesecure/estree-ast-utils/-/estree-ast-utils-1.3.0.tgz", - "integrity": "sha512-o7J+OM8+XHm61WdvF/K8mynPRS3SWUgNmynQm0MVnS3Pbz7yGbj+cKDvpg5EZ2zDpZhVDa5Y0u1+MS77eQl8BA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@nodesecure/estree-ast-utils/-/estree-ast-utils-1.3.1.tgz", + "integrity": "sha512-UiCBBrLiD6Q7Yb2OyVt4Ohse17+2gGgI1dnTnrrMi6EAcnJmodkRA6iFT8QLB+LmSfxo7NUSPPFvhGYnJ0sVRw==", "dependencies": { "@nodesecure/sec-literal": "^1.1.0" } @@ -5261,9 +5261,9 @@ } }, "@nodesecure/estree-ast-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@nodesecure/estree-ast-utils/-/estree-ast-utils-1.3.0.tgz", - "integrity": "sha512-o7J+OM8+XHm61WdvF/K8mynPRS3SWUgNmynQm0MVnS3Pbz7yGbj+cKDvpg5EZ2zDpZhVDa5Y0u1+MS77eQl8BA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@nodesecure/estree-ast-utils/-/estree-ast-utils-1.3.1.tgz", + "integrity": "sha512-UiCBBrLiD6Q7Yb2OyVt4Ohse17+2gGgI1dnTnrrMi6EAcnJmodkRA6iFT8QLB+LmSfxo7NUSPPFvhGYnJ0sVRw==", "requires": { "@nodesecure/sec-literal": "^1.1.0" } diff --git a/package.json b/package.json index 2c8afe7d..f8b30ec6 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ }, "homepage": "https://github.com/NodeSecure/js-x-ray#readme", "dependencies": { - "@nodesecure/estree-ast-utils": "^1.3.0", + "@nodesecure/estree-ast-utils": "^1.3.1", "@nodesecure/sec-literal": "^1.2.0", "estree-walker": "^3.0.1", "is-minified-code": "^2.0.0", diff --git a/src/obfuscators/index.js b/src/obfuscators/index.js index 0ae0d7f5..f5c87153 100644 --- a/src/obfuscators/index.js +++ b/src/obfuscators/index.js @@ -25,8 +25,12 @@ export function isObfuscatedCode(analysis) { } else { // TODO: also implement Dictionnary checkup + const identifiers = analysis.identifiersName + .map((value) => value) + .filter((value) => typeof value === "string"); + const { prefix, oneTimeOccurence } = Patterns.commonHexadecimalPrefix( - analysis.identifiersName.map((value) => value.name) + identifiers ); const uPrefixNames = new Set(Object.keys(prefix)); diff --git a/test/fixtures/regress/jsx.js b/test/fixtures/regress/jsx.js new file mode 100644 index 00000000..fb70f23c --- /dev/null +++ b/test/fixtures/regress/jsx.js @@ -0,0 +1,8 @@ +const Dropzone = forwardRef(({ children, ...params }, ref) => { + const { open, ...props } = useDropzone(params); + + useImperativeHandle(ref, () => ({ open }), [open]); + + // TODO: Figure out why react-styleguidist cannot create docs if we don't return a jsx element + return {children({ ...props, open })}; +}); diff --git a/test/module.spec.js b/test/module.spec.js index da15c83b..aefc44c4 100644 --- a/test/module.spec.js +++ b/test/module.spec.js @@ -4,7 +4,7 @@ import test from "tape"; // Import Internal Dependencies import { runASTAnalysis } from "../index.js"; -test("it should not crash even if module 'false' is provided", (tape) => { +test("it should not crash even if module 'false' is provided (import keyword)", (tape) => { runASTAnalysis("import * as foo from \"foo\";", { module: false }); @@ -12,6 +12,14 @@ test("it should not crash even if module 'false' is provided", (tape) => { tape.end(); }); +test("it should not crash even if module 'false' is provided (export keyword)", (tape) => { + runASTAnalysis("export const foo = 5;", { + module: false + }); + + tape.end(); +}); + test("it should be capable to extract dependencies name for ECMAScript Modules (ESM)", (tape) => { const { dependencies, warnings } = runASTAnalysis(` import * as http from "http"; diff --git a/test/regress.spec.js b/test/regress.spec.js index 5f6a1eac..92a104dd 100644 --- a/test/regress.spec.js +++ b/test/regress.spec.js @@ -14,5 +14,13 @@ const FIXTURE_URL = new URL("fixtures/regress/", import.meta.url); test("it should not crash for prop-types", (tape) => { const propTypes = readFileSync(new URL("prop-types.min.js", FIXTURE_URL), "utf-8"); runASTAnalysis(propTypes); + + tape.end(); +}); + +test("it should not crash for JSX", (tape) => { + const propTypes = readFileSync(new URL("jsx.js", FIXTURE_URL), "utf-8"); + runASTAnalysis(propTypes); + tape.end(); }); From 73a181aa35a3314f5f823550841b12e2081de8a1 Mon Sep 17 00:00:00 2001 From: fraxken Date: Sun, 15 Jan 2023 22:24:26 +0100 Subject: [PATCH 2/2] chore: remove error.js and fix obfuscated detection --- examples/error.js | 594 --------------------------------------- src/obfuscators/index.js | 4 +- test/obfuscated.spec.js | 1 - 3 files changed, 2 insertions(+), 597 deletions(-) delete mode 100644 examples/error.js diff --git a/examples/error.js b/examples/error.js deleted file mode 100644 index 15bf162e..00000000 --- a/examples/error.js +++ /dev/null @@ -1,594 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AnyItem = exports.Item = void 0; -const converter_1 = require("./aws/converter"); -const internal_1 = require("./aws/ddb/internal"); -const utils_1 = require("./utils"); -const Error_1 = require("./Error"); -const Internal_1 = require("./Internal"); -const { internalProperties } = Internal_1.default.General; -const dynamooseUndefined = Internal_1.default.Public.undefined; -const dynamooseAny = Internal_1.default.Public.any; -const Populate_1 = require("./Populate"); -const InternalPropertiesClass_1 = require("./InternalPropertiesClass"); -const Error_2 = require("./Error"); -// Item represents an item in a Model that is either pending (not saved) or saved -class Item extends InternalPropertiesClass_1.InternalPropertiesClass { - /** - * Create a new item. - * @param model Internal property. Not used publicly. - * @param object The object for the item. - * @param settings The settings for the item. - */ - constructor(model, object, settings) { - super(); - const itemObject = Item.isDynamoObject(object) ? Item.fromDynamo(object) : object; - Object.keys(itemObject).forEach((key) => this[key] = itemObject[key]); - this.setInternalProperties(internalProperties, { - "originalObject": utils_1.default.deep_copy(itemObject), - "originalSettings": Object.assign({}, settings), - model, - "storedInDynamo": settings.type === "fromDynamo" - }); - } - static objectToDynamo(object, settings = { "type": "object" }) { - if (object === undefined) { - return undefined; - } - const options = settings.type === "value" ? undefined : { "removeUndefinedValues": true }; - return (settings.type === "value" ? (0, converter_1.default)().convertToAttr : (0, converter_1.default)().marshall)(object, options); - } - static fromDynamo(object) { - const result = (0, converter_1.default)().unmarshall(object); - utils_1.default.object.entries(result).forEach(([key, value]) => { - if (value instanceof Uint8Array) { - utils_1.default.object.set(result, key, Buffer.from(value)); - } - }); - return result; - } - // This function will return null if it's unknown if it is a Dynamo object (ex. empty object). It will return true if it is a Dynamo object and false if it's not. - static isDynamoObject(object, recursive) { - function isValid(value) { - if (typeof value === "undefined" || value === null) { - return false; - } - const keys = Object.keys(value); - const key = keys[0]; - const nestedResult = typeof value[key] === "object" && !(value[key] instanceof Buffer) && !(value[key] instanceof Uint8Array) ? Array.isArray(value[key]) ? value[key].every((value) => Item.isDynamoObject(value, true)) : Item.isDynamoObject(value[key]) : true; - const { Schema } = require("./Schema"); - const attributeType = Schema.attributeTypes.findDynamoDBType(key); - return typeof value === "object" && keys.length === 1 && attributeType && (nestedResult || Object.keys(value[key]).length === 0 || attributeType.isSet); - } - const keys = Object.keys(object); - const values = Object.values(object); - if (keys.length === 0) { - return null; - } - else { - return recursive ? isValid(object) : values.every((value) => isValid(value)); - } - } - // This function handles actions that should take place before every response (get, scan, query, batchGet, etc.) - async prepareForResponse() { - if (this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.populate) { - return this.populate({ "properties": this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.populate }); - } - return this; - } - /** - * This function returns the original item that was received from DynamoDB. This function will return a JSON object that represents the original item. In the event no item has been retrieved from DynamoDB `null` will be returned. - * - * ```js - * const user = await User.get(1); - * console.log(user); // {"id": 1, "name": "Bob"} - * user.name = "Tim"; - * - * console.log(user); // {"id": 1, "name": "Tim"} - * console.log(user.original()); // {"id": 1, "name": "Bob"} - * ``` - * @returns Object | null - */ - original() { - return this.getInternalProperties(internalProperties).originalSettings.type === "fromDynamo" ? this.getInternalProperties(internalProperties).originalObject : null; - } - /** - * This function returns a JSON object representation of the item. This is most commonly used when comparing a item to an object you receive elsewhere without worrying about prototypes. - * - * ```js - * const user = new User({"id": 1, "name": "Tim"}); - * - * console.log(user); // Item {"id": 1, "name": "Tim"} - * console.log(user.toJSON()); // {"id": 1, "name": "Tim"} - * ``` - * - * Due to the fact that a item instance is based on an object it is rare that you will have to use this function since you can access all the properties of the item directly. For example, both of the results will yield the same output. - * - * ```js - * const user = new User({"id": 1, "name": "Tim"}); - * - * console.log(user.id); // 1 - * console.log(user.toJSON().id); // 1 - * ``` - * @returns Object - */ - toJSON() { - return utils_1.default.dynamoose.itemToJSON.bind(this)(); - } - /** - * This method will return a promise containing an object of the item that includes default values for any undefined values in the item. - * - * ```js - * const schema = new Schema({ - * "id": String, - * "data": { - * "type": String, - * "default": "Hello World" - * } - * }); - * const User = dynamoose.model("User", schema); - * const user = new User({"id": 1}); - * console.log(await user.withDefaults()); // {"id": 1, "data": "Hello World"} - * ``` - * @returns Promise - */ - async withDefaults() { - return Item.objectFromSchema(utils_1.default.deep_copy(this.toJSON()), this.getInternalProperties(internalProperties).model, { - "typeCheck": false, - "defaults": true, - "type": "toDynamo" - }); - } - // Serializer - serialize(nameOrOptions) { - return this.getInternalProperties(internalProperties).model.serializer.getInternalProperties(internalProperties).serialize(this, nameOrOptions); - } - delete(callback) { - const hashKey = this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).getHashKey(); - const rangeKey = this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).getRangeKey(); - const key = { [hashKey]: this[hashKey] }; - if (rangeKey) { - key[rangeKey] = this[rangeKey]; - } - return this.getInternalProperties(internalProperties).model.delete(key, callback); - } - save(settings, callback) { - if (typeof settings !== "object" && typeof settings !== "undefined") { - callback = settings; - settings = {}; - } - if (typeof settings === "undefined") { - settings = {}; - } - const table = this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).table(); - let savedItem; - const localSettings = settings; - const paramsPromise = this.toDynamo({ "defaults": true, "validate": true, "required": true, "enum": true, "forceDefault": true, "combine": true, "saveUnknown": true, "customTypesDynamo": true, "updateTimestamps": true, "modifiers": ["set"], "mapAttributes": true }).then(async (item) => { - savedItem = item; - let putItemObj = { - "Item": item, - "TableName": table.getInternalProperties(internalProperties).name - }; - if (localSettings.condition) { - putItemObj = Object.assign(Object.assign({}, putItemObj), await localSettings.condition.getInternalProperties(internalProperties).requestObject(this.getInternalProperties(internalProperties).model)); - } - if (localSettings.overwrite === false) { - const conditionExpression = "attribute_not_exists(#__hash_key)"; - putItemObj.ConditionExpression = putItemObj.ConditionExpression ? `(${putItemObj.ConditionExpression}) AND (${conditionExpression})` : conditionExpression; - putItemObj.ExpressionAttributeNames = Object.assign(Object.assign({}, putItemObj.ExpressionAttributeNames || {}), { "#__hash_key": this.getInternalProperties(internalProperties).model.getInternalProperties(internalProperties).getHashKey() }); - } - return putItemObj; - }); - if (settings.return === "request") { - if (callback) { - const localCallback = callback; - paramsPromise.then((result) => localCallback(null, result)); - return; - } - else { - return paramsPromise; - } - } - const promise = Promise.all([paramsPromise, table.getInternalProperties(internalProperties).pendingTaskPromise()]).then((promises) => { - const [putItemObj] = promises; - return (0, internal_1.default)(table.getInternalProperties(internalProperties).instance, "putItem", putItemObj); - }); - if (callback) { - const localCallback = callback; - promise.then(() => { - this.getInternalProperties(internalProperties).storedInDynamo = true; - const returnItem = new (this.getInternalProperties(internalProperties).model).Item(savedItem); - returnItem.getInternalProperties(internalProperties).storedInDynamo = true; - localCallback(null, returnItem); - }).catch((error) => callback(error)); - } - else { - return (async () => { - await promise; - this.getInternalProperties(internalProperties).storedInDynamo = true; - const returnItem = new (this.getInternalProperties(internalProperties).model).Item(savedItem); - returnItem.getInternalProperties(internalProperties).storedInDynamo = true; - return returnItem; - })(); - } - } - populate(...args) { - return Populate_1.PopulateItem.bind(this)(...args); - } -} -exports.Item = Item; -class AnyItem extends Item { -} -exports.AnyItem = AnyItem; -// This function will mutate the object passed in to run any actions to conform to the schema that cannot be achieved through non mutating methods in Item.objectFromSchema (setting timestamps, etc.) -Item.prepareForObjectFromSchema = async function (object, model, settings) { - if (settings.updateTimestamps) { - const schema = model.getInternalProperties(internalProperties).schemaForObject(object); - if (schema.getInternalProperties(internalProperties).settings.timestamps && settings.type === "toDynamo") { - const date = new Date(); - const timeResult = (prop) => { - const typeDetails = schema.getAttributeTypeDetails(prop.name); - if (Array.isArray(typeDetails)) { - throw new Error_2.default.InvalidType(`Not allowed to use an array of types for the timestamps attribute "${prop.name}".`); - } - switch (typeDetails.typeSettings.storage) { - case "iso": return date.toISOString(); - case "seconds": return Math.floor(date.getTime() / 1000); - default: return date.getTime(); - } - }; - const timestampProperties = schema.getInternalProperties(internalProperties).getTimestampAttributes(); - const createdAtProperties = timestampProperties.filter((val) => val.type === "createdAt"); - const updatedAtProperties = timestampProperties.filter((val) => val.type === "updatedAt"); - if (object.getInternalProperties && object.getInternalProperties(internalProperties) && !object.getInternalProperties(internalProperties).storedInDynamo && (typeof settings.updateTimestamps === "boolean" || settings.updateTimestamps.createdAt)) { - createdAtProperties.forEach((prop) => { - utils_1.default.object.set(object, prop.name, timeResult(prop)); - }); - } - if (typeof settings.updateTimestamps === "boolean" || settings.updateTimestamps.updatedAt) { - updatedAtProperties.forEach((prop) => { - utils_1.default.object.set(object, prop.name, timeResult(prop)); - }); - } - } - } - return object; -}; -// This function will return a list of attributes combining both the schema attributes with the item attributes. This also takes into account all attributes that could exist (ex. properties in sets that don't exist in item), adding the indexes for each item in the item set. -// https://stackoverflow.com/a/59928314/894067 -Item.attributesWithSchema = async function (item, model) { - const schema = model.getInternalProperties(internalProperties).schemaForObject(item); - const attributes = schema.attributes(); - // build a tree out of schema attributes - const root = {}; - attributes.forEach((attribute) => { - let node = root; - attribute.split(".").forEach((part) => { - node[part] = node[part] || {}; - node = node[part]; - }); - }); - // explore the tree - function traverse(node, treeNode, outPath, callback) { - callback(outPath); - if (Object.keys(treeNode).length === 0) { // a leaf - return; - } - Object.keys(treeNode).forEach((attr) => { - if (attr === "0") { - // We check for empty objects here (added `typeof node === "object" && Object.keys(node).length == 0`, see PR https://github.com/dynamoose/dynamoose/pull/1034) to handle the use case of 2d arrays, or arrays within arrays. `node` in that case will be an empty object. - if (!node || node.length == 0 || typeof node === "object" && Object.keys(node).length == 0) { - node = [{}]; // fake the path for arrays - } - node.forEach((a, index) => { - outPath.push(index); - traverse(node[index], treeNode[attr], outPath, callback); - outPath.pop(); - }); - } - else { - if (!node) { - node = {}; // fake the path for properties - } - outPath.push(attr); - traverse(node[attr], treeNode[attr], outPath, callback); - outPath.pop(); - } - }); - } - const out = []; - traverse(item, root, [], (val) => out.push(val.join("."))); - const result = out.slice(1); - return result; -}; -// This function will return an object that conforms to the schema (removing any properties that don't exist, using default values, etc.) & throws an error if there is a type mismatch. -Item.objectFromSchema = async function (object, model, settings = { "type": "toDynamo" }) { - if (settings.checkExpiredItem && model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.expires && (model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.expires.items || {}).returnExpired === false && object[model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.expires.attribute] && object[model.getInternalProperties(internalProperties).table().getInternalProperties(internalProperties).options.expires.attribute] * 1000 < Date.now()) { - return undefined; - } - let returnObject = utils_1.default.deep_copy(object); - const schema = settings.schema || model.getInternalProperties(internalProperties).schemaForObject(returnObject); - const schemaAttributes = schema.attributes(returnObject); - function mapAttributes(type) { - if (settings.mapAttributes && settings.type === type) { - const schemaInternalProperties = schema.getInternalProperties(internalProperties); - const mappedAttributesObject = type === "toDynamo" ? schemaInternalProperties.getMapSettingObject() : schema.attributes().reduce((obj, attribute) => { - const mapValues = schemaInternalProperties.getMapSettingValuesForKey(attribute); - if (mapValues && mapValues.length > 0) { - const defaultMapAttribute = schema.getInternalProperties(internalProperties).getDefaultMapAttribute(attribute); - if (defaultMapAttribute) { - if (defaultMapAttribute !== attribute) { - obj[attribute] = defaultMapAttribute; - } - } - else { - obj[attribute] = mapValues[0]; - } - } - return obj; - }, {}); - Object.entries(mappedAttributesObject).forEach(([oldKey, newKey]) => { - if (returnObject[oldKey] !== undefined && returnObject[newKey] !== undefined) { - throw new Error_2.default.InvalidParameter(`Cannot map attribute ${oldKey} to ${newKey} because both are defined`); - } - if (returnObject[oldKey] !== undefined) { - returnObject[newKey] = returnObject[oldKey]; - delete returnObject[oldKey]; - } - }); - } - } - // Map Attributes toDynamo - mapAttributes("toDynamo"); - // Type check - const typeIndexOptionMap = schema.getTypePaths(returnObject, settings); - if (settings.typeCheck === undefined || settings.typeCheck === true) { - const validParents = []; // This array is used to allow for set contents to not be type checked - const keysToDelete = []; - const checkTypeFunction = (item) => { - const [key, value] = item; - if (validParents.find((parent) => key.startsWith(parent.key) && (parent.infinite || key.split(".").length === parent.key.split(".").length + 1))) { - return; - } - const genericKey = key.replace(/\.\d+/gu, ".0"); // This is a key replacing all list numbers with 0 to standardize things like checking if it exists in the schema - const existsInSchema = schemaAttributes.includes(genericKey); - if (existsInSchema) { - const { isValidType, matchedTypeDetails, typeDetailsArray } = utils_1.default.dynamoose.getValueTypeCheckResult(schema, value, genericKey, settings, { "standardKey": true, typeIndexOptionMap }); - if (!isValidType) { - throw new Error_1.default.TypeMismatch(`Expected ${key} to be of type ${typeDetailsArray.map((detail) => detail.dynamicName ? detail.dynamicName() : detail.name.toLowerCase()).join(", ")}, instead found type ${utils_1.default.type_name(value, typeDetailsArray)}.`); - } - else if (matchedTypeDetails.isSet || matchedTypeDetails.name.toLowerCase() === "model" || (matchedTypeDetails.name === "Object" || matchedTypeDetails.name === "Array") && schema.getAttributeSettingValue("schema", genericKey) === dynamooseAny) { - validParents.push({ key, "infinite": true }); - } - else if ( /*typeDetails.dynamodbType === "M" || */matchedTypeDetails.dynamodbType === "L") { - // The code below is an optimization for large array types to speed up the process of not having to check the type for every element but only the ones that are different - value.forEach((subValue, index, array) => { - if (index === 0 || typeof subValue !== typeof array[0]) { - checkTypeFunction([`${key}.${index}`, subValue]); - } - else if (keysToDelete.includes(`${key}.0`) && typeof subValue === typeof array[0]) { - keysToDelete.push(`${key}.${index}`); - } - }); - validParents.push({ key }); - } - } - else { - // Check saveUnknown - if (!settings.saveUnknown || !utils_1.default.dynamoose.wildcard_allowed_check(schema.getSettingValue("saveUnknown"), key)) { - keysToDelete.push(key); - } - } - }; - utils_1.default.object.entries(returnObject).filter((item) => item[1] !== undefined && item[1] !== dynamooseUndefined).map(checkTypeFunction); - keysToDelete.reverse().forEach((key) => utils_1.default.object.delete(returnObject, key)); - } - if (settings.defaults || settings.forceDefault) { - await Promise.all((await Item.attributesWithSchema(returnObject, model)).map(async (key) => { - const value = utils_1.default.object.get(returnObject, key); - if (value === dynamooseUndefined) { - utils_1.default.object.set(returnObject, key, undefined); - } - else { - const defaultValue = await schema.defaultCheck(key, value, settings); - const isDefaultValueUndefined = Array.isArray(defaultValue) ? defaultValue.some((defaultValue) => typeof defaultValue === "undefined" || defaultValue === null) : typeof defaultValue === "undefined" || defaultValue === null; - const parentKey = utils_1.default.parentKey(key); - const parentValue = parentKey.length === 0 ? returnObject : utils_1.default.object.get(returnObject, parentKey); - if (!isDefaultValueUndefined) { - const { isValidType, typeDetailsArray } = utils_1.default.dynamoose.getValueTypeCheckResult(schema, defaultValue, key, settings, { typeIndexOptionMap }); - if (!isValidType) { - throw new Error_1.default.TypeMismatch(`Expected ${key} to be of type ${typeDetailsArray.map((detail) => detail.dynamicName ? detail.dynamicName() : detail.name.toLowerCase()).join(", ")}, instead found type ${typeof defaultValue}.`); - } - else if (typeof parentValue !== "undefined" && parentValue !== null) { - utils_1.default.object.set(returnObject, key, defaultValue); - } - } - } - })); - } - // Custom Types - if (settings.customTypesDynamo) { - (await Item.attributesWithSchema(returnObject, model)).map((key) => { - const value = utils_1.default.object.get(returnObject, key); - const isValueUndefined = typeof value === "undefined" || value === null; - if (!isValueUndefined) { - const typeDetails = utils_1.default.dynamoose.getValueTypeCheckResult(schema, value, key, settings, { typeIndexOptionMap }).matchedTypeDetails; - const { customType } = typeDetails; - const { "type": typeInfo } = typeDetails.isOfType(value); - const isCorrectTypeAlready = typeInfo === (settings.type === "toDynamo" ? "underlying" : "main"); - if (customType && customType.functions[settings.type] && !isCorrectTypeAlready) { - const customValue = customType.functions[settings.type](value); - utils_1.default.object.set(returnObject, key, customValue); - } - } - }); - } - // DynamoDB Type Handler (ex. converting sets to correct value for toDynamo & fromDynamo) - utils_1.default.object.entries(returnObject).filter((item) => typeof item[1] === "object").forEach((item) => { - const [key, value] = item; - let typeDetails; - try { - typeDetails = utils_1.default.dynamoose.getValueTypeCheckResult(schema, value, key, settings, { typeIndexOptionMap }).matchedTypeDetails; - } - catch (e) { - const { Schema } = require("./Schema"); - typeDetails = Schema.attributeTypes.findTypeForValue(value, settings.type, settings); - } - if (typeDetails && typeDetails[settings.type]) { - utils_1.default.object.set(returnObject, key, typeDetails[settings.type](value)); - } - }); - if (settings.combine) { - schemaAttributes.map((key) => { - try { - const typeDetails = schema.getAttributeTypeDetails(key); - return { - key, - "type": typeDetails - }; - } - catch (e) { } // eslint-disable-line no-empty - }).filter((item) => { - return Array.isArray(item.type) ? item.type.some((type) => type.name === "Combine") : item.type.name === "Combine"; - }).map((obj) => { - if (obj && Array.isArray(obj.type)) { - throw new Error_1.default.InvalidParameter("Combine type is not allowed to be used with multiple types."); - } - return obj; - }).forEach((item) => { - const { key, type } = item; - const value = type.typeSettings.attributes.map((attribute) => utils_1.default.object.get(returnObject, attribute)).filter((value) => typeof value !== "undefined" && value !== null).join(type.typeSettings.separator); - utils_1.default.object.set(returnObject, key, value); - }); - } - if (settings.modifiers) { - await Promise.all(settings.modifiers.map(async (modifier) => { - await Promise.all((await Item.attributesWithSchema(returnObject, model)).map(async (key) => { - const value = utils_1.default.object.get(returnObject, key); - const modifierFunction = await schema.getAttributeSettingValue(modifier, key, { "returnFunction": true, typeIndexOptionMap }); - const modifierFunctionExists = Array.isArray(modifierFunction) ? modifierFunction.some((val) => Boolean(val)) : Boolean(modifierFunction); - const isValueUndefined = typeof value === "undefined" || value === null; - if (modifierFunctionExists && !isValueUndefined) { - const oldValue = object.original ? utils_1.default.object.get(object.original(), key) : undefined; - utils_1.default.object.set(returnObject, key, await modifierFunction(value, oldValue)); - } - })); - const schemaModifier = schema.getInternalProperties(internalProperties).settings[modifier]; - if (schemaModifier) { - returnObject = await schemaModifier(returnObject); - } - })); - } - if (settings.validate) { - await Promise.all((await Item.attributesWithSchema(returnObject, model)).map(async (key) => { - const value = utils_1.default.object.get(returnObject, key); - const isValueUndefined = typeof value === "undefined" || value === null; - if (!isValueUndefined) { - const validator = await schema.getAttributeSettingValue("validate", key, { "returnFunction": true, typeIndexOptionMap }); - if (validator) { - let result; - if (validator instanceof RegExp) { - if (typeof value === "string") { - result = validator.test(value); - } - else { - throw new Error_1.default.ValidationError(`Trying to pass in ${typeof value} to a RegExp validator for key: ${key}.`); - } - } - else { - result = typeof validator === "function" ? await validator(value) : validator === value; - } - if (!result) { - throw new Error_1.default.ValidationError(`${key} with a value of ${value} had a validation error when trying to save the item`); - } - } - } - })); - const schemaValidator = schema.getInternalProperties(internalProperties).settings.validate; - if (schemaValidator) { - const result = await schemaValidator(returnObject); - if (!result) { - throw new Error_1.default.ValidationError(`${JSON.stringify(returnObject)} had a schema validation error when trying to save the item.`); - } - } - } - if (settings.required) { - let attributesToCheck = await Item.attributesWithSchema(returnObject, model); - if (settings.required === "nested") { - attributesToCheck = attributesToCheck.filter((attribute) => utils_1.default.object.keys(returnObject).find((key) => attribute === key || attribute.startsWith(key + "."))); - } - await Promise.all(attributesToCheck.map(async (key) => { - const check = async () => { - const value = utils_1.default.object.get(returnObject, key); - await schema.requiredCheck(key, value); - }; - const keyParts = key.split("."); - const parentKey = keyParts.slice(0, -1).join("."); - if (parentKey) { - const parentValue = utils_1.default.object.get(returnObject, parentKey); - const isParentValueUndefined = typeof parentValue === "undefined" || parentValue === null; - if (!isParentValueUndefined) { - await check(); - } - } - else { - await check(); - } - })); - } - if (settings.enum) { - await Promise.all((await Item.attributesWithSchema(returnObject, model)).map(async (key) => { - const value = utils_1.default.object.get(returnObject, key); - const isValueUndefined = typeof value === "undefined" || value === null; - if (!isValueUndefined) { - const enumArray = await schema.getAttributeSettingValue("enum", key, { "returnFunction": false, typeIndexOptionMap }); - if (enumArray && !enumArray.includes(value)) { - throw new Error_1.default.ValidationError(`${key} must equal ${JSON.stringify(enumArray)}, but is set to ${value}`); - } - } - })); - } - // Map Attributes fromDynamo - mapAttributes("fromDynamo"); - return Object.assign({}, returnObject); -}; -Item.prototype.toDynamo = async function (settings = {}) { - const newSettings = Object.assign(Object.assign({}, settings), { "type": "toDynamo" }); - await Item.prepareForObjectFromSchema(this, this.getInternalProperties(internalProperties).model, newSettings); - const object = await Item.objectFromSchema(this, this.getInternalProperties(internalProperties).model, newSettings); - return Item.objectToDynamo(object); -}; -// This function will modify the item to conform to the Schema -Item.prototype.conformToSchema = async function (settings = { "type": "fromDynamo" }) { - let item = this; - if (settings.type === "fromDynamo") { - item = await this.prepareForResponse(); - } - const model = item.getInternalProperties(internalProperties).model; - await Item.prepareForObjectFromSchema(item, model, settings); - const expectedObject = await Item.objectFromSchema(item, model, settings); - if (!expectedObject) { - return expectedObject; - } - const expectedKeys = Object.keys(expectedObject); - if (settings.mapAttributes) { - const schema = model.getInternalProperties(internalProperties).schemaForObject(expectedObject); - const schemaInternalProperties = schema.getInternalProperties(internalProperties); - const mapSettingObject = schemaInternalProperties.getMapSettingObject(); - for (const key in mapSettingObject) { - const expectedObjectValue = utils_1.default.object.get(expectedObject, key); - if (expectedObjectValue) { - utils_1.default.object.set(this, key, expectedObjectValue); - } - } - } - for (const key in item) { - if (!expectedKeys.includes(key)) { - delete this[key]; - } - else if (this[key] !== expectedObject[key]) { - this[key] = expectedObject[key]; - } - } - return this; -}; diff --git a/src/obfuscators/index.js b/src/obfuscators/index.js index f5c87153..b9f16b69 100644 --- a/src/obfuscators/index.js +++ b/src/obfuscators/index.js @@ -26,8 +26,8 @@ export function isObfuscatedCode(analysis) { else { // TODO: also implement Dictionnary checkup const identifiers = analysis.identifiersName - .map((value) => value) - .filter((value) => typeof value === "string"); + .map((value) => value?.name ?? null) + .filter((name) => typeof name === "string"); const { prefix, oneTimeOccurence } = Patterns.commonHexadecimalPrefix( identifiers diff --git a/test/obfuscated.spec.js b/test/obfuscated.spec.js index 3efae06f..c2dd5cf5 100644 --- a/test/obfuscated.spec.js +++ b/test/obfuscated.spec.js @@ -46,7 +46,6 @@ test("should detect 'freejsobfuscator' obfuscation", (tape) => { const trycatch = readFileSync(new URL("freejsobfuscator.js", FIXTURE_URL), "utf-8"); const { warnings } = runASTAnalysis(trycatch); - tape.strictEqual(warnings.length, 3); tape.deepEqual(getWarningKind(warnings), [ "encoded-literal", "encoded-literal", "obfuscated-code" ].sort());