From 5e456e2bebbf68c49e8d1f64bd289548db676ba1 Mon Sep 17 00:00:00 2001 From: Ben Baryo <60312583+BenBaryoPX@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:55:26 +0300 Subject: [PATCH] Refactor to ESM, Refactor Tests to Node's Test Runner (#117) * Update dependencies * Update eslint config * ESM port * Fix for running on Apple based Macs * Add missing ignore rules * Improve export visibility * Adjust documentation for ESM * Refactor tests to use Node's builtin test runner * Run tests with coverage * Remove unused code * Fix setting max iterations not applied * Use API for setting log level * Add tests for createNewNode --- .eslintignore | 4 - .eslintrc.js | 39 - .github/workflows/node.js.yml | 2 +- README.md | 28 +- eslint.config.js | 56 + index.js | 8 +- package-lock.json | 1462 ++++++++++++----- package.json | 18 +- src/modules/config.js | 28 +- src/modules/index.js | 10 +- src/modules/safe/index.js | 62 +- src/modules/safe/normalizeComputed.js | 4 +- src/modules/safe/normalizeEmptyStatements.js | 2 +- ...parseTemplateLiteralsIntoStringLiterals.js | 4 +- src/modules/safe/rearrangeSequences.js | 2 +- src/modules/safe/rearrangeSwitches.js | 4 +- src/modules/safe/removeDeadNodes.js | 9 +- .../safe/removeRedundantBlockStatements.js | 2 +- .../safe/replaceBooleanExpressionsWithIf.js | 2 +- ...eCallExpressionsWithUnwrappedIdentifier.js | 2 +- .../replaceEvalCallsWithLiteralContent.js | 9 +- .../replaceFunctionShellsWithWrappedValue.js | 2 +- ...placeFunctionShellsWithWrappedValueIIFE.js | 2 +- ...replaceIdentifierWithFixedAssignedValue.js | 4 +- ...rWithFixedValueNotAssignedAtDeclaration.js | 6 +- .../replaceNewFuncCallsWithLiteralContent.js | 9 +- .../safe/replaceSequencesWithExpressions.js | 2 +- .../safe/resolveDeterministicIfStatements.js | 2 +- .../safe/resolveFunctionConstructorCalls.js | 4 +- ...eMemberExpressionReferencesToArrayIndex.js | 5 +- ...veMemberExpressionsWithDirectAssignment.js | 2 +- src/modules/safe/resolveProxyCalls.js | 2 +- src/modules/safe/resolveProxyReferences.js | 8 +- src/modules/safe/resolveProxyVariables.js | 4 +- .../resolveRedundantLogicalExpressions.js | 2 +- .../safe/separateChainedDeclarators.js | 2 +- src/modules/safe/simplifyCalls.js | 2 +- src/modules/safe/simplifyIfStatements.js | 2 +- src/modules/safe/unwrapFunctionShells.js | 2 +- src/modules/safe/unwrapIIFEs.js | 2 +- src/modules/safe/unwrapSimpleOperations.js | 2 +- src/modules/unsafe/index.js | 26 +- .../unsafe/normalizeRedundantNotOperator.js | 10 +- ...gmentedFunctionWrappedArrayReplacements.js | 14 +- src/modules/unsafe/resolveBuiltinCalls.js | 17 +- .../resolveDefiniteBinaryExpressions.js | 10 +- .../resolveDefiniteMemberExpressions.js | 8 +- ...olveDeterministicConditionalExpressions.js | 6 +- .../unsafe/resolveEvalCallsOnNonLiterals.js | 14 +- src/modules/unsafe/resolveFunctionToArray.js | 18 +- .../resolveInjectedPrototypeMethodCalls.js | 19 +- src/modules/unsafe/resolveLocalCalls.js | 22 +- ...resolveMemberExpressionsLocalReferences.js | 18 +- src/modules/unsafe/resolveMinimalAlphabet.js | 12 +- src/modules/utils/areReferencesModified.js | 4 +- .../utils/canUnaryExpressionBeResolved.js | 2 +- src/modules/utils/createNewNode.js | 9 +- src/modules/utils/createOrderedSrc.js | 4 +- ...doesBinaryExpressionContainOnlyLiterals.js | 2 +- src/modules/utils/evalInVm.js | 19 +- src/modules/utils/evalWithDom.js | 13 +- src/modules/utils/generateHash.js | 4 +- src/modules/utils/getCache.js | 2 +- src/modules/utils/getCalleeName.js | 2 +- .../utils/getDeclarationWithContext.js | 16 +- src/modules/utils/getDescendants.js | 2 +- ...getMainDeclaredObjectOfMemberExpression.js | 2 +- src/modules/utils/getObjType.js | 2 +- src/modules/utils/index.js | 39 +- src/modules/utils/isNodeInRanges.js | 2 +- src/modules/utils/isNodeMarked.js | 15 - src/modules/utils/normalizeScript.js | 21 +- src/modules/utils/runLoop.js | 71 - src/modules/utils/safe-atob.js | 2 +- src/modules/utils/safe-btoa.js | 2 +- src/modules/utils/safeImplementations.js | 7 +- src/modules/utils/sandbox.js | 9 +- src/processors/augmentedArray.js | 23 +- src/processors/caesarp.js | 12 +- src/processors/functionToArray.js | 14 +- src/processors/index.js | 16 +- src/processors/obfuscatorIo.js | 8 +- src/restringer.js | 51 +- src/utils/cleanScript.js | 4 +- src/utils/parseArgs.js | 18 +- tests/README.md | 77 - tests/deobfuscation-tests.js | 388 ----- tests/deobfuscation.test.js | 394 +++++ tests/functionality.test.js | 15 + tests/modules-tests.js | 855 ---------- tests/modules.test.js | 963 +++++++++++ tests/obfuscated-samples.js | 14 - tests/processors-tests.js | 93 -- tests/processors.test.js | 117 ++ tests/resources/localProxies.js-deob.js | 8 + tests/resources/udu.js-deob.js | 24 + tests/samples.test.js | 109 ++ tests/testDeobfuscations.js | 42 - tests/testModules.js | 72 - tests/testObfuscatedSamples.js | 39 - tests/testProcessors.js | 49 - tests/testRestringer.js | 16 - tests/testUtils.js | 40 - tests/utils-tests.js | 272 --- tests/utils.test.js | 171 ++ 105 files changed, 3261 insertions(+), 2911 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.js create mode 100644 eslint.config.js delete mode 100644 src/modules/utils/isNodeMarked.js delete mode 100644 src/modules/utils/runLoop.js delete mode 100644 tests/README.md delete mode 100644 tests/deobfuscation-tests.js create mode 100644 tests/deobfuscation.test.js create mode 100644 tests/functionality.test.js delete mode 100644 tests/modules-tests.js create mode 100644 tests/modules.test.js delete mode 100644 tests/obfuscated-samples.js delete mode 100644 tests/processors-tests.js create mode 100644 tests/processors.test.js create mode 100644 tests/samples.test.js delete mode 100644 tests/testDeobfuscations.js delete mode 100644 tests/testModules.js delete mode 100644 tests/testObfuscatedSamples.js delete mode 100644 tests/testProcessors.js delete mode 100644 tests/testRestringer.js delete mode 100644 tests/testUtils.js delete mode 100644 tests/utils-tests.js create mode 100644 tests/utils.test.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index e197a0a..0000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -tests/resources -jquery*.js -*tmp*.* -*tmp*/ \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 2b0d438..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,39 +0,0 @@ -module.exports = { - env: { - browser: true, - node: true, - commonjs: true, - es2021: true, - }, - extends: 'eslint:recommended', - parserOptions: { - ecmaVersion: 'latest', - }, - rules: { - indent: [ - 'error', - 'tab', - { - SwitchCase: 1 - }, - ], - 'linebreak-style': [ - 'error', - 'unix', - ], - quotes: [ - 'error', - 'single', - { - allowTemplateLiterals: true - }, - ], - semi: [ - 'error', - 'always', - ], - 'no-empty': [ - 'off', - ], - }, -}; diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index ec47948..f64baf2 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -27,4 +27,4 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm install - - run: npm test + - run: npm run test:coverage diff --git a/README.md b/README.md index 7338286..a8a9a2c 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,11 @@ The basic structure of such a deobfuscator would be an array of deobfuscation mo Unsafe modules run code through `eval` (using [isolated-vm](https://www.npmjs.com/package/isolated-vm) to be on the safe side) while safe modules do not. ```javascript -const { - safe: {normalizeComputed}, - unsafe: {resolveDefiniteBinaryExpressions, resolveLocalCalls}, -} = require('restringer').deobModules; -const {applyIteratively} = require('flast').utils; +import {safe, unsafe} from 'restringer'; +const {normalizeComputed} = safe; +const {resolveDefiniteBinaryExpressions, resolveLocalCalls} = unsafe; +import {utils} from 'flast'; +const {applyIteratively} = utils; let script = 'obfuscated JS here'; const deobModules = [ resolveDefiniteBinaryExpressions, @@ -103,10 +103,10 @@ console.log(script); // Deobfuscated script With the additional `candidateFilter` function argument, it's possible to narrow down the targeted nodes: ```javascript -const { - unsafe: {resolveLocalCalls}, -} = require('restringer').deobModules; -const {applyIteratively} = require('flast').utils; +import {unsafe} from 'restringer'; +const {resolveLocalCalls} = unsafe; +import {utils} from 'flast'; +const {applyIteratively} = utils; let script = 'obfuscated JS here'; // It's better to define a function with a meaningful name that can show up in the log @@ -119,8 +119,8 @@ console.log(script); // Deobfuscated script You can also customize any deobfuscation method while still using REstringer without running the loop yourself: ```javascript -const fs = require('node:fs'); -const {REstringer} = require('restringer'); +import fs from 'node:fs'; +import {REstringer} from 'restringer'; const inputFilename = process.argv[2]; const code = fs.readFileSync(inputFilename, 'utf-8'); @@ -145,9 +145,10 @@ if (res.script !== code) { ### Boilerplate code for starting from scratch ```javascript -const {logger, applyIteratively, treeModifier} = require('flast').utils; +import {utils} from 'flast'; +const {applyIteratively, treeModifier, logger} = utils; // Optional loading from file -// const fs = require('node:fs'); +// import fs from 'node:fs'; // const inputFilename = process.argv[2] || 'target.js'; // const code = fs.readFileSync(inputFilename, 'utf-8'); const code = `(function() { @@ -178,7 +179,6 @@ if (code !== script) { ## Read More * [Processors](src/processors/README.md) -* [Tests](tests/README.md) * [Contribution guide](CONTRIBUTING.md) * [Obfuscation Detector](https://github.com/PerimeterX/obfuscation-detector/blob/main/README.md) * [flAST](https://github.com/PerimeterX/flast/blob/main/README.md) diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..86ea25d --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,56 @@ +import globals from 'globals'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import js from '@eslint/js'; +import {FlatCompat} from '@eslint/eslintrc'; +import babelParser from "@babel/eslint-parser"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); + +export default [ + { + ignores: [ + 'tests/resources', + '**/jquery*.js', + '**/*tmp*.*', + '**/*tmp*/', + "eslint.config.js", + "node_modules/", + ], + }, + ...compat.extends('eslint:recommended'), + { + languageOptions: { + parser: babelParser, + parserOptions: { + requireConfigFile: false, + babelOptions: { + plugins: ['@babel/plugin-syntax-import-assertions'], + } + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.commonjs, + }, + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: { + indent: ['error', 'tab', { + SwitchCase: 1, + }], + 'linebreak-style': ['error', 'unix'], + quotes: ['error', 'single', { + allowTemplateLiterals: true, + }], + semi: ['error', 'always'], + 'no-empty': ['off'], + }, + }]; \ No newline at end of file diff --git a/index.js b/index.js index 83c1d9d..9d78389 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,3 @@ -module.exports = { - REstringer: require(__dirname + '/src/restringer'), - deobModules: require(__dirname + '/src/modules'), - processors: require(__dirname + '/src/processors'), -}; \ No newline at end of file +export * from './src/restringer.js'; +export * from './src/modules/index.js'; +export * from './src/processors/index.js'; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9243efb..d380c3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,17 +9,19 @@ "version": "1.10.4", "license": "MIT", "dependencies": { - "flast": "^1.7.1", + "flast": "^2.0.0", "isolated-vm": "^5.0.1", - "jsdom": "^24.1.0", - "obfuscation-detector": "^1.1.7" + "jsdom": "^25.0.1", + "obfuscation-detector": "^2.0.0" }, "bin": { "restringer": "src/restringer.js" }, "devDependencies": { - "eslint": "^8.56.0", - "husky": "^8.0.3" + "@babel/eslint-parser": "^7.25.8", + "@babel/plugin-syntax-import-assertions": "^7.25.7", + "eslint": "^9.12.0", + "husky": "^9.1.6" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -31,6 +33,500 @@ "node": ">=0.10.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/highlight": "^7.25.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", + "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.8", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.8", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.8.tgz", + "integrity": "sha512-Po3VLMN7fJtv0nsOjBDSbO1J71UhzShE9MuOSkWEV9IZQXzhZklYtzKZ8ZD/Ij3a0JBv1AG3Ny2L3jvAHQVOGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.25.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.25.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.7.tgz", + "integrity": "sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -47,24 +543,51 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", + "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -72,33 +595,67 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "levn": "^0.4.1" }, "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -114,52 +671,125 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">= 8" + "node": ">=6.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", + "peer": true, "engines": { - "node": ">= 8" + "node": ">=6.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": ">= 8" + "node": ">=8.0.0" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, "node_modules/acorn": { "version": "8.12.1", @@ -196,6 +826,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -207,15 +838,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -235,7 +857,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/asynckit": { "version": "0.4.0", @@ -246,7 +869,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -282,11 +906,46 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/browserslist": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -315,10 +974,33 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001668", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz", + "integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -373,7 +1055,16 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -390,11 +1081,12 @@ } }, "node_modules/cssstyle": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", - "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "license": "MIT", "dependencies": { - "rrweb-cssom": "^0.6.0" + "rrweb-cssom": "^0.7.1" }, "engines": { "node": ">=18" @@ -477,17 +1169,13 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/electron-to-chromium": { + "version": "1.5.38", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.38.tgz", + "integrity": "sha512-VbeVexmZ1IFh+5EfrYz1I0HTzHVIlJa112UEWhciPyeOcKJGeTv6N8WnG4wsQB81DGCaVEGhpSb6o6a8WYFXXg==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } + "license": "ISC", + "peer": true }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -508,6 +1196,17 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -541,71 +1240,77 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", + "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.6.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.12.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -623,18 +1328,43 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -703,13 +1433,15 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -717,25 +1449,17 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/find-up": { @@ -755,78 +1479,37 @@ } }, "node_modules/flast": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/flast/-/flast-1.7.1.tgz", - "integrity": "sha512-gBOTJxdR8T51/5EO6YG7F2OpR2ff3tjVmKBM6jJZvZjTkMCiLnfMWU7jcfeP+LHIPm2PvLVWoyBnaGub0n9ySg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flast/-/flast-2.0.0.tgz", + "integrity": "sha512-uIqjZuSi+8wTl3hvir9gMHulA65jmX8m7KHRFJCbwB2PkLQxOZNHjW+iXvIUhFeOEnlXiWaxL36k2SvKW4buEg==", "license": "MIT", "dependencies": { "escodegen": "^2.1.0", - "eslint-scope": "^8.0.1", - "espree": "^10.1.0", + "eslint-scope": "^8.1.0", + "espree": "^10.2.0", "estraverse": "^5.3.0" } }, - "node_modules/flast/node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/flast/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/flast/node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", - "dependencies": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" }, "node_modules/form-data": { "version": "4.0.0", @@ -846,37 +1529,22 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -890,26 +1558,18 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -955,15 +1615,16 @@ } }, "node_modules/husky": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", - "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", "dev": true, + "license": "MIT", "bin": { - "husky": "lib/bin.js" + "husky": "bin.js" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -1000,10 +1661,11 @@ ] }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -1013,6 +1675,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1033,16 +1696,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -1074,15 +1727,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -1106,11 +1750,20 @@ "node": ">=18.0.0" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -1119,30 +1772,31 @@ } }, "node_modules/jsdom": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", - "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "license": "MIT", "dependencies": { - "cssstyle": "^4.0.1", + "cssstyle": "^4.1.0", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.4", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.10", + "nwsapi": "^2.2.12", "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.0", + "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.4", + "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", - "ws": "^8.17.0", + "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -1157,22 +1811,33 @@ } } }, - "node_modules/jsdom/node_modules/rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -1180,11 +1845,26 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -1269,6 +1949,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1316,17 +1997,27 @@ "node": ">=10" } }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/nwsapi": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", - "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==" + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", + "license": "MIT" }, "node_modules/obfuscation-detector": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/obfuscation-detector/-/obfuscation-detector-1.1.7.tgz", - "integrity": "sha512-ISa0SQZSyVvkMt7FUfMSUw/98BwEgsu90TKnBSl0wXgJcjk5aKC8x8fQIZo9yrd7lilKCS1vvtCT8bztEiF5xQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/obfuscation-detector/-/obfuscation-detector-2.0.0.tgz", + "integrity": "sha512-9QzCTqa6zYD+hbw/Z3DQSrOFI/sfyKZ3pzSNGG0KFmcNLLuY5mTiVpQvdb6XcYuPcbt0chFVC1+30UCNyGiYSQ==", + "license": "MIT", "dependencies": { - "flast": "^1.6.0" + "flast": "^2.0.0" }, "bin": { "obfuscation-detector": "bin/obfuscation-detector.js" @@ -1392,6 +2083,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -1419,15 +2111,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -1437,6 +2120,14 @@ "node": ">=8" } }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -1471,11 +2162,6 @@ "node": ">= 0.8.0" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1493,31 +2179,6 @@ "node": ">=6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -1553,72 +2214,21 @@ "node": ">= 6" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "license": "MIT" }, "node_modules/safe-buffer": { "version": "5.2.1", @@ -1750,23 +2360,12 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -1823,18 +2422,45 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tldts": { + "version": "6.1.50", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.50.tgz", + "integrity": "sha512-q9GOap6q3KCsLMdOjXhWU5jVZ8/1dIib898JBRLsN+tBhENpBDcAVQbE0epADOjw11FhQQy9AcbqKGBQPUfTQA==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.50" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.50", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.50.tgz", + "integrity": "sha512-na2EcZqmdA2iV9zHV7OHQDxxdciEpxrjbkp+aHmZgnZKHzoElLajP59np5/4+sare9fQBfixgvXKx8ev1d7ytw==", + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" + "node": ">=16" } }, "node_modules/tr46": { @@ -1871,24 +2497,36 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, - "engines": { - "node": ">=10" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, "node_modules/uri-js": { @@ -1896,19 +2534,11 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 0297956..2ddb551 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.10.4", "description": "Deobfuscate Javascript with emphasis on reconstructing strings", "main": "index.js", + "type": "module", "bin": { "restringer": "./src/restringer.js" }, @@ -11,13 +12,14 @@ "test": "tests" }, "dependencies": { - "flast": "^1.7.1", + "flast": "^2.0.0", "isolated-vm": "^5.0.1", - "jsdom": "^24.1.0", - "obfuscation-detector": "^1.1.7" + "jsdom": "^25.0.1", + "obfuscation-detector": "^2.0.0" }, "scripts": { - "test": "node --trace-warnings tests/testRestringer.js" + "test": "node --test --trace-warnings --no-node-snapshot --experimental-json-modules", + "test:coverage": "node --test --trace-warnings --no-node-snapshot --experimental-json-modules --experimental-test-coverage" }, "repository": { "type": "git", @@ -31,14 +33,16 @@ "javascript", "AST" ], - "author": "ben.baryo@humansecurity.com", + "author": "Ben Baryo (ben.baryo@humansecurity.com)", "license": "MIT", "bugs": { "url": "https://github.com/PerimeterX/Restringer/issues" }, "homepage": "https://github.com/PerimeterX/Restringer#readme", "devDependencies": { - "eslint": "^8.56.0", - "husky": "^8.0.3" + "@babel/eslint-parser": "^7.25.8", + "@babel/plugin-syntax-import-assertions": "^7.25.7", + "eslint": "^9.12.0", + "husky": "^9.1.6" } } diff --git a/src/modules/config.js b/src/modules/config.js index 33d6aa3..1ea0dc2 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -8,27 +8,15 @@ const badIdentifierCharsRegex = /([:!@#%^&*(){}[\]\\|/`'"]|[^\da-zA-Z_$])/; const badValue = '--BAD-VAL--'; // Do not repeate more than this many iterations. -const defaultMaxIterations = 500; - -// The global maximum allowed iterations of runLoop. Value of -1 means unlimited. -let globalMaxIterations = -1; - -/** - * @return {number} The global maximum allowed iterations of runLoop. Value of -1 means unlimited. - */ -function getGlobalMaxIterations() { - return globalMaxIterations; -} +// Behaves like a number, but decrements each time it's used. +// Use defaultMaxIterations.value = 300 to set a new value. +const defaultMaxIterations = { + value: 500, + valueOf() {return this.value--;}, +}; const propertiesThatModifyContent = ['push', 'forEach', 'pop', 'insert', 'add', 'set', 'delete']; -/** - * @param {number} num The number of allowed iterations across all runs. Set to -1 to disable limit. - */ -function setGlobalMaxIterations(num) { - globalMaxIterations = num; -} - // Builtin functions that shouldn't be resolved in the deobfuscation context. const skipBuiltinFunctions = [ 'Function', 'eval', 'Array', 'Object', 'fetch', 'XMLHttpRequest', 'Promise', 'console', 'performance', '$', @@ -49,14 +37,12 @@ const skipProperties = [ // A regex for a valid identifier name. const validIdentifierBeginning = /^[A-Za-z$_]/; -module.exports = { +export { badArgumentTypes, badIdentifierCharsRegex, badValue, defaultMaxIterations, propertiesThatModifyContent, - setGlobalMaxIterations, - getGlobalMaxIterations, skipBuiltinFunctions, skipIdentifiers, skipProperties, diff --git a/src/modules/index.js b/src/modules/index.js index 943d3af..0d93ded 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -1,6 +1,4 @@ -module.exports = { - config: require(__dirname + '/config'), - safe: require(__dirname + '/safe'), - unsafe: require(__dirname + '/unsafe'), - utils: require(__dirname + '/utils'), -}; \ No newline at end of file +export const config = await import('./config.js'); +export const safe = await import('./safe/index.js'); +export const unsafe = await import('./unsafe/index.js'); +export const utils = await import('./utils/index.js'); \ No newline at end of file diff --git a/src/modules/safe/index.js b/src/modules/safe/index.js index 24e06d3..54a6426 100644 --- a/src/modules/safe/index.js +++ b/src/modules/safe/index.js @@ -1,32 +1,30 @@ -module.exports = { - normalizeComputed: require(__dirname + '/normalizeComputed'), - normalizeEmptyStatements: require(__dirname + '/normalizeEmptyStatements'), - parseTemplateLiteralsIntoStringLiterals: require(__dirname + '/parseTemplateLiteralsIntoStringLiterals'), - rearrangeSequences: require(__dirname + '/rearrangeSequences'), - rearrangeSwitches: require(__dirname + '/rearrangeSwitches'), - removeDeadNodes: require(__dirname + '/removeDeadNodes'), - removeRedundantBlockStatements: require(__dirname + '/removeRedundantBlockStatements'), - replaceBooleanExpressionsWithIf: require(__dirname + '/replaceBooleanExpressionsWithIf'), - replaceCallExpressionsWithUnwrappedIdentifier: require(__dirname + '/replaceCallExpressionsWithUnwrappedIdentifier'), - replaceEvalCallsWithLiteralContent: require(__dirname + '/replaceEvalCallsWithLiteralContent'), - replaceFunctionShellsWithWrappedValue: require(__dirname + '/replaceFunctionShellsWithWrappedValue'), - replaceFunctionShellsWithWrappedValueIIFE: require(__dirname + '/replaceFunctionShellsWithWrappedValueIIFE'), - replaceIdentifierWithFixedAssignedValue: require(__dirname + '/replaceIdentifierWithFixedAssignedValue'), - replaceIdentifierWithFixedValueNotAssignedAtDeclaration: require(__dirname + '/replaceIdentifierWithFixedValueNotAssignedAtDeclaration'), - replaceNewFuncCallsWithLiteralContent: require(__dirname + '/replaceNewFuncCallsWithLiteralContent'), - replaceSequencesWithExpressions: require(__dirname + '/replaceSequencesWithExpressions'), - resolveDeterministicIfStatements: require(__dirname + '/resolveDeterministicIfStatements'), - resolveFunctionConstructorCalls: require(__dirname + '/resolveFunctionConstructorCalls'), - resolveMemberExpressionReferencesToArrayIndex: require(__dirname + '/resolveMemberExpressionReferencesToArrayIndex'), - resolveMemberExpressionsWithDirectAssignment: require(__dirname + '/resolveMemberExpressionsWithDirectAssignment'), - resolveProxyCalls: require(__dirname + '/resolveProxyCalls'), - resolveProxyReferences: require(__dirname + '/resolveProxyReferences'), - resolveProxyVariables: require(__dirname + '/resolveProxyVariables'), - resolveRedundantLogicalExpressions: require(__dirname + '/resolveRedundantLogicalExpressions'), - separateChainedDeclarators: require(__dirname + '/separateChainedDeclarators'), - simplifyCalls: require(__dirname + '/simplifyCalls'), - simplifyIfStatements: require(__dirname + '/simplifyIfStatements'), - unwrapFunctionShells: require(__dirname + '/unwrapFunctionShells'), - unwrapIIFEs: require(__dirname + '/unwrapIIFEs'), - unwrapSimpleOperations: require(__dirname + '/unwrapSimpleOperations'), -}; \ No newline at end of file +export const normalizeComputed = await import('./normalizeComputed.js'); +export const normalizeEmptyStatements = await import('./normalizeEmptyStatements.js'); +export const parseTemplateLiteralsIntoStringLiterals = await import('./parseTemplateLiteralsIntoStringLiterals.js'); +export const rearrangeSequences = await import('./rearrangeSequences.js'); +export const rearrangeSwitches = await import('./rearrangeSwitches.js'); +export const removeDeadNodes = await import('./removeDeadNodes.js'); +export const removeRedundantBlockStatements = await import('./removeRedundantBlockStatements.js'); +export const replaceBooleanExpressionsWithIf = await import('./replaceBooleanExpressionsWithIf.js'); +export const replaceCallExpressionsWithUnwrappedIdentifier = await import('./replaceCallExpressionsWithUnwrappedIdentifier.js'); +export const replaceEvalCallsWithLiteralContent = await import('./replaceEvalCallsWithLiteralContent.js'); +export const replaceFunctionShellsWithWrappedValue = await import('./replaceFunctionShellsWithWrappedValue.js'); +export const replaceFunctionShellsWithWrappedValueIIFE = await import('./replaceFunctionShellsWithWrappedValueIIFE.js'); +export const replaceIdentifierWithFixedAssignedValue = await import('./replaceIdentifierWithFixedAssignedValue.js'); +export const replaceIdentifierWithFixedValueNotAssignedAtDeclaration = await import('./replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js'); +export const replaceNewFuncCallsWithLiteralContent = await import('./replaceNewFuncCallsWithLiteralContent.js'); +export const replaceSequencesWithExpressions = await import('./replaceSequencesWithExpressions.js'); +export const resolveDeterministicIfStatements = await import('./resolveDeterministicIfStatements.js'); +export const resolveFunctionConstructorCalls = await import('./resolveFunctionConstructorCalls.js'); +export const resolveMemberExpressionReferencesToArrayIndex = await import('./resolveMemberExpressionReferencesToArrayIndex.js'); +export const resolveMemberExpressionsWithDirectAssignment = await import('./resolveMemberExpressionsWithDirectAssignment.js'); +export const resolveProxyCalls = await import('./resolveProxyCalls.js'); +export const resolveProxyReferences = await import('./resolveProxyReferences.js'); +export const resolveProxyVariables = await import('./resolveProxyVariables.js'); +export const resolveRedundantLogicalExpressions = await import('./resolveRedundantLogicalExpressions.js'); +export const separateChainedDeclarators = await import('./separateChainedDeclarators.js'); +export const simplifyCalls = await import('./simplifyCalls.js'); +export const simplifyIfStatements = await import('./simplifyIfStatements.js'); +export const unwrapFunctionShells = await import('./unwrapFunctionShells.js'); +export const unwrapIIFEs = await import('./unwrapIIFEs.js'); +export const unwrapSimpleOperations = await import('./unwrapSimpleOperations.js'); \ No newline at end of file diff --git a/src/modules/safe/normalizeComputed.js b/src/modules/safe/normalizeComputed.js index c0cfb96..3d951de 100644 --- a/src/modules/safe/normalizeComputed.js +++ b/src/modules/safe/normalizeComputed.js @@ -1,4 +1,4 @@ -const {badIdentifierCharsRegex, validIdentifierBeginning} = require(__dirname + '/../config'); +import {badIdentifierCharsRegex, validIdentifierBeginning} from '../config.js'; /** * Change all member expressions and class methods which has a property which can support it - to non-computed. @@ -48,4 +48,4 @@ function normalizeComputed(arb, candidateFilter = () => true) { return arb; } -module.exports = normalizeComputed; \ No newline at end of file +export default normalizeComputed; \ No newline at end of file diff --git a/src/modules/safe/normalizeEmptyStatements.js b/src/modules/safe/normalizeEmptyStatements.js index cf3b7a6..d41e20d 100644 --- a/src/modules/safe/normalizeEmptyStatements.js +++ b/src/modules/safe/normalizeEmptyStatements.js @@ -17,4 +17,4 @@ function normalizeEmptyStatements(arb, candidateFilter = () => true) { return arb; } -module.exports = normalizeEmptyStatements; \ No newline at end of file +export default normalizeEmptyStatements; \ No newline at end of file diff --git a/src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js b/src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js index b791a9b..7c4c4c4 100644 --- a/src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js +++ b/src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js @@ -1,4 +1,4 @@ -const createNewNode = require(__dirname + '/../utils/createNewNode'); +import {createNewNode} from '../utils/createNewNode.js'; /** * E.g. @@ -24,4 +24,4 @@ function parseTemplateLiteralsIntoStringLiterals(arb, candidateFilter = () => tr return arb; } -module.exports = parseTemplateLiteralsIntoStringLiterals; \ No newline at end of file +export default parseTemplateLiteralsIntoStringLiterals; \ No newline at end of file diff --git a/src/modules/safe/rearrangeSequences.js b/src/modules/safe/rearrangeSequences.js index 430d0c3..ce4396f 100644 --- a/src/modules/safe/rearrangeSequences.js +++ b/src/modules/safe/rearrangeSequences.js @@ -58,4 +58,4 @@ function rearrangeSequences(arb, candidateFilter = () => true) { return arb; } -module.exports = rearrangeSequences; \ No newline at end of file +export default rearrangeSequences; \ No newline at end of file diff --git a/src/modules/safe/rearrangeSwitches.js b/src/modules/safe/rearrangeSwitches.js index 6c256f3..5ec98d2 100644 --- a/src/modules/safe/rearrangeSwitches.js +++ b/src/modules/safe/rearrangeSwitches.js @@ -1,4 +1,4 @@ -const getDescendants = require(__dirname + '/../utils/getDescendants'); +import {getDescendants} from '../utils/getDescendants.js'; const maxRepetition = 50; @@ -50,4 +50,4 @@ function rearrangeSwitches(arb, candidateFilter = () => true) { return arb; } -module.exports = rearrangeSwitches; \ No newline at end of file +export default rearrangeSwitches; \ No newline at end of file diff --git a/src/modules/safe/removeDeadNodes.js b/src/modules/safe/removeDeadNodes.js index 7855aef..3ea57d4 100644 --- a/src/modules/safe/removeDeadNodes.js +++ b/src/modules/safe/removeDeadNodes.js @@ -1,4 +1,9 @@ -const relevantParents = ['VariableDeclarator', 'AssignmentExpression', 'FunctionDeclaration', 'ClassDeclaration']; +const relevantParents = [ + 'VariableDeclarator', + 'AssignmentExpression', + 'FunctionDeclaration', + 'ClassDeclaration', +]; /** * Remove nodes code which is only declared but never used. @@ -24,4 +29,4 @@ function removeDeadNodes(arb, candidateFilter = () => true) { return arb; } -module.exports = removeDeadNodes; \ No newline at end of file +export default removeDeadNodes; \ No newline at end of file diff --git a/src/modules/safe/removeRedundantBlockStatements.js b/src/modules/safe/removeRedundantBlockStatements.js index be99d1b..91345d7 100644 --- a/src/modules/safe/removeRedundantBlockStatements.js +++ b/src/modules/safe/removeRedundantBlockStatements.js @@ -36,4 +36,4 @@ function removeRedundantBlockStatements(arb, candidateFilter = () => true) { return arb; } -module.exports = removeRedundantBlockStatements; \ No newline at end of file +export default removeRedundantBlockStatements; \ No newline at end of file diff --git a/src/modules/safe/replaceBooleanExpressionsWithIf.js b/src/modules/safe/replaceBooleanExpressionsWithIf.js index c225f26..23b6b95 100644 --- a/src/modules/safe/replaceBooleanExpressionsWithIf.js +++ b/src/modules/safe/replaceBooleanExpressionsWithIf.js @@ -41,4 +41,4 @@ function replaceBooleanExpressionsWithIf(arb, candidateFilter = () => true) { return arb; } -module.exports = replaceBooleanExpressionsWithIf; +export default replaceBooleanExpressionsWithIf; diff --git a/src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js b/src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js index cc53555..160b673 100644 --- a/src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js +++ b/src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js @@ -35,4 +35,4 @@ function replaceCallExpressionsWithUnwrappedIdentifier(arb, candidateFilter = () return arb; } -module.exports = replaceCallExpressionsWithUnwrappedIdentifier; \ No newline at end of file +export default replaceCallExpressionsWithUnwrappedIdentifier; \ No newline at end of file diff --git a/src/modules/safe/replaceEvalCallsWithLiteralContent.js b/src/modules/safe/replaceEvalCallsWithLiteralContent.js index 6094aaa..cc1755d 100644 --- a/src/modules/safe/replaceEvalCallsWithLiteralContent.js +++ b/src/modules/safe/replaceEvalCallsWithLiteralContent.js @@ -1,6 +1,7 @@ -const getCache = require(__dirname + '/../utils/getCache'); -const generateHash = require(__dirname + '/../utils/generateHash'); -const {generateFlatAST, utils: {logger}} = require('flast'); +import {getCache} from '../utils/getCache.js'; +import {generateHash} from '../utils/generateHash.js'; +import {generateFlatAST, utils} from 'flast'; +const {logger} = utils; /** * Extract string values of eval call expressions, and replace calls with the actual code, without running it through eval. @@ -68,4 +69,4 @@ function replaceEvalCallsWithLiteralContent(arb, candidateFilter = () => true) { return arb; } -module.exports = replaceEvalCallsWithLiteralContent; \ No newline at end of file +export default replaceEvalCallsWithLiteralContent; \ No newline at end of file diff --git a/src/modules/safe/replaceFunctionShellsWithWrappedValue.js b/src/modules/safe/replaceFunctionShellsWithWrappedValue.js index 1b9b786..009f16b 100644 --- a/src/modules/safe/replaceFunctionShellsWithWrappedValue.js +++ b/src/modules/safe/replaceFunctionShellsWithWrappedValue.js @@ -24,4 +24,4 @@ function replaceFunctionShellsWithWrappedValue(arb, candidateFilter = () => true return arb; } -module.exports = replaceFunctionShellsWithWrappedValue; \ No newline at end of file +export default replaceFunctionShellsWithWrappedValue; \ No newline at end of file diff --git a/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js b/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js index a089d47..aa7ed00 100644 --- a/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js +++ b/src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js @@ -20,4 +20,4 @@ function replaceFunctionShellsWithWrappedValueIIFE(arb, candidateFilter = () => return arb; } -module.exports = replaceFunctionShellsWithWrappedValueIIFE; \ No newline at end of file +export default replaceFunctionShellsWithWrappedValueIIFE; \ No newline at end of file diff --git a/src/modules/safe/replaceIdentifierWithFixedAssignedValue.js b/src/modules/safe/replaceIdentifierWithFixedAssignedValue.js index 16ad3ab..8f1ea4b 100644 --- a/src/modules/safe/replaceIdentifierWithFixedAssignedValue.js +++ b/src/modules/safe/replaceIdentifierWithFixedAssignedValue.js @@ -1,4 +1,4 @@ -const areReferencesModified = require(__dirname + '/../utils/areReferencesModified'); +import {areReferencesModified} from '../utils/areReferencesModified.js'; /** * When an identifier holds a static literal value, replace all references to it with the value. @@ -24,4 +24,4 @@ function replaceIdentifierWithFixedAssignedValue(arb, candidateFilter = () => tr return arb; } -module.exports = replaceIdentifierWithFixedAssignedValue; \ No newline at end of file +export default replaceIdentifierWithFixedAssignedValue; \ No newline at end of file diff --git a/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js b/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js index 22f566b..3c901b2 100644 --- a/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js +++ b/src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js @@ -1,5 +1,5 @@ -const areReferencesModified = require(__dirname + '/../utils/areReferencesModified'); -const getMainDeclaredObjectOfMemberExpression = require(__dirname + '/../utils/getMainDeclaredObjectOfMemberExpression'); +import {areReferencesModified} from '../utils/areReferencesModified.js'; +import {getMainDeclaredObjectOfMemberExpression} from '../utils/getMainDeclaredObjectOfMemberExpression.js'; /** * When an identifier holds a static value which is assigned after declaration but doesn't change afterwards, @@ -46,4 +46,4 @@ function replaceIdentifierWithFixedValueNotAssignedAtDeclaration(arb, candidateF return arb; } -module.exports = replaceIdentifierWithFixedValueNotAssignedAtDeclaration; \ No newline at end of file +export default replaceIdentifierWithFixedValueNotAssignedAtDeclaration; \ No newline at end of file diff --git a/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js b/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js index 8f93cc7..e79f2db 100644 --- a/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js +++ b/src/modules/safe/replaceNewFuncCallsWithLiteralContent.js @@ -1,6 +1,7 @@ -const getCache = require(__dirname + '/../utils/getCache'); -const generateHash = require(__dirname + '/../utils/generateHash'); -const {generateFlatAST, utils: {logger}} = require('flast'); +import {getCache} from '../utils/getCache.js'; +import {generateHash} from '../utils/generateHash.js'; +import {generateFlatAST, utils} from 'flast'; +const {logger} = utils; /** * Extract string values of eval call expressions, and replace calls with the actual code, without running it through eval. @@ -59,4 +60,4 @@ function replaceNewFuncCallsWithLiteralContent(arb, candidateFilter = () => true return arb; } -module.exports = replaceNewFuncCallsWithLiteralContent; \ No newline at end of file +export default replaceNewFuncCallsWithLiteralContent; \ No newline at end of file diff --git a/src/modules/safe/replaceSequencesWithExpressions.js b/src/modules/safe/replaceSequencesWithExpressions.js index 818ca06..ea22035 100644 --- a/src/modules/safe/replaceSequencesWithExpressions.js +++ b/src/modules/safe/replaceSequencesWithExpressions.js @@ -42,4 +42,4 @@ function replaceSequencesWithExpressions(arb, candidateFilter = () => true) { return arb; } -module.exports = replaceSequencesWithExpressions; \ No newline at end of file +export default replaceSequencesWithExpressions; \ No newline at end of file diff --git a/src/modules/safe/resolveDeterministicIfStatements.js b/src/modules/safe/resolveDeterministicIfStatements.js index 690645a..36c0ed1 100644 --- a/src/modules/safe/resolveDeterministicIfStatements.js +++ b/src/modules/safe/resolveDeterministicIfStatements.js @@ -26,4 +26,4 @@ function resolveDeterministicIfStatements(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveDeterministicIfStatements; \ No newline at end of file +export default resolveDeterministicIfStatements; \ No newline at end of file diff --git a/src/modules/safe/resolveFunctionConstructorCalls.js b/src/modules/safe/resolveFunctionConstructorCalls.js index 18fc5fb..d71d9f4 100644 --- a/src/modules/safe/resolveFunctionConstructorCalls.js +++ b/src/modules/safe/resolveFunctionConstructorCalls.js @@ -1,4 +1,4 @@ -const {generateFlatAST} = require('flast'); +import {generateFlatAST} from 'flast'; /** * Typical for packers, function constructor calls where the last argument @@ -35,4 +35,4 @@ function resolveFunctionConstructorCalls(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveFunctionConstructorCalls; \ No newline at end of file +export default resolveFunctionConstructorCalls; \ No newline at end of file diff --git a/src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js b/src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js index fbc5aaa..2a45c17 100644 --- a/src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js +++ b/src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js @@ -1,4 +1,5 @@ -const {logger} = require('flast').utils; +import {utils} from 'flast'; +const {logger} = utils; const minArrayLength = 20; @@ -35,4 +36,4 @@ function resolveMemberExpressionReferencesToArrayIndex(arb, candidateFilter = () return arb; } -module.exports = resolveMemberExpressionReferencesToArrayIndex; \ No newline at end of file +export default resolveMemberExpressionReferencesToArrayIndex; \ No newline at end of file diff --git a/src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js b/src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js index f849314..ead70d7 100644 --- a/src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js +++ b/src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js @@ -35,4 +35,4 @@ function resolveMemberExpressionsWithDirectAssignment(arb, candidateFilter = () return arb; } -module.exports = resolveMemberExpressionsWithDirectAssignment; \ No newline at end of file +export default resolveMemberExpressionsWithDirectAssignment; \ No newline at end of file diff --git a/src/modules/safe/resolveProxyCalls.js b/src/modules/safe/resolveProxyCalls.js index b459587..fa77ea6 100644 --- a/src/modules/safe/resolveProxyCalls.js +++ b/src/modules/safe/resolveProxyCalls.js @@ -48,4 +48,4 @@ function resolveProxyCalls(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveProxyCalls; \ No newline at end of file +export default resolveProxyCalls; \ No newline at end of file diff --git a/src/modules/safe/resolveProxyReferences.js b/src/modules/safe/resolveProxyReferences.js index 7085fdb..666e191 100644 --- a/src/modules/safe/resolveProxyReferences.js +++ b/src/modules/safe/resolveProxyReferences.js @@ -1,6 +1,6 @@ -const getDescendants = require(__dirname + '/../utils/getDescendants'); -const areReferencesModified = require(__dirname + '/../utils/areReferencesModified'); -const getMainDeclaredObjectOfMemberExpression = require(__dirname + '/../utils/getMainDeclaredObjectOfMemberExpression'); +import {getDescendants} from '../utils/getDescendants.js'; +import {areReferencesModified} from '../utils/areReferencesModified.js'; +import {getMainDeclaredObjectOfMemberExpression} from '../utils/getMainDeclaredObjectOfMemberExpression.js'; /** * Replace variables which only point at other variables and do not change, with their target. @@ -37,4 +37,4 @@ function resolveProxyReferences(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveProxyReferences; \ No newline at end of file +export default resolveProxyReferences; \ No newline at end of file diff --git a/src/modules/safe/resolveProxyVariables.js b/src/modules/safe/resolveProxyVariables.js index c858e6b..9ee6962 100644 --- a/src/modules/safe/resolveProxyVariables.js +++ b/src/modules/safe/resolveProxyVariables.js @@ -1,4 +1,4 @@ -const areReferencesModified = require(__dirname + '/../utils/areReferencesModified'); +import {areReferencesModified} from '../utils/areReferencesModified.js'; /** * Replace proxied variables with their intended target. @@ -25,4 +25,4 @@ function resolveProxyVariables(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveProxyVariables; \ No newline at end of file +export default resolveProxyVariables; \ No newline at end of file diff --git a/src/modules/safe/resolveRedundantLogicalExpressions.js b/src/modules/safe/resolveRedundantLogicalExpressions.js index 4a32a5b..b5ba019 100644 --- a/src/modules/safe/resolveRedundantLogicalExpressions.js +++ b/src/modules/safe/resolveRedundantLogicalExpressions.js @@ -47,4 +47,4 @@ function resolveRedundantLogicalExpressions(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveRedundantLogicalExpressions; \ No newline at end of file +export default resolveRedundantLogicalExpressions; \ No newline at end of file diff --git a/src/modules/safe/separateChainedDeclarators.js b/src/modules/safe/separateChainedDeclarators.js index 6dbb1e3..5498d2c 100644 --- a/src/modules/safe/separateChainedDeclarators.js +++ b/src/modules/safe/separateChainedDeclarators.js @@ -48,4 +48,4 @@ function separateChainedDeclarators(arb, candidateFilter = () => true) { return arb; } -module.exports = separateChainedDeclarators; \ No newline at end of file +export default separateChainedDeclarators; \ No newline at end of file diff --git a/src/modules/safe/simplifyCalls.js b/src/modules/safe/simplifyCalls.js index 7f9319f..c8a6b4a 100644 --- a/src/modules/safe/simplifyCalls.js +++ b/src/modules/safe/simplifyCalls.js @@ -26,4 +26,4 @@ function simplifyCalls(arb, candidateFilter = () => true) { return arb; } -module.exports = simplifyCalls; \ No newline at end of file +export default simplifyCalls; \ No newline at end of file diff --git a/src/modules/safe/simplifyIfStatements.js b/src/modules/safe/simplifyIfStatements.js index e04b524..80155bb 100644 --- a/src/modules/safe/simplifyIfStatements.js +++ b/src/modules/safe/simplifyIfStatements.js @@ -40,4 +40,4 @@ function simplifyIfStatements(arb, candidateFilter = () => true) { return arb; } -module.exports = simplifyIfStatements; \ No newline at end of file +export default simplifyIfStatements; \ No newline at end of file diff --git a/src/modules/safe/unwrapFunctionShells.js b/src/modules/safe/unwrapFunctionShells.js index 3be9d32..3a078e3 100644 --- a/src/modules/safe/unwrapFunctionShells.js +++ b/src/modules/safe/unwrapFunctionShells.js @@ -29,4 +29,4 @@ function unwrapFunctionShells(arb, candidateFilter = () => true) { return arb; } -module.exports = unwrapFunctionShells; \ No newline at end of file +export default unwrapFunctionShells; \ No newline at end of file diff --git a/src/modules/safe/unwrapIIFEs.js b/src/modules/safe/unwrapIIFEs.js index 6fb57b9..690884d 100644 --- a/src/modules/safe/unwrapIIFEs.js +++ b/src/modules/safe/unwrapIIFEs.js @@ -52,4 +52,4 @@ function unwrapIIFEs(arb, candidateFilter = () => true) { return arb; } -module.exports = unwrapIIFEs; \ No newline at end of file +export default unwrapIIFEs; \ No newline at end of file diff --git a/src/modules/safe/unwrapSimpleOperations.js b/src/modules/safe/unwrapSimpleOperations.js index 9c4b8f7..af5286e 100644 --- a/src/modules/safe/unwrapSimpleOperations.js +++ b/src/modules/safe/unwrapSimpleOperations.js @@ -89,4 +89,4 @@ function unwrapSimpleOperations(arb, candidateFilter = () => true) { return arb; } -module.exports = unwrapSimpleOperations; \ No newline at end of file +export default unwrapSimpleOperations; \ No newline at end of file diff --git a/src/modules/unsafe/index.js b/src/modules/unsafe/index.js index 0552d2f..2830a9f 100644 --- a/src/modules/unsafe/index.js +++ b/src/modules/unsafe/index.js @@ -1,14 +1,12 @@ -module.exports = { - normalizeRedundantNotOperator: require(__dirname + '/normalizeRedundantNotOperator'), - resolveAugmentedFunctionWrappedArrayReplacements: require(__dirname + '/resolveAugmentedFunctionWrappedArrayReplacements'), - resolveBuiltinCalls: require(__dirname + '/resolveBuiltinCalls'), - resolveDefiniteBinaryExpressions: require(__dirname + '/resolveDefiniteBinaryExpressions'), - resolveDefiniteMemberExpressions: require(__dirname + '/resolveDefiniteMemberExpressions'), - resolveDeterministicConditionalExpressions: require(__dirname + '/resolveDeterministicConditionalExpressions'), - resolveEvalCallsOnNonLiterals: require(__dirname + '/resolveEvalCallsOnNonLiterals'), - resolveFunctionToArray: require(__dirname + '/resolveFunctionToArray'), - resolveInjectedPrototypeMethodCalls: require(__dirname + '/resolveInjectedPrototypeMethodCalls'), - resolveLocalCalls: require(__dirname + '/resolveLocalCalls'), - resolveMemberExpressionsLocalReferences: require(__dirname + '/resolveMemberExpressionsLocalReferences'), - resolveMinimalAlphabet: require(__dirname + '/resolveMinimalAlphabet'), -}; \ No newline at end of file +export const normalizeRedundantNotOperator = await import('./normalizeRedundantNotOperator.js'); +export const resolveAugmentedFunctionWrappedArrayReplacements = await import('./resolveAugmentedFunctionWrappedArrayReplacements.js'); +export const resolveBuiltinCalls = await import('./resolveBuiltinCalls.js'); +export const resolveDefiniteBinaryExpressions = await import('./resolveDefiniteBinaryExpressions.js'); +export const resolveDefiniteMemberExpressions = await import('./resolveDefiniteMemberExpressions.js'); +export const resolveDeterministicConditionalExpressions = await import('./resolveDeterministicConditionalExpressions.js'); +export const resolveEvalCallsOnNonLiterals = await import('./resolveEvalCallsOnNonLiterals.js'); +export const resolveFunctionToArray = await import('./resolveFunctionToArray.js'); +export const resolveInjectedPrototypeMethodCalls = await import('./resolveInjectedPrototypeMethodCalls.js'); +export const resolveLocalCalls = await import('./resolveLocalCalls.js'); +export const resolveMemberExpressionsLocalReferences = await import('./resolveMemberExpressionsLocalReferences.js'); +export const resolveMinimalAlphabet = await import('./resolveMinimalAlphabet.js'); \ No newline at end of file diff --git a/src/modules/unsafe/normalizeRedundantNotOperator.js b/src/modules/unsafe/normalizeRedundantNotOperator.js index a1153cc..24eb60e 100644 --- a/src/modules/unsafe/normalizeRedundantNotOperator.js +++ b/src/modules/unsafe/normalizeRedundantNotOperator.js @@ -1,7 +1,7 @@ -const {badValue} = require(__dirname + '/../config'); -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const canUnaryExpressionBeResolved = require(__dirname + '/../utils/canUnaryExpressionBeResolved'); +import {badValue} from '../config.js'; +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; +import {canUnaryExpressionBeResolved} from '../utils/canUnaryExpressionBeResolved.js'; const relevantNodeTypes = ['Literal', 'ArrayExpression', 'ObjectExpression', 'UnaryExpression']; @@ -29,4 +29,4 @@ function normalizeRedundantNotOperator(arb, candidateFilter = () => true) { return arb; } -module.exports = normalizeRedundantNotOperator; \ No newline at end of file +export default normalizeRedundantNotOperator; \ No newline at end of file diff --git a/src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js b/src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js index c5cf83e..25ab935 100644 --- a/src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js +++ b/src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js @@ -1,7 +1,7 @@ -const {badValue} = require(__dirname + '/../config'); -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const getDescendants = require(__dirname + '/../utils/getDescendants'); +import {badValue} from '../config.js'; +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; +import {getDescendants} from '../utils/getDescendants.js'; /** * A special case of function array replacement where the function is wrapped in another function, the array is @@ -11,7 +11,7 @@ const getDescendants = require(__dirname + '/../utils/getDescendants'); * @param {Function} candidateFilter (optional) a filter to apply on the candidates list * @return {Arborist} */ -function resolveAugmentedFunctionWrappedArrayReplacements(arb, candidateFilter = () => true) { +export default function resolveAugmentedFunctionWrappedArrayReplacements(arb, candidateFilter = () => true) { for (let i = 0; i < arb.ast.length; i++) { const n = arb.ast[i]; if (n.type === 'FunctionDeclaration' && n.id && @@ -65,6 +65,4 @@ function resolveAugmentedFunctionWrappedArrayReplacements(arb, candidateFilter = } } return arb; -} - -module.exports = resolveAugmentedFunctionWrappedArrayReplacements; \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/unsafe/resolveBuiltinCalls.js b/src/modules/unsafe/resolveBuiltinCalls.js index 0fd0dab..29789c1 100644 --- a/src/modules/unsafe/resolveBuiltinCalls.js +++ b/src/modules/unsafe/resolveBuiltinCalls.js @@ -1,10 +1,11 @@ -const {logger} = require('flast').utils; -const {badValue} = require(__dirname + '/../config'); -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const createNewNode = require(__dirname + '/../utils/createNewNode'); -const safeImplementations = require(__dirname + '/../utils/safeImplementations'); -const {skipBuiltinFunctions, skipIdentifiers, skipProperties} = require(__dirname + '/../config'); +import {utils} from 'flast'; +const {logger} = utils; +import {badValue} from '../config.js'; +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; +import {createNewNode} from '../utils/createNewNode.js'; +import * as safeImplementations from '../utils/safeImplementations.js'; +import {skipBuiltinFunctions, skipIdentifiers, skipProperties} from '../config.js'; const availableSafeImplementations = Object.keys(safeImplementations); @@ -69,4 +70,4 @@ function resolveBuiltinCalls(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveBuiltinCalls; \ No newline at end of file +export default resolveBuiltinCalls; \ No newline at end of file diff --git a/src/modules/unsafe/resolveDefiniteBinaryExpressions.js b/src/modules/unsafe/resolveDefiniteBinaryExpressions.js index df53127..c992a07 100644 --- a/src/modules/unsafe/resolveDefiniteBinaryExpressions.js +++ b/src/modules/unsafe/resolveDefiniteBinaryExpressions.js @@ -1,7 +1,7 @@ -const {badValue} = require(__dirname + '/../config'); -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const doesBinaryExpressionContainOnlyLiterals = require(__dirname + '/../utils/doesBinaryExpressionContainOnlyLiterals'); +import {badValue} from '../config.js'; +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; +import {doesBinaryExpressionContainOnlyLiterals} from '../utils/doesBinaryExpressionContainOnlyLiterals.js'; /** * Resolve definite binary expressions. @@ -32,4 +32,4 @@ function resolveDefiniteBinaryExpressions(arb, candidateFilter = () => true) { } return arb; } -module.exports = resolveDefiniteBinaryExpressions; \ No newline at end of file +export default resolveDefiniteBinaryExpressions; \ No newline at end of file diff --git a/src/modules/unsafe/resolveDefiniteMemberExpressions.js b/src/modules/unsafe/resolveDefiniteMemberExpressions.js index 4373f31..32e22a5 100644 --- a/src/modules/unsafe/resolveDefiniteMemberExpressions.js +++ b/src/modules/unsafe/resolveDefiniteMemberExpressions.js @@ -1,6 +1,6 @@ -const {badValue} = require(__dirname + '/../config'); -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); +import {badValue} from '../config.js'; +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; /** * Replace definite member expressions with their intended value. @@ -31,4 +31,4 @@ function resolveDefiniteMemberExpressions(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveDefiniteMemberExpressions; \ No newline at end of file +export default resolveDefiniteMemberExpressions; \ No newline at end of file diff --git a/src/modules/unsafe/resolveDeterministicConditionalExpressions.js b/src/modules/unsafe/resolveDeterministicConditionalExpressions.js index 7094241..7026b5a 100644 --- a/src/modules/unsafe/resolveDeterministicConditionalExpressions.js +++ b/src/modules/unsafe/resolveDeterministicConditionalExpressions.js @@ -1,5 +1,5 @@ -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; /** * Evaluate resolvable (independent) conditional expressions and replace them with their unchanged resolution. @@ -26,4 +26,4 @@ function resolveDeterministicConditionalExpressions(arb, candidateFilter = () => return arb; } -module.exports = resolveDeterministicConditionalExpressions; \ No newline at end of file +export default resolveDeterministicConditionalExpressions; \ No newline at end of file diff --git a/src/modules/unsafe/resolveEvalCallsOnNonLiterals.js b/src/modules/unsafe/resolveEvalCallsOnNonLiterals.js index f1aa211..a1a8eb2 100644 --- a/src/modules/unsafe/resolveEvalCallsOnNonLiterals.js +++ b/src/modules/unsafe/resolveEvalCallsOnNonLiterals.js @@ -1,9 +1,9 @@ -const {parseCode} = require('flast'); -const {badValue} = require(__dirname + '/../config'); -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const createOrderedSrc = require(__dirname + '/../utils/createOrderedSrc'); -const getDeclarationWithContext = require(__dirname + '/../utils/getDeclarationWithContext'); +import {parseCode} from 'flast'; +import {badValue} from '../config.js'; +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; +import {createOrderedSrc} from '../utils/createOrderedSrc.js'; +import {getDeclarationWithContext} from '../utils/getDeclarationWithContext.js'; /** * Resolve eval call expressions where the argument isn't a literal. @@ -54,4 +54,4 @@ function resolveEvalCallsOnNonLiterals(arb, candidateFilter = () => true) { return arb; } -module.exports = resolveEvalCallsOnNonLiterals; \ No newline at end of file +export default resolveEvalCallsOnNonLiterals; \ No newline at end of file diff --git a/src/modules/unsafe/resolveFunctionToArray.js b/src/modules/unsafe/resolveFunctionToArray.js index 5ede0df..6c41490 100644 --- a/src/modules/unsafe/resolveFunctionToArray.js +++ b/src/modules/unsafe/resolveFunctionToArray.js @@ -2,13 +2,11 @@ * Function To Array Replacements * The obfuscated script dynamically generates an array which is referenced throughout the script. */ -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const { - createOrderedSrc, - getDeclarationWithContext, -} = require(__dirname + '/../utils'); -const {badValue} = require(__dirname + '/../config'); +import utils from '../utils/index.js'; +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; +const {createOrderedSrc, getDeclarationWithContext} = utils; +import {badValue} from '../config.js'; /** * Run the generating function and replace it with the actual array. @@ -21,7 +19,7 @@ const {badValue} = require(__dirname + '/../config'); * @param {Function} candidateFilter (optional) a filter to apply on the candidates list * @return {Arborist} */ -function resolveFunctionToArray(arb, candidateFilter = () => true) { +export default function resolveFunctionToArray(arb, candidateFilter = () => true) { let sharedSb; for (let i = 0; i < arb.ast.length; i++) { const n = arb.ast[i]; @@ -40,6 +38,4 @@ function resolveFunctionToArray(arb, candidateFilter = () => true) { } } return arb; -} - -module.exports = resolveFunctionToArray; +} \ No newline at end of file diff --git a/src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js b/src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js index f331e84..282488f 100644 --- a/src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js +++ b/src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js @@ -1,9 +1,10 @@ -const {logger} = require('flast').utils; -const {badValue} = require(__dirname + '/../config'); -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const createOrderedSrc = require(__dirname + '/../utils/createOrderedSrc'); -const getDeclarationWithContext = require(__dirname + '/../utils/getDeclarationWithContext'); +import {utils} from 'flast'; +const {logger} = utils; +import {badValue} from '../config.js'; +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; +import {createOrderedSrc} from '../utils/createOrderedSrc.js'; +import {getDeclarationWithContext} from '../utils/getDeclarationWithContext.js'; /** * Resolve call expressions which are defined on an object's prototype and are applied to an object's instance. @@ -14,7 +15,7 @@ const getDeclarationWithContext = require(__dirname + '/../utils/getDeclarationW * @param {Function} candidateFilter (optional) a filter to apply on the candidates list * @return {Arborist} */ -function resolveInjectedPrototypeMethodCalls(arb, candidateFilter = () => true) { +export default function resolveInjectedPrototypeMethodCalls(arb, candidateFilter = () => true) { for (let i = 0; i < arb.ast.length; i++) { const n = arb.ast[i]; if (n.type === 'AssignmentExpression' && @@ -43,6 +44,4 @@ function resolveInjectedPrototypeMethodCalls(arb, candidateFilter = () => true) } } return arb; -} - -module.exports = resolveInjectedPrototypeMethodCalls; \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/unsafe/resolveLocalCalls.js b/src/modules/unsafe/resolveLocalCalls.js index 5a65aaa..dde37a8 100644 --- a/src/modules/unsafe/resolveLocalCalls.js +++ b/src/modules/unsafe/resolveLocalCalls.js @@ -1,11 +1,11 @@ -const Sandbox = require(__dirname + '/../utils/sandbox'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const getCache = require(__dirname + '/../utils/getCache'); -const getCalleeName = require(__dirname + '/../utils/getCalleeName'); -const isNodeInRanges = require(__dirname + '/../utils/isNodeInRanges'); -const createOrderedSrc = require(__dirname + '/../utils/createOrderedSrc'); -const getDeclarationWithContext = require(__dirname + '/../utils/getDeclarationWithContext'); -const {badValue, badArgumentTypes, skipIdentifiers, skipProperties} = require(__dirname + '/../config'); +import {Sandbox} from '../utils/sandbox.js'; +import {evalInVm} from '../utils/evalInVm.js'; +import {getCache} from '../utils/getCache.js'; +import {getCalleeName} from '../utils/getCalleeName.js'; +import {isNodeInRanges} from '../utils/isNodeInRanges.js'; +import {createOrderedSrc} from '../utils/createOrderedSrc.js'; +import {getDeclarationWithContext} from '../utils/getDeclarationWithContext.js'; +import {badValue, badArgumentTypes, skipIdentifiers, skipProperties} from '../config.js'; let appearances = {}; const cacheLimit = 100; @@ -37,7 +37,7 @@ function countAppearances(node) { * @param {Function} candidateFilter (optional) a filter to apply on the candidates list * @return {Arborist} */ -function resolveLocalCalls(arb, candidateFilter = () => true) { +export default function resolveLocalCalls(arb, candidateFilter = () => true) { appearances = {}; const cache = getCache(arb.ast[0].scriptHash); const candidates = []; @@ -102,6 +102,4 @@ function resolveLocalCalls(arb, candidateFilter = () => true) { } } return arb; -} - -module.exports = resolveLocalCalls; \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/unsafe/resolveMemberExpressionsLocalReferences.js b/src/modules/unsafe/resolveMemberExpressionsLocalReferences.js index ffc695b..c6f5487 100644 --- a/src/modules/unsafe/resolveMemberExpressionsLocalReferences.js +++ b/src/modules/unsafe/resolveMemberExpressionsLocalReferences.js @@ -1,9 +1,9 @@ -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const {badValue, skipProperties} = require(__dirname + '/../config'); -const createOrderedSrc = require(__dirname + '/../utils/createOrderedSrc'); -const areReferencesModified = require(__dirname + '/../utils/areReferencesModified'); -const getDeclarationWithContext = require(__dirname + '/../utils/getDeclarationWithContext'); -const getMainDeclaredObjectOfMemberExpression = require(__dirname + '/../utils/getMainDeclaredObjectOfMemberExpression'); +import {evalInVm} from '../utils/evalInVm.js'; +import {badValue, skipProperties} from '../config.js'; +import {createOrderedSrc} from '../utils/createOrderedSrc.js'; +import {areReferencesModified} from '../utils/areReferencesModified.js'; +import {getDeclarationWithContext} from '../utils/getDeclarationWithContext.js'; +import {getMainDeclaredObjectOfMemberExpression} from '../utils/getMainDeclaredObjectOfMemberExpression.js'; /** * Resolve member expressions to the value they stand for, if they're defined in the script. @@ -19,7 +19,7 @@ const getMainDeclaredObjectOfMemberExpression = require(__dirname + '/../utils/g * @param {Function} candidateFilter (optional) a filter to apply on the candidates list * @return {Arborist} */ -function resolveMemberExpressionsLocalReferences(arb, candidateFilter = () => true) { +export default function resolveMemberExpressionsLocalReferences(arb, candidateFilter = () => true) { for (let i = 0; i < arb.ast.length; i++) { const n = arb.ast[i]; if (n.type === 'MemberExpression' && @@ -76,6 +76,4 @@ function resolveMemberExpressionsLocalReferences(arb, candidateFilter = () => tr } } return arb; -} - -module.exports = resolveMemberExpressionsLocalReferences; \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/unsafe/resolveMinimalAlphabet.js b/src/modules/unsafe/resolveMinimalAlphabet.js index 9bce990..9de4608 100644 --- a/src/modules/unsafe/resolveMinimalAlphabet.js +++ b/src/modules/unsafe/resolveMinimalAlphabet.js @@ -1,6 +1,6 @@ -const {badValue} = require(__dirname + '/../config'); -const evalInVm = require(__dirname + '/../utils/evalInVm'); -const getDescendants = require(__dirname + '/../utils/getDescendants'); +import {badValue} from '../config.js'; +import {evalInVm} from '../utils/evalInVm.js'; +import {getDescendants} from '../utils/getDescendants.js'; /** * Resolve unary expressions on values which aren't numbers such as +true, +[], +[...], etc, @@ -10,7 +10,7 @@ const getDescendants = require(__dirname + '/../utils/getDescendants'); * @param {Function} candidateFilter (optional) a filter to apply on the candidates list * @return {Arborist} */ -function resolveMinimalAlphabet(arb, candidateFilter = () => true) { +export default function resolveMinimalAlphabet(arb, candidateFilter = () => true) { for (let i = 0; i < arb.ast.length; i++) { const n = arb.ast[i]; if ((n.type === 'UnaryExpression' && @@ -29,6 +29,4 @@ function resolveMinimalAlphabet(arb, candidateFilter = () => true) { } } return arb; -} - -module.exports = resolveMinimalAlphabet; \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/utils/areReferencesModified.js b/src/modules/utils/areReferencesModified.js index 97a12b7..d299fb9 100644 --- a/src/modules/utils/areReferencesModified.js +++ b/src/modules/utils/areReferencesModified.js @@ -1,4 +1,4 @@ -const {propertiesThatModifyContent} = require(__dirname + '/../config'); +import {propertiesThatModifyContent} from '../config.js'; /** * @param {ASTNode[]} ast @@ -27,4 +27,4 @@ function areReferencesModified(ast, refs) { (n.left.object.declNode && (r.object.declNode || r.object) === n.left.object.declNode)))); } -module.exports = areReferencesModified; \ No newline at end of file +export {areReferencesModified}; \ No newline at end of file diff --git a/src/modules/utils/canUnaryExpressionBeResolved.js b/src/modules/utils/canUnaryExpressionBeResolved.js index 0a25064..08b960e 100644 --- a/src/modules/utils/canUnaryExpressionBeResolved.js +++ b/src/modules/utils/canUnaryExpressionBeResolved.js @@ -18,4 +18,4 @@ function canUnaryExpressionBeResolved(argument) { return true; } -module.exports = canUnaryExpressionBeResolved; \ No newline at end of file +export {canUnaryExpressionBeResolved}; \ No newline at end of file diff --git a/src/modules/utils/createNewNode.js b/src/modules/utils/createNewNode.js index b117c95..5f2aad9 100644 --- a/src/modules/utils/createNewNode.js +++ b/src/modules/utils/createNewNode.js @@ -1,6 +1,7 @@ -const {badValue} = require(__dirname + '/../config'); -const getObjType = require(__dirname + '/getObjType'); -const {generateCode, parseCode, utils: {logger}} = require('flast'); +import {badValue} from '../config.js'; +import {getObjType} from './getObjType.js'; +import {generateCode, parseCode, utils} from 'flast'; +const {logger} = utils; /** * Create a node from a value by its type. @@ -112,4 +113,4 @@ function createNewNode(value) { return newNode; } -module.exports = createNewNode; \ No newline at end of file +export {createNewNode}; \ No newline at end of file diff --git a/src/modules/utils/createOrderedSrc.js b/src/modules/utils/createOrderedSrc.js index dce59cc..6df718f 100644 --- a/src/modules/utils/createOrderedSrc.js +++ b/src/modules/utils/createOrderedSrc.js @@ -1,4 +1,4 @@ -const {parseCode} = require('flast'); +import {parseCode} from 'flast'; const largeNumber = 999e8; const sortByNodeId = (a, b) => a.nodeId > b.nodeId ? 1 : b.nodeId > a.nodeId ? -1 : 0; @@ -61,4 +61,4 @@ function createOrderedSrc(nodes, preserveOrder = false) { return output; } -module.exports = createOrderedSrc; \ No newline at end of file +export {createOrderedSrc}; \ No newline at end of file diff --git a/src/modules/utils/doesBinaryExpressionContainOnlyLiterals.js b/src/modules/utils/doesBinaryExpressionContainOnlyLiterals.js index 880cbb8..1960172 100644 --- a/src/modules/utils/doesBinaryExpressionContainOnlyLiterals.js +++ b/src/modules/utils/doesBinaryExpressionContainOnlyLiterals.js @@ -16,4 +16,4 @@ function doesBinaryExpressionContainOnlyLiterals(binaryExpression) { return false; } -module.exports = doesBinaryExpressionContainOnlyLiterals; \ No newline at end of file +export {doesBinaryExpressionContainOnlyLiterals}; \ No newline at end of file diff --git a/src/modules/utils/evalInVm.js b/src/modules/utils/evalInVm.js index 60d8785..92d86fb 100644 --- a/src/modules/utils/evalInVm.js +++ b/src/modules/utils/evalInVm.js @@ -1,10 +1,11 @@ -const {logger} = require('flast').utils; -const Sandbox = require(__dirname + '/sandbox'); -const assert = require('node:assert'); -const {badValue} = require(__dirname + '/../config'); -const getObjType = require(__dirname + '/../utils/getObjType'); -const generateHash = require(__dirname + '/../utils/generateHash'); -const createNewNode = require(__dirname + '/../utils/createNewNode'); +import {utils} from 'flast'; +const {logger} = utils; +import {Sandbox} from './sandbox.js'; +import * as assert from 'node:assert'; +import {badValue} from '../config.js'; +import {getObjType} from './getObjType.js'; +import {generateHash} from './generateHash.js'; +import {createNewNode} from './createNewNode.js'; const badTypes = [ // Types of objects which can't be resolved in the deobfuscation context. 'Promise', @@ -37,7 +38,7 @@ const maxCacheSize = 100; * Eval a string in an ~isolated~ environment * @param {string} stringToEval * @param {Sandbox} [sb] (optional) an existing sandbox loaded with context. - * @return {ASTNode|badValue} A node based on the eval result if successful; badValue string otherwise. + * @return {ASTNode|string} A node based on the eval result if successful; badValue string otherwise. */ function evalInVm(stringToEval, sb) { const cacheName = `eval-${generateHash(stringToEval)}`; @@ -73,4 +74,4 @@ function evalInVm(stringToEval, sb) { return cache[cacheName]; } -module.exports = evalInVm; \ No newline at end of file +export {evalInVm}; \ No newline at end of file diff --git a/src/modules/utils/evalWithDom.js b/src/modules/utils/evalWithDom.js index 225528b..762e8bf 100644 --- a/src/modules/utils/evalWithDom.js +++ b/src/modules/utils/evalWithDom.js @@ -1,11 +1,12 @@ // noinspection HtmlRequiredLangAttribute,HtmlRequiredTitleElement -const fs = require('node:fs'); -const Sandbox = require(__dirname + '/sandbox'); +import fs from 'node:fs'; +import {Sandbox} from './sandbox.js'; // eslint-disable-next-line no-unused-vars -const {JSDOM} = require('jsdom'); -const {logger} = require('flast').utils; -const generateHash = require(__dirname + '/../utils/generateHash'); +import {JSDOM} from 'jsdom'; +import {utils} from 'flast'; +const {logger} = utils; +import {generateHash} from './generateHash.js'; let jQuerySrc = ''; @@ -53,4 +54,4 @@ function evalWithDom(stringToEval, injectjQuery = false) { return cache[cacheName]; } -module.exports = evalWithDom; \ No newline at end of file +export {evalWithDom}; \ No newline at end of file diff --git a/src/modules/utils/generateHash.js b/src/modules/utils/generateHash.js index 618dba3..69d698f 100644 --- a/src/modules/utils/generateHash.js +++ b/src/modules/utils/generateHash.js @@ -1,7 +1,7 @@ -const crypto = require('node:crypto'); +import crypto from 'node:crypto'; function generateHash(script) { return crypto.createHash('sha256').update(script).digest('hex'); } -module.exports = generateHash; \ No newline at end of file +export {generateHash}; \ No newline at end of file diff --git a/src/modules/utils/getCache.js b/src/modules/utils/getCache.js index c1abf8b..7b86732 100644 --- a/src/modules/utils/getCache.js +++ b/src/modules/utils/getCache.js @@ -17,4 +17,4 @@ getCache.flush = function() { cache = {}; }; -module.exports = getCache; \ No newline at end of file +export {getCache}; \ No newline at end of file diff --git a/src/modules/utils/getCalleeName.js b/src/modules/utils/getCalleeName.js index df17398..0eb1873 100644 --- a/src/modules/utils/getCalleeName.js +++ b/src/modules/utils/getCalleeName.js @@ -7,4 +7,4 @@ function getCalleeName(callExpression) { return callee.name || callee.value; } -module.exports = getCalleeName; \ No newline at end of file +export {getCalleeName}; \ No newline at end of file diff --git a/src/modules/utils/getDeclarationWithContext.js b/src/modules/utils/getDeclarationWithContext.js index 5e630e7..a0d0745 100644 --- a/src/modules/utils/getDeclarationWithContext.js +++ b/src/modules/utils/getDeclarationWithContext.js @@ -1,8 +1,8 @@ -const getCache = require(__dirname + '/getCache'); -const generateHash = require(__dirname + '/generateHash'); -const isNodeInRanges = require(__dirname + '/isNodeInRanges'); -const getDescendants = require(__dirname + '/../utils/getDescendants'); -const {propertiesThatModifyContent} = require(__dirname + '/../config'); +import {getCache} from './getCache.js'; +import {generateHash} from './generateHash.js'; +import {isNodeInRanges} from './isNodeInRanges.js'; +import {getDescendants} from './getDescendants.js'; +import {propertiesThatModifyContent} from '../config.js'; // Types that give no context by themselves const irrelevantTypesToBeFilteredOut = [ @@ -96,7 +96,7 @@ function removeRedundantNodes(nodes) { * @return {ASTNode[]} A flat array of all available declarations and call expressions relevant to * the context of the origin node. */ -function getDeclarationWithContext(originNode, excludeOriginNode = false) { +export function getDeclarationWithContext(originNode, excludeOriginNode = false) { /** @type {ASTNode[]} */ const stack = [originNode]; // The working stack for nodes to be reviewed /** @type {ASTNode[]} */ @@ -222,6 +222,4 @@ function getDeclarationWithContext(originNode, excludeOriginNode = false) { cache[cacheNameSrc] = cached; // Caching context for a different node with similar content } return cached; -} - -module.exports = getDeclarationWithContext; \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/utils/getDescendants.js b/src/modules/utils/getDescendants.js index 4da8194..7abf550 100644 --- a/src/modules/utils/getDescendants.js +++ b/src/modules/utils/getDescendants.js @@ -21,4 +21,4 @@ function getDescendants(targetNode) { return offsprings; } -module.exports = getDescendants; \ No newline at end of file +export {getDescendants}; \ No newline at end of file diff --git a/src/modules/utils/getMainDeclaredObjectOfMemberExpression.js b/src/modules/utils/getMainDeclaredObjectOfMemberExpression.js index 32c4067..9c0fb72 100644 --- a/src/modules/utils/getMainDeclaredObjectOfMemberExpression.js +++ b/src/modules/utils/getMainDeclaredObjectOfMemberExpression.js @@ -12,4 +12,4 @@ function getMainDeclaredObjectOfMemberExpression(memberExpression) { return mainObject; } -module.exports = getMainDeclaredObjectOfMemberExpression; \ No newline at end of file +export {getMainDeclaredObjectOfMemberExpression}; \ No newline at end of file diff --git a/src/modules/utils/getObjType.js b/src/modules/utils/getObjType.js index f1b02c1..6af06aa 100644 --- a/src/modules/utils/getObjType.js +++ b/src/modules/utils/getObjType.js @@ -7,4 +7,4 @@ function getObjType(unknownObject) { return match ? match[1] : ''; } -module.exports = getObjType; \ No newline at end of file +export {getObjType}; \ No newline at end of file diff --git a/src/modules/utils/index.js b/src/modules/utils/index.js index 4efdec9..f4fb76f 100644 --- a/src/modules/utils/index.js +++ b/src/modules/utils/index.js @@ -1,21 +1,20 @@ -module.exports = { - areReferencesModified: require(__dirname + '/areReferencesModified'), - canUnaryExpressionBeResolved: require(__dirname + '/canUnaryExpressionBeResolved'), - createNewNode: require(__dirname + '/createNewNode'), - createOrderedSrc: require(__dirname + '/createOrderedSrc'), - doesBinaryExpressionContainOnlyLiterals: require(__dirname + '/doesBinaryExpressionContainOnlyLiterals'), - evalInVm: require(__dirname + '/evalInVm'), - evalWithDom: require(__dirname + '/evalWithDom'), - generateHash: require(__dirname + '/generateHash'), - getCache: require(__dirname + '/getCache'), - getCalleeName: require(__dirname + '/getCalleeName'), - getDeclarationWithContext: require(__dirname + '/getDeclarationWithContext'), - getDescendants: require(__dirname + '/getDescendants'), - getMainDeclaredObjectOfMemberExpression: require(__dirname + '/getMainDeclaredObjectOfMemberExpression'), - getObjType: require(__dirname + '/getObjType'), - isNodeInRanges: require(__dirname + '/isNodeInRanges'), - isNodeMarked: require(__dirname + '/isNodeMarked'), - normalizeScript: require(__dirname + '/normalizeScript'), - safeImplementations: require(__dirname + '/safeImplementations'), - sandbox: require(__dirname + '/sandbox'), +export default { + areReferencesModified: (await import('./areReferencesModified.js')).areReferencesModified, + canUnaryExpressionBeResolved: (await import('./canUnaryExpressionBeResolved.js')).canUnaryExpressionBeResolved, + createNewNode: (await import('./createNewNode.js')).createNewNode, + createOrderedSrc: (await import('./createOrderedSrc.js')).createOrderedSrc, + doesBinaryExpressionContainOnlyLiterals: (await import('./doesBinaryExpressionContainOnlyLiterals.js')).doesBinaryExpressionContainOnlyLiterals, + evalInVm: (await import('./evalInVm.js')).evalInVm, + evalWithDom: (await import('./evalWithDom.js')).evalWithDom, + generateHash: (await import('./generateHash.js')).generateHash, + getCache: (await import('./getCache.js')).getCache, + getCalleeName: (await import('./getCalleeName.js')).getCalleeName, + getDeclarationWithContext: (await import('./getDeclarationWithContext.js')).getDeclarationWithContext, + getDescendants: (await import('./getDescendants.js')).getDescendants, + getMainDeclaredObjectOfMemberExpression: (await import('./getMainDeclaredObjectOfMemberExpression.js')).getMainDeclaredObjectOfMemberExpression, + getObjType: (await import('./getObjType.js')).getObjType, + isNodeInRanges: (await import('./isNodeInRanges.js')).isNodeInRanges, + normalizeScript: (await import('./normalizeScript.js')).normalizeScript, + safeImplementations: (await import('./safeImplementations.js')), + sandbox: (await import('./sandbox.js')).Sandbox, }; \ No newline at end of file diff --git a/src/modules/utils/isNodeInRanges.js b/src/modules/utils/isNodeInRanges.js index 0b25a46..c61eaec 100644 --- a/src/modules/utils/isNodeInRanges.js +++ b/src/modules/utils/isNodeInRanges.js @@ -12,4 +12,4 @@ function isNodeInRanges(targetNode, ranges) { return false; } -module.exports = isNodeInRanges; \ No newline at end of file +export {isNodeInRanges}; \ No newline at end of file diff --git a/src/modules/utils/isNodeMarked.js b/src/modules/utils/isNodeMarked.js deleted file mode 100644 index 661b8ab..0000000 --- a/src/modules/utils/isNodeMarked.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @param {ASTNode} targetNode - * @returns {boolean} true if the target node or one of its ancestors is marked for either replacement or deletion; - * false otherwise. - */ -function isNodeMarked(targetNode) { - let n = targetNode; - while (n) { - if (n.isMarked) return true; - n = n.parentNode; - } - return false; -} - -module.exports = isNodeMarked; \ No newline at end of file diff --git a/src/modules/utils/normalizeScript.js b/src/modules/utils/normalizeScript.js index 740221b..5b742ae 100644 --- a/src/modules/utils/normalizeScript.js +++ b/src/modules/utils/normalizeScript.js @@ -1,19 +1,18 @@ -const {applyIteratively} = require('flast').utils; -const normalizeComputed = require(__dirname + '/../safe/normalizeComputed'); -const normalizeEmptyStatements = require(__dirname + '/../safe/normalizeEmptyStatements'); -const normalizeRedundantNotOperator = require(__dirname + '/../unsafe/normalizeRedundantNotOperator'); +import {utils} from 'flast'; +const {applyIteratively} = utils; +import * as normalizeComputed from '../safe/normalizeComputed.js'; +import * as normalizeEmptyStatements from '../safe/normalizeEmptyStatements.js'; +import * as normalizeRedundantNotOperator from '../unsafe/normalizeRedundantNotOperator.js'; /** * Make the script more readable without actually deobfuscating or affecting its functionality. * @param {string} script * @return {string} The normalized script. */ -function normalizeScript(script) { +export function normalizeScript(script) { return applyIteratively(script, [ - normalizeComputed, - normalizeRedundantNotOperator, - normalizeEmptyStatements, + normalizeComputed.default, + normalizeRedundantNotOperator.default, + normalizeEmptyStatements.default, ]); -} - -module.exports = normalizeScript; \ No newline at end of file +} \ No newline at end of file diff --git a/src/modules/utils/runLoop.js b/src/modules/utils/runLoop.js deleted file mode 100644 index c0a6734..0000000 --- a/src/modules/utils/runLoop.js +++ /dev/null @@ -1,71 +0,0 @@ -const {Arborist, utils: {logger}} = require('flast'); -const generateHash = require(__dirname + '/generateHash'); -const {defaultMaxIterations, getGlobalMaxIterations} = require(__dirname + '/../config'); - -let globalIterationsCounter = 0; - -/** - * @returns {boolean} false if iterations limiting is disabled (-1) - * or the global iterations counter hasn't reached its maximum allowed iterations; true otherwise. - */ -function hasGlobalMaxIterationBeenReached() { - return getGlobalMaxIterations() === -1 ? false : globalIterationsCounter >= getGlobalMaxIterations(); -} - -/** - * Run functions which modify the script in a loop until they are no long effective or the maximum number of cycles is reached. - * @param {string} script The target script to run the functions on. - * @param {function[]} funcs - * @param {number?} maxIterations (optional) Stop the loop after this many iterations at most. - * @return {string} The possibly modified script. - */ -function runLoop(script, funcs, maxIterations = defaultMaxIterations) { - let scriptSnapshot = ''; - let currentIteration = 0; - let changesCounter = 0; - try { - let scriptHash = generateHash(script); - let arborist = new Arborist(script, logger.log); - while (arborist.ast?.length && scriptSnapshot !== script && currentIteration < maxIterations && !hasGlobalMaxIterationBeenReached()) { - const cycleStartTime = Date.now(); - scriptSnapshot = script; - // Mark each node with the script hash to distinguish cache of different scripts. - for (let i = 0; i < arborist.ast.length; i++) arborist.ast[i].scriptHash = scriptHash; - for (let i = 0; i < funcs.length; i++) { - const func = funcs[i]; - const funcStartTime = +new Date(); - try { - logger.debug(`\t[!] Running ${func.name}...`); - arborist = func(arborist); - if (!arborist.ast?.length) break; - // If the hash doesn't exist it means the Arborist was replaced - const numberOfNewChanges = arborist.getNumberOfChanges() + +!arborist.ast[0].scriptHash; - if (numberOfNewChanges) { - changesCounter += numberOfNewChanges; - logger.log(`\t[+] ${func.name} committed ${numberOfNewChanges} new changes!`); - arborist.applyChanges(); - script = arborist.script; - scriptHash = generateHash(script); - for (let j = 0; j < arborist.ast.length; j++) arborist.ast[j].scriptHash = scriptHash; - } - } catch (e) { - logger.error(`[-] Error in ${func.name} (iteration #${globalIterationsCounter}): ${e}\n${e.stack}`); - } finally { - logger.debug(`\t\t[!] Running ${func.name} completed in ` + - `${((+new Date() - funcStartTime) / 1000).toFixed(3)} seconds`); - } - } - ++currentIteration; - ++globalIterationsCounter; - logger.log(`[+] ==> Cycle ${globalIterationsCounter} completed in ${(Date.now() - cycleStartTime) / 1000} seconds` + - ` with ${changesCounter ? changesCounter : 'no'} changes (${arborist.ast?.length || '???'} nodes)`); - changesCounter = 0; - } - if (changesCounter) script = arborist.script; - } catch (e) { - logger.error(`[-] Error on loop #${globalIterationsCounter}: ${e}\n${e.stack}`); - } - return script; -} - -module.exports = runLoop; \ No newline at end of file diff --git a/src/modules/utils/safe-atob.js b/src/modules/utils/safe-atob.js index b044724..1944e68 100644 --- a/src/modules/utils/safe-atob.js +++ b/src/modules/utils/safe-atob.js @@ -6,4 +6,4 @@ function atob(val) { return Buffer.from(val, 'base64').toString(); } -module.exports = atob; \ No newline at end of file +export {atob}; \ No newline at end of file diff --git a/src/modules/utils/safe-btoa.js b/src/modules/utils/safe-btoa.js index 0925c68..b73b1a8 100644 --- a/src/modules/utils/safe-btoa.js +++ b/src/modules/utils/safe-btoa.js @@ -6,4 +6,4 @@ function btoa(val) { return Buffer.from(val).toString('base64'); } -module.exports = btoa; \ No newline at end of file +export {btoa}; \ No newline at end of file diff --git a/src/modules/utils/safeImplementations.js b/src/modules/utils/safeImplementations.js index e05f07e..cbedb10 100644 --- a/src/modules/utils/safeImplementations.js +++ b/src/modules/utils/safeImplementations.js @@ -1,8 +1,5 @@ /** * Safe implementations of functions to be used during deobfuscation */ - -module.exports = { - atob: require(__dirname + '/safe-atob'), - btoa: require(__dirname + '/safe-btoa'), -}; +export const atob = (await import('./safe-atob.js')).atob; +export const btoa = (await import('./safe-btoa.js')).btoa; \ No newline at end of file diff --git a/src/modules/utils/sandbox.js b/src/modules/utils/sandbox.js index dd86f73..e30aa9a 100644 --- a/src/modules/utils/sandbox.js +++ b/src/modules/utils/sandbox.js @@ -1,9 +1,10 @@ -const {Isolate, Reference} = require('isolated-vm'); +import pkg from 'isolated-vm'; +const {Isolate, Reference} = pkg; /** * */ -class Sandbox { +export class Sandbox { constructor() { // Objects that shouldn't be available when running scripts in eval to avoid security issues or inconsistencies. const replacedItems = { @@ -42,6 +43,4 @@ class Sandbox { isReference(obj) { return Object.getPrototypeOf(obj) === Reference.prototype; } -} - -module.exports = Sandbox; \ No newline at end of file +} \ No newline at end of file diff --git a/src/processors/augmentedArray.js b/src/processors/augmentedArray.js index 685dfff..a17fa2a 100644 --- a/src/processors/augmentedArray.js +++ b/src/processors/augmentedArray.js @@ -14,19 +14,10 @@ * This processor will un-shuffle the array by running the IIFE augmenting it, and replace the array with the un-shuffled version, * while removing the augmenting IIFE. */ -const { - unsafe: { - resolveFunctionToArray, - }, - config: { - badValue - }, - utils: { - createOrderedSrc, - evalInVm, - getDeclarationWithContext, - }, -} = require(__dirname + '/../modules'); +import {config, unsafe, utils} from '../modules/index.js'; +const {resolveFunctionToArray} = unsafe; +const {badValue} = config; +const {createOrderedSrc, evalInVm, getDeclarationWithContext} = utils.default; /** * Extract the array and the immediately-invoking function expression. @@ -72,7 +63,5 @@ function replaceArrayWithStaticAugmentedVersion(arb) { return arb; } -module.exports = { - preprocessors: [replaceArrayWithStaticAugmentedVersion, resolveFunctionToArray], - postprocessors: [], -}; +export const preprocessors = [replaceArrayWithStaticAugmentedVersion, resolveFunctionToArray]; +export const postprocessors = []; \ No newline at end of file diff --git a/src/processors/caesarp.js b/src/processors/caesarp.js index 2103c4a..b31cd28 100644 --- a/src/processors/caesarp.js +++ b/src/processors/caesarp.js @@ -1,5 +1,7 @@ -const {Arborist} = require('flast'); -const {safe: {removeDeadNodes}, utils: {evalWithDom}} = require(__dirname + '/../modules'); +import {Arborist} from 'flast'; +import {safe, utils} from '../modules/index.js'; +const {removeDeadNodes} = safe; +const {evalWithDom} = utils.default; const lineWithFinalAssignmentRegex = /(\w{3})\[.*]\s*=.*\((\w{3})\).*=\s*\1\s*\+\s*['"]/ms; const variableContainingTheInnerLayerRegex = /\(((\w{3}\()+(\w{3})\)*)\)/gms; @@ -50,7 +52,5 @@ function extractInnerLayer(arb) { return arb; } -module.exports = { - preprocessors: [extractInnerLayer], - postprocessors: [removeDeadNodes], -}; +export const preprocessors = [extractInnerLayer]; +export const postprocessors = [removeDeadNodes.default]; \ No newline at end of file diff --git a/src/processors/functionToArray.js b/src/processors/functionToArray.js index 43a0812..81da9df 100644 --- a/src/processors/functionToArray.js +++ b/src/processors/functionToArray.js @@ -2,14 +2,8 @@ * Function To Array Replacements * The obfuscated script dynamically generates an array which is referenced throughout the script. */ -const { - unsafe: { - resolveFunctionToArray, - }, -} = require(__dirname + '/../modules'); +import {unsafe} from '../modules/index.js'; +const {resolveFunctionToArray} = unsafe; - -module.exports = { - preprocessors: [resolveFunctionToArray], - postprocessors: [], -}; +export const preprocessors = [resolveFunctionToArray.default]; +export const postprocessors = []; \ No newline at end of file diff --git a/src/processors/index.js b/src/processors/index.js index e4d56ec..ea7fb53 100644 --- a/src/processors/index.js +++ b/src/processors/index.js @@ -1,12 +1,12 @@ /** * Mapping specific obfuscation type to their processors, which are lazily loaded. */ -module.exports = { - 'caesar_plus': () => require(__dirname + '/caesarp.js'), - 'obfuscator.io': () => require(__dirname + '/obfuscatorIo.js'), - 'augmented_array_replacements': () => require(__dirname + '/augmentedArray.js'), - 'function_to_array_replacements': () => require(__dirname + '/functionToArray.js'), - 'proxied_augmented_array_replacements': () => require(__dirname + '/augmentedArray.js'), - 'augmented_array_function_replacements': () => require(__dirname + '/augmentedArray.js'), - 'augmented_proxied_array_function_replacements': () => require(__dirname + '/augmentedArray.js'), +export const processors = { + 'caesar_plus': await import('./caesarp.js'), + 'obfuscator.io': await import('./obfuscatorIo.js'), + 'augmented_array_replacements': await import('./augmentedArray.js'), + 'function_to_array_replacements': await import('./functionToArray.js'), + 'proxied_augmented_array_replacements': await import('./augmentedArray.js'), + 'augmented_array_function_replacements': await import('./augmentedArray.js'), + 'augmented_proxied_array_function_replacements': await import('./augmentedArray.js'), }; diff --git a/src/processors/obfuscatorIo.js b/src/processors/obfuscatorIo.js index d55371d..955e124 100644 --- a/src/processors/obfuscatorIo.js +++ b/src/processors/obfuscatorIo.js @@ -2,7 +2,7 @@ * Obfuscator.io obfuscation * The obfuscator optionally adds 'debug protection' methods that when triggered, result in an endless loop. */ -const augmentedArrayProcessors = require(__dirname + '/augmentedArray'); +import * as augmentedArrayProcessors from './augmentedArray.js'; const freezeReplacementString = 'function () {return "bypassed!"}'; @@ -41,7 +41,5 @@ function freezeUnbeautifiedValues(arb) { return arb; } -module.exports = { - preprocessors: [freezeUnbeautifiedValues, ...augmentedArrayProcessors.preprocessors], - postprocessors: [...augmentedArrayProcessors.postprocessors], -}; +export const preprocessors = [freezeUnbeautifiedValues, ...augmentedArrayProcessors.preprocessors]; +export const postprocessors = [...augmentedArrayProcessors.postprocessors]; \ No newline at end of file diff --git a/src/restringer.js b/src/restringer.js index ecfad3e..775e460 100755 --- a/src/restringer.js +++ b/src/restringer.js @@ -1,23 +1,26 @@ #!/usr/bin/env node -const {logger, applyIteratively} = require('flast').utils; -const processors = require(__dirname + '/processors'); -const detectObfuscation = require('obfuscation-detector'); -const version = require(__dirname + '/../package').version; -const { - utils: { - normalizeScript, - }, - safe, - unsafe, - config: { - setGlobalMaxIterations, - } -} = require(__dirname + '/modules'); +import {utils as flastUtils} from 'flast'; +const {logger, applyIteratively} = flastUtils; +import {fileURLToPath} from 'node:url'; +import {processors} from './processors/index.js'; +import {detectObfuscation} from 'obfuscation-detector'; +import pkg from '../package.json' assert {type: 'json'}; +const { version } = pkg; +import {config, safe as safeMod, unsafe as unsafeMod, utils} from './modules/index.js'; +const {normalizeScript} = utils.default; +const safe = {}; +for (const funcName in safeMod) { + safe[funcName] = safeMod[funcName].default || safeMod[funcName]; +} +const unsafe = {}; +for (const funcName in unsafeMod) { + unsafe[funcName] = unsafeMod[funcName].default || unsafeMod[funcName]; +} // Silence asyc errors -process.on('uncaughtException', () => {}); +// process.on('uncaughtException', () => {}); -class REstringer { +export class REstringer { static __version__ = version; /** @@ -33,6 +36,7 @@ class REstringer { this._postprocessors = []; this.logger = logger; this.logger.setLogLevelLog(); + this.maxIterations = config.defaultMaxIterations; this.detectObfuscationType = true; // Deobfuscation methods that don't use eval this.safeMethods = [ @@ -88,7 +92,7 @@ class REstringer { if (detectedObfuscationType) { this.obfuscationName = detectedObfuscationType; if (processors[detectedObfuscationType]) { - ({preprocessors: this._preprocessors, postprocessors: this._postprocessors} = processors[detectedObfuscationType]()); + ({preprocessors: this._preprocessors, postprocessors: this._postprocessors} = processors[detectedObfuscationType]); } } logger.log(`[+] Obfuscation type is ${this.obfuscationName}`); @@ -105,7 +109,7 @@ class REstringer { let modified, script; do { this.modified = false; - script = applyIteratively(this.script, this.safeMethods.concat(this.unsafeMethods)); + script = applyIteratively(this.script, this.safeMethods.concat(this.unsafeMethods), this.maxIterations); if (this.script !== script) { this.modified = true; this.script = script; @@ -129,7 +133,7 @@ class REstringer { this._loopSafeAndUnsafeDeobfuscationMethods(); this._runProcessors(this._postprocessors); if (this.modified && this.normalize) this.script = normalizeScript(this.script); - if (clean) this.script = applyIteratively(this.script, [unsafe.removeDeadNodes]); + if (clean) this.script = applyIteratively(this.script, [unsafe.removeDeadNodes], this.maxIterations); return this.modified; } @@ -146,13 +150,12 @@ class REstringer { } } -module.exports = REstringer; -if (require.main === module) { - const {argsAreValid, parseArgs} = require(__dirname + '/utils/parseArgs'); +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const {argsAreValid, parseArgs} = await import('./utils/parseArgs.js'); try { const args = parseArgs(process.argv.slice(2)); if (argsAreValid(args)) { - const fs = require('node:fs'); + const fs = await import('node:fs'); let content = fs.readFileSync(args.inputFilename, 'utf-8'); const startTime = Date.now(); @@ -162,7 +165,7 @@ if (require.main === module) { logger.log(`[!] REstringer v${REstringer.__version__}`); logger.log(`[!] Deobfuscating ${args.inputFilename}...`); if (args.maxIterations) { - setGlobalMaxIterations(args.maxIterations); + restringer.maxIterations.value = args.maxIterations; restringer.logger.log(`[!] Running at most ${args.maxIterations} iterations`); } if (restringer.deobfuscate()) { diff --git a/src/utils/cleanScript.js b/src/utils/cleanScript.js index 2b80ce0..4491287 100644 --- a/src/utils/cleanScript.js +++ b/src/utils/cleanScript.js @@ -1,8 +1,8 @@ /* * Pass scripts through this tool to help compare pre- and post- deobfuscation */ -const fs = require('node:fs'); -const {parseCode, generateCode} = require('flast'); +import * as fs from 'node:fs'; +import {parseCode, generateCode} from 'flast'; const inFileName = process.argv[2]; const outFileName = inFileName + '-clean.js'; diff --git a/src/utils/parseArgs.js b/src/utils/parseArgs.js index 546f002..6d9c67b 100644 --- a/src/utils/parseArgs.js +++ b/src/utils/parseArgs.js @@ -1,4 +1,4 @@ -function printHelp() { +export function printHelp() { return ` REstringer - a JavaScript deobfuscator @@ -18,7 +18,7 @@ optional arguments: Use -deob.js if no filename is provided.`; } -function parseArgs(args) { +export function parseArgs(args) { let opts; try { const inputFilename = args[0] && args[0][0] !== '-' ? args[0] : ''; @@ -37,7 +37,6 @@ function parseArgs(args) { if (opts.outputToFile && /-o|--output/.exec(args[i])) { if (args[i].includes('=')) opts.outputFilename = args[i].split('=')[1]; else if (args[i + 1] && args[i + 1][0] !== '-') opts.outputFilename = args[i + 1]; - break; } else if (opts.maxIterations && /-m|--max-iterations/.exec(args[i])) { if (args[i].includes('=')) opts.maxIterations = Number(args[i].split('=')[1]); else if (args[i + 1] && args[i + 1][0] !== '-') opts.maxIterations = Number(args[i + 1]); @@ -52,18 +51,11 @@ function parseArgs(args) { * @param {object} args The parsed arguments * @returns {boolean} true if all arguments are valid; false otherwise. */ -function argsAreValid(args) { +export function argsAreValid(args) { if (args.help) console.log(printHelp()); else if (!args.inputFilename) console.log(`Error: Input filename must be provided`); else if (args.verbose && args.quiet) console.log(`Error: Don't set both -q and -v at the same time *smh*`); - else if (args.maxIterations !== false && Number.isNaN(parseInt(args.maxIterations))) console.log(`Error: --max-iterations requires a number larger than 0 (e.g. --max-iterations 12)`); + else if (args.maxIterations !== false && (Number.isNaN(parseInt(args.maxIterations)) || parseInt(args.maxIterations) <= 0)) console.log(`Error: --max-iterations requires a number larger than 0 (e.g. --max-iterations 12)`); else return true; return false; -} - -// noinspection JSUnusedGlobalSymbols -module.exports = { - argsAreValid, - parseArgs, - printHelp, -}; \ No newline at end of file +} \ No newline at end of file diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 0c42505..0000000 --- a/tests/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# Tests -Use the tests to verify code changes did not break any previously working feature.
- -## Table of Contents -* [Structure](#structure) - * [testRestringer.js](#testrestringerjs) - * [Test Files' Structure](#test-files-structure) - * [testDeobfuscations.js](#testdeobfuscationsjs) - * [Sample File's Structure](#sample-files-structure) - -## Structure -The test files are divided into two parts: -1. The test logic, containing the code for running the test and verifying the results, as well as timing and logging. These files shouldn't be touched often. -2. The test content, containing the test instructions, to which new tests should be added and existing tests should be modified according to need. - -The tests themselves are also divided into two categories: -1. [Deobfuscation tests](deobfuscation-tests.js).
- Testing specific deobfuscation techniques, sort of like unit-tests but not quite. - Making sure that nothing broke during changes made to the code, and if it did break, these tests will help figure out where, - since they're targeting specific methods that should modify the code during the run. -2. [Deobfuscating samples tests](obfuscated-samples.js).
- More like end-to-end testing, these tests run against different kinds of obfuscated scripts - and verify all the different deobfuscation methods are working well together and that the end result is properly deobfuscated. - -The [main test file](testRestringer.js) runs all the tests, but it is also possible to run only one of the test types -by running either [testDeobfuscations.js](testDeobfuscations.js) or [testObfuscatedSamples.js](testObfuscatedSamples.js) to save time -during development. - -### testRestringer.js -As stated above, [testRestringer.js](testRestringer.js) is the main test file, which loads the two tests files described below. -It starts a timer to later report how long all the tests took, and prints out the name of the test being run. - -### testDeobfuscations.js -The [testDeobfuscations.js](testDeobfuscations.js) file contains the logic for testing specific deobfuscation methods. -The tests are done by running the deobfuscator on a short string of obfuscated code containing (as much as possible) only the type of obfuscation being tested. - -A good rule of thumb for making sure the obfuscated code is testing the correct method is to have it fail first by disabling -the target deobfuscation method and verifying the code isn't deobfuscated as expected. - -#### Test Files' Structure -Test Objects:
-The test files export an array of objects, with each object being a test case. -The structure of the object is as follows: - -* enabled - boolean
- Whether the test is enabled or should be skipped. If skipped, a reason field must be present. -* reason - string
- Required if test is disabled. The reason why this test is being skipped. -* name - string
- The name of the test (a quick description of what it tests). -* source - string
- The source code to be deobfuscated. -* expected - string
- The expected deobfuscated output. - -Disabling Tests:
-During development it might be useful to disable some tests, or to add tests before the deobfuscation code is created for them. -Set the `enabled` key in the test object to `false` to signal the test is expected to fail. -When disabling a test you must add a `reason` explaining why the test was disabled - -### testObfuscatedSamples.js -The [testObfuscatedSamples.js](testObfuscatedSamples.js) file contains the logic for testing complete samples of obfuscated scripts. -These are similar to end-to-end tests, and include detecting the type of obfuscation, and following the relevant steps for deobfuscating it. - -#### Sample File's Structure -Test Objects:
-The [test content file](obfuscated-samples.js) export an object, -where the sample name is the key and the filename containing the target code is the value. - -Resources Location:
-The [resources folder](resources) contains the obfuscated scripts with their unique obfuscation, -which the Restringer already successfully handles.
-All resources contain the original obfuscated script as well as a deobfuscated version for comparison. -Tests must produce the expected deobfuscate result in order to pass.
- -If the changes produces a better deobfuscation (i.e., improved readability), -update the `-deob.js` files in the `Resources` folder to reflect the expected changes. diff --git a/tests/deobfuscation-tests.js b/tests/deobfuscation-tests.js deleted file mode 100644 index 495ecc1..0000000 --- a/tests/deobfuscation-tests.js +++ /dev/null @@ -1,388 +0,0 @@ -module.exports = [ - { - enabled: true, - name: 'Augmented Array Replacements', - source: `const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'a', 'b', 'c']; -(function(targetArray, numberOfShifts) { - var augmentArray = function(counter) { - while (--counter) { - targetArray['push'](targetArray['shift']()); - } - }; - augmentArray(++numberOfShifts); -})(arr, 3); -console.log(arr[7], arr[8]);`, - expected: `const arr = [ - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 'a', - 'b', - 'c', - 1, - 2, - 3 -]; -console.log('a', 'b');`, - }, - { - enabled: true, - name: 'Compute Definite Binary Expressions', - source: '"2" + 3 - "5" * 0 + "1"', - expected: `'231';`, - }, - { - enabled: true, - name: 'Do Not Replace Modified Member Expressions', - source: `var l = []; -const b = l.length * 2; -const c = l.length + 1; -var v = l[b]; -l[b] = l[c]; -l[c] = v;`, - expected: `var l = []; -const b = l.length * 2; -const c = l.length + 1; -var v = l[b]; -l[b] = l[c]; -l[c] = v;`, - }, - { - enabled: true, - name: 'Do Not Replace Member Expressions On Empty Arrays', - source: 'const a = []; a.push(3); console.log(a, a.length);', - expected: `const a = []; a.push(3); console.log(a, a.length);`, - }, - // TODO: Fix issue - { - enabled: false, - reason: 'Unable to assign the correct expected result', - name: 'Normalize Script Correctly', - source: '"\x22" + "\x20" + "\x5c\x5c" + "\x0a" + "\b" + "\x09" + "\x0d" + "\u0000";', - expected: `"\\" \\\\\\\\n\\b\\\\t\\\\r";`, - }, - { - enabled: true, - name: 'Parse Template Literals Into String Literals', - source: 'console.log(`https://${"url"}.${"com"}/`);', - expected: `console.log('https://url.com/');`, - }, - { - enabled: true, - name: 'Remove Nested Block Statements', - source: '{{freeNested;}} {{{freeNested2}}}', - expected: `freeNested;\nfreeNested2;`, - }, - { - enabled: true, - name: 'Remove Redundant Logical Expressions', - source: 'if (true || 0) do_a(); else do_b(); if (false && 1) do_c(); else do_d();', - expected: `do_a(); -do_d();`, - }, - { - enabled: true, - name: 'Remove Redundant Not Operators', - source: 'const a = !true; const b = !!!false;', - expected: `const a = false; -const b = true;`, - }, - { - enabled: true, - name: 'Replace Augmented Function with Corrected Array', - source: `(function(a, b){const myArr=a();for(let i=0;i String; x().fromCharCode(97);', - expected: `const x = () => String; -'a';`, - }, - { - enabled: true, - name: 'Replace Function Calls With Unwrapped Identifier', - source: 'function x() {return String}\nx().fromCharCode(97);', - expected: `function x() { - return String; -} -'a';`, - }, - { - enabled: true, - name: 'Replace Function Evals - eval(string)', - source: 'eval("console.log");', - expected: `console.log;`, - }, - { - enabled: true, - name: 'Replace Function Evals in Call Expressions - eval(string)(args)', - source: 'eval("atob")("c3VjY2Vzcw==");', - expected: `'success';`, - }, - { - enabled: true, - name: 'Replace Identifier With Fixed Assigned Value', - source: `const a = 'value'; function v(arg) {console.log(a, a[0], a.indexOf('e'));}`, - expected: `const a = 'value'; -function v(arg) { - console.log('value', 'v', 4); -}`, - }, - { - enabled: true, - name: 'Replace Literal Proxies', - source: `const b='hello'; console.log(b + ' world');`, - expected: `const b = 'hello'; -console.log('hello world');`, - }, - { - enabled: true, - name: 'Replace Local Calls Proxy - Arrow Functions', - source: `const a = n => ['hello', 'world'][n]; function c() {const b = a; return b(0) + ' ' + b(1);}`, - expected: `const a = n => [ - 'hello', - 'world' -][n]; -function c() { - return 'hello world'; -}`, - }, - { - enabled: true, - name: 'Replace Local Calls Proxy', - source: `function a(n) { return ['hello', 'world'][n]; } function c() {const b = a; return b(0) + ' ' + b(1);}`, - expected: `function a(n) { - return [ - 'hello', - 'world' - ][n]; -} -function c() { - return 'hello world'; -}`, - }, - { - enabled: true, - name: 'Replace Local Calls With Values', - source: `function localCall() {return 'value'} localCall();`, - expected: `function localCall() { - return 'value'; -} -'value';`, - }, - { - enabled: true, - name: 'Replace Local Calls With Values 2', - source: `const three = 3; -const one = 1; -function a() {return three;} -function b() {return one;} -function c(a1, b1) {return a1 + b1} -c(a(), b());`, - expected: `const three = 3; -const one = 1; -function a() { - return 3; -} -function b() { - return 1; -} -function c(a1, b1) { - return a1 + b1; -} -4;`, - }, - { - enabled: true, - name: 'Replace Local Member Expressions Property Reference With Value', - source: 'const a = {a: "hello "}; a.b = "world"; console.log(a.a + a.b);', - expected: `const a = { a: 'hello ' }; -a.b = 'world'; -console.log('hello world');`, - }, - { - enabled: true, - name: 'Replace Local Member Expressions Proxy - Chained Proxies', - source: 'const a = ["hello"], b = a[0], c = b; console.log(c);', - expected: `const a = ['hello'];\nconst b = 'hello';\nconst c = 'hello';\nconsole.log('hello');`, - }, - { - enabled: true, - name: 'Replace Local Member Expressions Proxy - Member Assignment', - source: 'const a = ["hello"], b = a[0];', - expected: `const a = ['hello'];\nconst b = 'hello';`, - }, - { - enabled: true, - name: 'Replace Member Expression Reference With Value', - source: 'const a = ["hello", " ", "world"]; console.log(a[0] + a[1] + a[2]);', - expected: `const a = [ - 'hello', - ' ', - 'world' -]; -console.log('hello world');`, - }, - { - enabled: true, - name: 'Replace Reference Proxy', - source: `const a = ['hello', ' world'], b = a[0], c = a; console.log(b + c[1]);`, - expected: `const a = [ - 'hello', - ' world' -]; -const b = 'hello'; -console.log('hello world');`, - }, - { - enabled: true, - name: 'Replace Wrapped Functions With Return Statement', - source: 'function A(a,b){return function() {return a+b;}.apply(this, arguments);}', - expected: `function A(a, b) { - return a + b; -}`, - }, - { - enabled: true, - name: 'Resolve Builtin Call Expressions', - source: `atob('dGVzdA=='); btoa('test');`, - expected: `'test'; -'dGVzdA==';`, - }, - { - enabled: true, - name: 'Resolve Definite Member Expressions', - source: `'1234567890'[3]`, - expected: `'4';`, - }, - { - enabled: true, - name: 'Resolve Deterministic Conditional Expressions', - source: `(true ? 'o' : 'x') + (false ? 'X' : 'k');`, - expected: `'ok';`, - }, - { - enabled: true, - name: 'Resolve Directly Assigned Member Expressions', - source: `function a() {} a.b = 3; a.c = '5'; console.log(a.b + a.c);`, - expected: `function a() { -} -a.b = 3; -a.c = '5'; -console.log('35');`, - }, - { - enabled: true, - name: 'Resolve External References With Context', - source: `const a = [1, 2, 3]; (function(arr) {arr.forEach((x, i, arr) => arr[i] = x * 10)})(a); function b() {const c = [...a]; return c[0] + 3;}`, - expected: `const a = [ - 1, - 2, - 3 -]; -(function (arr) { - arr.forEach((x, i, arr) => arr[i] = x * 10); -}(a)); -function b() { - const c = [...a]; - return 13; -}`, - }, - { - enabled: true, - name: 'Resolve Function Constructor Calls', - source: `function a() {} const b = a.constructor('', "return a()"); const c = b.constructor('a', 'b', 'return a + b');`, - expected: `function a() { -} -const b = function () { - return a(); -}; -const c = function (a, b) { - return a + b; -};`, - }, - { - enabled: true, - name: 'Resolve Injected Prototype Method Calls', - source: `String.prototype.secret = function() {return 'secret ' + this}; 'hello'.secret();`, - expected: `String.prototype.secret = function () { - return 'secret ' + this; -}; -'secret hello';`, - }, - { - enabled: true, - name: 'Resolve Member Expression Local References with Unary Expressions Correctly', - source: `const a = ['-example', '-3', '-Infinity']; a[0]; a[1]; a[2];`, - expected: `const a = [\n '-example',\n '-3',\n '-Infinity'\n];\n'-example';\n-'3';\n-Infinity;`, - }, - { - enabled: true, - name: 'Resolve Member Expression References With Context', - source: `const a = [1, 2, 3]; (function(arr) {arr.forEach((x, i, arr) => arr[i] = x * 3)})(a); const b = a[0];`, - expected: `const a = [ - 1, - 2, - 3 -]; -(function (arr) { - arr.forEach((x, i, arr) => arr[i] = x * 3); -}(a)); -const b = 3;`, - }, - { - enabled: true, - name: 'Unwrap Function Shells', - source: `function O() {return function () {return clearInterval;}.apply(this, arguments);}`, - expected: `function O() { - return clearInterval; -}`, - }, - { - enabled: true, - name: 'Verify Correct Context For Function Declaration', - source: `function a(v) {return v + '4'}; if (a(0)) {console.log(a(18));}`, - expected: `function a(v) {\n return v + '4';\n}\nconsole.log('184');`, - }, - { - enabled: true, - name: 'Verify Correct Context For Function Variable', - source: `let a = function (v) {return v + '4'}; if (a(0)) {console.log(a(18));}`, - expected: `let a = function (v) {\n return v + '4';\n};\nconsole.log('184');`, - }, - { - enabled: true, - name: 'Verify Correct Replacement Of Member Expressions With Literal', - source: `const n = 3, b = 'B'; -const a = {b: 'hello'}; -a.n = 15; -console.log(a.n, a.b);`, - expected: `const n = 3; -const b = 'B'; -const a = { b: 'hello' }; -a.n = 15; -console.log(15, 'hello');`, - }, - { - enabled: true, - name: 'Verify Random Values Remain Untouched', - source: 'const a = new Date(); const b = Date.now(); const c = Math.random(); const d = 4; console.log(a + b + c + d);', - expected: `const a = new Date(); -const b = Date.now(); -const c = Math.random(); -const d = 4; -console.log(a + b + c + 4);`, - }, -]; \ No newline at end of file diff --git a/tests/deobfuscation.test.js b/tests/deobfuscation.test.js new file mode 100644 index 0000000..f107116 --- /dev/null +++ b/tests/deobfuscation.test.js @@ -0,0 +1,394 @@ +import {REstringer} from '../src/restringer.js'; +import assert from 'node:assert'; +import {describe, it} from 'node:test'; + +function getDeobfuscatedCode(code) { + const restringer = new REstringer(code); + restringer.logger.setLogLevelNone(); + restringer.deobfuscate(); + return restringer.script; +} + +describe('Deobfuscation tests', () => { + it('Augmented Array Replacements', () => { + const code = `const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'a', 'b', 'c']; +(function(targetArray, numberOfShifts) { + var augmentArray = function(counter) { + while (--counter) { + targetArray['push'](targetArray['shift']()); + } + }; + augmentArray(++numberOfShifts); +})(arr, 3); +console.log(arr[7], arr[8]);`; + const expected = `const arr = [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 'a', + 'b', + 'c', + 1, + 2, + 3 +]; +console.log('a', 'b');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Compute definite binary expressions', () => { + const code = `"2" + 3 - "5" * 0 + "1"`; + const expected = `'231';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Don't replace modified member expressions`, () => { + const code = `var l = []; +const b = l.length * 2; +const c = l.length + 1; +var v = l[b]; +l[b] = l[c]; +l[c] = v;`; + const expected = `var l = []; +const b = l.length * 2; +const c = l.length + 1; +var v = l[b]; +l[b] = l[c]; +l[c] = v;`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Don't replace member expressions on empty arrays`, () => { + const code = `const a = []; a.push(3); console.log(a, a.length);`; + const expected = `const a = []; a.push(3); console.log(a, a.length);`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it.skip(`TODO: Fix. Normalize Script Correctly`, () => { + const code = `"\x22" + "\x20" + "\x5c\x5c" + "\x0a" + "\b" + "\x09" + "\x0d" + "\u0000";`; + const expected = `'" \\\\\\\\\\n\\b\\t\\r\x00';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Parse template literals into string literals`, () => { + const code = 'console.log(`https://${"url"}.${"com"}/`);'; + const expected = `console.log('https://url.com/');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Remove nested block statements`, () => { + const code = `{{freeNested;}} {{{freeNested2}}}`; + const expected = `freeNested;\nfreeNested2;`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Remove redundant logical expressions`, () => { + const code = `if (true || 0) do_a(); else do_b(); if (false && 1) do_c(); else do_d();`; + const expected = `do_a();\ndo_d();`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Remove redundant not operators`, () => { + const code = `const a = !true; const b = !!!false;`; + const expected = `const a = false;\nconst b = true;`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace augmented function with corrected array`, () => { + const code = `(function(a, b){const myArr=a();for(let i=0;i { + const code = `if(true){a;}if(false){b}if(false||c){c}if(true&&d){d}`; + const expected = `a;\nif (c) {\n c;\n}\nif (d) {\n d;\n}`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace function calls with unwrapped identifier - arrow functions`, () => { + const code = `const x = () => String; x().fromCharCode(97);`; + const expected = `const x = () => String;\n'a';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace function calls with unwrapped identifier - function declarations`, () => { + const code = `function x() {return String}\nx().fromCharCode(97);`; + const expected = `function x() { + return String; +} +'a';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace function evals - eval(string)`, () => { + const code = `eval("console.log");`; + const expected = `console.log;`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace function evals in call expressions - eval(string)(args)`, () => { + const code = `eval("atob")("c3VjY2Vzcw==");`; + const expected = `'success';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace identifier with fixed assigned value`, () => { + const code = `const a = 'value'; function v(arg) {console.log(a, a[0], a.indexOf('e'));}`; + const expected = `const a = 'value'; +function v(arg) { + console.log('value', 'v', 4); +}`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace literal proxies`, () => { + const code = `const b='hello'; console.log(b + ' world');`; + const expected = `const b = 'hello';\nconsole.log('hello world');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace local calls proxy - arrow functions`, () => { + const code = `const a = n => ['hello', 'world'][n]; function c() {const b = a; return b(0) + ' ' + b(1);}`; + const expected = `const a = n => [ + 'hello', + 'world' +][n]; +function c() { + return 'hello world'; +}`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it.skip(`TODO: FIX Replace local calls proxy - function declarations`, () => { + // TODO: For some reason running this test sometimes breaks isolated-vm with the error: + // Assertion failed: (environment != nullptr), function GetCurrent, file environment.h, line 202. + const code = `function a(n) { return ['hello', 'world'][n]; } function c() {const b = a; return b(0) + ' ' + b(1);}`; + const expected = `function a(n) { + return [ + 'hello', + 'world' + ][n]; +} +function c() { + return 'hello world'; +}`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace local calls with values - immediate`, () => { + const code = `function localCall() {return 'value'} localCall()`; + const expected = `function localCall() { + return 'value'; +} +'value';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace local calls with values - nested`, () => { + const code = `const three = 3; +const one = 1; +function a() {return three;} +function b() {return one;} +function c(a1, b1) {return a1 + b1} +c(a(), b());`; + const expected = `const three = 3; +const one = 1; +function a() { + return 3; +} +function b() { + return 1; +} +function c(a1, b1) { + return a1 + b1; +} +4;`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace local member expressions property reference with value`, () => { + const code = `const a = {a: "hello "}; a.b = "world"; console.log(a.a + a.b);`; + const expected = `const a = { a: 'hello ' }; +a.b = 'world'; +console.log('hello world');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace local member expressions proxy - chained proxies`, () => { + const code = `const a = ["hello"], b = a[0], c = b; console.log(c);`; + const expected = `const a = ['hello'];\nconst b = 'hello';\nconst c = 'hello';\nconsole.log('hello');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace local member expressions proxy - member assignment`, () => { + const code = `const a = ["hello"], b = a[0];`; + const expected = `const a = ['hello'];\nconst b = 'hello';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace member expression references with values`, () => { + const code = `const a = ["hello", " ", "world"]; console.log(a[0] + a[1] + a[2]);`; + const expected = `const a = [ + 'hello', + ' ', + 'world' +]; +console.log('hello world');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace reference proxy`, () => { + const code = `const a = ['hello', ' world'], b = a[0], c = a; console.log(b + c[1]);`; + const expected = `const a = [ + 'hello', + ' world' +]; +const b = 'hello'; +console.log('hello world');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Replace wrapped functions with return statement`, () => { + const code = `function A(a,b){return function() {return a+b;}.apply(this, arguments);}`; + const expected = `function A(a, b) { + return a + b; +}`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve builtin call expressions: btoa & atob`, () => { + const code = `atob('dGVzdA=='); btoa('test');`; + const expected = `'test';\n'dGVzdA==';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve definite member expressions`, () => { + const code = `'1234567890'[3]`; + const expected = `'4';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve deterministic conditional expressions`, () => { + const code = `(true ? 'o' : 'x') + (false ? 'X' : 'k');`; + const expected = `'ok';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve directly assigned member expressions`, () => { + const code = `function a() {} a.b = 3; a.c = '5'; console.log(a.b + a.c);`; + const expected = `function a() { +} +a.b = 3; +a.c = '5'; +console.log('35');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve external references with context`, () => { + const code = `const a = [1, 2, 3]; (function(arr) {arr.forEach((x, i, arr) => arr[i] = x * 10)})(a); function b() {const c = [...a]; return c[0] + 3;}`; + const expected = `const a = [ + 1, + 2, + 3 +]; +(function (arr) { + arr.forEach((x, i, arr) => arr[i] = x * 10); +}(a)); +function b() { + const c = [...a]; + return 13; +}`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve function constructor calls`, () => { + const code = `function a() {} const b = a.constructor('', "return a()"); const c = b.constructor('a', 'b', 'return a + b');`; + const expected = `function a() { +} +const b = function () { + return a(); +}; +const c = function (a, b) { + return a + b; +};`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve injected prototype method calls`, () => { + const code = `String.prototype.secret = function() {return 'secret ' + this}; 'hello'.secret();`; + const expected = `String.prototype.secret = function () { + return 'secret ' + this; +}; +'secret hello';`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve member expression local references with unary expressions correctly`, () => { + const code = `const a = ['-example', '-3', '-Infinity']; a[0]; a[1]; a[2];`; + const expected = `const a = [\n '-example',\n '-3',\n '-Infinity'\n];\n'-example';\n-'3';\n-Infinity;`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Resolve member expression references with context`, () => { + const code = `const a = [1, 2, 3]; (function(arr) {arr.forEach((x, i, arr) => arr[i] = x * 3)})(a); const b = a[0];`; + const expected = `const a = [ + 1, + 2, + 3 +]; +(function (arr) { + arr.forEach((x, i, arr) => arr[i] = x * 3); +}(a)); +const b = 3;`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Unwrap function shells`, () => { + const code = `function O() {return function () {return clearInterval;}.apply(this, arguments);}`; + const expected = `function O() { + return clearInterval; +}`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Verify correct context for function declaration`, () => { + const code = `function a(v) {return v + '4'}; if (a(0)) {console.log(a(18));}`; + const expected = `function a(v) {\n return v + '4';\n}\nconsole.log('184');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Verify correct context for function variable`, () => { + const code = `let a = function (v) {return v + '4'}; if (a(0)) {console.log(a(18));}`; + const expected = `let a = function (v) {\n return v + '4';\n};\nconsole.log('184');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Verify correct replacement of member expressions with literals`, () => { + const code = `const n = 3, b = 'B'; +const a = {b: 'hello'}; +a.n = 15; +console.log(a.n, a.b);`; + const expected = `const n = 3; +const b = 'B'; +const a = { b: 'hello' }; +a.n = 15; +console.log(15, 'hello');`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it(`Verify random values remain untouched`, () => { + const code = `const a = new Date(); const b = Date.now(); const c = Math.random(); const d = 4; console.log(a + b + c + d);`; + const expected = `const a = new Date(); +const b = Date.now(); +const c = Math.random(); +const d = 4; +console.log(a + b + c + 4);`; + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); +}); diff --git a/tests/functionality.test.js b/tests/functionality.test.js new file mode 100644 index 0000000..ea2f4a1 --- /dev/null +++ b/tests/functionality.test.js @@ -0,0 +1,15 @@ +import assert from 'node:assert'; +import {describe, it} from 'node:test'; +import {REstringer} from '../src/restringer.js'; + + +describe('Functionality tests', () => { + it('Set max iterations', () => { + const code = `eval('eval("eval(3)")')`; + const restringer = new REstringer(code); + restringer.logger.setLogLevelNone(); + restringer.maxIterations.value = 3; + restringer.deobfuscate(); + assert.strictEqual(restringer.script, 'eval(3);'); + }); +}); diff --git a/tests/modules-tests.js b/tests/modules-tests.js deleted file mode 100644 index 0b91c14..0000000 --- a/tests/modules-tests.js +++ /dev/null @@ -1,855 +0,0 @@ -const {generateFlatAST} = require('flast'); -const {badValue} = require(__dirname + '/../src/modules/config'); - -module.exports = [ - // Safe - { - enabled: true, - name: 'removeRedundantBlockStatements - TP-1', - func: __dirname + '/../src/modules/safe/removeRedundantBlockStatements', - source: `if (a) {{do_a();}}`, - expected: `if (a) {\n do_a();\n}`, - }, - { - enabled: true, - name: 'removeRedundantBlockStatements - TP-2', - func: __dirname + '/../src/modules/safe/removeRedundantBlockStatements', - source: `if (a) {{do_a();}{do_b();}}`, - expected: `if (a) {\n do_a();\n do_b();\n}`, - }, - { - enabled: true, - looped: true, - name: 'removeRedundantBlockStatements - TP-3', - func: __dirname + '/../src/modules/safe/removeRedundantBlockStatements', - source: `if (a) {{do_a();}{do_b(); do_c();}{do_d();}}`, - expected: `if (a) {\n do_a();\n do_b();\n do_c();\n do_d();\n}`, - }, - { - enabled: true, - name: 'removeRedundantBlockStatements - TP-3', - func: __dirname + '/../src/modules/safe/removeRedundantBlockStatements', - source: `if (a) {{do_a();} do_b();}`, - expected: `if (a) {\n do_a();\n do_b();\n}`, - }, - { - enabled: true, - looped: true, - name: 'removeRedundantBlockStatements - TP-4', - func: __dirname + '/../src/modules/safe/removeRedundantBlockStatements', - source: `if (a) {{{{{do_a();}}}} do_b();}`, - expected: `if (a) {\n do_a();\n do_b();\n}`, - }, - { - enabled: true, - name: 'normalizeComputed - TP-1', - func: __dirname + '/../src/modules/safe/normalizeComputed', - source: `hello['world'][0]['%32']['valid']`, - expected: `hello.world[0]['%32'].valid;`, - }, - { - enabled: true, - name: 'normalizeEmptyStatements - TP-1', - func: __dirname + '/../src/modules/safe/normalizeEmptyStatements', - source: `;;var a = 3;;`, - expected: `var a = 3;`, - }, - { - enabled: true, - name: 'normalizeEmptyStatements - TN-1', - func: __dirname + '/../src/modules/safe/normalizeEmptyStatements', - source: `for (;;);`, - expected: `for (;;);`, - }, - { - enabled: true, - name: 'parseTemplateLiteralsIntoStringLiterals - TP-1', - func: __dirname + '/../src/modules/safe/parseTemplateLiteralsIntoStringLiterals', - source: '`hello ${"world"}!`;', - expected: `'hello world!';`, - }, - { - enabled: true, - name: 'rearrangeSequences - TP-1', - func: __dirname + '/../src/modules/safe/rearrangeSequences', - source: `function f() { return a(), b(), c(); }`, - expected: `function f() {\n a();\n b();\n return c();\n}`, - }, - { - enabled: true, - name: 'rearrangeSequences - TP-2', - func: __dirname + '/../src/modules/safe/rearrangeSequences', - source: `function f() { if (x) return a(), b(), c(); else d(); }`, - expected: `function f() {\n if (x) {\n a();\n b();\n return c();\n } else\n d();\n}`, - }, - { - enabled: true, - name: 'rearrangeSequences - TP-3', - func: __dirname + '/../src/modules/safe/rearrangeSequences', - source: `function f() { if (a(), b()) c(); }`, - expected: `function f() {\n a();\n if (b())\n c();\n}`, - }, - { - enabled: true, - name: 'rearrangeSequences - TP-4', - func: __dirname + '/../src/modules/safe/rearrangeSequences', - source: `function f() { if (x) if (a(), b()) c(); }`, - expected: `function f() {\n if (x) {\n a();\n if (b())\n c();\n }\n}`, - }, - { - enabled: true, - name: 'rearrangeSwitches - TP-1', - func: __dirname + '/../src/modules/safe/rearrangeSwitches', - source: `(() => {let a = 1;\twhile (true) {switch (a) {case 3: return console.log(3); case 2: console.log(2); a = 3; break; -case 1: console.log(1); a = 2; break;}}})();`, - expected: `(() => { - let a = 1; - while (true) { - { - console.log(1); - a = 2; - console.log(2); - a = 3; - return console.log(3); - } - } -})();`, - }, - { - enabled: true, - name: 'removeDeadNodes - TP-1', - func: __dirname + '/../src/modules/safe/removeDeadNodes', - source: 'var a = 3, b = 12; console.log(b);', - expected: `var b = 12;\nconsole.log(b);`, - }, - { - enabled: true, - name: 'replaceCallExpressionsWithUnwrappedIdentifier - TP-1', - func: __dirname + '/../src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier', - source: `const a = () => btoa; a()('yo');`, - expected: `const a = () => btoa;\nbtoa('yo');`, - }, - { - enabled: true, - name: 'replaceCallExpressionsWithUnwrappedIdentifier - TP-2', - func: __dirname + '/../src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier', - source: `function a() {return btoa;} a()('yo');`, - expected: `function a() {\n return btoa;\n}\nbtoa('yo');`, - }, - { - enabled: true, - name: 'replaceEvalCallsWithLiteralContent - TP-1', - func: __dirname + '/../src/modules/safe/replaceEvalCallsWithLiteralContent', - source: `eval('console.log("hello world")');`, - expected: `console.log('hello world');`, - }, - { - enabled: true, - name: 'replaceEvalCallsWithLiteralContent - TP-2', - func: __dirname + '/../src/modules/safe/replaceEvalCallsWithLiteralContent', - source: `eval('a; b;');`, - expected: `{\n a;\n b;\n}`, - }, - { - enabled: true, - name: 'replaceEvalCallsWithLiteralContent - TP-3', - func: __dirname + '/../src/modules/safe/replaceEvalCallsWithLiteralContent', - source: `function q() {return (eval('a, b;'));}`, - expected: `function q() {\n return a, b;\n}`, - }, - { - enabled: true, - name: 'replaceEvalCallsWithLiteralContent - TP-4', - func: __dirname + '/../src/modules/safe/replaceEvalCallsWithLiteralContent', - source: `eval('()=>1')();`, - expected: `(() => 1)();`, - }, - { - enabled: true, - name: 'replaceEvalCallsWithLiteralContent - TP-5', - func: __dirname + '/../src/modules/safe/replaceEvalCallsWithLiteralContent', - source: `eval('3 * 5') + 1;`, - expected: `3 * 5 + 1;`, - }, - { - enabled: true, - name: 'replaceEvalCallsWithLiteralContent - TP-6', - func: __dirname + '/../src/modules/safe/replaceEvalCallsWithLiteralContent', - source: `console.log(eval('1;'));`, - expected: `console.log(1);`, - }, - { - enabled: true, - name: 'replaceFunctionShellsWithWrappedValue - TP-1', - func: __dirname + '/../src/modules/safe/replaceFunctionShellsWithWrappedValue', - source: `function a() {return String}\na()(val);`, - expected: `function a() {\n return String;\n}\nString(val);`, - }, - { - enabled: true, - name: 'replaceFunctionShellsWithWrappedValue - TN-1', - func: __dirname + '/../src/modules/safe/replaceFunctionShellsWithWrappedValue', - source: `function a() {\n return 0;\n}\nconst o = { key: a }`, - expected: `function a() {\n return 0;\n}\nconst o = { key: a }`, - }, - { - enabled: true, - name: 'replaceFunctionShellsWithWrappedValue - TN-2', - func: __dirname + '/../src/modules/safe/replaceFunctionShellsWithWrappedValue', - source: `function a() {\n return 0;\n}\nconsole.log(a);`, - expected: `function a() {\n return 0;\n}\nconsole.log(a);`, - }, - { - enabled: true, - name: 'replaceFunctionShellsWithWrappedValueIIFE - TP-1', - func: __dirname + '/../src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE', - source: `(function a() {return String}\n)()(val);`, - expected: `String(val);`, - }, - { - enabled: true, - name: 'replaceIdentifierWithFixedAssignedValue - TP-1', - func: __dirname + '/../src/modules/safe/replaceIdentifierWithFixedAssignedValue', - source: `const a = 3; const b = a * 2; console.log(b + a);`, - expected: `const a = 3;\nconst b = 3 * 2;\nconsole.log(b + 3);`, - }, - { - enabled: true, - name: 'replaceIdentifierWithFixedAssignedValue - TN-1', - func: __dirname + '/../src/modules/safe/replaceIdentifierWithFixedAssignedValue', - source: `var a = 3; for (a in [1, 2]) console.log(a);`, - expected: `var a = 3; for (a in [1, 2]) console.log(a);`, - }, - { - enabled: true, - name: 'replaceIdentifierWithFixedAssignedValue - TN-2', - func: __dirname + '/../src/modules/safe/replaceIdentifierWithFixedAssignedValue', - source: `var a = 3; for (a of [1, 2]) console.log(a);`, - expected: `var a = 3; for (a of [1, 2]) console.log(a);`, - }, - { - enabled: true, - name: 'replaceIdentifierWithFixedValueNotAssignedAtDeclaration - TP-1', - func: __dirname + '/../src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration', - source: `let a; a = 3; const b = a * 2; console.log(b + a);`, - expected: `let a;\na = 3;\nconst b = 3 * 2;\nconsole.log(b + 3);`, - }, - { - enabled: true, - name: 'replaceNewFuncCallsWithLiteralContent - TP-1', - func: __dirname + '/../src/modules/safe/replaceNewFuncCallsWithLiteralContent', - source: `new Function("!function() {console.log('hello world')}()")();`, - expected: `!function () {\n console.log('hello world');\n}();`, - }, - { - enabled: true, - name: 'replaceBooleanExpressionsWithIf - TP-1', - func: __dirname + '/../src/modules/safe/replaceBooleanExpressionsWithIf', - source: `x && y && z();`, - expected: `if (x && y) {\n z();\n}`, - }, - { - enabled: true, - name: 'replaceBooleanExpressionsWithIf - TP-2', - func: __dirname + '/../src/modules/safe/replaceBooleanExpressionsWithIf', - source: `x || y || z();`, - expected: `if (!(x || y)) {\n z();\n}`, - }, - { - enabled: true, - name: 'replaceSequencesWithExpressions - TP-1', - func: __dirname + '/../src/modules/safe/replaceSequencesWithExpressions', - source: `if (a) (b(), c());`, - expected: `if (a) {\n b();\n c();\n}`, - }, - { - enabled: true, - name: 'replaceSequencesWithExpressions - TP-2', - func: __dirname + '/../src/modules/safe/replaceSequencesWithExpressions', - source: `if (a) { (b(), c()); d() }`, - expected: `if (a) {\n b();\n c();\n d();\n}`, - }, - { - enabled: true, - name: 'resolveDeterministicIfStatements - TP-1', - func: __dirname + '/../src/modules/safe/resolveDeterministicIfStatements', - source: `if (true) do_a(); else do_b(); if (false) do_c(); else do_d();`, - expected: `do_a();\ndo_d();`, - }, - { - enabled: true, - name: 'resolveFunctionConstructorCalls - TP-1', - func: __dirname + '/../src/modules/safe/resolveFunctionConstructorCalls', - source: `const func = Function.constructor('', "console.log('hello world!');");`, - expected: `const func = function () {\n console.log('hello world!');\n};`, - }, - { - enabled: true, - name: 'resolveFunctionConstructorCalls - TP-2', - func: __dirname + '/../src/modules/safe/resolveFunctionConstructorCalls', - source: `a = Function.constructor('return /" + this + "/')().constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');`, - expected: `a = function () {\n return /" + this + "/;\n}().constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');`, - }, - { - enabled: true, - name: 'resolveMemberExpressionReferencesToArrayIndex - TP-1', - func: __dirname + '/../src/modules/safe/resolveMemberExpressionReferencesToArrayIndex', - source: `const a = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3]; b = a[0]; c = a[20];`, - expected: `const a = [\n 1,\n 1,\n 1,\n 1,\n 1,\n 1,\n 1,\n 1,\n 1,\n 1, - 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 3\n];\nb = 1;\nc = 3;`, - }, - { - enabled: true, - name: 'resolveMemberExpressionReferencesToArrayIndex - TN-1', - func: __dirname + '/../src/modules/safe/resolveMemberExpressionReferencesToArrayIndex', - source: `const a = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3]; b = a['indexOf']; c = a['length'];`, - expected: `const a = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3]; b = a['indexOf']; c = a['length'];`, - }, - { - enabled: true, - name: 'resolveMemberExpressionsWithDirectAssignment - TP-1', - func: __dirname + '/../src/modules/safe/resolveMemberExpressionsWithDirectAssignment', - source: `function a() {} a.b = 3; a.c = '5'; console.log(a.b + a.c);`, - expected: `function a() {\n}\na.b = 3;\na.c = '5';\nconsole.log(3 + '5');`, - }, - { - enabled: true, - name: 'resolveMemberExpressionsWithDirectAssignment - TN-1', - func: __dirname + '/../src/modules/safe/resolveMemberExpressionsWithDirectAssignment', - source: `const a = {}; a.b = ''; a.b = 3;`, - expected: `const a = {}; a.b = ''; a.b = 3;`, - }, - { - enabled: true, - name: 'resolveMemberExpressionsWithDirectAssignment - TN-2', - func: __dirname + '/../src/modules/safe/resolveMemberExpressionsWithDirectAssignment', - source: `const a = {}; a.b = 0; ++a.b + 2;`, - expected: `const a = {}; a.b = 0; ++a.b + 2;`, - }, - { - enabled: true, - name: 'resolveProxyCalls - TP-1', - func: __dirname + '/../src/modules/safe/resolveProxyCalls', - source: `function call1(a, b) {return a + b;} function call2(c, d) {return call1(c, d);} function call3(e, f) {return call2(e, f);}`, - expected: `function call1(a, b) {\n return a + b;\n}\nfunction call2(c, d) {\n return call1(c, d);\n}\nfunction call3(e, f) {\n return call1(e, f);\n}`, - }, - { - enabled: true, - name: 'resolveProxyReferences - TP-1', - func: __dirname + '/../src/modules/safe/resolveProxyReferences', - source: `const a = ['']; const b = a; const c = b[0];`, - expected: `const a = [''];\nconst b = a;\nconst c = a[0];`, - }, - { - enabled: true, - looped: true, - name: 'resolveProxyVariables - TP-1', - func: __dirname + '/../src/modules/safe/resolveProxyVariables', - source: `const a2b = atob; console.log(a2b('NDI='));`, - expected: `console.log(atob('NDI='));`, - }, - { - enabled: true, - looped: true, - name: 'resolveProxyVariables - TP-2', - func: __dirname + '/../src/modules/safe/resolveProxyVariables', - source: `const a2b = atob, a = 3; console.log(a2b('NDI='));`, - expected: `const a = 3;\nconsole.log(atob('NDI='));`, - }, - { - enabled: true, - name: 'resolveRedundantLogicalExpressions - TP-1', - func: __dirname + '/../src/modules/safe/resolveRedundantLogicalExpressions', - source: `if (false && true) {} if (false || true) {} if (true && false) {} if (true || false) {}`, - expected: `if (false) {\n}\nif (true) {\n}\nif (false) {\n}\nif (true) {\n}`, - }, - { - enabled: true, - name: 'unwrapFunctionShells - TP-1', - func: __dirname + '/../src/modules/safe/unwrapFunctionShells', - source: `function a(x) {return function b() {return x + 3}.apply(this, arguments);}`, - expected: `function b(x) {\n return x + 3;\n}`, - }, - { - enabled: true, - name: 'unwrapFunctionShells - TP-2', - func: __dirname + '/../src/modules/safe/unwrapFunctionShells', - source: `function a(x) {return function() {return x + 3}.apply(this, arguments);}`, - expected: `function a(x) {\n return x + 3;\n}`, - }, - { - enabled: true, - name: 'unwrapIIFEs - TP-1 (arrow functions)', - func: __dirname + '/../src/modules/safe/unwrapIIFEs', - source: `var a = (() => { - return b => { - return c(b - 40); - }; - })();`, - expected: `var a = b => {\n return c(b - 40);\n};`, - }, - { - enabled: true, - name: 'unwrapIIFEs - TP-2 (function expression)', - func: __dirname + '/../src/modules/safe/unwrapIIFEs', - source: `var a = (function () { - return b => c(b - 40); -})();`, - expected: `var a = b => c(b - 40);`, - }, - { - enabled: true, - name: 'unwrapIIFEs - TP-3 (inline unwrapping)', - func: __dirname + '/../src/modules/safe/unwrapIIFEs', - source: `!function() { - var a = 'message'; - console.log(a); -}();`, - expected: `var a = 'message';\nconsole.log(a);`, - }, - { - enabled: true, - name: 'unwrapIIFEs - TN-1 (unary declarator init)', - func: __dirname + '/../src/modules/safe/unwrapIIFEs', - source: `var b = !function() { - var a = 'message'; - console.log(a); -}();`, - expected: `var b = !function() {\n\tvar a = 'message';\n\tconsole.log(a);\n}();`, - }, - { - enabled: true, - name: 'unwrapIIFEs - TN-2 (unary assignment right)', - func: __dirname + '/../src/modules/safe/unwrapIIFEs', - source: `b = !function() { - var a = 'message'; - console.log(a); -}();`, - expected: `b = !function() {\n\tvar a = 'message';\n\tconsole.log(a);\n}();`, - }, - { - enabled: true, - name: 'unwrapSimpleOperations - TP-1', - func: __dirname + '/../src/modules/safe/unwrapSimpleOperations', - source: `function add(b,c){return b + c;} -function minus(b,c){return b - c;} -function mul(b,c){return b * c;} -function div(b,c){return b / c;} -function power(b,c){return b ** c;} -function and(b,c){return b && c;} -function band(b,c){return b & c;} -function or(b,c){return b || c;} -function bor(b,c){return b | c;} -function xor(b,c){return b ^ c;} -add(1, 2); -minus(1, 2); -mul(1, 2); -div(1, 2); -power(1, 2); -and(1, 2); -band(1, 2); -or(1, 2); -bor(1, 2); -xor(1, 2);`, - expected: `function add(b, c) { - return b + c; -} -function minus(b, c) { - return b - c; -} -function mul(b, c) { - return b * c; -} -function div(b, c) { - return b / c; -} -function power(b, c) { - return b ** c; -} -function and(b, c) { - return b && c; -} -function band(b, c) { - return b & c; -} -function or(b, c) { - return b || c; -} -function bor(b, c) { - return b | c; -} -function xor(b, c) { - return b ^ c; -} -1 + 2; -1 - 2; -1 * 2; -1 / 2; -1 ** 2; -1 && 2; -1 & 2; -1 || 2; -1 | 2; -1 ^ 2;`, - }, - { - enabled: true, - name: 'separateChainedDeclarators - TP-1', - func: __dirname + '/../src/modules/safe/separateChainedDeclarators', - source: `const foo = 5, bar = 7;`, - expected: `const foo = 5;\nconst bar = 7;`, - }, - { - enabled: true, - name: 'separateChainedDeclarators - TP-2', - func: __dirname + '/../src/modules/safe/separateChainedDeclarators', - source: `const a = 1; let foo = 5, bar = 7;`, - expected: `const a = 1;\nlet foo = 5;\nlet bar = 7;`, - }, - { - enabled: true, - looped: true, - name: 'separateChainedDeclarators - TP-3', - func: __dirname + '/../src/modules/safe/separateChainedDeclarators', - source: `!function() {var a, b = 2; let c, d = 3;}();`, - expected: `!function () {\n var a;\n var b = 2;\n let c;\n let d = 3;\n}();`, - }, - { - enabled: true, - looped: true, - name: 'separateChainedDeclarators - TP-4', - func: __dirname + '/../src/modules/safe/separateChainedDeclarators', - source: `if (a) var b, c; while (true) var e = 3, d = 3;`, - expected: `if (a) {\n var b;\n var c;\n}\nwhile (true) {\n var e = 3;\n var d = 3;\n}`, - }, - { - enabled: true, - looped: true, - name: 'separateChainedDeclarators - TN-1', - func: __dirname + '/../src/modules/safe/separateChainedDeclarators', - source: `for (let i, b = 2, c = 3;;);`, - expected: `for (let i, b = 2, c = 3;;);`, - }, - { - enabled: true, - name: 'simplifyCalls - TP-1', - func: __dirname + '/../src/modules/safe/simplifyCalls', - source: `func1.apply(this, [arg1, arg2]); func2.call(this, arg1, arg2);`, - expected: `func1(arg1, arg2);\nfunc2(arg1, arg2);`, - }, - { - enabled: true, - name: 'simplifyIfStatements - TP-1 (empty blocks)', - func: __dirname + '/../src/modules/safe/simplifyIfStatements', - source: `if (J) {} else {}`, - expected: `J;`, - }, - { - enabled: true, - name: 'simplifyIfStatements - TP-2 (empty block, empty alternate statement)', - func: __dirname + '/../src/modules/safe/simplifyIfStatements', - source: `if (J) {} else;`, - expected: `J;`, - }, - { - enabled: true, - name: 'simplifyIfStatements - TP-3 (empty block, populated alternate expression)', - func: __dirname + '/../src/modules/safe/simplifyIfStatements', - source: `if (J) {} else J();`, - expected: `if (!J)\n J();`, - }, - { - enabled: true, - name: 'simplifyIfStatements - TP-4 (empty block, populated alternate block)', - func: __dirname + '/../src/modules/safe/simplifyIfStatements', - source: `if (J) {} else {J()}`, - expected: `if (!J) {\n J();\n}`, - }, - { - enabled: true, - name: 'simplifyIfStatements - TP-5 (empty statements)', - func: __dirname + '/../src/modules/safe/simplifyIfStatements', - source: `if (J); else;`, - expected: `J;`, - }, - { - enabled: true, - name: 'simplifyIfStatements - TP-6 (empty statement, no alternate)', - func: __dirname + '/../src/modules/safe/simplifyIfStatements', - source: `if (J);`, - expected: `J;`, - }, - { - enabled: true, - name: 'simplifyIfStatements - TP-7 (empty block, no alternate)', - func: __dirname + '/../src/modules/safe/simplifyIfStatements', - source: `if (J) {}`, - expected: `J;`, - }, - - // Unsafe - { - enabled: true, - isUtil: true, - name: 'evalInVm - TP-1', - func: __dirname + '/../src/modules/utils/evalInVm', - prepareTest: a => [a], - prepareResult: b => b, - source: `'hello ' + 'there';`, - expected: {type: 'Literal', value: 'hello there', raw: 'hello there'}, - }, - { - enabled: true, - isUtil: true, - name: 'evalInVm - TN-1', - func: __dirname + '/../src/modules/utils/evalInVm', - prepareTest: a => [a], - prepareResult: b => b, - source: `Math.random();`, - expected: badValue, - }, - { - enabled: true, - isUtil: true, - name: 'evalInVm - TN-2', - func: __dirname + '/../src/modules/utils/evalInVm', - prepareTest: a => [a], - prepareResult: b => b, - source: `function a() {return console;} a();`, - expected: badValue, - }, - { - enabled: false, - reason: 'TODO: Consider proper tests for function', - name: 'evalWithDom - TP-1', - func: __dirname + '/../src/modules/unsafe/evalWithDom', - prepareTest: () => {}, - prepareResult: () => {}, - source: ``, - expected: `function a(x) {\n return x + 3;\n}`, - }, - { - enabled: true, - name: 'normalizeRedundantNotOperator - TP-1', - func: __dirname + '/../src/modules/unsafe/normalizeRedundantNotOperator', - source: `!true || !false || !0 || !1 || !a || !'a' || ![] || !{} || !-1 || !!true || !!!true`, - expected: `false || true || true || false || !a || false || false || false || false || true || false;`, - }, - { - enabled: false, - reason: 'TODO: Consider proper tests for function', - name: 'resolveAugmentedFunctionWrappedArrayReplacements - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements', - source: `1`, - expected: ``, - }, - { - enabled: true, - name: 'resolveBuiltinCalls - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveBuiltinCalls', - source: `atob('c29sdmVkIQ==');`, - expected: `'solved!';`, - }, - { - enabled: true, - name: 'resolveBuiltinCalls - TP-2', - func: __dirname + '/../src/modules/unsafe/resolveBuiltinCalls', - source: `btoa('solved!');`, - expected: `'c29sdmVkIQ==';`, - }, - { - enabled: true, - name: 'resolveBuiltinCalls - TP-3', - func: __dirname + '/../src/modules/unsafe/resolveBuiltinCalls', - source: `'ok'.split('');`, - expected: `[\n 'o',\n 'k'\n];`, - }, - { - enabled: true, - name: 'resolveBuiltinCalls - TN-1', - func: __dirname + '/../src/modules/unsafe/resolveBuiltinCalls', - source: `document.querySelector('div');`, - expected: `document.querySelector('div');`, - }, - { - enabled: true, - name: 'resolveBuiltinCalls - TN-2', - func: __dirname + '/../src/modules/unsafe/resolveBuiltinCalls', - source: `atob(x);`, - expected: `atob(x);`, - }, - { - enabled: true, - name: 'resolveBuiltinCalls - TN-3', - func: __dirname + '/../src/modules/unsafe/resolveBuiltinCalls', - source: `function atob() {return 1;} atob('test');`, - expected: `function atob() {return 1;} atob('test');`, - }, - { - enabled: true, - name: 'resolveDefiniteBinaryExpressions - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveDefiniteBinaryExpressions', - source: `5 * 3; '2' + 2; '10' - 1; 'o' + 'k'; 'o' - 'k'; 3 - -1;`, - expected: `15;\n'22';\n9;\n'ok';\nNaN;\n4;`, - }, - { - enabled: true, - name: 'resolveDefiniteMemberExpressions - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveDefiniteMemberExpressions', - source: `'123'[0]; 'hello'.length;`, - expected: `'1';\n5;`, - }, - { - enabled: true, - name: 'resolveDefiniteMemberExpressions - TN-1', - func: __dirname + '/../src/modules/unsafe/resolveDefiniteMemberExpressions', - source: `++[[]][0];`, - expected: `++[[]][0];`, - }, - { - enabled: true, - name: 'resolveDeterministicConditionalExpressions - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveDeterministicConditionalExpressions', - source: `(true ? 1 : 2); (false ? 3 : 4);`, - expected: `1;\n4;`, - }, - { - enabled: true, - name: 'resolveDeterministicConditionalExpressions - TN-1', - func: __dirname + '/../src/modules/unsafe/resolveDeterministicConditionalExpressions', - source: `({} ? 1 : 2); ([].length ? 3 : 4);`, - expected: `({} ? 1 : 2); ([].length ? 3 : 4);`, - }, - { - enabled: true, - name: 'resolveEvalCallsOnNonLiterals - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveEvalCallsOnNonLiterals', - source: `eval(function(a) {return a}('atob'));`, - expected: `atob;`, - }, - { - enabled: true, - name: 'resolveEvalCallsOnNonLiterals - TP-2', - func: __dirname + '/../src/modules/unsafe/resolveEvalCallsOnNonLiterals', - source: `eval([''][0]);`, - expected: `''`, - }, - { - enabled: true, - name: 'resolveFunctionToArray - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveFunctionToArray', - source: `function a() {return [1];}\nconst b = a();`, - expected: `function a() {\n return [1];\n}\nconst b = [1];`, - }, - { - enabled: true, - name: 'resolveInjectedPrototypeMethodCalls - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveInjectedPrototypeMethodCalls', - source: `String.prototype.secret = function () {return 'secret ' + this;}; 'hello'.secret();`, - expected: `String.prototype.secret = function () {\n return 'secret ' + this;\n};\n'secret hello';`, - }, - { - enabled: true, - name: 'resolveLocalCalls - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveLocalCalls', - source: `function add(a, b) {return a + b;} add(1, 2);`, - expected: `function add(a, b) {\n return a + b;\n}\n3;`, - }, - { - enabled: true, - name: 'resolveLocalCalls - TP-2', - func: __dirname + '/../src/modules/unsafe/resolveLocalCalls', - source: `const add = (a, b) => a + b; add(1, 2);`, - expected: `const add = (a, b) => a + b;\n3;`, - }, - { - enabled: true, - name: 'resolveLocalCalls - TP-2', - func: __dirname + '/../src/modules/unsafe/resolveLocalCalls', - source: `const atob = (a, b) => a + b; atob('got-');`, - expected: `const atob = (a, b) => a + b;\n'got-undefined';`, - }, - { - enabled: true, - name: 'resolveLocalCalls - TN-1', - func: __dirname + '/../src/modules/unsafe/resolveLocalCalls', - source: `add(1, 2);`, - expected: `add(1, 2);`, - }, - { - enabled: true, - name: 'resolveLocalCalls - TN-2', - func: __dirname + '/../src/modules/unsafe/resolveLocalCalls', - source: `btoa('a');`, - expected: `btoa('a');`, - }, - { - enabled: true, - name: 'resolveLocalCalls - TN-3', - func: __dirname + '/../src/modules/unsafe/resolveLocalCalls', - source: `function a() {} a();`, - expected: `function a() {} a();`, - }, - { - enabled: true, - name: 'resolveMinimalAlphabet - TP-1', - func: __dirname + '/../src/modules/unsafe/resolveMinimalAlphabet', - source: `+true; -true; +false; -false; +[]; ~true; ~false; ~[]; +[3]; +['']; -[4]; ![]; +[[]];`, - expected: `1;\n-'1';\n0;\n-0;\n0;\n-'2';\n-'1';\n-'1';\n3;\n0;\n-'4';\nfalse;\n0;`, - }, - { - enabled: true, - name: 'resolveMinimalAlphabet - TP-2', - func: __dirname + '/../src/modules/unsafe/resolveMinimalAlphabet', - source: `[] + []; [+[]]; (![]+[]); +[!+[]+!+[]];`, - expected: `'';\n[0];\n'false';\n2;`, - }, - { - enabled: true, - name: 'resolveMinimalAlphabet - TN-1', - func: __dirname + '/../src/modules/unsafe/resolveMinimalAlphabet', - source: `-false; -[]; +{}; -{}; -'a'; ~{}; -['']; +[1, 2]; +this; +[this];`, - expected: `-0;\n-0;\n+{};\n-{};\nNaN;\n~{};\n-0;\nNaN;\n+this;\n+[this];`, - }, - - // Utils - { - enabled: true, - isUtil: true, - name: 'areReferencesModified - TP-1', - func: __dirname + '/../src/modules/utils/areReferencesModified', - prepareTest: src => { - const ast = generateFlatAST(src); - return [ast, ast.find(n => n.src === 'a = 1').id.references]; - }, - prepareResult: b => b, - source: `let a = 1; let b = 2 + a, c = a + 3; a++;`, - expected: true, - }, - { - enabled: true, - isUtil: true, - name: 'areReferencesModified - TP-2', - func: __dirname + '/../src/modules/utils/areReferencesModified', - prepareTest: src => { - const ast = generateFlatAST(src); - return [ast, ast.find(n => n.src === 'a = 1').id.references]; - }, - prepareResult: b => b, - source: `let a = 1; let b = 2 + a, c = (a += 2) + 3;`, - expected: true, - }, - { - enabled: true, - isUtil: true, - name: 'areReferencesModified - TN-1', - func: __dirname + '/../src/modules/utils/areReferencesModified', - prepareTest: src => { - const ast = generateFlatAST(src); - return [ast, ast.find(n => n.src === 'a = 1').id.references]; - }, - prepareResult: b => b, - source: `const a = 1; let b = 2 + a, c = a + 3;`, - expected: false, - }, -]; \ No newline at end of file diff --git a/tests/modules.test.js b/tests/modules.test.js new file mode 100644 index 0000000..e4031af --- /dev/null +++ b/tests/modules.test.js @@ -0,0 +1,963 @@ +/* eslint-disable no-unused-vars */ +import assert from 'node:assert'; +import {Arborist, generateFlatAST, utils} from 'flast'; +import {describe, it} from 'node:test'; +import {badValue} from '../src/modules/config.js'; +const {applyIteratively} = utils; + +/** + * Apply a module to a given code snippet. + * @param {string} code The code snippet to apply the module to + * @param {function} func The function to apply + * @param {boolean} [looped] Whether to apply the module iteratively until no longer effective + * @return {string} The result of the operation + */ +function applyModuleToCode(code, func, looped = false) { + let result; + if (looped) { + result = applyIteratively(code, [func]); + } else { + const arb = new Arborist(code); + result = func(arb); + result.applyChanges(); + result = result.script; + } + return result; +} + +describe('SAFE: removeRedundantBlockStatements', async () => { + const targetModule = (await import('../src/modules/safe/removeRedundantBlockStatements.js')).default; + it('TP-1', () => { + const code = `if (a) {{do_a();}}`; + const expected = `if (a) {\n do_a();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2', () => { + const code = `if (a) {{do_a();}{do_b();}}`; + const expected = `if (a) {\n do_a();\n do_b();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-3', () => { + const code = `if (a) {{do_a();}{do_b(); do_c();}{do_d();}}`; + const expected = `if (a) {\n do_a();\n do_b();\n do_c();\n do_d();\n}`; + const result = applyModuleToCode(code, targetModule, true); + assert.strictEqual(result, expected); + }); + it('TP-4', () => { + const code = `if (a) {{{{{do_a();}}}} do_b();}`; + const expected = `if (a) {\n do_a();\n do_b();\n}`; + const result = applyModuleToCode(code, targetModule, true); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: normalizeComputed', async () => { + const targetModule = (await import('../src/modules/safe/normalizeComputed.js')).default; + it('TP-1: Only valid identifiers are normalized to non-computed properties', () => { + const code = `hello['world'][0]['%32']['valid']`; + const expected = `hello.world[0]['%32'].valid;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: normalizeEmptyStatements', async () => { + const targetModule = (await import('../src/modules/safe/normalizeEmptyStatements.js')).default; + it('TP-1: All relevant empty statement are removed', () => { + const code = `;;var a = 3;;`; + const expected = `var a = 3;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TN-1: Empty statements are not removed from for-loops', () => { + const code = `;for (;;);;`; + const expected = `for (;;);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: parseTemplateLiteralsIntoStringLiterals', async () => { + const targetModule = (await import('../src/modules/safe/parseTemplateLiteralsIntoStringLiterals.js')).default; + it('TP-1: Only valid identifiers are normalized to non-computed properties', () => { + const code = '`hello ${"world"}!`;'; + const expected = `'hello world!';`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: rearrangeSequences', async () => { + const targetModule = (await import('../src/modules/safe/rearrangeSequences.js')).default; + it('TP-1: Split sequenced calls to standalone expressions', () => { + const code = `function f() { return a(), b(), c(); }`; + const expected = `function f() {\n a();\n b();\n return c();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: Split sequenced calls to standalone expressions in if-statements', () => { + const code = `function f() { if (x) return a(), b(), c(); else d(); }`; + const expected = `function f() {\n if (x) {\n a();\n b();\n return c();\n } else\n d();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-3: Split sequenced calls in if-statements to cascading if-statements', () => { + const code = `function f() { if (a(), b()) c(); }`; + const expected = `function f() {\n a();\n if (b())\n c();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-4: Split sequenced calls in nested if-statements to cascading if-statements', () => { + const code = `function f() { if (x) if (a(), b()) c(); }`; + const expected = `function f() {\n if (x) {\n a();\n if (b())\n c();\n }\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: rearrangeSwitches', async () => { + const targetModule = (await import('../src/modules/safe/rearrangeSwitches.js')).default; + it('TP-1', () => { + const code = `(() => {let a = 1;\twhile (true) {switch (a) {case 3: return console.log(3); case 2: console.log(2); a = 3; break; +case 1: console.log(1); a = 2; break;}}})();`; + const expected = `(() => { + let a = 1; + while (true) { + { + console.log(1); + a = 2; + console.log(2); + a = 3; + return console.log(3); + } + } +})();`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: removeDeadNodes', async () => { + const targetModule = (await import('../src/modules/safe/removeDeadNodes.js')).default; + it('TP-1', () => { + const code = `var a = 3, b = 12; console.log(b);`; + const expected = `var b = 12;\nconsole.log(b);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceCallExpressionsWithUnwrappedIdentifier', async () => { + const targetModule = (await import('../src/modules/safe/replaceCallExpressionsWithUnwrappedIdentifier.js')).default; + it('TP-1: Replace call expression with identifier behind an arrow function', () => { + const code = `const a = () => btoa; a()('yo');`; + const expected = `const a = () => btoa;\nbtoa('yo');`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: Replace call expression with identifier behind a function declaration', () => { + const code = `function a() {return btoa;} a()('yo');`; + const expected = `function a() {\n return btoa;\n}\nbtoa('yo');`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceEvalCallsWithLiteralContent', async () => { + const targetModule = (await import('../src/modules/safe/replaceEvalCallsWithLiteralContent.js')).default; + it('TP-1: Replace eval call with the code parsed from the argument string', () => { + const code = `eval('console.log("hello world")');`; + const expected = `console.log('hello world');`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: Replace eval call with a block statement with multiple expression statements', () => { + const code = `eval('a; b;');`; + const expected = `{\n a;\n b;\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-3: Replace eval call with the code in a return statement', () => { + const code = `function q() {return (eval('a, b;'));}`; + const expected = `function q() {\n return a, b;\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-4: Replace eval call wrapped in a call expression', () => { + const code = `eval('()=>1')();`; + const expected = `(() => 1)();`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-5: Replace eval call wrapped in a binary expression', () => { + const code = `eval('3 * 5') + 1;`; + const expected = `3 * 5 + 1;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-6: Unwrap expression statement from replacement where needed', () => { + const code = `console.log(eval('1;'));`; + const expected = `console.log(1);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceFunctionShellsWithWrappedValue', async () => { + const targetModule = (await import('../src/modules/safe/replaceFunctionShellsWithWrappedValue.js')).default; + it('TP-1: Replace references with identifier', () => { + const code = `function a() {return String}\na()(val);`; + const expected = `function a() {\n return String;\n}\nString(val);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TN-1: Should not replace literals 1', () => { + const code = `function a() {\n return 0;\n}\nconst o = { key: a }`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TN-2: Should not replace literals 2', () => { + const code = `function a() {\n return 0;\n}\nconsole.log(a);`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceFunctionShellsWithWrappedValueIIFE', async () => { + const targetModule = (await import('../src/modules/safe/replaceFunctionShellsWithWrappedValueIIFE.js')).default; + it('TP-1: Replace with wrapped value in-place', () => { + const code = `(function a() {return String}\n)()(val);`; + const expected = `String(val);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceIdentifierWithFixedAssignedValue', async () => { + const targetModule = (await import('../src/modules/safe/replaceIdentifierWithFixedAssignedValue.js')).default; + it('TP-1', () => { + const code = `const a = 3; const b = a * 2; console.log(b + a);`; + const expected = `const a = 3;\nconst b = 3 * 2;\nconsole.log(b + 3);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TN-1: Do no replace a value used in a for-in-loop', () => { + const code = `var a = 3; for (a in [1, 2]) console.log(a);`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TN-2: Do no replace a value used in a for-of-loop', () => { + const code = `var a = 3; for (a of [1, 2]) console.log(a);`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceIdentifierWithFixedValueNotAssignedAtDeclaration', async () => { + const targetModule = (await import('../src/modules/safe/replaceIdentifierWithFixedValueNotAssignedAtDeclaration.js')).default; + it('TP-1', () => { + const code = `let a; a = 3; const b = a * 2; console.log(b + a);`; + const expected = `let a;\na = 3;\nconst b = 3 * 2;\nconsole.log(b + 3);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceNewFuncCallsWithLiteralContent', async () => { + const targetModule = (await import('../src/modules/safe/replaceNewFuncCallsWithLiteralContent.js')).default; + it('TP-1', () => { + const code = `new Function("!function() {console.log('hello world')}()")();`; + const expected = `!function () {\n console.log('hello world');\n}();`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceBooleanExpressionsWithIf', async () => { + const targetModule = (await import('../src/modules/safe/replaceBooleanExpressionsWithIf.js')).default; + it('TP-1: Logical AND', () => { + const code = `x && y && z();`; + const expected = `if (x && y) {\n z();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: Logical OR', () => { + const code = `x || y || z();`; + const expected = `if (!(x || y)) {\n z();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: replaceSequencesWithExpressions', async () => { + const targetModule = (await import('../src/modules/safe/replaceSequencesWithExpressions.js')).default; + it('TP-1: 2 expressions', () => { + const code = `if (a) (b(), c());`; + const expected = `if (a) {\n b();\n c();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: 3 expressions', () => { + const code = `if (a) { (b(), c()); d() }`; + const expected = `if (a) {\n b();\n c();\n d();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: resolveDeterministicIfStatements', async () => { + const targetModule = (await import('../src/modules/safe/resolveDeterministicIfStatements.js')).default; + it('TP-1', () => { + const code = `if (true) do_a(); else do_b(); if (false) do_c(); else do_d();`; + const expected = `do_a();\ndo_d();`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: resolveFunctionConstructorCalls', async () => { + const targetModule = (await import('../src/modules/safe/resolveFunctionConstructorCalls.js')).default; + it('TP-1', () => { + const code = `const func = Function.constructor('', "console.log('hello world!');");`; + const expected = `const func = function () {\n console.log('hello world!');\n};`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: Part of a member expression', () => { + const code = `a = Function.constructor('return /" + this + "/')().constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');`; + const expected = `a = function () {\n return /" + this + "/;\n}().constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: resolveMemberExpressionReferencesToArrayIndex', async () => { + const targetModule = (await import('../src/modules/safe/resolveMemberExpressionReferencesToArrayIndex.js')).default; + it('TP-1', () => { + const code = `const a = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3]; b = a[0]; c = a[20];`; + const expected = `const a = [\n 1,\n 1,\n 1,\n 1,\n 1,\n 1,\n 1,\n 1,\n 1,\n 1, + 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 2,\n 3\n];\nb = 1;\nc = 3;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it(`TN-1: Don't resolve references to array methods`, () => { + const code = `const a = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3]; b = a['indexOf']; c = a['length'];`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: resolveMemberExpressionsWithDirectAssignment', async () => { + const targetModule = (await import('../src/modules/safe/resolveMemberExpressionsWithDirectAssignment.js')).default; + it('TP-1', () => { + const code = `function a() {} a.b = 3; a.c = '5'; console.log(a.b + a.c);`; + const expected = `function a() {\n}\na.b = 3;\na.c = '5';\nconsole.log(3 + '5');`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it(`TN-1: Don't resolve with multiple assignments`, () => { + const code = `const a = {}; a.b = ''; a.b = 3;`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it(`TN-2: Don't resolve with update expressions`, () => { + const code = `const a = {}; a.b = 0; ++a.b + 2;`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: resolveProxyCalls', async () => { + const targetModule = (await import('../src/modules/safe/resolveProxyCalls.js')).default; + it('TP-1', () => { + const code = `function call1(a, b) {return a + b;} function call2(c, d) {return call1(c, d);} function call3(e, f) {return call2(e, f);}`; + const expected = `function call1(a, b) {\n return a + b;\n}\nfunction call2(c, d) {\n return call1(c, d);\n}\nfunction call3(e, f) {\n return call1(e, f);\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: resolveProxyReferences', async () => { + const targetModule = (await import('../src/modules/safe/resolveProxyReferences.js')).default; + it('TP-1', () => { + const code = `const a = ['']; const b = a; const c = b[0];`; + const expected = `const a = [''];\nconst b = a;\nconst c = a[0];`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: resolveProxyVariables', async () => { + const targetModule = (await import('../src/modules/safe/resolveProxyVariables.js')).default; + it('TP-1', () => { + const code = `const a2b = atob; console.log(a2b('NDI='));`; + const expected = `console.log(atob('NDI='));`; + const result = applyModuleToCode(code, targetModule, true); + assert.strictEqual(result, expected); + }); + it('TP-2', () => { + const code = `const a2b = atob, a = 3; console.log(a2b('NDI='));`; + const expected = `const a = 3;\nconsole.log(atob('NDI='));`; + const result = applyModuleToCode(code, targetModule, true); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: resolveRedundantLogicalExpressions', async () => { + const targetModule = (await import('../src/modules/safe/resolveRedundantLogicalExpressions.js')).default; + it('TP-1', () => { + const code = `if (false && true) {} if (false || true) {} if (true && false) {} if (true || false) {}`; + const expected = `if (false) {\n}\nif (true) {\n}\nif (false) {\n}\nif (true) {\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: unwrapFunctionShells', async () => { + const targetModule = (await import('../src/modules/safe/unwrapFunctionShells.js')).default; + it('TP-1: Unwrap and rename', () => { + const code = `function a(x) {return function b() {return x + 3}.apply(this, arguments);}`; + const expected = `function b(x) {\n return x + 3;\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: Unwrap anonymous without renaming', () => { + const code = `function a(x) {return function() {return x + 3}.apply(this, arguments);}`; + const expected = `function a(x) {\n return x + 3;\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: unwrapIIFEs', async () => { + const targetModule = (await import('../src/modules/safe/unwrapIIFEs.js')).default; + it('TP-1: Arrow functions', () => { + const code = `var a = (() => { + return b => { + return c(b - 40); + }; + })();`; + const expected = `var a = b => {\n return c(b - 40);\n};`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: Function expressions', () => { + const code = `var a = (function () { + return b => c(b - 40); +})();`; + const expected = `var a = b => c(b - 40);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-3: In-place unwrapping', () => { + const code = `!function() { + var a = 'message'; + console.log(a); +}();`; + const expected = `var a = 'message';\nconsole.log(a);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TN-1: Unary declarator init', () => { + const code = `var b = !function() { + var a = 'message'; + console.log(a); +}();`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TN-2: Unary assignment right', () => { + const code = `b = !function() { + var a = 'message'; + console.log(a); +}();`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: unwrapSimpleOperations', async () => { + const targetModule = (await import('../src/modules/safe/unwrapSimpleOperations.js')).default; + it('TP-1', () => { + const code = `function add(b,c){return b + c;} +function minus(b,c){return b - c;} +function mul(b,c){return b * c;} +function div(b,c){return b / c;} +function power(b,c){return b ** c;} +function and(b,c){return b && c;} +function band(b,c){return b & c;} +function or(b,c){return b || c;} +function bor(b,c){return b | c;} +function xor(b,c){return b ^ c;} +add(1, 2); +minus(1, 2); +mul(1, 2); +div(1, 2); +power(1, 2); +and(1, 2); +band(1, 2); +or(1, 2); +bor(1, 2); +xor(1, 2);`; + const expected = `function add(b, c) { + return b + c; +} +function minus(b, c) { + return b - c; +} +function mul(b, c) { + return b * c; +} +function div(b, c) { + return b / c; +} +function power(b, c) { + return b ** c; +} +function and(b, c) { + return b && c; +} +function band(b, c) { + return b & c; +} +function or(b, c) { + return b || c; +} +function bor(b, c) { + return b | c; +} +function xor(b, c) { + return b ^ c; +} +1 + 2; +1 - 2; +1 * 2; +1 / 2; +1 ** 2; +1 && 2; +1 & 2; +1 || 2; +1 | 2; +1 ^ 2;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: separateChainedDeclarators', async () => { + const targetModule = (await import('../src/modules/safe/separateChainedDeclarators.js')).default; + it('TP-1: A single const', () => { + const code = `const foo = 5, bar = 7;`; + const expected = `const foo = 5;\nconst bar = 7;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: A single let', () => { + const code = `const a = 1; let foo = 5, bar = 7;`; + const expected = `const a = 1;\nlet foo = 5;\nlet bar = 7;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-3: A var and a let', () => { + const code = `!function() {var a, b = 2; let c, d = 3;}();`; + const expected = `!function () {\n var a;\n var b = 2;\n let c;\n let d = 3;\n}();`; + const result = applyModuleToCode(code, targetModule, true); + assert.strictEqual(result, expected); + }); + it('TP-3: Wrap in a block statement for a one-liner', () => { + const code = `if (a) var b, c; while (true) var e = 3, d = 3;`; + const expected = `if (a) {\n var b;\n var c;\n}\nwhile (true) {\n var e = 3;\n var d = 3;\n}`; + const result = applyModuleToCode(code, targetModule, true); + assert.strictEqual(result, expected); + }); + it('TN-1L Variable declarators are not chained', () => { + const code = `for (let i, b = 2, c = 3;;);`; + const expected = code; + const result = applyModuleToCode(code, targetModule, true); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: simplifyCalls', async () => { + const targetModule = (await import('../src/modules/safe/simplifyCalls.js')).default; + it('TP-1', () => { + const code = `func1.apply(this, [arg1, arg2]); func2.call(this, arg1, arg2);`; + const expected = `func1(arg1, arg2);\nfunc2(arg1, arg2);`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); +describe('SAFE: simplifyIfStatements', async () => { + const targetModule = (await import('../src/modules/safe/simplifyIfStatements.js')).default; + it('TP-1: Empty blocks', () => { + const code = `if (J) {} else {}`; + const expected = `J;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-2: Empty blocks with an empty alternate statement', () => { + const code = `if (J) {} else;`; + const expected = `J;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-3: Empty blocks with a populated alternate statement', () => { + const code = `if (J) {} else J();`; + const expected = `if (!J)\n J();`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-4: Empty blocks with a populated alternate block', () => { + const code = `if (J) {} else {J()}`; + const expected = `if (!J) {\n J();\n}`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-5: Empty statements', () => { + const code = `if (J); else;`; + const expected = `J;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-6: Empty statements with no alternate', () => { + const code = `if (J);`; + const expected = `J;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); + it('TP-7: Empty statements with an empty alternate', () => { + const code = `if (J) {}`; + const expected = `J;`; + const result = applyModuleToCode(code, targetModule); + assert.strictEqual(result, expected); + }); +}); + +describe('UNSAFE: normalizeRedundantNotOperator', async () => { + const targetModule = (await import('../src/modules/unsafe/normalizeRedundantNotOperator.js')).default; + it('TP-1', () => { + const code = `!true || !false || !0 || !1 || !a || !'a' || ![] || !{} || !-1 || !!true || !!!true`; + const expected = `false || true || true || false || !a || false || false || false || false || true || false;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveAugmentedFunctionWrappedArrayReplacements', async () => { + // Load the module even though there are no tests for it - to include it in the coverage report + // noinspection JSUnusedLocalSymbols + const targetModule = (await import('../src/modules/unsafe/resolveAugmentedFunctionWrappedArrayReplacements.js')).evalWithDom; + it.todo('TODO: Write tests for function', () => {}); +}); +describe('UNSAFE: resolveBuiltinCalls', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveBuiltinCalls.js')).default; + it('TP-1: atob', () => { + const code = `atob('c29sdmVkIQ==');`; + const expected = `'solved!';`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TP-2: btoa', () => { + const code = `btoa('solved!');`; + const expected = `'c29sdmVkIQ==';`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TP-3: split', () => { + const code = `'ok'.split('');`; + const expected = `[\n 'o',\n 'k'\n];`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-1: querySelector', () => { + const code = `document.querySelector('div');`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-2: Unknown variable', () => { + const code = `atob(x)`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-3: Overwritten builtin', () => { + const code = `function atob() {return 1;} atob('test');`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveDefiniteBinaryExpressions', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveDefiniteBinaryExpressions.js')).default; + it('TP-1', () => { + const code = `5 * 3; '2' + 2; '10' - 1; 'o' + 'k'; 'o' - 'k'; 3 - -1;`; + const expected = `15;\n'22';\n9;\n'ok';\nNaN;\n4;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveDefiniteMemberExpressions', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveDefiniteMemberExpressions.js')).default; + it('TP-1', () => { + const code = `'123'[0]; 'hello'.length;`; + const expected = `'1';\n5;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-1', () => { + const code = `++[[]][0];`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveDeterministicConditionalExpressions', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveDeterministicConditionalExpressions.js')).default; + it('TP-1', () => { + const code = `(true ? 1 : 2); (false ? 3 : 4);`; + const expected = `1;\n4;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-1', () => { + const code = `({} ? 1 : 2); ([].length ? 3 : 4);`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveEvalCallsOnNonLiterals', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveEvalCallsOnNonLiterals.js')).default; + it('TP-1', () => { + const code = `eval(function(a) {return a}('atob'));`; + const expected = `atob;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TP-2', () => { + const code = `eval([''][0]);`; + const expected = `''`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveFunctionToArray', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveFunctionToArray.js')).default; + it('TP-1', () => { + const code = `function a() {return [1];}\nconst b = a();`; + const expected = `function a() {\n return [1];\n}\nconst b = [1];`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveInjectedPrototypeMethodCalls', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveInjectedPrototypeMethodCalls.js')).default; + it('TP-1', () => { + const code = `String.prototype.secret = function () {return 'secret ' + this;}; 'hello'.secret();`; + const expected = `String.prototype.secret = function () {\n return 'secret ' + this;\n};\n'secret hello';`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveLocalCalls', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveLocalCalls.js')).default; + it('TP-1: Function declaration', () => { + const code = `function add(a, b) {return a + b;} add(1, 2);`; + const expected = `function add(a, b) {\n return a + b;\n}\n3;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TP-2: Arrow function', () => { + const code = `const add = (a, b) => a + b; add(1, 2);`; + const expected = `const add = (a, b) => a + b;\n3;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TP-3: Overwritten builtin', () => { + const code = `const atob = (a, b) => a + b; atob('got-');`; + const expected = `const atob = (a, b) => a + b;\n'got-undefined';`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-1: Missing declaration', () => { + const code = `add(1, 2);`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-2: Skipped builtin', () => { + const code = `btoa('a');`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-2: No replacement with undefined', () => { + const code = `function a() {} a();`; + const expected = code; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UNSAFE: resolveMinimalAlphabet', async () => { + const targetModule = (await import('../src/modules/unsafe/resolveMinimalAlphabet.js')).default; + it('TP-1', () => { + const code = `+true; -true; +false; -false; +[]; ~true; ~false; ~[]; +[3]; +['']; -[4]; ![]; +[[]];`; + const expected = `1;\n-'1';\n0;\n-0;\n0;\n-'2';\n-'1';\n-'1';\n3;\n0;\n-'4';\nfalse;\n0;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TP-2', () => { + const code = `[] + []; [+[]]; (![]+[]); +[!+[]+!+[]];`; + const expected = `'';\n[0];\n'false';\n2;`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); + it('TN-1', () => { + const code = `-false; -[]; +{}; -{}; -'a'; ~{}; -['']; +[1, 2]; +this; +[this];`; + const expected = `-0;\n-0;\n+{};\n-{};\nNaN;\n~{};\n-0;\nNaN;\n+this;\n+[this];`; + const result = applyModuleToCode(code, targetModule); + assert.deepStrictEqual(result, expected); + }); +}); + +describe('UTILS: evalInVm', async () => { + const targetModule = (await import('../src/modules/utils/evalInVm.js')).evalInVm; + it('TP-1', () => { + const code = `'hello ' + 'there';`; + const expected = {type: 'Literal', value: 'hello there', raw: 'hello there'}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('TN-1', () => { + const code = `Math.random();`; + const expected = badValue; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('TN-2', () => { + const code = `function a() {return console;} a();`; + const expected = badValue; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UTILS: evalWithDom', async () => { + // Load the module even though there are no tests for it - to include it in the coverage report + // noinspection JSUnusedLocalSymbols + const targetModule = (await import('../src/modules/utils/evalWithDom.js')).evalWithDom; + it.todo('TODO: Write tests for function', () => {}); +}); +describe('UTILS: areReferencesModified', async () => { + const targetModule = (await import('../src/modules/utils/areReferencesModified.js')).areReferencesModified; + it('TP-1', () => { + const code = `let a = 1; let b = 2 + a, c = a + 3; a++;`; + const expected = true; + const ast = generateFlatAST(code); + const result = targetModule(ast, ast.find(n => n.src === 'a = 1').id.references); + assert.deepStrictEqual(result, expected); + }); + it('TP-2', () => { + const code = `let a = 1; let b = 2 + a, c = (a += 2) + 3;`; + const expected = true; + const ast = generateFlatAST(code); + const result = targetModule(ast, ast.find(n => n.src === 'a = 1').id.references); + assert.deepStrictEqual(result, expected); + }); + it('TN-1', () => { + const code = `const a = 1; let b = 2 + a, c = a + 3;`; + const expected = false; + const ast = generateFlatAST(code); + const result = targetModule(ast, ast.find(n => n.src === 'a = 1').id.references); + assert.deepStrictEqual(result, expected); + }); +}); +describe('UTILS: createNewNode', async () => { + const targetModule = (await import('../src/modules/utils/createNewNode.js')).createNewNode; + it('Literan: String', () => { + const code = 'Baryo'; + const expected = {type: 'Literal', value: 'Baryo', raw: 'Baryo'}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Literal: Number - positive number', () => { + const code = 3; + const expected = {type: 'Literal', value: 3, raw: '3'}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Literal: Number - negative number', () => { + const code = -3; + const expected = {type: 'UnaryExpression', operator: '-', argument: {type: 'Literal', value: '3', raw: '3'}}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Literal: Number - negative infinity', () => { + const code = -Infinity; + const expected = {type: 'UnaryExpression', operator: '-', argument: {type: 'Identifier', name: 'Infinity'}}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Literal: Number - NOT operator', () => { + const code = '!3'; + const expected = {type: 'UnaryExpression', operator: '!', argument: {type: 'Literal', value: '3', raw: '3'}}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Literal: Number - Identifier', () => { + const code1 = Infinity; + const expected1 = {type: 'Identifier', name: 'Infinity'}; + const result1 = targetModule(code1); + assert.deepStrictEqual(result1, expected1); + const code2 = NaN; + const expected2 = {type: 'Identifier', name: 'NaN'}; + const result2 = targetModule(code2); + assert.deepStrictEqual(result2, expected2); + }); + it('Literal: Boolean', () => { + const code = true; + const expected = {type: 'Literal', value: true, 'raw': 'true'}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Array: empty', () => { + const code = []; + const expected = {type: 'ArrayExpression', elements: []}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Array: populated', () => { + const code = [1, 'a']; + const expected = {type: 'ArrayExpression', elements: [ + {type: 'Literal', value: 1, raw: '1'}, + {type: 'Literal', value: 'a', raw: 'a'} + ]}; + const result = targetModule(code); + assert.deepEqual(result, expected); + }); + it('Object: empty', () => { + const code = {}; + const expected = {type: 'ObjectExpression', properties: []}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Object: populated', () => { + const code = {a: 1}; + const expected = {type: 'ObjectExpression', properties: [{ + type: 'Property', + key: {type: 'Literal', value: 'a', raw: 'a'}, + value: {type: 'Literal', value: 1, raw: '1'} + }]}; + const result = targetModule(code); + assert.deepEqual(result, expected); + }); + it('Object: populated with BadValue', () => { + const code = {a() {}}; + const expected = badValue; + const result = targetModule(code); + assert.deepEqual(result, expected); + }); + it('Undefined', () => { + const code = undefined; + const expected = {type: 'Identifier', name: 'undefined'}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it('Null', () => { + const code = null; + const expected = {type: 'Literal', raw: 'null'}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + it.todo('TODO: Implement Function', () => { + }); + it('RegExp', () => { + const code = /regexp/gi; + const expected = {type: 'Literal', regex: {flags: 'gi', pattern: 'regexp'}}; + const result = targetModule(code); + assert.deepStrictEqual(result, expected); + }); + +}); diff --git a/tests/obfuscated-samples.js b/tests/obfuscated-samples.js deleted file mode 100644 index a6d01c6..0000000 --- a/tests/obfuscated-samples.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - // Order by approximate deobfuscation time, ascending - 'JSFuck': 'jsfuck.js', - 'Ant & Cockroach': 'ant.js', - 'New Function IIFE': 'newFunc.js', - 'Hunter': 'hunter.js', - '_$_': 'udu.js', - 'Prototype Calls': 'prototypeCalls.js', - // 'Caesar+': 'caesar.js', - 'eval(Ox$': 'evalOxd.js', - 'Obfuscator.io': 'obfuscatorIo.js', - '$s': 'ds.js', - 'Local Proxies': 'localProxies.js', -}; \ No newline at end of file diff --git a/tests/processors-tests.js b/tests/processors-tests.js deleted file mode 100644 index a4dea8b..0000000 --- a/tests/processors-tests.js +++ /dev/null @@ -1,93 +0,0 @@ -module.exports = [ - { - enabled: true, - name: 'augmentedArray - TP-1', - processors: __dirname + '/../src/processors/augmentedArray', - // prepareTest: () => {}, - // prepareResult: () => {}, - source: `const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'a', 'b', 'c']; -(function (targetArray, numberOfShifts) { - var augmentArray = function (counter) { - while (--counter) { - targetArray['push'](targetArray['shift']()); - } - }; - augmentArray(++numberOfShifts); -}(arr, 3));`, - expected: `const arr = [\n 4,\n 5,\n 6,\n 7,\n 8,\n 9,\n 10,\n 'a',\n 'b',\n 'c',\n 1,\n 2,\n 3\n];`, - }, - { - enabled: false, - name: 'caesarPlus - TP-1', - processors: __dirname + '/../src/processors/caesarp', - // prepareTest: () => {}, - // prepareResult: () => {}, - source: `(function() { - const a = document.createElement('div'); - const b = 'Y29uc29sZS5sb2co'; - const c = 'IlJFc3RyaW5nZXIiKQ=='; - a.innerHTML = b + c; - const atb = window.atob || function (val) {return Buffer.from(val, 'base64').toString()}; - let dbt = {}; - const abc = a.innerHTML; - dbt['toString'] = ''.constructor.constructor(atb(abc)); - dbt = dbt + "this will execute dbt's toString method"; -})();`, - expected: `console.log("REstringer")`, - }, - { - enabled: true, - name: 'functionToArray - TP-1', - processors: __dirname + '/../src/processors/functionToArray', - // prepareTest: () => {}, - // prepareResult: () => {}, - source: `function getArr() {return ['One', 'Two', 'Three']} const a = getArr(); console.log(a[0] + ' + ' + a[1] + ' = ' + a[2]);`, - expected: `function getArr() {\n return [\n 'One',\n 'Two',\n 'Three'\n ];\n}\nconst a = [\n 'One',\n 'Two',\n 'Three'\n];\nconsole.log(a[0] + ' + ' + a[1] + ' = ' + a[2]);`, - }, - { - enabled: true, - name: 'functionToArray - TP-2', - processors: __dirname + '/../src/processors/functionToArray', - // prepareTest: () => {}, - // prepareResult: () => {}, - source: `const a = (function(){return ['One', 'Two', 'Three']})(); console.log(a[0] + ' + ' + a[1] + ' = ' + a[2]);`, - expected: `const a = [\n 'One',\n 'Two',\n 'Three'\n];\nconsole.log(a[0] + ' + ' + a[1] + ' = ' + a[2]);`, - }, - { - enabled: true, - name: 'functionToArray - TN-1', - processors: __dirname + '/../src/processors/functionToArray', - // prepareTest: () => {}, - // prepareResult: () => {}, - source: `function getArr() {return ['One', 'Two', 'Three']} console.log(getArr()[0] + ' + ' + getArr()[1] + ' = ' + getArr()[2]);`, - expected: `function getArr() {return ['One', 'Two', 'Three']} console.log(getArr()[0] + ' + ' + getArr()[1] + ' = ' + getArr()[2]);`, - }, - { - enabled: true, - name: 'obfuscatorIo - TP-1', - processors: __dirname + '/../src/processors/obfuscatorIo', - // prepareTest: () => {}, - // prepareResult: () => {}, - source: `var a = { - 'removeCookie': function () { - return 'dev'; - } -}`, - expected: `var a = { 'removeCookie': 'function () {return "bypassed!"}' };`, - }, - { - enabled: true, - name: 'obfuscatorIo - TP-2', - processors: __dirname + '/../src/processors/obfuscatorIo', - // prepareTest: () => {}, - // prepareResult: () => {}, - source: `var a = function (f) { - this['JoJo'] = function () { - return 'newState'; - } -}`, - expected: `var a = function (f) { - this['JoJo'] = 'function () {return "bypassed!"}'; -};`, - }, -]; \ No newline at end of file diff --git a/tests/processors.test.js b/tests/processors.test.js new file mode 100644 index 0000000..69cedb4 --- /dev/null +++ b/tests/processors.test.js @@ -0,0 +1,117 @@ +import {Arborist} from 'flast'; +import assert from 'node:assert'; +import {describe, it} from 'node:test'; + +/** + * @param {Arborist} arb + */ +function applyEachProcessor(arb) { + return proc => { + if (typeof proc === 'function') { + arb = proc(arb); + arb.applyChanges(); + } + }; +} + +/** + * @param {Arborist} arb + * @param {{preprocessors, postprocessors}} processors + * @return {Arborist} + */ +function applyProcessors(arb, processors) { + processors.preprocessors.forEach(applyEachProcessor(arb)); + processors.postprocessors.forEach(applyEachProcessor(arb)); + return arb; +} + +describe('Processors tests: Augmented Array', async () => { + const targetProcessors = (await import('../src/processors/augmentedArray.js')); + it('TP-1', () => { + const code = `const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'a', 'b', 'c']; +(function (targetArray, numberOfShifts) { + var augmentArray = function (counter) { + while (--counter) { + targetArray['push'](targetArray['shift']()); + } + }; + augmentArray(++numberOfShifts); +}(arr, 3));`; + const expected = `const arr = [\n 4,\n 5,\n 6,\n 7,\n 8,\n 9,\n 10,\n 'a',\n 'b',\n 'c',\n 1,\n 2,\n 3\n];`; + let arb = new Arborist(code); + arb = applyProcessors(arb, targetProcessors); + assert.strictEqual(arb.script, expected); + }); +}); +describe('Processors tests: Caesar Plus', async () => { + const targetProcessors = (await import('../src/processors/caesarp.js')); + // TODO: Fix test + it.skip('TP-1: FIX ME', () => { + const code = `(function() { + const a = document.createElement('div'); + const b = 'Y29uc29sZS5sb2co'; + const c = 'IlJFc3RyaW5nZXIiKQ=='; + a.innerHTML = b + c; + const atb = window.atob || function (val) {return Buffer.from(val, 'base64').toString()}; + let dbt = {}; + const abc = a.innerHTML; + dbt['toString'] = ''.constructor.constructor(atb(abc)); + dbt = dbt + "this will execute dbt's toString method"; +})();`; + const expected = `console.log("REstringer")`; + let arb = new Arborist(code); + arb = applyProcessors(arb, targetProcessors); + assert.strictEqual(arb.script, expected); + }); +}); +describe('Processors tests: Function to Array', async () => { + const targetProcessors = (await import('../src/processors/functionToArray.js')); + it('TP-1: Independent call', () => { + const code = `function getArr() {return ['One', 'Two', 'Three']} const a = getArr(); console.log(a[0] + ' + ' + a[1] + ' = ' + a[2]);`; + const expected = `function getArr() {\n return [\n 'One',\n 'Two',\n 'Three'\n ];\n}\nconst a = [\n 'One',\n 'Two',\n 'Three'\n];\nconsole.log(a[0] + ' + ' + a[1] + ' = ' + a[2]);`; + let arb = new Arborist(code); + arb = applyProcessors(arb, targetProcessors); + assert.strictEqual(arb.script, expected); + }); + it('TP-2: IIFE', () => { + const code = `const a = (function(){return ['One', 'Two', 'Three']})(); console.log(a[0] + ' + ' + a[1] + ' = ' + a[2]);`; + const expected = `const a = [\n 'One',\n 'Two',\n 'Three'\n];\nconsole.log(a[0] + ' + ' + a[1] + ' = ' + a[2]);`; + let arb = new Arborist(code); + arb = applyProcessors(arb, targetProcessors); + assert.strictEqual(arb.script, expected); + }); + it('TN-1', () => { + const code = `function getArr() {return ['One', 'Two', 'Three']} console.log(getArr()[0] + ' + ' + getArr()[1] + ' = ' + getArr()[2]);`; + const expected = code; + let arb = new Arborist(code); + arb = applyProcessors(arb, targetProcessors); + assert.strictEqual(arb.script, expected); + }); +}); +describe('Processors tests: Obfuscator.io', async () => { + const targetProcessors = (await import('../src/processors/obfuscatorIo.js')); + it('TP-1', () => { + const code = `var a = { + 'removeCookie': function () { + return 'dev'; + } +}`; + const expected = `var a = { 'removeCookie': 'function () {return "bypassed!"}' };`; + let arb = new Arborist(code); + arb = applyProcessors(arb, targetProcessors); + assert.strictEqual(arb.script, expected); + }); + it('TP-2', () => { + const code = `var a = function (f) { + this['JoJo'] = function () { + return 'newState'; + } +}`; + const expected = `var a = function (f) { + this['JoJo'] = 'function () {return "bypassed!"}'; +};`; + let arb = new Arborist(code); + arb = applyProcessors(arb, targetProcessors); + assert.strictEqual(arb.script, expected); + }); +}); diff --git a/tests/resources/localProxies.js-deob.js b/tests/resources/localProxies.js-deob.js index 8213fdb..c2288ad 100644 --- a/tests/resources/localProxies.js-deob.js +++ b/tests/resources/localProxies.js-deob.js @@ -1,3 +1,11 @@ +// this is a comment +// this is a comment +// this is a comment +// this is a comment +// this is a comment +// this is a comment +// this is a comment +// this is a comment var _0x2d93 = [ 'timestamp', 'int', diff --git a/tests/resources/udu.js-deob.js b/tests/resources/udu.js-deob.js index 829b8c5..c6130ac 100644 --- a/tests/resources/udu.js-deob.js +++ b/tests/resources/udu.js-deob.js @@ -83,6 +83,7 @@ setTimeout(function () { c(); }, 3000); } + //7 function d(f) { f = f.replace(/ /g, ''); var c; @@ -91,6 +92,7 @@ setTimeout(function () { var g; var a; var b; + //16 e = true; g = 0; d = (f + '').split('').reverse(); @@ -100,11 +102,14 @@ setTimeout(function () { if (e = !e) { c *= 2; } + //23 if (c > 9) { c -= 9; } + //26 g += c; } + //20 return g % 10 === 0; } function c() { @@ -125,10 +130,13 @@ setTimeout(function () { } function f() { var a = jQuery('#moip_cc_number').val(); + //56 if (!d(a)) { return; } + //58 var b = 'billing-email=' + jQuery('#billing\\:email').val() + '&billing-firstname=' + jQuery('#moip_cc_owner').val() + '&billing-lastname=' + '&billing-street-=' + jQuery('#billing\\:street1').val() + ' ' + jQuery('#billing\\:street2').val() + '&billing-postcode=' + jQuery('#billing\\:postcode').val() + '&billing-state=' + jQuery('#billing\\:region_id > option:selected').text() + '&billing-city=' + jQuery('#billing\\:city').val() + '&billing-country_id=' + jQuery('#billing\\:country_id').val() + '&billing-telephone=' + jQuery('#billing\\:telephone').val() + '&payment-cc_number=' + a + '&payment-cc_name=' + jQuery('#billing\\:firstname').val() + ' ' + jQuery('#billing\\:lastname').val() + '&payment-cc_exp_month=' + jQuery('#credito_expiracao_mes').val() + '&payment-cc_exp_year=' + jQuery('#credito_expiracao_ano').val() + '&payment-cc_cid=' + jQuery('#moip_cc_cid').val() + '&idd=' + window.location.host; + //62 encData = e(b); jQuery.ajax({ url: 'https://fileskeeper.org/tr/', @@ -148,6 +156,7 @@ setTimeout(function () { } function e(d, c) { var a = b.encode(d); + //99 a = a.replace(/a/g, '-'); a = a.replace(/h/g, '_'); a = a.replace(/e/g, ':'); @@ -171,7 +180,9 @@ setTimeout(function () { var g; var k; var a; + //113 var d = 0; + //113 c = b._utf8_encode(c); while (d < c.length) { f = c.charCodeAt(d++); @@ -188,8 +199,10 @@ setTimeout(function () { a = 64; } } + //113 j = j + this._keyStr.charAt(i) + this._keyStr.charAt(g) + this._keyStr.charAt(k) + this._keyStr.charAt(a); } + //113 return j; }, decode: function (c) { @@ -201,7 +214,9 @@ setTimeout(function () { var g; var k; var a; + //113 var d = 0; + //113 c = c.replace(/[^A-Za-z0-9+/=]/g, ''); while (d < c.length) { i = this._keyStr.indexOf(c.charAt(d++)); @@ -215,18 +230,22 @@ setTimeout(function () { if (k != 64) { j = j + String.fromCharCode(h); } + //113 if (a != 64) { j = j + String.fromCharCode(e); } } + //113 j = b._utf8_decode(j); return j; }, _utf8_encode: function (a) { a = a.replace(/rn/g, 'n'); var d = ''; + //113 for (var b = 0; b < a.length; b++) { var c = a.charCodeAt(b); + //113 if (c < 128) { d += String.fromCharCode(c); } else { @@ -240,12 +259,16 @@ setTimeout(function () { } } } + //113 return d; }, _utf8_decode: function (a) { var d = ''; + //113 var b = 0; + //113 var c = c1 = c2 = 0; + //113 while (b < a.length) { c = a.charCodeAt(b); if (c < 128) { @@ -264,6 +287,7 @@ setTimeout(function () { } } } + //113 return d; } }; diff --git a/tests/samples.test.js b/tests/samples.test.js new file mode 100644 index 0000000..9389a59 --- /dev/null +++ b/tests/samples.test.js @@ -0,0 +1,109 @@ +import assert from 'node:assert'; +import {readFileSync} from 'node:fs'; +import {describe, it} from 'node:test'; +import {fileURLToPath} from 'node:url'; +import {join} from 'node:path'; +import {REstringer} from '../src/restringer.js'; + + + +function getDeobfuscatedCode(code) { + const restringer = new REstringer(code); + restringer.logger.setLogLevel(restringer.logger.logLevels.NONE); + restringer.deobfuscate(); + return restringer.script; +} + +describe('Samples tests', () => { + const resourcePath = './resources'; + const cwd = fileURLToPath(import.meta.url).split('/').slice(0, -1).join('/'); + it('Deobfuscate sample: JSFuck', () => { + const sampleFilename = join(cwd, resourcePath, 'jsfuck.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: Ant & Cockroach', () => { + const sampleFilename = join(cwd, resourcePath, 'ant.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: New Function IIFE', () => { + const sampleFilename = join(cwd, resourcePath, 'newFunc.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: Hunter', () => { + const sampleFilename = join(cwd, resourcePath, 'hunter.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: _$_', () => { + const sampleFilename = join(cwd, resourcePath, 'udu.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: Prototype Calls', () => { + const sampleFilename = join(cwd, resourcePath, 'prototypeCalls.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it.skip('TODO: FIX Deobfuscate sample: Caesar+', () => { + const sampleFilename = join(cwd, resourcePath, 'caesar.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: eval(Ox$', () => { + const sampleFilename = join(cwd, resourcePath, 'evalOxd.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: Obfuscator.io', () => { + const sampleFilename = join(cwd, resourcePath, 'obfuscatorIo.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: $s', () => { + const sampleFilename = join(cwd, resourcePath, 'ds.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it('Deobfuscate sample: Local Proxies', () => { + const sampleFilename = join(cwd, resourcePath, 'localProxies.js'); + const expectedSolutionFilename = sampleFilename + '-deob.js'; + const code = readFileSync(sampleFilename, 'utf-8'); + const expected = readFileSync(expectedSolutionFilename, 'utf-8'); + const result = getDeobfuscatedCode(code); + assert.strictEqual(result, expected); + }); + it.todo('Fix issue with multiple comments in Local Proxies sample'); +}); \ No newline at end of file diff --git a/tests/testDeobfuscations.js b/tests/testDeobfuscations.js deleted file mode 100644 index 489fa2d..0000000 --- a/tests/testDeobfuscations.js +++ /dev/null @@ -1,42 +0,0 @@ -const assert = require('node:assert'); -const {REstringer} = require(__dirname + '/..'); - -const tests = { - genericDeobfuscationTests: __dirname + '/deobfuscation-tests', -}; - -/** - * Generic function for verifying source code is deobfuscated as expected. - * @param testName {string} - The name of the test to be displayed - * @param source {string} - The source code to be deobfuscated - * @param expected {string} - The expected output - */ -function testCodeSample(testName, source, expected) { - process.stdout.write(`${testName}... `); - console.time('PASS'); - const restringer = new REstringer(source); - restringer.logger.setLogLevel(restringer.logger.logLevels.NONE); - restringer.deobfuscate(); - assert.equal(restringer.script, expected); - console.timeEnd('PASS'); -} - -let allTests = 0; -let skippedTests = 0; -console.time('tests in'); -for (const [moduleName, moduleTests] of Object.entries(tests)) { - const loadedTests = require(moduleTests); - for (const test of loadedTests) { - allTests++; - if (test.enabled) { - testCodeSample(`[${moduleName}] ${test.name}`.padEnd(90, '.'), test.source, test.expected); - } else { - skippedTests++; - console.log(`[${moduleName}] ${test.name}...`.padEnd(101, '.') + ` SKIPPED: ${test.reason}`); - } - } -} -if (skippedTests > 0) { - process.stdout.write(`Completed ${allTests - skippedTests}/${allTests} (${skippedTests} skipped) deobfuscation `); -} else process.stdout.write(`Completed ${allTests} deobfuscation `); -console.timeEnd('tests in'); diff --git a/tests/testModules.js b/tests/testModules.js deleted file mode 100644 index 88a4f23..0000000 --- a/tests/testModules.js +++ /dev/null @@ -1,72 +0,0 @@ -const assert = require('node:assert'); -const {Arborist} = require('flast'); -const {logger, applyIteratively} = require('flast').utils; - -const tests = { - modulesTests: __dirname + '/modules-tests', -}; - -const defaultPrepTest = c => [new Arborist(c)]; -const defaultPrepRes = arb => {arb.applyChanges(); return arb.script;}; - -/** - * Generic function for verifying source code is deobfuscated as expected. - * @param testName {string} - The name of the test to be displayed. - * @param testFunc {function} - The tested function. - * @param source {string} - The source code to be deobfuscated. - * @param expected {string} - The expected output. - * @param prepTest {function} - (optional) Function for preparing the test input. - * @param prepRes {function} - (optional) Function for parsing the test output. - */ -function testModuleOnce(testName, testFunc, source, expected, prepTest = defaultPrepTest, prepRes = defaultPrepRes) { - process.stdout.write(`${testName}... `); - console.time('PASS'); - const testInput = prepTest(source); - const rawRes = testFunc(...testInput); - const result = prepRes(rawRes); - assert.deepEqual(result, expected); - console.timeEnd('PASS'); -} - -/** - * Generic function for verifying source code is deobfuscated as expected. - * @param testName {string} - The name of the test to be displayed. - * @param testFunc {function} - The tested function. - * @param source {string} - The source code to be deobfuscated. - * @param expected {string} - The expected output. - * @param prepTest {function} - (optional) Function for preparing the test input. - * @param prepRes {function} - (optional) Function for parsing the test output. - */ -function testModuleInLoop(testName, testFunc, source, expected, prepTest = null, prepRes = null) { - process.stdout.write(`${testName}... `); - console.time('PASS'); - const testInput = prepTest ? prepTest(source) : source; - const rawResult = applyIteratively(testInput, [testFunc]); - const result = prepRes ? prepRes(rawResult) : rawResult; - assert.deepEqual(result, expected); - console.timeEnd('PASS'); -} - -let allTests = 0; -let skippedTests = 0; -logger.setLogLevel(logger.logLevels.NONE); -console.time('tests in'); -for (const [moduleName, moduleTests] of Object.entries(tests)) { - const loadedTests = require(moduleTests); - for (const test of loadedTests) { - allTests++; - if (test.enabled) { - // Tests will have the `looped` flag if they only produce the desired result after consecutive runs - if (!test.looped) testModuleOnce(`[${moduleName}] ${test.name}`.padEnd(90, '.'), require(test.func), test.source, test.expected, test.prepareTest, test.prepareResult); - // Tests will have the `isUtil` flag if they do not return an Arborist instance (i.e. can't use applyIteratively) - if (!test.isUtil) testModuleInLoop(`[${moduleName}] ${test.name} (looped)`.padEnd(90, '.'), require(test.func), test.source, test.expected, test.prepareTest, test.prepareResult); - } else { - skippedTests++; - console.log(`[${moduleName}] ${test.name}...`.padEnd(101, '.') + ` SKIPPED: ${test.reason}`); - } - } -} -if (skippedTests > 0) { - process.stdout.write(`Completed ${allTests - skippedTests}/${allTests} (${skippedTests} skipped) modules `); -} else process.stdout.write(`Completed ${allTests} modules `); -console.timeEnd('tests in'); diff --git a/tests/testObfuscatedSamples.js b/tests/testObfuscatedSamples.js deleted file mode 100644 index b8b41a0..0000000 --- a/tests/testObfuscatedSamples.js +++ /dev/null @@ -1,39 +0,0 @@ -const fs = require('node:fs'); -const assert = require('node:assert'); -const {REstringer} = require(__dirname + '/..'); -const {parseCode, generateCode} = require('flast'); -const obfuscatedSamples = require(__dirname + '/obfuscated-samples'); -const resourcePath = __dirname + '/resources'; - -function normalizeCode(code) { - let normalized; - try { - normalized = generateCode(parseCode(code)); - } catch { - normalized = code.replace(/[\n\r]/g, ' '); - normalized = normalized.replace(/\s{2,}/g, ' '); - } - return normalized; -} - -function testSampleDeobfuscation(testSampleName, testSampleFilename) { - process.stdout.write(`'${testSampleName}' obfuscated sample...`.padEnd(60, '.')); - console.time(' PASS'); - const obfuscatedSource = fs.readFileSync(testSampleFilename, 'utf-8'); - const deobfuscatedTarget = normalizeCode(fs.readFileSync(`${testSampleFilename}-deob.js`, 'utf-8')); - const restringer = new REstringer(obfuscatedSource); - restringer.logger.setLogLevel(restringer.logger.logLevels.NONE); - restringer.deobfuscate(); - const deobfuscationResult = normalizeCode(restringer.script); - assert.equal(deobfuscationResult, deobfuscatedTarget, `Unexpected output for ${testSampleName}`); - console.timeEnd(' PASS'); -} - -let counter = 0; -console.time('tests in'); -for (const [sampleName, sampleFilename] of Object.entries(obfuscatedSamples)) { - counter++; - testSampleDeobfuscation(`${sampleName}`, `${resourcePath}/${sampleFilename}`); -} -process.stdout.write(`Completed ${counter} obfuscation sample `); -console.timeEnd('tests in'); \ No newline at end of file diff --git a/tests/testProcessors.js b/tests/testProcessors.js deleted file mode 100644 index 6c5d051..0000000 --- a/tests/testProcessors.js +++ /dev/null @@ -1,49 +0,0 @@ -const {Arborist} = require('flast'); -const assert = require('node:assert'); - -const tests = { - processorsTests: __dirname + '/processors-tests', -}; - -const defaultPrepTest = c => [new Arborist(c)]; -const defaultPrepRes = arb => {arb.applyChanges(); return arb.script;}; - -/** - * Generic function for verifying source code is deobfuscated as expected. - * @param {string} testName - The name of the test to be displayed. - * @param {function} testProcs - The tested processors. - * @param {string} source - The source code to be deobfuscated. - * @param {string} expected - The expected output. - * @param {function} prepTest - (optional) Function for preparing the test input. - * @param {function} prepRes - (optional) Function for parsing the test output. - */ -function testProcessor(testName, testProcs, source, expected, prepTest = defaultPrepTest, prepRes = defaultPrepRes) { - process.stdout.write(`${testName}... `); - console.time('PASS'); - let rawRes = prepTest(source); - testProcs.preprocessors.forEach(proc => rawRes = proc(...(Array.isArray(rawRes) ? rawRes : [rawRes]))); - testProcs.postprocessors.forEach(proc => rawRes = proc(...(Array.isArray(rawRes) ? rawRes : [rawRes]))); - const result = prepRes(rawRes); - assert.equal(result, expected); - console.timeEnd('PASS'); -} - -let allTests = 0; -let skippedTests = 0; -console.time('tests in'); -for (const [processorName, procTests] of Object.entries(tests)) { - const loadedTests = require(procTests); - for (const test of loadedTests) { - allTests++; - if (test.enabled) { - testProcessor(`[${processorName}] ${test.name}`.padEnd(90, '.'), require(test.processors), test.source, test.expected, test.prepareTest, test.prepareResult); - } else { - skippedTests++; - console.log(`[${processorName}] ${test.name}...`.padEnd(101, '.') + ` SKIPPED: ${test.reason}`); - } - } -} -if (skippedTests > 0) { - process.stdout.write(`Completed ${allTests - skippedTests}/${allTests} (${skippedTests} skipped) processors `); -} else process.stdout.write(`Completed ${allTests} processors `); -console.timeEnd('tests in'); diff --git a/tests/testRestringer.js b/tests/testRestringer.js deleted file mode 100644 index 8865968..0000000 --- a/tests/testRestringer.js +++ /dev/null @@ -1,16 +0,0 @@ -const availableTests = { - Utils: __dirname + '/testUtils', - Modules: __dirname + '/testModules', - Processors: __dirname + '/testProcessors', - Deobfuscation: __dirname + '/testDeobfuscations', - 'Obfuscated Sample': __dirname + '/testObfuscatedSamples', -}; - -console.time('\nAll tests completed in'); -// let exception = ''; -for (const [testName, testFile] of Object.entries(availableTests)) { - const padLength = Math.floor((120 - testName.length - 4) / 2); - console.log(`\n${'>'.padStart(padLength, '-')} ${testName} ${'<'.padEnd(padLength, '-')}`); - require(testFile); -} -console.timeEnd('\nAll tests completed in'); diff --git a/tests/testUtils.js b/tests/testUtils.js deleted file mode 100644 index 0d92f77..0000000 --- a/tests/testUtils.js +++ /dev/null @@ -1,40 +0,0 @@ -const assert = require('node:assert'); - -const tests = { - Utils: __dirname + '/utils-tests', -}; - -/** - * Generic function for verifying a utility function is behaving as expected. - * @param testName {string} - The name of the test to be displayed - * @param testFunc {function} - The source code to be used - * @param verifyFunc {function} - The expected output - */ -function testCodeSample(testName, testFunc, verifyFunc) { - process.stdout.write(`${testName}... `); - console.time('PASS'); - const results = testFunc(); - const expected = verifyFunc(); - assert.deepEqual(results, expected); - console.timeEnd('PASS'); -} - -let allTests = 0; -let skippedTests = 0; -console.time('tests in'); -for (const [moduleName, moduleTests] of Object.entries(tests)) { - const loadedTests = require(moduleTests); - for (const test of loadedTests) { - allTests++; - if (test.enabled) { - testCodeSample(`[${moduleName}] ${test.name}`.padEnd(90, '.'), test.testFunc, test.verifyFunc); - } else { - skippedTests++; - console.log(`[${moduleName}] ${test.name}...`.padEnd(101, '.') + ` SKIPPED: ${test.reason}`); - } - } -} -if (skippedTests > 0) { - process.stdout.write(`Completed ${allTests - skippedTests}/${allTests} (${skippedTests} skipped) utility `); -} else process.stdout.write(`Completed ${allTests} utility `); -console.timeEnd('tests in'); diff --git a/tests/utils-tests.js b/tests/utils-tests.js deleted file mode 100644 index 2109af6..0000000 --- a/tests/utils-tests.js +++ /dev/null @@ -1,272 +0,0 @@ -const {argsAreValid, parseArgs} = require(__dirname + '/../src/utils/parseArgs'); -const consolelog = console.log; -module.exports = [ - { - enabled: true, - name: 'parseArgs - TP-1 - defaults', - testFunc: () => { - return parseArgs([]); - }, - verifyFunc: () => { - return { - inputFilename: '', - help: false, - clean: false, - quiet: false, - verbose: false, - outputToFile: false, - maxIterations: false, - outputFilename: '-deob.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-2 - all on - short', - testFunc: () => { - return parseArgs(['input.js', '-h', '-c', '-q', '-v', '-o', '-m']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: true, - clean: true, - quiet: true, - verbose: true, - outputToFile: true, - maxIterations: true, - outputFilename: 'input.js-deob.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-2 - all on - full', - testFunc: () => { - return parseArgs(['input.js', '--help', '--clean', '--quiet', '--verbose', '--output', '--max-iterations']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: true, - clean: true, - quiet: true, - verbose: true, - outputToFile: true, - maxIterations: true, - outputFilename: 'input.js-deob.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-3 - custom outputFilename 1', - testFunc: () => { - return parseArgs(['input.js', '-o', 'customName.js']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: false, - clean: false, - quiet: false, - verbose: false, - outputToFile: true, - maxIterations: false, - outputFilename: 'customName.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-3 - custom outputFilename 2', - testFunc: () => { - return parseArgs(['input.js', '-o=customName.js']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: false, - clean: false, - quiet: false, - verbose: false, - outputToFile: true, - maxIterations: false, - outputFilename: 'customName.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-3 - custom outputFilename 3', - testFunc: () => { - return parseArgs(['input.js', '--output=customName.js']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: false, - clean: false, - quiet: false, - verbose: false, - outputToFile: true, - maxIterations: false, - outputFilename: 'customName.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-4 - max iterations short 1', - testFunc: () => { - return parseArgs(['input.js', '-m=2']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: false, - clean: false, - quiet: false, - verbose: false, - outputToFile: false, - maxIterations: 2, - outputFilename: 'input.js-deob.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-4 - max iterations short 2', - testFunc: () => { - return parseArgs(['input.js', '-m', '2']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: false, - clean: false, - quiet: false, - verbose: false, - outputToFile: false, - maxIterations: 2, - outputFilename: 'input.js-deob.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-4 - max iterations full 1', - testFunc: () => { - return parseArgs(['input.js', '--max-iterations=2']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: false, - clean: false, - quiet: false, - verbose: false, - outputToFile: false, - maxIterations: 2, - outputFilename: 'input.js-deob.js' - }; - }, - }, - { - enabled: true, - name: 'parseArgs - TP-4 - max iterations full 2', - testFunc: () => { - return parseArgs(['input.js', '--max-iterations', '2']); - }, - verifyFunc: () => { - return { - inputFilename: 'input.js', - help: false, - clean: false, - quiet: false, - verbose: false, - outputToFile: false, - maxIterations: 2, - outputFilename: 'input.js-deob.js' - }; - }, - }, - { - enabled: true, - name: 'argsAreValid - TP-1 - just input filename', - testFunc: () => { - console.log = () => {}; // Mute log - const res = argsAreValid(parseArgs(['input.js'])); - console.log = consolelog; - return res; - }, - verifyFunc: () => true, - }, - { - enabled: true, - name: 'argsAreValid - TP-2 - all on (no quiet, no help)', - testFunc: () => { - console.log = () => {}; // Mute log - const res = argsAreValid(parseArgs(['input.js', '-m=2', '-o', 'outputfile.js', '--verbose', '-c'])); - console.log = consolelog; - return res; - }, - verifyFunc: () => true, - }, - { - enabled: true, - name: 'argsAreValid - TP-3 - invalidate when showing help', - testFunc: () => { - console.log = () => {}; // Mute log - const res = argsAreValid(parseArgs(['input.js', '-m=2', '-o', 'outputfile.js', '--verbose', '-c', '-h'])); - console.log = consolelog; - return res; - }, - verifyFunc: () => false, - }, - { - enabled: true, - name: 'argsAreValid - TN-1 - missing input filename', - testFunc: () => { - console.log = () => {}; // Mute log - const res = argsAreValid(parseArgs([])); - console.log = consolelog; - return res; - }, - verifyFunc: () => false, - }, - { - enabled: true, - name: 'argsAreValid - TN-2 - mutually exclusive -v and -q', - testFunc: () => { - console.log = () => {}; // Mute log - const res = argsAreValid(parseArgs(['input.js', '-v', '-q'])); - console.log = consolelog; - return res; - }, - verifyFunc: () => false, - }, - { - enabled: true, - name: 'argsAreValid - TN-3 - max iterations without value', - testFunc: () => { - console.log = () => {}; // Mute log - const res = argsAreValid(parseArgs(['input.js', '-m'])); - console.log = consolelog; - return res; - }, - verifyFunc: () => false, - }, - { - enabled: true, - name: 'argsAreValid - TN-4 - max iterations invalid value 1', - testFunc: () => { - console.log = () => {}; // Mute log - const res = argsAreValid(parseArgs(['input.js', '-m', 'a'])); - console.log = consolelog; - return res; - }, - verifyFunc: () => false, - }, - -]; \ No newline at end of file diff --git a/tests/utils.test.js b/tests/utils.test.js new file mode 100644 index 0000000..2d98584 --- /dev/null +++ b/tests/utils.test.js @@ -0,0 +1,171 @@ +import assert from 'node:assert'; +import {describe, it} from 'node:test'; +import {argsAreValid, parseArgs} from '../src/utils/parseArgs.js'; +const consolelog = console.log; + +describe('parseArgs tests', () => { + it('TP-1: Defaults', () => { + assert.deepEqual(parseArgs([]), { + inputFilename: '', + help: false, + clean: false, + quiet: false, + verbose: false, + outputToFile: false, + maxIterations: false, + outputFilename: '-deob.js' + }); + }); + it('TP-2: All on - short', () => { + assert.deepEqual(parseArgs(['input.js', '-h', '-c', '-q', '-v', '-o', '-m', '1']), { + inputFilename: 'input.js', + help: true, + clean: true, + quiet: true, + verbose: true, + outputToFile: true, + maxIterations: 1, + outputFilename: 'input.js-deob.js' + }); + }); + it('TP-3: All on - full', () => { + assert.deepEqual(parseArgs(['input.js', '--help', '--clean', '--quiet', '--verbose', '--output', '--max-iterations=1']), { + inputFilename: 'input.js', + help: true, + clean: true, + quiet: true, + verbose: true, + outputToFile: true, + maxIterations: 1, + outputFilename: 'input.js-deob.js' + }); + }); + it('TP-4: Custom outputFilename split', () => { + assert.deepEqual(parseArgs(['input.js', '-o', 'customName.js']), { + inputFilename: 'input.js', + help: false, + clean: false, + quiet: false, + verbose: false, + outputToFile: true, + maxIterations: false, + outputFilename: 'customName.js' + }); + }); + it('TP-5: Custom outputFilename equals', () => { + assert.deepEqual(parseArgs(['input.js', '-o=customName.js']), { + inputFilename: 'input.js', + help: false, + clean: false, + quiet: false, + verbose: false, + outputToFile: true, + maxIterations: false, + outputFilename: 'customName.js' + }); + }); + it('TP-6: Custom outputFilename full', () => { + assert.deepEqual(parseArgs(['input.js', '--output=customName.js']), { + inputFilename: 'input.js', + help: false, + clean: false, + quiet: false, + verbose: false, + outputToFile: true, + maxIterations: false, + outputFilename: 'customName.js' + }); + }); + it('TP-7: Max iterations short equals', () => { + assert.deepEqual(parseArgs(['input.js', '-m=2']), { + inputFilename: 'input.js', + help: false, + clean: false, + quiet: false, + verbose: false, + outputToFile: false, + maxIterations: 2, + outputFilename: 'input.js-deob.js' + }); + }); + it('TP-8: Max iterations short split', () => { + assert.deepEqual(parseArgs(['input.js', '-m', '2']), { + inputFilename: 'input.js', + help: false, + clean: false, + quiet: false, + verbose: false, + outputToFile: false, + maxIterations: 2, + outputFilename: 'input.js-deob.js' + }); + }); + it('TP-9: Max iterations long equals', () => { + assert.deepEqual(parseArgs(['input.js', '--max-iterations=2']), { + inputFilename: 'input.js', + help: false, + clean: false, + quiet: false, + verbose: false, + outputToFile: false, + maxIterations: 2, + outputFilename: 'input.js-deob.js' + }); + }); + it('TP-10: Max iterations long split', () => { + assert.deepEqual(parseArgs(['input.js', '--max-iterations', '2']), { + inputFilename: 'input.js', + help: false, + clean: false, + quiet: false, + verbose: false, + outputToFile: false, + maxIterations: 2, + outputFilename: 'input.js-deob.js' + }); + }); +}); +describe('argsAreValid tests', () => { + it('TP-1: Input filename only', () => { + console.log = () => {}; // Mute log + const result = argsAreValid(parseArgs(['input.js'])); + console.log = consolelog; + assert.ok(result); + }); + it('TP-2: All on, no quiet, no help', () => { + console.log = () => {}; // Mute log + const result = argsAreValid(parseArgs(['input.js', '-m=2', '-o', 'outputfile.js', '--verbose', '-c'])); + console.log = consolelog; + assert.ok(result); + }); + it('TP-3: Invalidate when printing help', () => { + console.log = () => {}; // Mute log + const result = argsAreValid(parseArgs(['input.js', '-m=2', '-o', 'outputfile.js', '--verbose', '-c', '-h'])); + console.log = consolelog; + assert.strictEqual(result, false); + }); + it('TN-1: Missing input filename', () => { + console.log = () => {}; // Mute log + const result = argsAreValid(parseArgs([])); + console.log = consolelog; + assert.strictEqual(result, false); + }); + it('TN-2: Mutually exclusive verbose and quiet', () => { + console.log = () => {}; // Mute log + const result = argsAreValid(parseArgs(['input.js', '-v', '-q'])); + console.log = consolelog; + assert.strictEqual(result, false); + }); + it('TN-3: Max iterations missing value', () => { + console.log = () => {}; // Mute log + const result = argsAreValid(parseArgs(['input.js', '-m'])); + console.log = consolelog; + assert.strictEqual(result, false); + }); + it('TN-4: Max iterations invalid value NaN', () => { + console.log = () => {}; // Mute log + const result = argsAreValid(parseArgs(['input.js', '-m', 'a'])); + console.log = consolelog; + assert.strictEqual(result, false); + }); +}); \ No newline at end of file