diff --git a/.babelrc b/.babelrc index 92a857cf..094c0592 100644 --- a/.babelrc +++ b/.babelrc @@ -5,7 +5,7 @@ "chrome": 40, "firefox": 35, "edge": 14, - "node": "6.5", + "node": "6.5" }, "modules": false, "useBuiltIns": true diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a523a504 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/.eslintignore b/.eslintignore index 1034aa8a..6ff4ce5d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ -src/core/lib/** -src/core/config/MetaConfig.js \ No newline at end of file +src/core/vendor/** +src/core/operations/legacy/** \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index d63e35e8..e512df1b 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,6 @@ { "parserOptions": { - "ecmaVersion": 8, + "ecmaVersion": 9, "ecmaFeatures": { "impliedStrict": true }, @@ -84,12 +84,12 @@ "no-whitespace-before-property": "error", "operator-linebreak": ["error", "after"], "space-in-parens": "error", - "no-var": "error" + "no-var": "error", + "prefer-const": "error" }, "globals": { "$": false, "jQuery": false, - "moment": false, "log": false, "COMPILE_TIME": false, diff --git a/.gitignore b/.gitignore index 79c28325..31b15bd9 100755 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ build docs/* !docs/*.conf.json !docs/*.ico -.vscode -src/core/config/MetaConfig.js \ No newline at end of file +.vscode \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 0cb60d89..805f6b45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "8.4" + - node install: npm install before_script: - npm install -g grunt diff --git a/Gruntfile.js b/Gruntfile.js index 4595a48d..eb66cd95 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,7 +4,8 @@ const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const NodeExternals = require("webpack-node-externals"); const Inliner = require("web-resource-inliner"); -const fs = require("fs"); +const glob = require("glob"); +const path = require("path"); /** * Grunt configuration for building the app in various formats. @@ -21,15 +22,15 @@ module.exports = function (grunt) { // Tasks grunt.registerTask("dev", "A persistent task which creates a development build whenever source files are modified.", - ["clean:dev", "concurrent:dev"]); + ["clean:dev", "webpack-dev-server:start"]); grunt.registerTask("node", "Compiles CyberChef into a single NodeJS module.", - ["clean:node", "webpack:metaConf", "webpack:node", "chmod:build"]); + ["clean:node", "clean:config", "webpack:node", "chmod:build"]); grunt.registerTask("test", "A task which runs all the tests in test/tests.", - ["clean:test", "webpack:metaConf", "webpack:tests", "execute:test"]); + ["exec:tests"]); grunt.registerTask("docs", "Compiles documentation in the /docs directory.", @@ -37,7 +38,7 @@ module.exports = function (grunt) { grunt.registerTask("prod", "Creates a production-ready build. Use the --msg flag to add a compile message.", - ["eslint", "clean:prod", "webpack:metaConf", "webpack:web", "inline", "chmod"]); + ["eslint", "clean:prod", "webpack:web", "inline", "chmod"]); grunt.registerTask("default", "Lints the code base", @@ -62,9 +63,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks("grunt-contrib-copy"); grunt.loadNpmTasks("grunt-chmod"); grunt.loadNpmTasks("grunt-exec"); - grunt.loadNpmTasks("grunt-execute"); grunt.loadNpmTasks("grunt-accessibility"); - grunt.loadNpmTasks("grunt-concurrent"); // Project configuration @@ -118,12 +117,12 @@ module.exports = function (grunt) { * Generates an entry list for all the modules. */ function listEntryModules() { - const path = "./src/core/config/modules/"; - let entryModules = {}; + const entryModules = {}; - fs.readdirSync(path).forEach(file => { - if (file !== "Default.js" && file !== "OpModules.js") - entryModules[file.split(".js")[0]] = path + file; + glob.sync("./src/core/config/modules/*.mjs").forEach(file => { + const basename = path.basename(file); + if (basename !== "Default.mjs" && basename !== "OpModules.mjs") + entryModules[basename.split(".mjs")[0]] = path.resolve(file); }); return entryModules; @@ -131,10 +130,10 @@ module.exports = function (grunt) { grunt.initConfig({ clean: { - dev: ["build/dev/*", "src/core/config/MetaConfig.js"], - prod: ["build/prod/*", "src/core/config/MetaConfig.js"], - test: ["build/test/*", "src/core/config/MetaConfig.js"], - node: ["build/node/*", "src/core/config/MetaConfig.js"], + dev: ["build/dev/*"], + prod: ["build/prod/*"], + node: ["build/node/*"], + config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"], docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"], inlineScripts: ["build/prod/scripts.js"], }, @@ -143,10 +142,10 @@ module.exports = function (grunt) { configFile: "./.eslintrc.json" }, configs: ["Gruntfile.js"], - core: ["src/core/**/*.js", "!src/core/lib/**/*", "!src/core/config/MetaConfig.js"], - web: ["src/web/**/*.js"], - node: ["src/node/**/*.js"], - tests: ["test/**/*.js"], + core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*"], + web: ["src/web/**/*.{js,mjs}"], + node: ["src/node/**/*.{js,mjs}"], + tests: ["test/**/*.{js,mjs}"], }, jsdoc: { options: { @@ -159,17 +158,11 @@ module.exports = function (grunt) { all: { src: [ "src/**/*.js", - "!src/core/lib/**/*", - "!src/core/config/MetaConfig.js" + "src/**/*.mjs", + "!src/core/vendor/**/*" ], } }, - concurrent: { - options: { - logConcurrentOutput: true - }, - dev: ["webpack:metaConfDev", "webpack-dev-server:start"] - }, accessibility: { options: { accessibilityLevel: "WCAG2A", @@ -184,39 +177,6 @@ module.exports = function (grunt) { }, webpack: { options: webpackConfig, - metaConf: { - mode: "production", - target: "node", - entry: [ - "babel-polyfill", - "./src/core/config/OperationConfig.js" - ], - output: { - filename: "MetaConfig.js", - path: __dirname + "/src/core/config/", - library: "MetaConfig", - libraryTarget: "commonjs2", - libraryExport: "default" - }, - externals: [NodeExternals()], - }, - metaConfDev: { - mode: "development", - target: "node", - entry: [ - "babel-polyfill", - "./src/core/config/OperationConfig.js" - ], - output: { - filename: "MetaConfig.js", - path: __dirname + "/src/core/config/", - library: "MetaConfig", - libraryTarget: "commonjs2", - libraryExport: "default" - }, - externals: [NodeExternals()], - watch: true - }, web: { mode: "production", target: "web", @@ -229,7 +189,7 @@ module.exports = function (grunt) { }, resolve: { alias: { - "./config/modules/OpModules.js": "./config/modules/Default.js" + "./config/modules/OpModules": "./config/modules/Default" } }, plugins: [ @@ -279,7 +239,7 @@ module.exports = function (grunt) { tests: { mode: "development", target: "node", - entry: "./test/index.js", + entry: "./test/index.mjs", externals: [NodeExternals()], output: { filename: "index.js", @@ -292,7 +252,7 @@ module.exports = function (grunt) { node: { mode: "production", target: "node", - entry: "./src/node/index.js", + entry: "./src/node/index.mjs", externals: [NodeExternals()], output: { filename: "CyberChef.js", @@ -330,7 +290,7 @@ module.exports = function (grunt) { }, moduleEntryPoints), resolve: { alias: { - "./config/modules/OpModules.js": "./config/modules/Default.js" + "./config/modules/OpModules": "./config/modules/Default" } }, plugins: [ @@ -401,10 +361,10 @@ module.exports = function (grunt) { }, sitemap: { command: "node build/prod/sitemap.js > build/prod/sitemap.xml" + }, + tests: { + command: "node --experimental-modules test/index.mjs" } }, - execute: { - test: "build/test/index.js" - }, }); }; diff --git a/package-lock.json b/package-lock.json index 6e80565d..0354d97d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "7.8.0", + "version": "7.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -519,6 +519,14 @@ "lodash": "4.17.5", "source-map": "0.5.7", "trim-right": "1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + } } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -1161,6 +1169,11 @@ "tweetnacl": "0.14.5" } }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, "big.js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", @@ -1440,6 +1453,11 @@ } } }, + "bson": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-2.0.4.tgz", + "integrity": "sha512-e/GPy6CE0xL7MOYYRMIEwPGKF21WNaQdPIpV0YvaQDoR7oc47KUZ8c2P/TlRJVQP8RZ4CEsArGBC1NbkCRvl1w==" + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -5282,26 +5300,6 @@ "shelljs": "0.5.3" } }, - "grunt-concurrent": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-2.3.1.tgz", - "integrity": "sha1-Hj2zjM71o9oRleYdYx/n4yE0TSM=", - "dev": true, - "requires": { - "arrify": "1.0.1", - "async": "1.5.2", - "indent-string": "2.1.0", - "pad-stream": "1.2.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - } - } - }, "grunt-contrib-clean": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.1.0.tgz", @@ -5442,12 +5440,6 @@ "integrity": "sha512-cgAlreXf3muSYS5LzW0Cc4xHK03BjFOYk0MqCQ/MZ3k1Xz2GU7D+IAJg4UKicxpO+XdONJdx/NJ6kpy2wI+uHg==", "dev": true }, - "grunt-execute": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz", - "integrity": "sha1-TpRf5XlZzA3neZCDtrQq7ZYWNQo=", - "dev": true - }, "grunt-jsdoc": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/grunt-jsdoc/-/grunt-jsdoc-2.2.1.tgz", @@ -6877,10 +6869,9 @@ } }, "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" }, "jshint": { "version": "2.9.5", @@ -8402,19 +8393,6 @@ "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", "dev": true }, - "pad-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-1.2.0.tgz", - "integrity": "sha1-Yx3Mn3mBC3BZZeid7eps/w/B38k=", - "dev": true, - "requires": { - "meow": "3.7.0", - "pumpify": "1.3.5", - "repeating": "2.0.1", - "split2": "1.1.1", - "through2": "2.0.3" - } - }, "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -10473,6 +10451,11 @@ "ajv": "5.2.3" } }, + "scryptsy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.0.0.tgz", + "integrity": "sha1-Jiw28CMc+nZU4jY/o5TNLexm83g=" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -11024,15 +11007,6 @@ "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.3.5.tgz", "integrity": "sha1-YuLOZtLPkcx3SqXwdJ/yUTgDn1A=" }, - "split2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.1.1.tgz", - "integrity": "sha1-Fi2bGIZfAqsvKtlYVSLbm1TEgfk=", - "dev": true, - "requires": { - "through2": "2.0.3" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12176,15 +12150,6 @@ "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", "dev": true }, - "val-loader": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/val-loader/-/val-loader-1.1.0.tgz", - "integrity": "sha512-8m62XF42FcfrBBl02rtDY9hQhDcDczrEcr60/aSMxlzJiXAcbAimRPvsDoDa5QcGAusOgOmVTpFtK5EbfZdDwA==", - "dev": true, - "requires": { - "loader-utils": "1.1.0" - } - }, "valid-data-url": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.4.tgz", @@ -12687,6 +12652,12 @@ "integrity": "sha1-Iyxi7GCSsQBjWj0p2DwXRxKN+b0=", "dev": true }, + "webpack-shell-plugin": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/webpack-shell-plugin/-/webpack-shell-plugin-0.5.0.tgz", + "integrity": "sha1-Kbih2A3erg3bEOcpZn9yhlPCx0I=", + "dev": true + }, "webpack-sources": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz", diff --git a/package.json b/package.json index 68c19352..119d915b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "7.8.0", + "version": "7.9.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", @@ -41,12 +41,10 @@ "grunt": ">=1.0.2", "grunt-accessibility": "~6.0.0", "grunt-chmod": "~1.1.1", - "grunt-concurrent": "^2.3.1", "grunt-contrib-clean": "~1.1.0", "grunt-contrib-copy": "~1.0.0", "grunt-eslint": "^20.1.0", "grunt-exec": "~3.0.0", - "grunt-execute": "^0.2.2", "grunt-jsdoc": "^2.2.1", "grunt-webpack": "^3.0.2", "html-webpack-plugin": "^3.0.4", @@ -61,19 +59,21 @@ "sitemap": "^1.13.0", "style-loader": "^0.20.2", "url-loader": "^0.6.2", - "val-loader": "^1.1.0", "web-resource-inliner": "^4.2.1", "webpack": "^4.0.1", "webpack-dev-server": "^3.1.0", "webpack-node-externals": "^1.6.0", + "webpack-shell-plugin": "^0.5.0", "worker-loader": "^1.1.1" }, "dependencies": { "babel-polyfill": "^6.26.0", + "bcryptjs": "^2.4.3", "bignumber.js": "^6.0.0", "bootstrap": "^3.3.7", "bootstrap-colorpicker": "^2.5.2", "bootstrap-switch": "^3.3.4", + "bson": "^2.0.4", "crypto-api": "^0.8.0", "crypto-js": "^3.1.9-1", "ctph.js": "0.0.5", @@ -88,6 +88,7 @@ "js-crc": "^0.2.0", "js-sha3": "^0.7.0", "jsbn": "^1.1.0", + "jsesc": "^2.5.1", "jsonpath": "^1.0.0", "jsrsasign": "8.0.6", "lodash": "^4.17.5", @@ -99,6 +100,7 @@ "node-md6": "^0.1.0", "nwmatcher": "^1.4.3", "otp": "^0.1.3", + "scryptsy": "^2.0.0", "sladex-blowfish": "^0.8.1", "sortablejs": "^1.7.0", "split.js": "^1.3.5", diff --git a/src/core/Chef.js b/src/core/Chef.js deleted file mode 100755 index aba3f4f7..00000000 --- a/src/core/Chef.js +++ /dev/null @@ -1,165 +0,0 @@ -import Dish from "./Dish.js"; -import Recipe from "./Recipe.js"; - - -/** - * The main controller for CyberChef. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - */ -const Chef = function() { - this.dish = new Dish(); -}; - - -/** - * Runs the recipe over the input. - * - * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer - * @param {Object[]} recipeConfig - The recipe configuration object - * @param {Object} options - The options object storing various user choices - * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting - * @param {number} progress - The position in the recipe to start from - * @param {number} [step] - Whether to only execute one operation in the recipe - * - * @returns {Object} response - * @returns {string} response.result - The output of the recipe - * @returns {string} response.type - The data type of the result - * @returns {number} response.progress - The position that we have got to in the recipe - * @returns {number} response.duration - The number of ms it took to execute the recipe - * @returns {number} response.error - The error object thrown by a failed operation (false if no error) -*/ -Chef.prototype.bake = async function(input, recipeConfig, options, progress, step) { - log.debug("Chef baking"); - const startTime = new Date().getTime(), - recipe = new Recipe(recipeConfig), - containsFc = recipe.containsFlowControl(), - notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8; - let error = false; - - if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); - - // Clean up progress - if (progress >= recipeConfig.length) { - progress = 0; - } - - if (step) { - // Unset breakpoint on this step - recipe.setBreakpoint(progress, false); - // Set breakpoint on next step - recipe.setBreakpoint(progress + 1, true); - } - - // If stepping with flow control, we have to start from the beginning - // but still want to skip all previous breakpoints - if (progress > 0 && containsFc) { - recipe.removeBreaksUpTo(progress); - progress = 0; - } - - // If starting from scratch, load data - if (progress === 0) { - const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING; - this.dish.set(input, type); - } - - try { - progress = await recipe.execute(this.dish, progress); - } catch (err) { - log.error(err); - error = { - displayStr: err.displayStr, - }; - progress = err.progress; - } - - // Depending on the size of the output, we may send it back as a string or an ArrayBuffer. - // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file. - // The threshold is specified in KiB. - const threshold = (options.ioDisplayThreshold || 1024) * 1024; - const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; - - return { - result: this.dish.type === Dish.HTML ? - this.dish.get(Dish.HTML, notUTF8) : - this.dish.get(returnType, notUTF8), - type: Dish.enumLookup(this.dish.type), - progress: progress, - duration: new Date().getTime() - startTime, - error: error - }; -}; - - -/** - * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs, - * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a - * minute, we run a silent bake which will force the browser to load and cache all the relevant - * JavaScript code needed to do a real bake. - * - * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a - * long time and the browser has swapped out all its memory. - * - * The output will not be modified (hence "silent" bake). - * - * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load - * the recipe, ingredients and dish. - * - * @param {Object[]} recipeConfig - The recipe configuration object - * @returns {number} The time it took to run the silent bake in milliseconds. -*/ -Chef.prototype.silentBake = function(recipeConfig) { - log.debug("Running silent bake"); - - let startTime = new Date().getTime(), - recipe = new Recipe(recipeConfig), - dish = new Dish("", Dish.STRING); - - try { - recipe.execute(dish); - } catch (err) { - // Suppress all errors - } - return new Date().getTime() - startTime; -}; - - -/** - * Calculates highlight offsets if possible. - * - * @param {Object[]} recipeConfig - * @param {string} direction - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - * @returns {Object} - */ -Chef.prototype.calculateHighlights = function(recipeConfig, direction, pos) { - const recipe = new Recipe(recipeConfig); - const highlights = recipe.generateHighlightList(); - - if (!highlights) return false; - - for (let i = 0; i < highlights.length; i++) { - // Remove multiple highlights before processing again - pos = [pos[0]]; - - const func = direction === "forward" ? highlights[i].f : highlights[i].b; - - if (typeof func == "function") { - pos = func(pos, highlights[i].args); - } - } - - return { - pos: pos, - direction: direction - }; -}; - -export default Chef; diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs new file mode 100755 index 00000000..9c52695e --- /dev/null +++ b/src/core/Chef.mjs @@ -0,0 +1,172 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Dish from "./Dish"; +import Recipe from "./Recipe"; +import log from "loglevel"; + +/** + * The main controller for CyberChef. + */ +class Chef { + + /** + * Chef constructor + */ + constructor() { + this.dish = new Dish(); + } + + + /** + * Runs the recipe over the input. + * + * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer + * @param {Object[]} recipeConfig - The recipe configuration object + * @param {Object} options - The options object storing various user choices + * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting + * @param {number} progress - The position in the recipe to start from + * @param {number} [step] - Whether to only execute one operation in the recipe + * + * @returns {Object} response + * @returns {string} response.result - The output of the recipe + * @returns {string} response.type - The data type of the result + * @returns {number} response.progress - The position that we have got to in the recipe + * @returns {number} response.duration - The number of ms it took to execute the recipe + * @returns {number} response.error - The error object thrown by a failed operation (false if no error) + */ + async bake(input, recipeConfig, options, progress, step) { + log.debug("Chef baking"); + const startTime = new Date().getTime(), + recipe = new Recipe(recipeConfig), + containsFc = recipe.containsFlowControl(), + notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8; + let error = false; + + if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + + // Clean up progress + if (progress >= recipeConfig.length) { + progress = 0; + } + + if (step) { + // Unset breakpoint on this step + recipe.setBreakpoint(progress, false); + // Set breakpoint on next step + recipe.setBreakpoint(progress + 1, true); + } + + // If stepping with flow control, we have to start from the beginning + // but still want to skip all previous breakpoints + if (progress > 0 && containsFc) { + recipe.removeBreaksUpTo(progress); + progress = 0; + } + + // If starting from scratch, load data + if (progress === 0) { + const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING; + this.dish.set(input, type); + } + + try { + progress = await recipe.execute(this.dish, progress); + } catch (err) { + log.error(err); + error = { + displayStr: err.displayStr, + }; + progress = err.progress; + } + + // Depending on the size of the output, we may send it back as a string or an ArrayBuffer. + // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file. + // The threshold is specified in KiB. + const threshold = (options.ioDisplayThreshold || 1024) * 1024; + const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; + + return { + result: this.dish.type === Dish.HTML ? + this.dish.get(Dish.HTML, notUTF8) : + this.dish.get(returnType, notUTF8), + type: Dish.enumLookup(this.dish.type), + progress: progress, + duration: new Date().getTime() - startTime, + error: error + }; + } + + + /** + * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs, + * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a + * minute, we run a silent bake which will force the browser to load and cache all the relevant + * JavaScript code needed to do a real bake. + * + * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a + * long time and the browser has swapped out all its memory. + * + * The output will not be modified (hence "silent" bake). + * + * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load + * the recipe, ingredients and dish. + * + * @param {Object[]} recipeConfig - The recipe configuration object + * @returns {number} The time it took to run the silent bake in milliseconds. + */ + silentBake(recipeConfig) { + log.debug("Running silent bake"); + + const startTime = new Date().getTime(), + recipe = new Recipe(recipeConfig), + dish = new Dish("", Dish.STRING); + + try { + recipe.execute(dish); + } catch (err) { + // Suppress all errors + } + return new Date().getTime() - startTime; + } + + + /** + * Calculates highlight offsets if possible. + * + * @param {Object[]} recipeConfig + * @param {string} direction + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + * @returns {Object} + */ + calculateHighlights(recipeConfig, direction, pos) { + const recipe = new Recipe(recipeConfig); + const highlights = recipe.generateHighlightList(); + + if (!highlights) return false; + + for (let i = 0; i < highlights.length; i++) { + // Remove multiple highlights before processing again + pos = [pos[0]]; + + const func = direction === "forward" ? highlights[i].f : highlights[i].b; + + if (typeof func == "function") { + pos = func(pos, highlights[i].args); + } + } + + return { + pos: pos, + direction: direction + }; + } + +} + +export default Chef; diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index a091e09c..604189e7 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -7,9 +7,9 @@ */ import "babel-polyfill"; -import Chef from "./Chef.js"; -import OperationConfig from "./config/MetaConfig.js"; -import OpModules from "./config/modules/Default.js"; +import Chef from "./Chef"; +import OperationConfig from "./config/OperationConfig.json"; +import OpModules from "./config/modules/Default"; // Add ">" to the start of all log messages in the Chef Worker import loglevelMessagePrefix from "loglevel-message-prefix"; @@ -132,7 +132,7 @@ function silentBake(data) { */ function loadRequiredModules(recipeConfig) { recipeConfig.forEach(op => { - let module = self.OperationConfig[op.op].module; + const module = self.OperationConfig[op.op].module; if (!OpModules.hasOwnProperty(module)) { log.info("Loading module " + module); diff --git a/src/core/Dish.js b/src/core/Dish.js deleted file mode 100755 index f0093e81..00000000 --- a/src/core/Dish.js +++ /dev/null @@ -1,274 +0,0 @@ -import Utils from "./Utils.js"; -import BigNumber from "bignumber.js"; - -/** - * The data being operated on by each operation. - * - * @author n1474335 [n1474335@gmail.com] - * @author Matt C [matt@artemisbot.uk] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data. - * @param {number} type - The data type of value, see Dish enums. - */ -const Dish = function(value, type) { - this.value = value || typeof value === "string" ? value : null; - this.type = type || Dish.BYTE_ARRAY; -}; - - -/** - * Dish data type enum for byte arrays. - * @readonly - * @enum - */ -Dish.BYTE_ARRAY = 0; -/** - * Dish data type enum for strings. - * @readonly - * @enum - */ -Dish.STRING = 1; -/** - * Dish data type enum for numbers. - * @readonly - * @enum - */ -Dish.NUMBER = 2; -/** - * Dish data type enum for HTML. - * @readonly - * @enum - */ -Dish.HTML = 3; -/** - * Dish data type enum for ArrayBuffers. - * @readonly - * @enum - */ -Dish.ARRAY_BUFFER = 4; -/** - * Dish data type enum for BigNumbers. - * @readonly - * @enum - */ -Dish.BIG_NUMBER = 5; - - -/** - * Returns the data type enum for the given type string. - * - * @static - * @param {string} typeStr - The name of the data type. - * @returns {number} The data type enum value. - */ -Dish.typeEnum = function(typeStr) { - switch (typeStr.toLowerCase()) { - case "bytearray": - case "byte array": - return Dish.BYTE_ARRAY; - case "string": - return Dish.STRING; - case "number": - return Dish.NUMBER; - case "html": - return Dish.HTML; - case "arraybuffer": - case "array buffer": - return Dish.ARRAY_BUFFER; - case "bignumber": - case "big number": - return Dish.BIG_NUMBER; - default: - throw "Invalid data type string. No matching enum."; - } -}; - - -/** - * Returns the data type string for the given type enum. - * - * @static - * @param {number} typeEnum - The enum value of the data type. - * @returns {string} The data type as a string. - */ -Dish.enumLookup = function(typeEnum) { - switch (typeEnum) { - case Dish.BYTE_ARRAY: - return "byteArray"; - case Dish.STRING: - return "string"; - case Dish.NUMBER: - return "number"; - case Dish.HTML: - return "html"; - case Dish.ARRAY_BUFFER: - return "ArrayBuffer"; - case Dish.BIG_NUMBER: - return "BigNumber"; - default: - throw "Invalid data type enum. No matching type."; - } -}; - - -/** - * Sets the data value and type and then validates them. - * - * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data. - * @param {number} type - The data type of value, see Dish enums. - */ -Dish.prototype.set = function(value, type) { - log.debug("Dish type: " + Dish.enumLookup(type)); - this.value = value; - this.type = type; - - if (!this.valid()) { - const sample = Utils.truncate(JSON.stringify(this.value), 13); - throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample; - } -}; - - -/** - * Returns the value of the data in the type format specified. - * - * @param {number} type - The data type of value, see Dish enums. - * @param {boolean} [notUTF8] - Do not treat strings as UTF8. - * @returns {byteArray|string|number|ArrayBuffer|BigNumber} The value of the output data. - */ -Dish.prototype.get = function(type, notUTF8) { - if (this.type !== type) { - this.translate(type, notUTF8); - } - return this.value; -}; - - -/** - * Translates the data to the given type format. - * - * @param {number} toType - The data type of value, see Dish enums. - * @param {boolean} [notUTF8] - Do not treat strings as UTF8. - */ -Dish.prototype.translate = function(toType, notUTF8) { - log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); - const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8; - - // Convert data to intermediate byteArray type - switch (this.type) { - case Dish.STRING: - this.value = this.value ? Utils.strToByteArray(this.value) : []; - break; - case Dish.NUMBER: - this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : []; - break; - case Dish.HTML: - this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; - break; - case Dish.ARRAY_BUFFER: - // Array.from() would be nicer here, but it's slightly slower - this.value = Array.prototype.slice.call(new Uint8Array(this.value)); - break; - case Dish.BIG_NUMBER: - this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : []; - break; - default: - break; - } - - this.type = Dish.BYTE_ARRAY; - - // Convert from byteArray to toType - switch (toType) { - case Dish.STRING: - case Dish.HTML: - this.value = this.value ? byteArrayToStr(this.value) : ""; - this.type = Dish.STRING; - break; - case Dish.NUMBER: - this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0; - this.type = Dish.NUMBER; - break; - case Dish.ARRAY_BUFFER: - this.value = new Uint8Array(this.value).buffer; - this.type = Dish.ARRAY_BUFFER; - break; - case Dish.BIG_NUMBER: - try { - this.value = new BigNumber(byteArrayToStr(this.value)); - } catch (err) { - this.value = new BigNumber(NaN); - } - this.type = Dish.BIG_NUMBER; - break; - default: - break; - } -}; - - -/** - * Validates that the value is the type that has been specified. - * May have to disable parts of BYTE_ARRAY validation if it effects performance. - * - * @returns {boolean} Whether the data is valid or not. -*/ -Dish.prototype.valid = function() { - switch (this.type) { - case Dish.BYTE_ARRAY: - if (!(this.value instanceof Array)) { - return false; - } - - // Check that every value is a number between 0 - 255 - for (let i = 0; i < this.value.length; i++) { - if (typeof this.value[i] !== "number" || - this.value[i] < 0 || - this.value[i] > 255) { - return false; - } - } - return true; - case Dish.STRING: - case Dish.HTML: - return typeof this.value === "string"; - case Dish.NUMBER: - return typeof this.value === "number"; - case Dish.ARRAY_BUFFER: - return this.value instanceof ArrayBuffer; - case Dish.BIG_NUMBER: - return this.value instanceof BigNumber; - default: - return false; - } -}; - - -/** - * Determines how much space the Dish takes up. - * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish, - * we measure how many bytes are taken up when the number is written as a string. - * - * @returns {number} -*/ -Dish.prototype.size = function() { - switch (this.type) { - case Dish.BYTE_ARRAY: - case Dish.STRING: - case Dish.HTML: - return this.value.length; - case Dish.NUMBER: - case Dish.BIG_NUMBER: - return this.value.toString().length; - case Dish.ARRAY_BUFFER: - return this.value.byteLength; - default: - return -1; - } -}; - - -export default Dish; diff --git a/src/core/Dish.mjs b/src/core/Dish.mjs new file mode 100755 index 00000000..792395c1 --- /dev/null +++ b/src/core/Dish.mjs @@ -0,0 +1,293 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "./Utils"; +import BigNumber from "bignumber.js"; +import log from "loglevel"; + +/** + * The data being operated on by each operation. + */ +class Dish { + + /** + * Dish constructor + * + * @param {byteArray|string|number|ArrayBuffer|BigNumber} [value=null] + * - The value of the input data. + * @param {number} [type=Dish.BYTE_ARRAY] + * - The data type of value, see Dish enums. + */ + constructor(value=null, type=Dish.BYTE_ARRAY) { + this.value = value; + this.type = type; + } + + + /** + * Returns the data type enum for the given type string. + * + * @param {string} typeStr - The name of the data type. + * @returns {number} The data type enum value. + */ + static typeEnum(typeStr) { + switch (typeStr.toLowerCase()) { + case "bytearray": + case "byte array": + return Dish.BYTE_ARRAY; + case "string": + return Dish.STRING; + case "number": + return Dish.NUMBER; + case "html": + return Dish.HTML; + case "arraybuffer": + case "array buffer": + return Dish.ARRAY_BUFFER; + case "bignumber": + case "big number": + return Dish.BIG_NUMBER; + default: + throw "Invalid data type string. No matching enum."; + } + } + + + /** + * Returns the data type string for the given type enum. + * + * @param {number} typeEnum - The enum value of the data type. + * @returns {string} The data type as a string. + */ + static enumLookup(typeEnum) { + switch (typeEnum) { + case Dish.BYTE_ARRAY: + return "byteArray"; + case Dish.STRING: + return "string"; + case Dish.NUMBER: + return "number"; + case Dish.HTML: + return "html"; + case Dish.ARRAY_BUFFER: + return "ArrayBuffer"; + case Dish.BIG_NUMBER: + return "BigNumber"; + default: + throw "Invalid data type enum. No matching type."; + } + } + + + /** + * Sets the data value and type and then validates them. + * + * @param {byteArray|string|number|ArrayBuffer|BigNumber} value + * - The value of the input data. + * @param {number} type + * - The data type of value, see Dish enums. + */ + set(value, type) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + + log.debug("Dish type: " + Dish.enumLookup(type)); + this.value = value; + this.type = type; + + if (!this.valid()) { + const sample = Utils.truncate(JSON.stringify(this.value), 13); + throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample; + } + } + + + /** + * Returns the value of the data in the type format specified. + * + * @param {number} type - The data type of value, see Dish enums. + * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. + * @returns {byteArray|string|number|ArrayBuffer|BigNumber} + * The value of the output data. + */ + get(type, notUTF8=false) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + if (this.type !== type) { + this.translate(type, notUTF8); + } + return this.value; + } + + + /** + * Translates the data to the given type format. + * + * @param {number} toType - The data type of value, see Dish enums. + * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. + */ + translate(toType, notUTF8=false) { + log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); + const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8; + + // Convert data to intermediate byteArray type + switch (this.type) { + case Dish.STRING: + this.value = this.value ? Utils.strToByteArray(this.value) : []; + break; + case Dish.NUMBER: + this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : []; + break; + case Dish.HTML: + this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; + break; + case Dish.ARRAY_BUFFER: + // Array.from() would be nicer here, but it's slightly slower + this.value = Array.prototype.slice.call(new Uint8Array(this.value)); + break; + case Dish.BIG_NUMBER: + this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : []; + break; + default: + break; + } + + this.type = Dish.BYTE_ARRAY; + + // Convert from byteArray to toType + switch (toType) { + case Dish.STRING: + case Dish.HTML: + this.value = this.value ? byteArrayToStr(this.value) : ""; + this.type = Dish.STRING; + break; + case Dish.NUMBER: + this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0; + this.type = Dish.NUMBER; + break; + case Dish.ARRAY_BUFFER: + this.value = new Uint8Array(this.value).buffer; + this.type = Dish.ARRAY_BUFFER; + break; + case Dish.BIG_NUMBER: + try { + this.value = new BigNumber(byteArrayToStr(this.value)); + } catch (err) { + this.value = new BigNumber(NaN); + } + this.type = Dish.BIG_NUMBER; + break; + default: + break; + } + } + + + /** + * Validates that the value is the type that has been specified. + * May have to disable parts of BYTE_ARRAY validation if it effects performance. + * + * @returns {boolean} Whether the data is valid or not. + */ + valid() { + switch (this.type) { + case Dish.BYTE_ARRAY: + if (!(this.value instanceof Array)) { + return false; + } + + // Check that every value is a number between 0 - 255 + for (let i = 0; i < this.value.length; i++) { + if (typeof this.value[i] !== "number" || + this.value[i] < 0 || + this.value[i] > 255) { + return false; + } + } + return true; + case Dish.STRING: + case Dish.HTML: + return typeof this.value === "string"; + case Dish.NUMBER: + return typeof this.value === "number"; + case Dish.ARRAY_BUFFER: + return this.value instanceof ArrayBuffer; + case Dish.BIG_NUMBER: + return this.value instanceof BigNumber; + default: + return false; + } + } + + + /** + * Determines how much space the Dish takes up. + * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish, + * we measure how many bytes are taken up when the number is written as a string. + * + * @returns {number} + */ + get size() { + switch (this.type) { + case Dish.BYTE_ARRAY: + case Dish.STRING: + case Dish.HTML: + return this.value.length; + case Dish.NUMBER: + case Dish.BIG_NUMBER: + return this.value.toString().length; + case Dish.ARRAY_BUFFER: + return this.value.byteLength; + default: + return -1; + } + } + +} + + +/** + * Dish data type enum for byte arrays. + * @readonly + * @enum + */ +Dish.BYTE_ARRAY = 0; +/** + * Dish data type enum for strings. + * @readonly + * @enum + */ +Dish.STRING = 1; +/** + * Dish data type enum for numbers. + * @readonly + * @enum + */ +Dish.NUMBER = 2; +/** + * Dish data type enum for HTML. + * @readonly + * @enum + */ +Dish.HTML = 3; +/** + * Dish data type enum for ArrayBuffers. + * @readonly + * @enum + */ +Dish.ARRAY_BUFFER = 4; +/** + * Dish data type enum for BigNumbers. + * @readonly + * @enum + */ +Dish.BIG_NUMBER = 5; + + +export default Dish; diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js index fd2c0785..73f29d78 100755 --- a/src/core/FlowControl.js +++ b/src/core/FlowControl.js @@ -1,5 +1,5 @@ -import Recipe from "./Recipe.js"; -import Dish from "./Dish.js"; +import Recipe from "./Recipe"; +import Dish from "./Dish"; /** @@ -23,16 +23,16 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runFork: async function(state) { - let opList = state.opList, + const opList = state.opList, inputType = opList[state.progress].inputType, outputType = opList[state.progress].outputType, input = state.dish.get(inputType), - ings = opList[state.progress].getIngValues(), + ings = opList[state.progress].ingValues, splitDelim = ings[0], mergeDelim = ings[1], ignoreErrors = ings[2], - subOpList = [], - inputs = [], + subOpList = []; + let inputs = [], i; if (input) @@ -41,15 +41,15 @@ const FlowControl = { // Create subOpList for each tranche to operate on // (all remaining operations unless we encounter a Merge) for (i = state.progress + 1; i < opList.length; i++) { - if (opList[i].name === "Merge" && !opList[i].isDisabled()) { + if (opList[i].name === "Merge" && !opList[i].disabled) { break; } else { subOpList.push(opList[i]); } } - let recipe = new Recipe(), - output = "", + const recipe = new Recipe(); + let output = "", progress = 0; state.forkOffset += state.progress + 1; @@ -57,7 +57,7 @@ const FlowControl = { recipe.addOperations(subOpList); // Take a deep(ish) copy of the ingredient values - const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.getIngValues()))); + const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues))); // Run recipe over each tranche for (i = 0; i < inputs.length; i++) { @@ -65,7 +65,7 @@ const FlowControl = { // Baseline ing values for each tranche so that registers are reset subOpList.forEach((op, i) => { - op.setIngValues(JSON.parse(JSON.stringify(ingValues[i]))); + op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); }); const dish = new Dish(inputs[i], inputType); @@ -112,7 +112,7 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runRegister: function(state) { - const ings = state.opList[state.progress].getIngValues(), + const ings = state.opList[state.progress].ingValues, extractorStr = ings[0], i = ings[1], m = ings[2]; @@ -150,9 +150,9 @@ const FlowControl = { // Step through all subsequent ops and replace registers in args with extracted content for (let i = state.progress + 1; i < state.opList.length; i++) { - if (state.opList[i].isDisabled()) continue; + if (state.opList[i].disabled) continue; - let args = state.opList[i].getIngValues(); + let args = state.opList[i].ingValues; args = args.map(arg => { if (typeof arg !== "string" && typeof arg !== "object") return arg; @@ -181,7 +181,7 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runJump: function(state) { - const ings = state.opList[state.progress].getIngValues(), + const ings = state.opList[state.progress].ingValues, label = ings[0], maxJumps = ings[1], jmpIndex = FlowControl._getLabelIndex(label, state); @@ -209,7 +209,7 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runCondJump: function(state) { - const ings = state.opList[state.progress].getIngValues(), + const ings = state.opList[state.progress].ingValues, dish = state.dish, regexStr = ings[0], invert = ings[1], @@ -223,7 +223,7 @@ const FlowControl = { } if (regexStr !== "") { - let strMatch = dish.get(Dish.STRING).search(regexStr) > -1; + const strMatch = dish.get(Dish.STRING).search(regexStr) > -1; if (!invert && strMatch || invert && !strMatch) { state.progress = jmpIndex; state.numJumps++; @@ -274,9 +274,9 @@ const FlowControl = { */ _getLabelIndex: function(name, state) { for (let o = 0; o < state.opList.length; o++) { - let operation = state.opList[o]; + const operation = state.opList[o]; if (operation.name === "Label"){ - let ings = operation.getIngValues(); + const ings = operation.ingValues; if (name === ings[0]) { return o; } diff --git a/src/core/Ingredient.js b/src/core/Ingredient.js deleted file mode 100755 index e8d8a8cc..00000000 --- a/src/core/Ingredient.js +++ /dev/null @@ -1,92 +0,0 @@ -import Utils from "./Utils.js"; - - -/** - * The arguments to operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {Object} ingredientConfig - */ -const Ingredient = function(ingredientConfig) { - this.name = ""; - this.type = ""; - this.value = null; - - if (ingredientConfig) { - this._parseConfig(ingredientConfig); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} ingredientConfig - */ -Ingredient.prototype._parseConfig = function(ingredientConfig) { - this.name = ingredientConfig.name; - this.type = ingredientConfig.type; -}; - - -/** - * Returns the value of the Ingredient as it should be displayed in a recipe config. - * - * @returns {*} - */ -Ingredient.prototype.getConfig = function() { - return this.value; -}; - - -/** - * Sets the value of the Ingredient. - * - * @param {*} value - */ -Ingredient.prototype.setValue = function(value) { - this.value = Ingredient.prepare(value, this.type); -}; - - -/** - * Most values will be strings when they are entered. This function converts them to the correct - * type. - * - * @static - * @param {*} data - * @param {string} type - The name of the data type. -*/ -Ingredient.prepare = function(data, type) { - let number; - - switch (type) { - case "binaryString": - case "binaryShortString": - case "editableOption": - return Utils.parseEscapedChars(data); - case "byteArray": - if (typeof data == "string") { - data = data.replace(/\s+/g, ""); - return Utils.fromHex(data); - } else { - return data; - } - case "number": - number = parseFloat(data); - if (isNaN(number)) { - const sample = Utils.truncate(data.toString(), 10); - throw "Invalid ingredient value. Not a number: " + sample; - } - return number; - default: - return data; - } -}; - -export default Ingredient; diff --git a/src/core/Ingredient.mjs b/src/core/Ingredient.mjs new file mode 100755 index 00000000..18823daf --- /dev/null +++ b/src/core/Ingredient.mjs @@ -0,0 +1,110 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "./Utils"; +import {fromHex} from "./lib/Hex"; + +/** + * The arguments to operations. + */ +class Ingredient { + + /** + * Ingredient constructor + * + * @param {Object} ingredientConfig + */ + constructor(ingredientConfig) { + this.name = ""; + this.type = ""; + this._value = null; + + if (ingredientConfig) { + this._parseConfig(ingredientConfig); + } + } + + + /** + * Reads and parses the given config. + * + * @private + * @param {Object} ingredientConfig + */ + _parseConfig(ingredientConfig) { + this.name = ingredientConfig.name; + this.type = ingredientConfig.type; + this.defaultValue = ingredientConfig.value; + } + + + /** + * Returns the value of the Ingredient as it should be displayed in a recipe config. + * + * @returns {*} + */ + get config() { + return this._value; + } + + + /** + * Sets the value of the Ingredient. + * + * @param {*} value + */ + set value(value) { + this._value = Ingredient.prepare(value, this.type); + } + + + /** + * Gets the value of the Ingredient. + * + * @returns {*} + */ + get value() { + return this._value; + } + + + /** + * Most values will be strings when they are entered. This function converts them to the correct + * type. + * + * @param {*} data + * @param {string} type - The name of the data type. + */ + static prepare(data, type) { + let number; + + switch (type) { + case "binaryString": + case "binaryShortString": + case "editableOption": + return Utils.parseEscapedChars(data); + case "byteArray": + if (typeof data == "string") { + data = data.replace(/\s+/g, ""); + return fromHex(data); + } else { + return data; + } + case "number": + number = parseFloat(data); + if (isNaN(number)) { + const sample = Utils.truncate(data.toString(), 10); + throw "Invalid ingredient value. Not a number: " + sample; + } + return number; + default: + return data; + } + } + +} + +export default Ingredient; diff --git a/src/core/Operation.js b/src/core/Operation.js deleted file mode 100755 index d4ee21d3..00000000 --- a/src/core/Operation.js +++ /dev/null @@ -1,174 +0,0 @@ -import Dish from "./Dish.js"; -import Ingredient from "./Ingredient.js"; -import OperationConfig from "./config/MetaConfig.js"; -import OpModules from "./config/modules/OpModules.js"; - - -/** - * The Operation specified by the user to be run. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {string} operationName - */ -const Operation = function(operationName) { - this.name = operationName; - this.module = ""; - this.description = ""; - this.inputType = -1; - this.outputType = -1; - this.run = null; - this.highlight = null; - this.highlightReverse = null; - this.breakpoint = false; - this.disabled = false; - this.ingList = []; - - if (OperationConfig.hasOwnProperty(this.name)) { - this._parseConfig(OperationConfig[this.name]); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} operationConfig - */ -Operation.prototype._parseConfig = function(operationConfig) { - this.module = operationConfig.module; - this.description = operationConfig.description; - this.inputType = Dish.typeEnum(operationConfig.inputType); - this.outputType = Dish.typeEnum(operationConfig.outputType); - this.highlight = operationConfig.highlight; - this.highlightReverse = operationConfig.highlightReverse; - this.flowControl = operationConfig.flowControl; - this.run = OpModules[this.module][this.name]; - - for (let a = 0; a < operationConfig.args.length; a++) { - const ingredientConfig = operationConfig.args[a]; - const ingredient = new Ingredient(ingredientConfig); - this.addIngredient(ingredient); - } - - if (this.highlight === "func") { - this.highlight = OpModules[this.module][`${this.name}-highlight`]; - } - - if (this.highlightReverse === "func") { - this.highlightReverse = OpModules[this.module][`${this.name}-highlightReverse`]; - } -}; - - -/** - * Returns the value of the Operation as it should be displayed in a recipe config. - * - * @returns {Object} - */ -Operation.prototype.getConfig = function() { - const ingredientConfig = []; - - for (let o = 0; o < this.ingList.length; o++) { - ingredientConfig.push(this.ingList[o].getConfig()); - } - - const operationConfig = { - "op": this.name, - "args": ingredientConfig - }; - - return operationConfig; -}; - - -/** - * Adds a new Ingredient to this Operation. - * - * @param {Ingredient} ingredient - */ -Operation.prototype.addIngredient = function(ingredient) { - this.ingList.push(ingredient); -}; - - -/** - * Set the Ingredient values for this Operation. - * - * @param {Object[]} ingValues - */ -Operation.prototype.setIngValues = function(ingValues) { - for (let i = 0; i < ingValues.length; i++) { - this.ingList[i].setValue(ingValues[i]); - } -}; - - -/** - * Get the Ingredient values for this Operation. - * - * @returns {Object[]} - */ -Operation.prototype.getIngValues = function() { - const ingValues = []; - for (let i = 0; i < this.ingList.length; i++) { - ingValues.push(this.ingList[i].value); - } - return ingValues; -}; - - -/** - * Set whether this Operation has a breakpoint. - * - * @param {boolean} value - */ -Operation.prototype.setBreakpoint = function(value) { - this.breakpoint = !!value; -}; - - -/** - * Returns true if this Operation has a breakpoint set. - * - * @returns {boolean} - */ -Operation.prototype.isBreakpoint = function() { - return this.breakpoint; -}; - - -/** - * Set whether this Operation is disabled. - * - * @param {boolean} value - */ -Operation.prototype.setDisabled = function(value) { - this.disabled = !!value; -}; - - -/** - * Returns true if this Operation is disabled. - * - * @returns {boolean} - */ -Operation.prototype.isDisabled = function() { - return this.disabled; -}; - - -/** - * Returns true if this Operation is a flow control. - * - * @returns {boolean} - */ -Operation.prototype.isFlowControl = function() { - return this.flowControl; -}; - -export default Operation; diff --git a/src/core/Operation.mjs b/src/core/Operation.mjs new file mode 100755 index 00000000..30ad71e4 --- /dev/null +++ b/src/core/Operation.mjs @@ -0,0 +1,239 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Dish from "./Dish"; +import Ingredient from "./Ingredient"; + +/** + * The Operation specified by the user to be run. + */ +class Operation { + + /** + * Operation constructor + */ + constructor() { + // Private fields + this._inputType = -1; + this._outputType = -1; + this._breakpoint = false; + this._disabled = false; + this._flowControl = false; + this._ingList = []; + + // Public fields + this.name = ""; + this.module = ""; + this.description = ""; + } + + + /** + * Interface for operation runner + * + * @param {*} input + * @param {Object[]} args + * @returns {*} + */ + run(input, args) { + return input; + } + + + /** + * Interface for forward highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return false; + } + + + /** + * Interface for reverse highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return false; + } + + + /** + * Sets the input type as a Dish enum. + * + * @param {string} typeStr + */ + set inputType(typeStr) { + this._inputType = Dish.typeEnum(typeStr); + } + + + /** + * Gets the input type as a readable string. + * + * @returns {string} + */ + get inputType() { + return Dish.enumLookup(this._inputType); + } + + + /** + * Sets the output type as a Dish enum. + * + * @param {string} typeStr + */ + set outputType(typeStr) { + this._outputType = Dish.typeEnum(typeStr); + } + + + /** + * Gets the output type as a readable string. + * + * @returns {string} + */ + get outputType() { + return Dish.enumLookup(this._outputType); + } + + + /** + * Sets the args for the current operation. + * + * @param {Object[]} conf + */ + set args(conf) { + conf.forEach(arg => { + const ingredient = new Ingredient(arg); + this.addIngredient(ingredient); + }); + } + + + /** + * Gets the args for the current operation. + * + * @param {Object[]} conf + */ + get args() { + return this._ingList.map(ing => { + return { + name: ing.name, + type: ing.type, + value: ing.defaultValue + }; + }); + } + + + /** + * Returns the value of the Operation as it should be displayed in a recipe config. + * + * @returns {Object} + */ + get config() { + return { + "op": this.name, + "args": this._ingList.map(ing => ing.conf) + }; + } + + + /** + * Adds a new Ingredient to this Operation. + * + * @param {Ingredient} ingredient + */ + addIngredient(ingredient) { + this._ingList.push(ingredient); + } + + + /** + * Set the Ingredient values for this Operation. + * + * @param {Object[]} ingValues + */ + set ingValues(ingValues) { + ingValues.forEach((val, i) => { + this._ingList[i].value = val; + }); + } + + + /** + * Get the Ingredient values for this Operation. + * + * @returns {Object[]} + */ + get ingValues() { + return this._ingList.map(ing => ing.value); + } + + + /** + * Set whether this Operation has a breakpoint. + * + * @param {boolean} value + */ + set breakpoint(value) { + this._breakpoint = !!value; + } + + + /** + * Returns true if this Operation has a breakpoint set. + * + * @returns {boolean} + */ + get breakpoint() { + return this._breakpoint; + } + + + /** + * Set whether this Operation is disabled. + * + * @param {boolean} value + */ + set disabled(value) { + this._disabled = !!value; + } + + + /** + * Returns true if this Operation is disabled. + * + * @returns {boolean} + */ + get disabled() { + return this._disabled; + } + + + /** + * Returns true if this Operation is a flow control. + * + * @returns {boolean} + */ + get flowControl() { + return this._flowControl; + } + +} + +export default Operation; diff --git a/src/core/Recipe.js b/src/core/Recipe.js deleted file mode 100755 index 9305c32d..00000000 --- a/src/core/Recipe.js +++ /dev/null @@ -1,264 +0,0 @@ -import Operation from "./Operation.js"; - - -/** - * The Recipe controls a list of Operations and the Dish they operate on. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {Object} recipeConfig - */ -const Recipe = function(recipeConfig) { - this.opList = []; - - if (recipeConfig) { - this._parseConfig(recipeConfig); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} recipeConfig - */ -Recipe.prototype._parseConfig = function(recipeConfig) { - for (let c = 0; c < recipeConfig.length; c++) { - const operationName = recipeConfig[c].op; - const operation = new Operation(operationName); - operation.setIngValues(recipeConfig[c].args); - operation.setBreakpoint(recipeConfig[c].breakpoint); - operation.setDisabled(recipeConfig[c].disabled); - this.addOperation(operation); - } -}; - - -/** - * Returns the value of the Recipe as it should be displayed in a recipe config. - * - * @returns {*} - */ -Recipe.prototype.getConfig = function() { - const recipeConfig = []; - - for (let o = 0; o < this.opList.length; o++) { - recipeConfig.push(this.opList[o].getConfig()); - } - - return recipeConfig; -}; - - -/** - * Adds a new Operation to this Recipe. - * - * @param {Operation} operation - */ -Recipe.prototype.addOperation = function(operation) { - this.opList.push(operation); -}; - - -/** - * Adds a list of Operations to this Recipe. - * - * @param {Operation[]} operations - */ -Recipe.prototype.addOperations = function(operations) { - this.opList = this.opList.concat(operations); -}; - - -/** - * Set a breakpoint on a specified Operation. - * - * @param {number} position - The index of the Operation - * @param {boolean} value - */ -Recipe.prototype.setBreakpoint = function(position, value) { - try { - this.opList[position].setBreakpoint(value); - } catch (err) { - // Ignore index error - } -}; - - -/** - * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow - * Control Fork operation. - * - * @param {number} pos - */ -Recipe.prototype.removeBreaksUpTo = function(pos) { - for (let i = 0; i < pos; i++) { - this.opList[i].setBreakpoint(false); - } -}; - - -/** - * Returns true if there is an Flow Control Operation in this Recipe. - * - * @returns {boolean} - */ -Recipe.prototype.containsFlowControl = function() { - for (let i = 0; i < this.opList.length; i++) { - if (this.opList[i].isFlowControl()) return true; - } - return false; -}; - - -/** - * Returns the index of the last Operation index that will be executed, taking into account disabled - * Operations and breakpoints. - * - * @param {number} [startIndex=0] - The index to start searching from - * @returns (number} - */ -Recipe.prototype.lastOpIndex = function(startIndex) { - let i = startIndex + 1 || 0, - op; - - for (; i < this.opList.length; i++) { - op = this.opList[i]; - if (op.isDisabled()) return i-1; - if (op.isBreakpoint()) return i-1; - } - - return i-1; -}; - - -/** - * Executes each operation in the recipe over the given Dish. - * - * @param {Dish} dish - * @param {number} [startFrom=0] - The index of the Operation to start executing from - * @param {number} [forkState={}] - If this is a forked recipe, the state of the recipe up to this point - * @returns {number} - The final progress through the recipe - */ -Recipe.prototype.execute = async function(dish, startFrom = 0, forkState = {}) { - let op, input, output, - numJumps = 0, - numRegisters = forkState.numRegisters || 0; - - log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`); - - for (let i = startFrom; i < this.opList.length; i++) { - op = this.opList[i]; - log.debug(`[${i}] ${op.name} ${JSON.stringify(op.getIngValues())}`); - if (op.isDisabled()) { - log.debug("Operation is disabled, skipping"); - continue; - } - if (op.isBreakpoint()) { - log.debug("Pausing at breakpoint"); - return i; - } - - try { - input = dish.get(op.inputType); - log.debug("Executing operation"); - - if (op.isFlowControl()) { - // Package up the current state - let state = { - "progress": i, - "dish": dish, - "opList": this.opList, - "numJumps": numJumps, - "numRegisters": numRegisters, - "forkOffset": forkState.forkOffset || 0 - }; - - state = await op.run(state); - i = state.progress; - numJumps = state.numJumps; - numRegisters = state.numRegisters; - } else { - output = await op.run(input, op.getIngValues()); - dish.set(output, op.outputType); - } - } catch (err) { - const e = typeof err == "string" ? { message: err } : err; - - e.progress = i; - if (e.fileName) { - e.displayStr = op.name + " - " + e.name + " in " + - e.fileName + " on line " + e.lineNumber + - ".

Message: " + (e.displayStr || e.message); - } else { - e.displayStr = op.name + " - " + (e.displayStr || e.message); - } - - throw e; - } - } - - log.debug("Recipe complete"); - return this.opList.length; -}; - - -/** - * Returns the recipe configuration in string format. - * - * @returns {string} - */ -Recipe.prototype.toString = function() { - return JSON.stringify(this.getConfig()); -}; - - -/** - * Creates a Recipe from a given configuration string. - * - * @param {string} recipeStr - */ -Recipe.prototype.fromString = function(recipeStr) { - const recipeConfig = JSON.parse(recipeStr); - this._parseConfig(recipeConfig); -}; - - -/** - * Generates a list of all the highlight functions assigned to operations in the recipe, if the - * entire recipe supports highlighting. - * - * @returns {Object[]} highlights - * @returns {function} highlights[].f - * @returns {function} highlights[].b - * @returns {Object[]} highlights[].args - */ -Recipe.prototype.generateHighlightList = function() { - const highlights = []; - - for (let i = 0; i < this.opList.length; i++) { - let op = this.opList[i]; - if (op.isDisabled()) continue; - - // If any breakpoints are set, do not attempt to highlight - if (op.isBreakpoint()) return false; - - // If any of the operations do not support highlighting, fail immediately. - if (op.highlight === false || op.highlight === undefined) return false; - - highlights.push({ - f: op.highlight, - b: op.highlightReverse, - args: op.getIngValues() - }); - } - - return highlights; -}; - - -export default Recipe; diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs new file mode 100755 index 00000000..7afaeab9 --- /dev/null +++ b/src/core/Recipe.mjs @@ -0,0 +1,250 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +// import Operation from "./Operation.js"; +import OpModules from "./config/modules/OpModules"; +import OperationConfig from "./config/OperationConfig.json"; +import log from "loglevel"; + +/** + * The Recipe controls a list of Operations and the Dish they operate on. + */ +class Recipe { + + /** + * Recipe constructor + * + * @param {Object} recipeConfig + */ + constructor(recipeConfig) { + this.opList = []; + + if (recipeConfig) { + this._parseConfig(recipeConfig); + } + } + + + /** + * Reads and parses the given config. + * + * @private + * @param {Object} recipeConfig + */ + _parseConfig(recipeConfig) { + for (let c = 0; c < recipeConfig.length; c++) { + const operationName = recipeConfig[c].op; + const opConf = OperationConfig[operationName]; + const opObj = OpModules[opConf.module][operationName]; + const operation = new opObj(); + operation.ingValues = recipeConfig[c].args; + operation.breakpoint = recipeConfig[c].breakpoint; + operation.disabled = recipeConfig[c].disabled; + this.addOperation(operation); + } + } + + + /** + * Returns the value of the Recipe as it should be displayed in a recipe config. + * + * @returns {Object[]} + */ + get config() { + return this.opList.map(op => op.config); + } + + + /** + * Adds a new Operation to this Recipe. + * + * @param {Operation} operation + */ + addOperation(operation) { + this.opList.push(operation); + } + + + /** + * Adds a list of Operations to this Recipe. + * + * @param {Operation[]} operations + */ + addOperations(operations) { + this.opList = this.opList.concat(operations); + } + + + /** + * Set a breakpoint on a specified Operation. + * + * @param {number} position - The index of the Operation + * @param {boolean} value + */ + setBreakpoint(position, value) { + try { + this.opList[position].breakpoint = value; + } catch (err) { + // Ignore index error + } + } + + + /** + * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow + * Control Fork operation. + * + * @param {number} pos + */ + removeBreaksUpTo(pos) { + for (let i = 0; i < pos; i++) { + this.opList[i].breakpoint = false; + } + } + + + /** + * Returns true if there is an Flow Control Operation in this Recipe. + * + * @returns {boolean} + */ + containsFlowControl() { + return this.opList.reduce((acc, curr) => { + return acc || curr.flowControl; + }, false); + } + + + /** + * Executes each operation in the recipe over the given Dish. + * + * @param {Dish} dish + * @param {number} [startFrom=0] + * - The index of the Operation to start executing from + * @param {number} [forkState={}] + * - If this is a forked recipe, the state of the recipe up to this point + * @returns {number} + * - The final progress through the recipe + */ + async execute(dish, startFrom=0, forkState={}) { + let op, input, output, + numJumps = 0, + numRegisters = forkState.numRegisters || 0; + + log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`); + + for (let i = startFrom; i < this.opList.length; i++) { + op = this.opList[i]; + log.debug(`[${i}] ${op.name} ${JSON.stringify(op.ingValues)}`); + if (op.disabled) { + log.debug("Operation is disabled, skipping"); + continue; + } + if (op.breakpoint) { + log.debug("Pausing at breakpoint"); + return i; + } + + try { + input = dish.get(op.inputType); + log.debug("Executing operation"); + + if (op.flowControl) { + // Package up the current state + let state = { + "progress": i, + "dish": dish, + "opList": this.opList, + "numJumps": numJumps, + "numRegisters": numRegisters, + "forkOffset": forkState.forkOffset || 0 + }; + + state = await op.run(state); + i = state.progress; + numJumps = state.numJumps; + numRegisters = state.numRegisters; + } else { + output = await op.run(input, op.ingValues); + dish.set(output, op.outputType); + } + } catch (err) { + const e = typeof err == "string" ? { message: err } : err; + + e.progress = i; + if (e.fileName) { + e.displayStr = op.name + " - " + e.name + " in " + + e.fileName + " on line " + e.lineNumber + + ".

Message: " + (e.displayStr || e.message); + } else { + e.displayStr = op.name + " - " + (e.displayStr || e.message); + } + + throw e; + } + } + + log.debug("Recipe complete"); + return this.opList.length; + } + + + /** + * Returns the recipe configuration in string format. + * + * @returns {string} + */ + toString() { + return JSON.stringify(this.config); + } + + + /** + * Creates a Recipe from a given configuration string. + * + * @param {string} recipeStr + */ + fromString(recipeStr) { + const recipeConfig = JSON.parse(recipeStr); + this._parseConfig(recipeConfig); + } + + + /** + * Generates a list of all the highlight functions assigned to operations in the recipe, if the + * entire recipe supports highlighting. + * + * @returns {Object[]} highlights + * @returns {function} highlights[].f + * @returns {function} highlights[].b + * @returns {Object[]} highlights[].args + */ + generateHighlightList() { + const highlights = []; + + for (let i = 0; i < this.opList.length; i++) { + const op = this.opList[i]; + if (op.disabled) continue; + + // If any breakpoints are set, do not attempt to highlight + if (op.breakpoint) return false; + + // If any of the operations do not support highlighting, fail immediately. + if (op.highlight === false || op.highlight === undefined) return false; + + highlights.push({ + f: op.highlight, + b: op.highlightReverse, + args: op.ingValues + }); + } + + return highlights; + } + +} + +export default Recipe; diff --git a/src/core/Utils.js b/src/core/Utils.mjs similarity index 77% rename from src/core/Utils.js rename to src/core/Utils.mjs index 3a34af9e..2be207eb 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.mjs @@ -1,16 +1,19 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + import utf8 from "utf8"; +import moment from "moment-timezone"; +import {fromBase64} from "./lib/Base64"; +import {toHexFast, fromHex} from "./lib/Hex"; /** * Utility functions for use in operations, the core framework and the stage. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace */ -const Utils = { +class Utils { /** * Translates an ordinal into a character. @@ -22,7 +25,7 @@ const Utils = { * // returns 'a' * Utils.chr(97); */ - chr: function(o) { + static chr(o) { // Detect astral symbols // Thanks to @mathiasbynens for this solution // https://mathiasbynens.be/notes/javascript-unicode @@ -34,7 +37,7 @@ const Utils = { } return String.fromCharCode(o); - }, + } /** @@ -47,7 +50,7 @@ const Utils = { * // returns 97 * Utils.ord('a'); */ - ord: function(c) { + static ord(c) { // Detect astral symbols // Thanks to @mathiasbynens for this solution // https://mathiasbynens.be/notes/javascript-unicode @@ -61,7 +64,7 @@ const Utils = { } return c.charCodeAt(0); - }, + } /** @@ -87,8 +90,7 @@ const Utils = { * // returns ["t", "e", "s", "t", 1, 1, 1, 1] * Utils.padBytesRight("test", 8, 1); */ - padBytesRight: function(arr, numBytes, padByte) { - padByte = padByte || 0; + static padBytesRight(arr, numBytes, padByte=0) { const paddedBytes = new Array(numBytes); paddedBytes.fill(padByte); @@ -97,7 +99,7 @@ const Utils = { }); return paddedBytes; - }, + } /** @@ -115,13 +117,12 @@ const Utils = { * // returns "A long s-" * Utils.truncate("A long string", 9, "-"); */ - truncate: function(str, max, suffix) { - suffix = suffix || "..."; + static truncate(str, max, suffix="...") { if (str.length > max) { str = str.slice(0, max - suffix.length) + suffix; } return str; - }, + } /** @@ -138,11 +139,10 @@ const Utils = { * // returns "6e" * Utils.hex(110); */ - hex: function(c, length) { + static hex(c, length=2) { c = typeof c == "string" ? Utils.ord(c) : c; - length = length || 2; return c.toString(16).padStart(length, "0"); - }, + } /** @@ -159,11 +159,10 @@ const Utils = { * // returns "01101110" * Utils.bin(110); */ - bin: function(c, length) { + static bin(c, length=8) { c = typeof c == "string" ? Utils.ord(c) : c; - length = length || 8; return c.toString(2).padStart(length, "0"); - }, + } /** @@ -173,7 +172,7 @@ const Utils = { * @param {boolean} [preserveWs=false] - Whether or not to print whitespace. * @returns {string} */ - printable: function(str, preserveWs) { + static printable(str, preserveWs=false) { if (ENVIRONMENT_IS_WEB() && window.app && !window.app.options.treatAsUtf8) { str = Utils.byteArrayToChars(Utils.strToByteArray(str)); } @@ -184,7 +183,7 @@ const Utils = { str = str.replace(re, "."); if (!preserveWs) str = str.replace(wsRe, "."); return str; - }, + } /** @@ -200,25 +199,38 @@ const Utils = { * // returns "\n" * Utils.parseEscapedChars("\\n"); */ - parseEscapedChars: function(str) { - return str.replace(/(\\)?\\([nrtbf]|x[\da-fA-F]{2})/g, function(m, a, b) { + static parseEscapedChars(str) { + return str.replace(/(\\)?\\([bfnrtv0'"]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) { if (a === "\\") return "\\"+b; switch (b[0]) { - case "n": - return "\n"; - case "r": - return "\r"; - case "t": - return "\t"; + case "0": + return "\0"; case "b": return "\b"; + case "t": + return "\t"; + case "n": + return "\n"; + case "v": + return "\v"; case "f": return "\f"; + case "r": + return "\r"; + case '"': + return '"'; + case "'": + return "'"; case "x": - return Utils.chr(parseInt(b.substr(1), 16)); + return String.fromCharCode(parseInt(b.substr(1), 16)); + case "u": + if (b[1] === "{") + return String.fromCodePoint(parseInt(b.slice(2, -1), 16)); + else + return String.fromCharCode(parseInt(b.substr(1), 16)); } }); - }, + } /** @@ -232,9 +244,9 @@ const Utils = { * // returns "\[example\]" * Utils.escapeRegex("[example]"); */ - escapeRegex: function(str) { + static escapeRegex(str) { return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1"); - }, + } /** @@ -253,14 +265,14 @@ const Utils = { * // returns ["a", "b", "c", "d", "0", "-", "3"] * Utils.expandAlphRange("a-d0\\-3") */ - expandAlphRange: function(alphStr) { + static expandAlphRange(alphStr) { const alphArr = []; for (let i = 0; i < alphStr.length; i++) { if (i < alphStr.length - 2 && alphStr[i+1] === "-" && alphStr[i] !== "\\") { - let start = Utils.ord(alphStr[i]), + const start = Utils.ord(alphStr[i]), end = Utils.ord(alphStr[i+2]); for (let j = start; j <= end; j++) { @@ -277,7 +289,7 @@ const Utils = { } } return alphArr; - }, + } /** @@ -298,19 +310,19 @@ const Utils = { * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] * Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); */ - convertToByteArray: function(str, type) { + static convertToByteArray(str, type) { switch (type.toLowerCase()) { case "hex": - return Utils.fromHex(str); + return fromHex(str); case "base64": - return Utils.fromBase64(str, null, "byteArray"); + return fromBase64(str, null, "byteArray"); case "utf8": return Utils.strToUtf8ByteArray(str); case "latin1": default: return Utils.strToByteArray(str); } - }, + } /** @@ -322,28 +334,28 @@ const Utils = { * @returns {string} * * @example - * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] - * Utils.convertToByteArray("Привет", "utf8"); + * // returns "Привет" + * Utils.convertToByteString("Привет", "utf8"); * - * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] - * Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex"); + * // returns "Здравствуйте" + * Utils.convertToByteString("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex"); * - * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] - * Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); + * // returns "Здравствуйте" + * Utils.convertToByteString("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); */ - convertToByteString: function(str, type) { + static convertToByteString(str, type) { switch (type.toLowerCase()) { case "hex": - return Utils.byteArrayToChars(Utils.fromHex(str)); + return Utils.byteArrayToChars(fromHex(str)); case "base64": - return Utils.byteArrayToChars(Utils.fromBase64(str, null, "byteArray")); + return Utils.byteArrayToChars(fromBase64(str, null, "byteArray")); case "utf8": return utf8.encode(str); case "latin1": default: return str; } - }, + } /** @@ -360,7 +372,7 @@ const Utils = { * // returns [228,189,160,229,165,189] * Utils.strToByteArray("你好"); */ - strToByteArray: function(str) { + static strToByteArray(str) { const byteArray = new Array(str.length); let i = str.length, b; while (i--) { @@ -370,7 +382,7 @@ const Utils = { if (b > 255) return Utils.strToUtf8ByteArray(str); } return byteArray; - }, + } /** @@ -386,7 +398,7 @@ const Utils = { * // returns [228,189,160,229,165,189] * Utils.strToUtf8ByteArray("你好"); */ - strToUtf8ByteArray: function(str) { + static strToUtf8ByteArray(str) { const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -398,7 +410,7 @@ const Utils = { } return Utils.strToByteArray(utf8Str); - }, + } /** @@ -414,7 +426,7 @@ const Utils = { * // returns [20320,22909] * Utils.strToCharcode("你好"); */ - strToCharcode: function(str) { + static strToCharcode(str) { const charcode = []; for (let i = 0; i < str.length; i++) { @@ -432,7 +444,7 @@ const Utils = { } return charcode; - }, + } /** @@ -448,7 +460,7 @@ const Utils = { * // returns "你好" * Utils.byteArrayToUtf8([228,189,160,229,165,189]); */ - byteArrayToUtf8: function(byteArray) { + static byteArrayToUtf8(byteArray) { const str = Utils.byteArrayToChars(byteArray); try { const utf8Str = utf8.decode(str); @@ -465,7 +477,7 @@ const Utils = { // If it fails, treat it as ANSI return str; } - }, + } /** @@ -481,14 +493,14 @@ const Utils = { * // returns "你好" * Utils.byteArrayToChars([20320,22909]); */ - byteArrayToChars: function(byteArray) { + static byteArrayToChars(byteArray) { if (!byteArray) return ""; let str = ""; for (let i = 0; i < byteArray.length;) { str += String.fromCharCode(byteArray[i++]); } return str; - }, + } /** @@ -502,221 +514,10 @@ const Utils = { * // returns "hello" * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); */ - arrayBufferToStr: function(arrayBuffer, utf8=true) { + static arrayBufferToStr(arrayBuffer, utf8=true) { const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer)); return utf8 ? Utils.byteArrayToUtf8(byteArray) : Utils.byteArrayToChars(byteArray); - }, - - - /** - * Base64's the input byte array using the given alphabet, returning a string. - * - * @param {byteArray|Uint8Array|string} data - * @param {string} [alphabet] - * @returns {string} - * - * @example - * // returns "SGVsbG8=" - * Utils.toBase64([72, 101, 108, 108, 111]); - * - * // returns "SGVsbG8=" - * Utils.toBase64("Hello"); - */ - toBase64: function(data, alphabet) { - if (!data) return ""; - if (typeof data == "string") { - data = Utils.strToByteArray(data); - } - - alphabet = alphabet ? - Utils.expandAlphRange(alphabet).join("") : - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - let output = "", - chr1, chr2, chr3, - enc1, enc2, enc3, enc4, - i = 0; - - while (i < data.length) { - chr1 = data[i++]; - chr2 = data[i++]; - chr3 = data[i++]; - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + - alphabet.charAt(enc3) + alphabet.charAt(enc4); - } - - return output; - }, - - - /** - * UnBase64's the input string using the given alphabet, returning a byte array. - * - * @param {byteArray} data - * @param {string} [alphabet] - * @param {string} [returnType="string"] - Either "string" or "byteArray" - * @param {boolean} [removeNonAlphChars=true] - * @returns {byteArray} - * - * @example - * // returns "Hello" - * Utils.fromBase64("SGVsbG8="); - * - * // returns [72, 101, 108, 108, 111] - * Utils.fromBase64("SGVsbG8=", null, "byteArray"); - */ - fromBase64: function(data, alphabet, returnType, removeNonAlphChars) { - returnType = returnType || "string"; - - if (!data) { - return returnType === "string" ? "" : []; - } - - alphabet = alphabet ? - Utils.expandAlphRange(alphabet).join("") : - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - if (removeNonAlphChars === undefined) - removeNonAlphChars = true; - - let output = [], - chr1, chr2, chr3, - enc1, enc2, enc3, enc4, - i = 0; - - if (removeNonAlphChars) { - const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); - data = data.replace(re, ""); - } - - while (i < data.length) { - enc1 = alphabet.indexOf(data.charAt(i++)); - enc2 = alphabet.indexOf(data.charAt(i++) || "="); - enc3 = alphabet.indexOf(data.charAt(i++) || "="); - enc4 = alphabet.indexOf(data.charAt(i++) || "="); - - enc2 = enc2 === -1 ? 64 : enc2; - enc3 = enc3 === -1 ? 64 : enc3; - enc4 = enc4 === -1 ? 64 : enc4; - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output.push(chr1); - - if (enc3 !== 64) { - output.push(chr2); - } - if (enc4 !== 64) { - output.push(chr3); - } - } - - return returnType === "string" ? Utils.byteArrayToUtf8(output) : output; - }, - - - /** - * Convert a byte array into a hex string. - * - * @param {Uint8Array|byteArray} data - * @param {string} [delim=" "] - * @param {number} [padding=2] - * @returns {string} - * - * @example - * // returns "0a 14 1e" - * Utils.toHex([10,20,30]); - * - * // returns "0a:14:1e" - * Utils.toHex([10,20,30], ":"); - */ - toHex: function(data, delim, padding) { - if (!data) return ""; - - delim = typeof delim == "string" ? delim : " "; - padding = padding || 2; - let output = ""; - - for (let i = 0; i < data.length; i++) { - output += data[i].toString(16).padStart(padding, "0") + delim; - } - - // Add \x or 0x to beginning - if (delim === "0x") output = "0x" + output; - if (delim === "\\x") output = "\\x" + output; - - if (delim.length) - return output.slice(0, -delim.length); - else - return output; - }, - - - /** - * Convert a byte array into a hex string as efficiently as possible with no options. - * - * @param {byteArray} data - * @returns {string} - * - * @example - * // returns "0a141e" - * Utils.toHex([10,20,30]); - */ - toHexFast: function(data) { - if (!data) return ""; - - const output = []; - - for (let i = 0; i < data.length; i++) { - output.push((data[i] >>> 4).toString(16)); - output.push((data[i] & 0x0f).toString(16)); - } - - return output.join(""); - }, - - - /** - * Convert a hex string into a byte array. - * - * @param {string} data - * @param {string} [delim] - * @param {number} [byteLen=2] - * @returns {byteArray} - * - * @example - * // returns [10,20,30] - * Utils.fromHex("0a 14 1e"); - * - * // returns [10,20,30] - * Utils.fromHex("0a:14:1e", "Colon"); - */ - fromHex: function(data, delim, byteLen) { - delim = delim || (data.indexOf(" ") >= 0 ? "Space" : "None"); - byteLen = byteLen || 2; - if (delim !== "None") { - const delimRegex = Utils.regexRep[delim]; - data = data.replace(delimRegex, ""); - } - - const output = []; - for (let i = 0; i < data.length; i += byteLen) { - output.push(parseInt(data.substr(i, byteLen), 16)); - } - return output; - }, + } /** @@ -729,14 +530,13 @@ const Utils = { * // returns [["head1", "head2"], ["data1", "data2"]] * Utils.parseCSV("head1,head2\ndata1,data2"); */ - parseCSV: function(data) { - + static parseCSV(data) { let b, ignoreNext = false, inString = false, cell = "", - line = [], - lines = []; + line = []; + const lines = []; for (let i = 0; i < data.length; i++) { b = data[i]; @@ -769,26 +569,27 @@ const Utils = { } return lines; - }, + } /** * Removes all HTML (or XML) tags from the input string. * * @param {string} htmlStr - * @param {boolean} removeScriptAndStyle - Flag to specify whether to remove entire script or style blocks + * @param {boolean} [removeScriptAndStyle=false] + * - Flag to specify whether to remove entire script or style blocks * @returns {string} * * @example * // returns "Test" * Utils.stripHtmlTags("
Test
"); */ - stripHtmlTags: function(htmlStr, removeScriptAndStyle) { + static stripHtmlTags(htmlStr, removeScriptAndStyle=false) { if (removeScriptAndStyle) { htmlStr = htmlStr.replace(/<(script|style)[^>]*>.*<\/(script|style)>/gmi, ""); } return htmlStr.replace(/<[^>]+>/g, ""); - }, + } /** @@ -802,7 +603,7 @@ const Utils = { * // return "A <script> tag" * Utils.escapeHtml("A ", - staticSection = "", - padding = ""; - - if (input.length < 1) { - return "Please enter a string."; - } - - // Highlight offset 0 - if (len0 % 4 === 2) { - staticSection = offset0.slice(0, -3); - offset0 = "" + - staticSection + "" + - "" + offset0.substr(offset0.length - 3, 1) + "" + - "" + offset0.substr(offset0.length - 2) + ""; - } else if (len0 % 4 === 3) { - staticSection = offset0.slice(0, -2); - offset0 = "" + - staticSection + "" + - "" + offset0.substr(offset0.length - 2, 1) + "" + - "" + offset0.substr(offset0.length - 1) + ""; - } else { - staticSection = offset0; - offset0 = "" + - staticSection + ""; - } - - if (!showVariable) { - offset0 = staticSection; - } - - - // Highlight offset 1 - padding = "" + offset1.substr(0, 1) + "" + - "" + offset1.substr(1, 1) + ""; - offset1 = offset1.substr(2); - if (len1 % 4 === 2) { - staticSection = offset1.slice(0, -3); - offset1 = padding + "" + - staticSection + "" + - "" + offset1.substr(offset1.length - 3, 1) + "" + - "" + offset1.substr(offset1.length - 2) + ""; - } else if (len1 % 4 === 3) { - staticSection = offset1.slice(0, -2); - offset1 = padding + "" + - staticSection + "" + - "" + offset1.substr(offset1.length - 2, 1) + "" + - "" + offset1.substr(offset1.length - 1) + ""; - } else { - staticSection = offset1; - offset1 = padding + "" + - staticSection + ""; - } - - if (!showVariable) { - offset1 = staticSection; - } - - // Highlight offset 2 - padding = "" + offset2.substr(0, 2) + "" + - "" + offset2.substr(2, 1) + ""; - offset2 = offset2.substr(3); - if (len2 % 4 === 2) { - staticSection = offset2.slice(0, -3); - offset2 = padding + "" + - staticSection + "" + - "" + offset2.substr(offset2.length - 3, 1) + "" + - "" + offset2.substr(offset2.length - 2) + ""; - } else if (len2 % 4 === 3) { - staticSection = offset2.slice(0, -2); - offset2 = padding + "" + - staticSection + "" + - "" + offset2.substr(offset2.length - 2, 1) + "" + - "" + offset2.substr(offset2.length - 1) + ""; - } else { - staticSection = offset2; - offset2 = padding + "" + - staticSection + ""; - } - - if (!showVariable) { - offset2 = staticSection; - } - - return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." + - "\nCharacters highlighted in red are for padding purposes only." + - "\nUnhighlighted characters are static." + - "\nHover over the static sections to see what they decode to on their own.\n" + - "\nOffset 0: " + offset0 + - "\nOffset 1: " + offset1 + - "\nOffset 2: " + offset2 + - script : - offset0 + "\n" + offset1 + "\n" + offset2); - }, - - - /** - * Highlight to Base64 - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightTo: function(pos, args) { - pos[0].start = Math.floor(pos[0].start / 3 * 4); - pos[0].end = Math.ceil(pos[0].end / 3 * 4); - return pos; - }, - - /** - * Highlight from Base64 - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightFrom: function(pos, args) { - pos[0].start = Math.ceil(pos[0].start / 4 * 3); - pos[0].end = Math.floor(pos[0].end / 4 * 3); - return pos; - }, - -}; - -export default Base64; diff --git a/src/core/operations/Compress.js b/src/core/operations/Compress.js deleted file mode 100755 index 57b01027..00000000 --- a/src/core/operations/Compress.js +++ /dev/null @@ -1,578 +0,0 @@ -import Utils from "../Utils.js"; -import rawdeflate from "zlibjs/bin/rawdeflate.min"; -import rawinflate from "zlibjs/bin/rawinflate.min"; -import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; -import zip from "zlibjs/bin/zip.min"; -import unzip from "zlibjs/bin/unzip.min"; -import bzip2 from "exports-loader?bzip2!../lib/bzip2.js"; - -const Zlib = { - RawDeflate: rawdeflate.Zlib.RawDeflate, - RawInflate: rawinflate.Zlib.RawInflate, - Deflate: zlibAndGzip.Zlib.Deflate, - Inflate: zlibAndGzip.Zlib.Inflate, - Gzip: zlibAndGzip.Zlib.Gzip, - Gunzip: zlibAndGzip.Zlib.Gunzip, - Zip: zip.Zlib.Zip, - Unzip: unzip.Zlib.Unzip, -}; - - -/** - * Compression operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Compress = { - - /** - * @constant - * @default - */ - COMPRESSION_TYPE: ["Dynamic Huffman Coding", "Fixed Huffman Coding", "None (Store)"], - /** - * @constant - * @default - */ - INFLATE_BUFFER_TYPE: ["Adaptive", "Block"], - /** - * @constant - * @default - */ - COMPRESSION_METHOD: ["Deflate", "None (Store)"], - /** - * @constant - * @default - */ - OS: ["MSDOS", "Unix", "Macintosh"], - /** - * @constant - * @default - */ - RAW_COMPRESSION_TYPE_LOOKUP: { - "Fixed Huffman Coding": Zlib.RawDeflate.CompressionType.FIXED, - "Dynamic Huffman Coding": Zlib.RawDeflate.CompressionType.DYNAMIC, - "None (Store)": Zlib.RawDeflate.CompressionType.NONE, - }, - - /** - * Raw Deflate operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRawDeflate: function(input, args) { - const deflate = new Zlib.RawDeflate(input, { - compressionType: Compress.RAW_COMPRESSION_TYPE_LOOKUP[args[0]] - }); - return Array.prototype.slice.call(deflate.compress()); - }, - - - /** - * @constant - * @default - */ - INFLATE_INDEX: 0, - /** - * @constant - * @default - */ - INFLATE_BUFFER_SIZE: 0, - /** - * @constant - * @default - */ - INFLATE_RESIZE: false, - /** - * @constant - * @default - */ - INFLATE_VERIFY: false, - /** - * @constant - * @default - */ - RAW_BUFFER_TYPE_LOOKUP: { - "Adaptive": Zlib.RawInflate.BufferType.ADAPTIVE, - "Block": Zlib.RawInflate.BufferType.BLOCK, - }, - - /** - * Raw Inflate operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRawInflate: function(input, args) { - // Deal with character encoding issues - input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); - let inflate = new Zlib.RawInflate(input, { - index: args[0], - bufferSize: args[1], - bufferType: Compress.RAW_BUFFER_TYPE_LOOKUP[args[2]], - resize: args[3], - verify: args[4] - }), - result = Array.prototype.slice.call(inflate.decompress()); - - // Raw Inflate somethimes messes up and returns nonsense like this: - // ]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]... - // e.g. Input data of [8b, 1d, dc, 44] - // Look for the first two square brackets: - if (result.length > 158 && result[0] === 93 && result[5] === 93) { - // If the first two square brackets are there, check that the others - // are also there. If they are, throw an error. If not, continue. - let valid = false; - for (let i = 0; i < 155; i += 5) { - if (result[i] !== 93) { - valid = true; - } - } - - if (!valid) { - throw "Error: Unable to inflate data"; - } - } - // Trust me, this is the easiest way... - return result; - }, - - - /** - * @constant - * @default - */ - ZLIB_COMPRESSION_TYPE_LOOKUP: { - "Fixed Huffman Coding": Zlib.Deflate.CompressionType.FIXED, - "Dynamic Huffman Coding": Zlib.Deflate.CompressionType.DYNAMIC, - "None (Store)": Zlib.Deflate.CompressionType.NONE, - }, - - /** - * Zlib Deflate operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runZlibDeflate: function(input, args) { - const deflate = new Zlib.Deflate(input, { - compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] - }); - return Array.prototype.slice.call(deflate.compress()); - }, - - - /** - * @constant - * @default - */ - ZLIB_BUFFER_TYPE_LOOKUP: { - "Adaptive": Zlib.Inflate.BufferType.ADAPTIVE, - "Block": Zlib.Inflate.BufferType.BLOCK, - }, - - /** - * Zlib Inflate operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runZlibInflate: function(input, args) { - // Deal with character encoding issues - input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); - const inflate = new Zlib.Inflate(input, { - index: args[0], - bufferSize: args[1], - bufferType: Compress.ZLIB_BUFFER_TYPE_LOOKUP[args[2]], - resize: args[3], - verify: args[4] - }); - return Array.prototype.slice.call(inflate.decompress()); - }, - - - /** - * @constant - * @default - */ - GZIP_CHECKSUM: false, - - /** - * Gzip operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runGzip: function(input, args) { - let filename = args[1], - comment = args[2], - options = { - deflateOptions: { - compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] - }, - flags: { - fhcrc: args[3] - } - }; - - if (filename.length) { - options.flags.fname = true; - options.filename = filename; - } - if (comment.length) { - options.flags.fcommenct = true; - options.comment = comment; - } - - const gzip = new Zlib.Gzip(input, options); - return Array.prototype.slice.call(gzip.compress()); - }, - - - /** - * Gunzip operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runGunzip: function(input, args) { - // Deal with character encoding issues - input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); - const gunzip = new Zlib.Gunzip(input); - return Array.prototype.slice.call(gunzip.decompress()); - }, - - - /** - * @constant - * @default - */ - PKZIP_FILENAME: "file.txt", - /** - * @constant - * @default - */ - ZIP_COMPRESSION_METHOD_LOOKUP: { - "Deflate": Zlib.Zip.CompressionMethod.DEFLATE, - "None (Store)": Zlib.Zip.CompressionMethod.STORE - }, - /** - * @constant - * @default - */ - ZIP_OS_LOOKUP: { - "MSDOS": Zlib.Zip.OperatingSystem.MSDOS, - "Unix": Zlib.Zip.OperatingSystem.UNIX, - "Macintosh": Zlib.Zip.OperatingSystem.MACINTOSH - }, - - /** - * Zip operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runPkzip: function(input, args) { - let password = Utils.strToByteArray(args[2]), - options = { - filename: Utils.strToByteArray(args[0]), - comment: Utils.strToByteArray(args[1]), - compressionMethod: Compress.ZIP_COMPRESSION_METHOD_LOOKUP[args[3]], - os: Compress.ZIP_OS_LOOKUP[args[4]], - deflateOption: { - compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]] - }, - }, - zip = new Zlib.Zip(); - - if (password.length) - zip.setPassword(password); - zip.addFile(input, options); - return Array.prototype.slice.call(zip.compress()); - }, - - - /** - * @constant - * @default - */ - PKUNZIP_VERIFY: false, - - /** - * Unzip operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runPkunzip: function(input, args) { - let options = { - password: Utils.strToByteArray(args[0]), - verify: args[1] - }, - unzip = new Zlib.Unzip(input, options), - filenames = unzip.getFilenames(), - files = []; - - filenames.forEach(function(fileName) { - const bytes = unzip.decompress(fileName); - const contents = Utils.byteArrayToUtf8(bytes); - - const file = { - fileName: fileName, - size: contents.length, - }; - - const isDir = contents.length === 0 && fileName.endsWith("/"); - if (!isDir) { - file.bytes = bytes; - file.contents = contents; - } - - files.push(file); - }); - - return Utils.displayFilesAsHTML(files); - }, - - - /** - * Bzip2 Decompress operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runBzip2Decompress: function(input, args) { - let compressed = new Uint8Array(input), - bzip2Reader, - plain = ""; - - bzip2Reader = bzip2.array(compressed); - plain = bzip2.simple(bzip2Reader); - return plain; - }, - - - /** - * @constant - * @default - */ - TAR_FILENAME: "file.txt", - - - /** - * Tar pack operation. - * - * @author tlwr [toby@toby.codes] - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runTar: function(input, args) { - const Tarball = function() { - this.bytes = new Array(512); - this.position = 0; - }; - - Tarball.prototype.addEmptyBlock = function() { - const filler = new Array(512); - filler.fill(0); - this.bytes = this.bytes.concat(filler); - }; - - Tarball.prototype.writeBytes = function(bytes) { - const self = this; - - if (this.position + bytes.length > this.bytes.length) { - this.addEmptyBlock(); - } - - Array.prototype.forEach.call(bytes, function(b, i) { - if (typeof b.charCodeAt !== "undefined") { - b = b.charCodeAt(); - } - - self.bytes[self.position] = b; - self.position += 1; - }); - }; - - Tarball.prototype.writeEndBlocks = function() { - const numEmptyBlocks = 2; - for (let i = 0; i < numEmptyBlocks; i++) { - this.addEmptyBlock(); - } - }; - - const fileSize = input.length.toString(8).padStart(11, "0"); - const currentUnixTimestamp = Math.floor(Date.now() / 1000); - const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0"); - - const file = { - fileName: Utils.padBytesRight(args[0], 100), - fileMode: Utils.padBytesRight("0000664", 8), - ownerUID: Utils.padBytesRight("0", 8), - ownerGID: Utils.padBytesRight("0", 8), - size: Utils.padBytesRight(fileSize, 12), - lastModTime: Utils.padBytesRight(lastModTime, 12), - checksum: " ", - type: "0", - linkedFileName: Utils.padBytesRight("", 100), - USTARFormat: Utils.padBytesRight("ustar", 6), - version: "00", - ownerUserName: Utils.padBytesRight("", 32), - ownerGroupName: Utils.padBytesRight("", 32), - deviceMajor: Utils.padBytesRight("", 8), - deviceMinor: Utils.padBytesRight("", 8), - fileNamePrefix: Utils.padBytesRight("", 155), - }; - - let checksum = 0; - for (const key in file) { - const bytes = file[key]; - Array.prototype.forEach.call(bytes, function(b) { - if (typeof b.charCodeAt !== "undefined") { - checksum += b.charCodeAt(); - } else { - checksum += b; - } - }); - } - checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8); - file.checksum = checksum; - - const tarball = new Tarball(); - tarball.writeBytes(file.fileName); - tarball.writeBytes(file.fileMode); - tarball.writeBytes(file.ownerUID); - tarball.writeBytes(file.ownerGID); - tarball.writeBytes(file.size); - tarball.writeBytes(file.lastModTime); - tarball.writeBytes(file.checksum); - tarball.writeBytes(file.type); - tarball.writeBytes(file.linkedFileName); - tarball.writeBytes(file.USTARFormat); - tarball.writeBytes(file.version); - tarball.writeBytes(file.ownerUserName); - tarball.writeBytes(file.ownerGroupName); - tarball.writeBytes(file.deviceMajor); - tarball.writeBytes(file.deviceMinor); - tarball.writeBytes(file.fileNamePrefix); - tarball.writeBytes(Utils.padBytesRight("", 12)); - tarball.writeBytes(input); - tarball.writeEndBlocks(); - - return tarball.bytes; - }, - - - /** - * Untar unpack operation. - * - * @author tlwr [toby@toby.codes] - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {html} - */ - runUntar: function(input, args) { - const Stream = function(input) { - this.bytes = input; - this.position = 0; - }; - - Stream.prototype.getBytes = function(bytesToGet) { - const newPosition = this.position + bytesToGet; - const bytes = this.bytes.slice(this.position, newPosition); - this.position = newPosition; - return bytes; - }; - - Stream.prototype.readString = function(numBytes) { - let result = ""; - for (let i = this.position; i < this.position + numBytes; i++) { - const currentByte = this.bytes[i]; - if (currentByte === 0) break; - result += String.fromCharCode(currentByte); - } - this.position += numBytes; - return result; - }; - - Stream.prototype.readInt = function(numBytes, base) { - const string = this.readString(numBytes); - return parseInt(string, base); - }; - - Stream.prototype.hasMore = function() { - return this.position < this.bytes.length; - }; - - let stream = new Stream(input), - files = []; - - while (stream.hasMore()) { - const dataPosition = stream.position + 512; - - const file = { - fileName: stream.readString(100), - fileMode: stream.readString(8), - ownerUID: stream.readString(8), - ownerGID: stream.readString(8), - size: parseInt(stream.readString(12), 8), // Octal - lastModTime: new Date(1000 * stream.readInt(12, 8)), // Octal - checksum: stream.readString(8), - type: stream.readString(1), - linkedFileName: stream.readString(100), - USTARFormat: stream.readString(6).indexOf("ustar") >= 0, - }; - - if (file.USTARFormat) { - file.version = stream.readString(2); - file.ownerUserName = stream.readString(32); - file.ownerGroupName = stream.readString(32); - file.deviceMajor = stream.readString(8); - file.deviceMinor = stream.readString(8); - file.filenamePrefix = stream.readString(155); - } - - stream.position = dataPosition; - - if (file.type === "0") { - // File - files.push(file); - let endPosition = stream.position + file.size; - if (file.size % 512 !== 0) { - endPosition += 512 - (file.size % 512); - } - - file.bytes = stream.getBytes(file.size); - file.contents = Utils.byteArrayToUtf8(file.bytes); - stream.position = endPosition; - } else if (file.type === "5") { - // Directory - files.push(file); - } else { - // Symlink or empty bytes - } - } - - return Utils.displayFilesAsHTML(files); - }, -}; - -export default Compress; diff --git a/src/core/operations/FromBase32.mjs b/src/core/operations/FromBase32.mjs new file mode 100644 index 00000000..0334ef51 --- /dev/null +++ b/src/core/operations/FromBase32.mjs @@ -0,0 +1,90 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * From Base32 operation + */ +class FromBase32 extends Operation { + + /** + * FromBase32 constructor + */ + constructor() { + super(); + + this.name = "From Base32"; + this.module = "Default"; + this.description = "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7."; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Alphabet", + type: "binaryString", + value: "A-Z2-7=" + }, + { + name: "Remove non-alphabet chars", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (!input) return []; + + const alphabet = args[0] ? + Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=", + removeNonAlphChars = args[1], + output = []; + + let chr1, chr2, chr3, chr4, chr5, + enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, + i = 0; + + if (removeNonAlphChars) { + const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g"); + input = input.replace(re, ""); + } + + while (i < input.length) { + enc1 = alphabet.indexOf(input.charAt(i++)); + enc2 = alphabet.indexOf(input.charAt(i++) || "="); + enc3 = alphabet.indexOf(input.charAt(i++) || "="); + enc4 = alphabet.indexOf(input.charAt(i++) || "="); + enc5 = alphabet.indexOf(input.charAt(i++) || "="); + enc6 = alphabet.indexOf(input.charAt(i++) || "="); + enc7 = alphabet.indexOf(input.charAt(i++) || "="); + enc8 = alphabet.indexOf(input.charAt(i++) || "="); + + chr1 = (enc1 << 3) | (enc2 >> 2); + chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4); + chr3 = ((enc4 & 15) << 4) | (enc5 >> 1); + chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3); + chr5 = ((enc7 & 7) << 5) | enc8; + + output.push(chr1); + if (enc2 & 3 !== 0 || enc3 !== 32) output.push(chr2); + if (enc4 & 15 !== 0 || enc5 !== 32) output.push(chr3); + if (enc5 & 1 !== 0 || enc6 !== 32) output.push(chr4); + if (enc7 & 7 !== 0 || enc8 !== 32) output.push(chr5); + } + + return output; + } + +} + +export default FromBase32; diff --git a/src/core/operations/FromBase64.mjs b/src/core/operations/FromBase64.mjs new file mode 100644 index 00000000..cab86835 --- /dev/null +++ b/src/core/operations/FromBase64.mjs @@ -0,0 +1,82 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {fromBase64, ALPHABET_OPTIONS} from "../lib/Base64"; + +/** + * From Base64 operation + */ +class FromBase64 extends Operation { + + /** + * FromBase64 constructor + */ + constructor() { + super(); + + this.name = "From Base64"; + this.module = "Default"; + this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.

This operation decodes data from an ASCII Base64 string back into its raw format.

e.g. aGVsbG8= becomes hello"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Alphabet", + type: "editableOption", + value: ALPHABET_OPTIONS + }, + { + name: "Remove non-alphabet chars", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [alphabet, removeNonAlphChars] = args; + + return fromBase64(input, alphabet, "byteArray", removeNonAlphChars); + } + + /** + * Highlight to Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + pos[0].start = Math.ceil(pos[0].start / 4 * 3); + pos[0].end = Math.floor(pos[0].end / 4 * 3); + return pos; + } + + /** + * Highlight from Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + pos[0].start = Math.floor(pos[0].start / 3 * 4); + pos[0].end = Math.ceil(pos[0].end / 3 * 4); + return pos; + } +} + +export default FromBase64; diff --git a/src/core/operations/FromHex.mjs b/src/core/operations/FromHex.mjs new file mode 100644 index 00000000..17557196 --- /dev/null +++ b/src/core/operations/FromHex.mjs @@ -0,0 +1,98 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {fromHex, HEX_DELIM_OPTIONS} from "../lib/Hex"; +import Utils from "../Utils"; + +/** + * From Hex operation + */ +class FromHex extends Operation { + + /** + * FromHex constructor + */ + constructor() { + super(); + + this.name = "From Hex"; + this.module = "Default"; + this.description = "Converts a hexadecimal byte string back into its raw value.

e.g. ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a becomes the UTF-8 encoded string Γειά σου"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: HEX_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = args[0] || "Space"; + return fromHex(input, delim, 2); + } + + /** + * Highlight to Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const delim = Utils.charRep(args[0] || "Space"), + len = delim === "\r\n" ? 1 : delim.length, + width = len + 2; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + if (pos[0].start > 1) pos[0].start -= 2; + else pos[0].start = 0; + if (pos[0].end > 1) pos[0].end -= 2; + else pos[0].end = 0; + } + + pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width); + return pos; + } + + /** + * Highlight from Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "Space"), + len = delim === "\r\n" ? 1 : delim.length; + + pos[0].start = pos[0].start * (2 + len); + pos[0].end = pos[0].end * (2 + len) - len; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + pos[0].start += 2; + pos[0].end += 2; + } + return pos; + } +} + +export default FromHex; diff --git a/src/core/operations/Gunzip.mjs b/src/core/operations/Gunzip.mjs new file mode 100644 index 00000000..60e29d6f --- /dev/null +++ b/src/core/operations/Gunzip.mjs @@ -0,0 +1,46 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; + +const Zlib = zlibAndGzip.Zlib; + +/** + * Gunzip operation + */ +class Gunzip extends Operation { + + /** + * Gunzip constructor + */ + constructor() { + super(); + + this.name = "Gunzip"; + this.module = "Compression"; + this.description = "Decompresses data which has been compressed using the deflate algorithm with gzip headers."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + // Deal with character encoding issues + input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); + const gunzip = new Zlib.Gunzip(input); + return Array.prototype.slice.call(gunzip.decompress()); + } + +} + +export default Gunzip; diff --git a/src/core/operations/Gzip.mjs b/src/core/operations/Gzip.mjs new file mode 100644 index 00000000..86a96763 --- /dev/null +++ b/src/core/operations/Gzip.mjs @@ -0,0 +1,85 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; + +const Zlib = zlibAndGzip.Zlib; + +/** + * Gzip operation + */ +class Gzip extends Operation { + + /** + * Gzip constructor + */ + constructor() { + super(); + + this.name = "Gzip"; + this.module = "Compression"; + this.description = "Compresses data using the deflate algorithm with gzip headers."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + }, + { + name: "Filename (optional)", + type: "string", + value: "" + }, + { + name: "Comment (optional)", + type: "string", + value: "" + }, + { + name: "Include file checksum", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const filename = args[1], + comment = args[2], + options = { + deflateOptions: { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] + }, + flags: { + fhcrc: args[3] + } + }; + + if (filename.length) { + options.flags.fname = true; + options.filename = filename; + } + if (comment.length) { + options.flags.fcommenct = true; + options.comment = comment; + } + + const gzip = new Zlib.Gzip(input, options); + return Array.prototype.slice.call(gzip.compress()); + } + +} + +export default Gzip; diff --git a/src/core/operations/RawDeflate.mjs b/src/core/operations/RawDeflate.mjs new file mode 100644 index 00000000..a1a30981 --- /dev/null +++ b/src/core/operations/RawDeflate.mjs @@ -0,0 +1,58 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {COMPRESSION_TYPE} from "../lib/Zlib"; +import rawdeflate from "zlibjs/bin/rawdeflate.min"; + +const Zlib = rawdeflate.Zlib; + +const RAW_COMPRESSION_TYPE_LOOKUP = { + "Fixed Huffman Coding": Zlib.RawDeflate.CompressionType.FIXED, + "Dynamic Huffman Coding": Zlib.RawDeflate.CompressionType.DYNAMIC, + "None (Store)": Zlib.RawDeflate.CompressionType.NONE, +}; + +/** + * Raw Deflate operation + */ +class RawDeflate extends Operation { + + /** + * RawDeflate constructor + */ + constructor() { + super(); + + this.name = "Raw Deflate"; + this.module = "Compression"; + this.description = "Compresses data using the deflate algorithm with no headers."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const deflate = new Zlib.RawDeflate(input, { + compressionType: RAW_COMPRESSION_TYPE_LOOKUP[args[0]] + }); + return Array.prototype.slice.call(deflate.compress()); + } + +} + +export default RawDeflate; diff --git a/src/core/operations/RawInflate.mjs b/src/core/operations/RawInflate.mjs new file mode 100644 index 00000000..5ec74649 --- /dev/null +++ b/src/core/operations/RawInflate.mjs @@ -0,0 +1,105 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib"; +import rawinflate from "zlibjs/bin/rawinflate.min"; + +const Zlib = rawinflate.Zlib; + +const RAW_BUFFER_TYPE_LOOKUP = { + "Adaptive": Zlib.RawInflate.BufferType.ADAPTIVE, + "Block": Zlib.RawInflate.BufferType.BLOCK, +}; + +/** + * Raw Inflate operation + */ +class RawInflate extends Operation { + + /** + * RawInflate constructor + */ + constructor() { + super(); + + this.name = "Raw Inflate"; + this.module = "Compression"; + this.description = "Decompresses data which has been compressed using the deflate algorithm with no headers."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Start index", + type: "number", + value: 0 + }, + { + name: "Initial output buffer size", + type: "number", + value: 0 + }, + { + name: "Buffer expansion type", + type: "option", + value: INFLATE_BUFFER_TYPE + }, + { + name: "Resize buffer after decompression", + type: "boolean", + value: false + }, + { + name: "Verify result", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + // Deal with character encoding issues + input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); + const inflate = new Zlib.RawInflate(input, { + index: args[0], + bufferSize: args[1], + bufferType: RAW_BUFFER_TYPE_LOOKUP[args[2]], + resize: args[3], + verify: args[4] + }), + result = Array.prototype.slice.call(inflate.decompress()); + + // Raw Inflate somethimes messes up and returns nonsense like this: + // ]....]....]....]....]....]....]....]....]....]....]....]....]....]... + // e.g. Input data of [8b, 1d, dc, 44] + // Look for the first two square brackets: + if (result.length > 158 && result[0] === 93 && result[5] === 93) { + // If the first two square brackets are there, check that the others + // are also there. If they are, throw an error. If not, continue. + let valid = false; + for (let i = 0; i < 155; i += 5) { + if (result[i] !== 93) { + valid = true; + } + } + + if (!valid) { + throw "Error: Unable to inflate data"; + } + } + // This seems to be the easiest way... + return result; + } + +} + +export default RawInflate; diff --git a/src/core/operations/ShowBase64Offsets.mjs b/src/core/operations/ShowBase64Offsets.mjs new file mode 100644 index 00000000..2f44296f --- /dev/null +++ b/src/core/operations/ShowBase64Offsets.mjs @@ -0,0 +1,162 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {fromBase64, toBase64} from "../lib/Base64"; + +/** + * Show Base64 offsets operation + */ +class ShowBase64Offsets extends Operation { + + /** + * ShowBase64Offsets constructor + */ + constructor() { + super(); + + this.name = "Show Base64 offsets"; + this.module = "Default"; + this.description = "When a string is within a block of data and the whole block is Base64'd, the string itself could be represented in Base64 in three distinct ways depending on its offset within the block.

This operation shows all possible offsets for a given string so that each possible encoding can be considered."; + this.inputType = "byteArray"; + this.outputType = "html"; + this.args = [ + { + name: "Alphabet", + type: "binaryString", + value: "A-Za-z0-9+/=" + }, + { + name: "Show variable chars and padding", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [alphabet, showVariable] = args; + + let offset0 = toBase64(input, alphabet), + offset1 = toBase64([0].concat(input), alphabet), + offset2 = toBase64([0, 0].concat(input), alphabet), + staticSection = "", + padding = ""; + + const len0 = offset0.indexOf("="), + len1 = offset1.indexOf("="), + len2 = offset2.indexOf("="), + script = ""; + + if (input.length < 1) { + return "Please enter a string."; + } + + // Highlight offset 0 + if (len0 % 4 === 2) { + staticSection = offset0.slice(0, -3); + offset0 = "" + + staticSection + "" + + "" + offset0.substr(offset0.length - 3, 1) + "" + + "" + offset0.substr(offset0.length - 2) + ""; + } else if (len0 % 4 === 3) { + staticSection = offset0.slice(0, -2); + offset0 = "" + + staticSection + "" + + "" + offset0.substr(offset0.length - 2, 1) + "" + + "" + offset0.substr(offset0.length - 1) + ""; + } else { + staticSection = offset0; + offset0 = "" + + staticSection + ""; + } + + if (!showVariable) { + offset0 = staticSection; + } + + + // Highlight offset 1 + padding = "" + offset1.substr(0, 1) + "" + + "" + offset1.substr(1, 1) + ""; + offset1 = offset1.substr(2); + if (len1 % 4 === 2) { + staticSection = offset1.slice(0, -3); + offset1 = padding + "" + + staticSection + "" + + "" + offset1.substr(offset1.length - 3, 1) + "" + + "" + offset1.substr(offset1.length - 2) + ""; + } else if (len1 % 4 === 3) { + staticSection = offset1.slice(0, -2); + offset1 = padding + "" + + staticSection + "" + + "" + offset1.substr(offset1.length - 2, 1) + "" + + "" + offset1.substr(offset1.length - 1) + ""; + } else { + staticSection = offset1; + offset1 = padding + "" + + staticSection + ""; + } + + if (!showVariable) { + offset1 = staticSection; + } + + // Highlight offset 2 + padding = "" + offset2.substr(0, 2) + "" + + "" + offset2.substr(2, 1) + ""; + offset2 = offset2.substr(3); + if (len2 % 4 === 2) { + staticSection = offset2.slice(0, -3); + offset2 = padding + "" + + staticSection + "" + + "" + offset2.substr(offset2.length - 3, 1) + "" + + "" + offset2.substr(offset2.length - 2) + ""; + } else if (len2 % 4 === 3) { + staticSection = offset2.slice(0, -2); + offset2 = padding + "" + + staticSection + "" + + "" + offset2.substr(offset2.length - 2, 1) + "" + + "" + offset2.substr(offset2.length - 1) + ""; + } else { + staticSection = offset2; + offset2 = padding + "" + + staticSection + ""; + } + + if (!showVariable) { + offset2 = staticSection; + } + + return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." + + "\nCharacters highlighted in red are for padding purposes only." + + "\nUnhighlighted characters are static." + + "\nHover over the static sections to see what they decode to on their own.\n" + + "\nOffset 0: " + offset0 + + "\nOffset 1: " + offset1 + + "\nOffset 2: " + offset2 + + script : + offset0 + "\n" + offset1 + "\n" + offset2); + } + +} + +export default ShowBase64Offsets; diff --git a/src/core/operations/ToBase32.mjs b/src/core/operations/ToBase32.mjs new file mode 100644 index 00000000..1b217a34 --- /dev/null +++ b/src/core/operations/ToBase32.mjs @@ -0,0 +1,85 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * To Base32 operation + */ +class ToBase32 extends Operation { + + /** + * ToBase32 constructor + */ + constructor() { + super(); + + this.name = "To Base32"; + this.module = "Default"; + this.description = "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "binaryString", + value: "A-Z2-7=" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + + const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + let output = "", + chr1, chr2, chr3, chr4, chr5, + enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8, + i = 0; + + while (i < input.length) { + chr1 = input[i++]; + chr2 = input[i++]; + chr3 = input[i++]; + chr4 = input[i++]; + chr5 = input[i++]; + + enc1 = chr1 >> 3; + enc2 = ((chr1 & 7) << 2) | (chr2 >> 6); + enc3 = (chr2 >> 1) & 31; + enc4 = ((chr2 & 1) << 4) | (chr3 >> 4); + enc5 = ((chr3 & 15) << 1) | (chr4 >> 7); + enc6 = (chr4 >> 2) & 31; + enc7 = ((chr4 & 3) << 3) | (chr5 >> 5); + enc8 = chr5 & 31; + + if (isNaN(chr2)) { + enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr3)) { + enc5 = enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr4)) { + enc6 = enc7 = enc8 = 32; + } else if (isNaN(chr5)) { + enc8 = 32; + } + + output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) + + alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) + + alphabet.charAt(enc7) + alphabet.charAt(enc8); + } + + return output; + } + +} + +export default ToBase32; diff --git a/src/core/operations/ToBase64.mjs b/src/core/operations/ToBase64.mjs new file mode 100644 index 00000000..967ce8ed --- /dev/null +++ b/src/core/operations/ToBase64.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {toBase64, ALPHABET_OPTIONS} from "../lib/Base64"; + +/** + * To Base64 operation + */ +class ToBase64 extends Operation { + + /** + * ToBase64 constructor + */ + constructor() { + super(); + + this.name = "To Base64"; + this.module = "Default"; + this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.

This operation decodes data from an ASCII Base64 string back into its raw format.

e.g. aGVsbG8= becomes hello"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet", + type: "editableOption", + value: ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = args[0]; + return toBase64(new Uint8Array(input), alphabet); + } + + /** + * Highlight to Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + pos[0].start = Math.floor(pos[0].start / 3 * 4); + pos[0].end = Math.ceil(pos[0].end / 3 * 4); + return pos; + } + + /** + * Highlight from Base64 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + pos[0].start = Math.ceil(pos[0].start / 4 * 3); + pos[0].end = Math.floor(pos[0].end / 4 * 3); + return pos; + } +} + +export default ToBase64; diff --git a/src/core/operations/ToHex.mjs b/src/core/operations/ToHex.mjs new file mode 100644 index 00000000..d9b89b11 --- /dev/null +++ b/src/core/operations/ToHex.mjs @@ -0,0 +1,98 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {toHex, HEX_DELIM_OPTIONS} from "../lib/Hex"; +import Utils from "../Utils"; + +/** + * To Hex operation + */ +class ToHex extends Operation { + + /** + * ToHex constructor + */ + constructor() { + super(); + + this.name = "To Hex"; + this.module = "Default"; + this.description = "Converts the input string to hexadecimal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: HEX_DELIM_OPTIONS + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"); + return toHex(new Uint8Array(input), delim, 2); + } + + /** + * Highlight to Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const delim = Utils.charRep(args[0] || "Space"), + len = delim === "\r\n" ? 1 : delim.length; + + pos[0].start = pos[0].start * (2 + len); + pos[0].end = pos[0].end * (2 + len) - len; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + pos[0].start += 2; + pos[0].end += 2; + } + return pos; + } + + /** + * Highlight from Hex + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const delim = Utils.charRep(args[0] || "Space"), + len = delim === "\r\n" ? 1 : delim.length, + width = len + 2; + + // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly + if (delim === "0x" || delim === "\\x") { + if (pos[0].start > 1) pos[0].start -= 2; + else pos[0].start = 0; + if (pos[0].end > 1) pos[0].end -= 2; + else pos[0].end = 0; + } + + pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width); + pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width); + return pos; + } +} + +export default ToHex; diff --git a/src/core/operations/Unzip.mjs b/src/core/operations/Unzip.mjs new file mode 100644 index 00000000..1444551a --- /dev/null +++ b/src/core/operations/Unzip.mjs @@ -0,0 +1,80 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import unzip from "zlibjs/bin/unzip.min"; + +const Zlib = unzip.Zlib; + +/** + * Unzip operation + */ +class Unzip extends Operation { + + /** + * Unzip constructor + */ + constructor() { + super(); + + this.name = "Unzip"; + this.module = "Compression"; + this.description = "Decompresses data using the PKZIP algorithm and displays it per file, with support for passwords."; + this.inputType = "byteArray"; + this.outputType = "html"; + this.args = [ + { + name: "Password", + type: "binaryString", + value: "" + }, + { + name: "Verify result", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const options = { + password: Utils.strToByteArray(args[0]), + verify: args[1] + }, + unzip = new Zlib.Unzip(input, options), + filenames = unzip.getFilenames(), + files = []; + + filenames.forEach(function(fileName) { + const bytes = unzip.decompress(fileName); + const contents = Utils.byteArrayToUtf8(bytes); + + const file = { + fileName: fileName, + size: contents.length, + }; + + const isDir = contents.length === 0 && fileName.endsWith("/"); + if (!isDir) { + file.bytes = bytes; + file.contents = contents; + } + + files.push(file); + }); + + return Utils.displayFilesAsHTML(files); + } + +} + +export default Unzip; diff --git a/src/core/operations/Zip.mjs b/src/core/operations/Zip.mjs new file mode 100644 index 00000000..c487ccbb --- /dev/null +++ b/src/core/operations/Zip.mjs @@ -0,0 +1,101 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib"; +import zip from "zlibjs/bin/zip.min"; + +const Zlib = zip.Zlib; + +const ZIP_COMPRESSION_METHOD_LOOKUP = { + "Deflate": Zlib.Zip.CompressionMethod.DEFLATE, + "None (Store)": Zlib.Zip.CompressionMethod.STORE +}; + +const ZIP_OS_LOOKUP = { + "MSDOS": Zlib.Zip.OperatingSystem.MSDOS, + "Unix": Zlib.Zip.OperatingSystem.UNIX, + "Macintosh": Zlib.Zip.OperatingSystem.MACINTOSH +}; + +/** + * Zip operation + */ +class Zip extends Operation { + + /** + * Zip constructor + */ + constructor() { + super(); + + this.name = "Zip"; + this.module = "Compression"; + this.description = "Compresses data using the PKZIP algorithm with the given filename.

No support for multiple files at this time."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Filename", + type: "string", + value: "file.txt" + }, + { + name: "Comment", + type: "string", + value: "" + }, + { + name: "Password", + type: "binaryString", + value: "" + }, + { + name: "Compression method", + type: "option", + value: ["Deflate", "None (Store)"] + }, + { + name: "Operating system", + type: "option", + value: ["MSDOS", "Unix", "Macintosh"] + }, + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const password = Utils.strToByteArray(args[2]), + options = { + filename: Utils.strToByteArray(args[0]), + comment: Utils.strToByteArray(args[1]), + compressionMethod: ZIP_COMPRESSION_METHOD_LOOKUP[args[3]], + os: ZIP_OS_LOOKUP[args[4]], + deflateOption: { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]] + }, + }, + zip = new Zlib.Zip(); + + if (password.length) + zip.setPassword(password); + zip.addFile(input, options); + return Array.prototype.slice.call(zip.compress()); + } + +} + +export default Zip; diff --git a/src/core/operations/ZlibDeflate.mjs b/src/core/operations/ZlibDeflate.mjs new file mode 100644 index 00000000..99948f93 --- /dev/null +++ b/src/core/operations/ZlibDeflate.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {COMPRESSION_TYPE, ZLIB_COMPRESSION_TYPE_LOOKUP} from "../lib/Zlib"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; + +const Zlib = zlibAndGzip.Zlib; + +/** + * Zlib Deflate operation + */ +class ZlibDeflate extends Operation { + + /** + * ZlibDeflate constructor + */ + constructor() { + super(); + + this.name = "Zlib Deflate"; + this.module = "Compression"; + this.description = "Compresses data using the deflate algorithm adding zlib headers."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const deflate = new Zlib.Deflate(input, { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] + }); + return Array.prototype.slice.call(deflate.compress()); + } + +} + +export default ZlibDeflate; diff --git a/src/core/operations/ZlibInflate.mjs b/src/core/operations/ZlibInflate.mjs new file mode 100644 index 00000000..bb7d95ba --- /dev/null +++ b/src/core/operations/ZlibInflate.mjs @@ -0,0 +1,84 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib"; +import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; + +const Zlib = zlibAndGzip.Zlib; + +const ZLIB_BUFFER_TYPE_LOOKUP = { + "Adaptive": Zlib.Inflate.BufferType.ADAPTIVE, + "Block": Zlib.Inflate.BufferType.BLOCK, +}; + +/** + * Zlib Inflate operation + */ +class ZlibInflate extends Operation { + + /** + * ZlibInflate constructor + */ + constructor() { + super(); + + this.name = "Zlib Inflate"; + this.module = "Compression"; + this.description = "Decompresses data which has been compressed using the deflate algorithm with zlib headers."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Start index", + type: "number", + value: 0 + }, + { + name: "Initial output buffer size", + type: "number", + value: 0 + }, + { + name: "Buffer expansion type", + type: "option", + value: INFLATE_BUFFER_TYPE + }, + { + name: "Resize buffer after decompression", + type: "boolean", + value: false + }, + { + name: "Verify result", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + // Deal with character encoding issues + input = Utils.strToByteArray(Utils.byteArrayToUtf8(input)); + const inflate = new Zlib.Inflate(input, { + index: args[0], + bufferSize: args[1], + bufferType: ZLIB_BUFFER_TYPE_LOOKUP[args[2]], + resize: args[3], + verify: args[4] + }); + return Array.prototype.slice.call(inflate.decompress()); + } + +} + +export default ZlibInflate; diff --git a/src/core/operations/index.mjs b/src/core/operations/index.mjs new file mode 100644 index 00000000..55a8e79a --- /dev/null +++ b/src/core/operations/index.mjs @@ -0,0 +1,40 @@ +/** +* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs +* +* @author n1474335 [n1474335@gmail.com] +* @copyright Crown Copyright 2018 +* @license Apache-2.0 +*/ +import FromBase32 from "./FromBase32"; +import FromBase64 from "./FromBase64"; +import FromHex from "./FromHex"; +import Gunzip from "./Gunzip"; +import Gzip from "./Gzip"; +import RawDeflate from "./RawDeflate"; +import RawInflate from "./RawInflate"; +import ShowBase64Offsets from "./ShowBase64Offsets"; +import ToBase32 from "./ToBase32"; +import ToBase64 from "./ToBase64"; +import ToHex from "./ToHex"; +import Unzip from "./Unzip"; +import Zip from "./Zip"; +import ZlibDeflate from "./ZlibDeflate"; +import ZlibInflate from "./ZlibInflate"; + +export { + FromBase32, + FromBase64, + FromHex, + Gunzip, + Gzip, + RawDeflate, + RawInflate, + ShowBase64Offsets, + ToBase32, + ToBase64, + ToHex, + Unzip, + Zip, + ZlibDeflate, + ZlibInflate, +}; diff --git a/src/core/operations/Arithmetic.js b/src/core/operations/legacy/Arithmetic.js similarity index 99% rename from src/core/operations/Arithmetic.js rename to src/core/operations/legacy/Arithmetic.js index 070cea80..9be35fd7 100644 --- a/src/core/operations/Arithmetic.js +++ b/src/core/operations/legacy/Arithmetic.js @@ -120,7 +120,7 @@ const Arithmetic = { * @returns {BigNumber[]} */ _createNumArray: function(input, delim) { - delim = Utils.charRep[delim || "Space"]; + delim = Utils.charRep(delim || "Space"); let splitNumbers = input.split(delim), numbers = [], num; diff --git a/src/core/operations/BCD.js b/src/core/operations/legacy/BCD.js similarity index 100% rename from src/core/operations/BCD.js rename to src/core/operations/legacy/BCD.js diff --git a/src/core/operations/legacy/BSON.js b/src/core/operations/legacy/BSON.js new file mode 100644 index 00000000..52ee98bc --- /dev/null +++ b/src/core/operations/legacy/BSON.js @@ -0,0 +1,59 @@ +import bsonjs from "bson"; +import {Buffer} from "buffer"; + + +/** + * BSON operations. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * @namespace + */ +const BSON = { + + /** + * BSON serialise operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + runBSONSerialise(input, args) { + if (!input) return new ArrayBuffer(); + + const bson = new bsonjs(); + + try { + const data = JSON.parse(input); + return bson.serialize(data).buffer; + } catch (err) { + return err.toString(); + } + }, + + + /** + * BSON deserialise operation. + * + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + * + */ + runBSONDeserialise(input, args) { + if (!input.byteLength) return ""; + + const bson = new bsonjs(); + + try { + const data = bson.deserialize(new Buffer(input)); + return JSON.stringify(data, null, 2); + } catch (err) { + return err.toString(); + } + }, +}; + +export default BSON; diff --git a/src/core/operations/Base.js b/src/core/operations/legacy/Base.js similarity index 100% rename from src/core/operations/Base.js rename to src/core/operations/legacy/Base.js diff --git a/src/core/operations/Base58.js b/src/core/operations/legacy/Base58.js similarity index 100% rename from src/core/operations/Base58.js rename to src/core/operations/legacy/Base58.js diff --git a/src/core/operations/BitwiseOp.js b/src/core/operations/legacy/BitwiseOp.js similarity index 99% rename from src/core/operations/BitwiseOp.js rename to src/core/operations/legacy/BitwiseOp.js index f2551cba..26408096 100755 --- a/src/core/operations/BitwiseOp.js +++ b/src/core/operations/legacy/BitwiseOp.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import {toHex} from "../lib/Hex"; /** @@ -166,7 +167,7 @@ const BitwiseOp = { if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue; if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; if (outputHex) { - record += Utils.toHex(result); + record += toHex(result); } else { record += Utils.printable(resultUtf8, false); } diff --git a/src/core/operations/ByteRepr.js b/src/core/operations/legacy/ByteRepr.js similarity index 73% rename from src/core/operations/ByteRepr.js rename to src/core/operations/legacy/ByteRepr.js index 986926ca..6bf91bb6 100755 --- a/src/core/operations/ByteRepr.js +++ b/src/core/operations/legacy/ByteRepr.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import {toHex, fromHex} from "../lib/Hex"; /** @@ -17,42 +18,12 @@ const ByteRepr = { * @default */ DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"], - /** - * @constant - * @default - */ - HEX_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"], /** * @constant * @default */ BIN_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"], - /** - * To Hex operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runToHex: function(input, args) { - const delim = Utils.charRep[args[0] || "Space"]; - return Utils.toHex(new Uint8Array(input), delim, 2); - }, - - - /** - * From Hex operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromHex: function(input, args) { - const delim = args[0] || "Space"; - return Utils.fromHex(input, delim, 2); - }, - /** * To Octal operation. @@ -63,7 +34,7 @@ const ByteRepr = { * @returns {string} */ runToOct: function(input, args) { - const delim = Utils.charRep[args[0] || "Space"]; + const delim = Utils.charRep(args[0] || "Space"); return input.map(val => val.toString(8)).join(delim); }, @@ -77,7 +48,7 @@ const ByteRepr = { * @returns {byteArray} */ runFromOct: function(input, args) { - const delim = Utils.charRep[args[0] || "Space"]; + const delim = Utils.charRep(args[0] || "Space"); if (input.length === 0) return []; return input.split(delim).map(val => parseInt(val, 8)); }, @@ -97,7 +68,7 @@ const ByteRepr = { * @returns {string} */ runToCharcode: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], + let delim = Utils.charRep(args[0] || "Space"), base = args[1], output = "", padding = 2, @@ -139,7 +110,7 @@ const ByteRepr = { * @returns {byteArray} */ runFromCharcode: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], + let delim = Utils.charRep(args[0] || "Space"), base = args[1], bites = input.split(delim), i = 0; @@ -148,6 +119,10 @@ const ByteRepr = { throw "Error: Base argument must be between 2 and 36"; } + if (input.length === 0) { + return []; + } + if (base !== 16 && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); // Split into groups of 2 if the whole string is concatenated and @@ -167,59 +142,6 @@ const ByteRepr = { }, - /** - * Highlight to hex - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightTo: function(pos, args) { - let delim = Utils.charRep[args[0] || "Space"], - len = delim === "\r\n" ? 1 : delim.length; - - pos[0].start = pos[0].start * (2 + len); - pos[0].end = pos[0].end * (2 + len) - len; - - // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly - if (delim === "0x" || delim === "\\x") { - pos[0].start += 2; - pos[0].end += 2; - } - return pos; - }, - - - /** - * Highlight from hex - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightFrom: function(pos, args) { - let delim = Utils.charRep[args[0] || "Space"], - len = delim === "\r\n" ? 1 : delim.length, - width = len + 2; - - // 0x and \x are added to the beginning if they are selected, so increment the positions accordingly - if (delim === "0x" || delim === "\\x") { - if (pos[0].start > 1) pos[0].start -= 2; - else pos[0].start = 0; - if (pos[0].end > 1) pos[0].end -= 2; - else pos[0].end = 0; - } - - pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width); - pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width); - return pos; - }, - - /** * To Decimal operation. * @@ -228,7 +150,7 @@ const ByteRepr = { * @returns {string} */ runToDecimal: function(input, args) { - const delim = Utils.charRep[args[0]]; + const delim = Utils.charRep(args[0]); return input.join(delim); }, @@ -241,7 +163,7 @@ const ByteRepr = { * @returns {byteArray} */ runFromDecimal: function(input, args) { - const delim = Utils.charRep[args[0]]; + const delim = Utils.charRep(args[0]); let byteStr = input.split(delim), output = []; if (byteStr[byteStr.length-1] === "") byteStr = byteStr.slice(0, byteStr.length-1); @@ -261,7 +183,7 @@ const ByteRepr = { * @returns {string} */ runToBinary: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], + let delim = Utils.charRep(args[0] || "Space"), output = "", padding = 8; @@ -285,7 +207,7 @@ const ByteRepr = { * @returns {byteArray} */ runFromBinary: function(input, args) { - const delimRegex = Utils.regexRep[args[0] || "Space"]; + const delimRegex = Utils.regexRep(args[0] || "Space"); input = input.replace(delimRegex, ""); const output = []; @@ -307,7 +229,7 @@ const ByteRepr = { * @returns {Object[]} pos */ highlightToBinary: function(pos, args) { - const delim = Utils.charRep[args[0] || "Space"]; + const delim = Utils.charRep(args[0] || "Space"); pos[0].start = pos[0].start * (8 + delim.length); pos[0].end = pos[0].end * (8 + delim.length) - delim.length; return pos; @@ -324,7 +246,7 @@ const ByteRepr = { * @returns {Object[]} pos */ highlightFromBinary: function(pos, args) { - const delim = Utils.charRep[args[0] || "Space"]; + const delim = Utils.charRep(args[0] || "Space"); pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length)); pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length)); return pos; @@ -353,7 +275,7 @@ const ByteRepr = { const convert = args[0]; const spaces = args[1]; if (convert === "All chars") { - let result = "|" + Utils.toHex(input) + "|"; + let result = "|" + toHex(input) + "|"; if (!spaces) result = result.replace(/ /g, ""); return result; } @@ -369,7 +291,7 @@ const ByteRepr = { output += "|"; inHex = true; } else if (spaces) output += " "; - output += Utils.toHex([b]); + output += toHex([b]); } else { if (inHex) { output += "|"; @@ -399,7 +321,7 @@ const ByteRepr = { output.push(Utils.ord(input[i++])); // Add match - const bytes = Utils.fromHex(m[1]); + const bytes = fromHex(m[1]); if (bytes) { for (let a = 0; a < bytes.length;) output.push(bytes[a++]); diff --git a/src/core/operations/CharEnc.js b/src/core/operations/legacy/CharEnc.js similarity index 98% rename from src/core/operations/CharEnc.js rename to src/core/operations/legacy/CharEnc.js index 8696386b..6b509e1e 100755 --- a/src/core/operations/CharEnc.js +++ b/src/core/operations/legacy/CharEnc.js @@ -1,4 +1,4 @@ -import cptable from "../lib/js-codepage/cptable.js"; +import cptable from "../vendor/js-codepage/cptable.js"; /** diff --git a/src/core/operations/Checksum.js b/src/core/operations/legacy/Checksum.js similarity index 100% rename from src/core/operations/Checksum.js rename to src/core/operations/legacy/Checksum.js diff --git a/src/core/operations/Cipher.js b/src/core/operations/legacy/Cipher.js similarity index 99% rename from src/core/operations/Cipher.js rename to src/core/operations/legacy/Cipher.js index f44aca20..e350c17a 100755 --- a/src/core/operations/Cipher.js +++ b/src/core/operations/legacy/Cipher.js @@ -1,4 +1,6 @@ import Utils from "../Utils.js"; +import {toBase64} from "../lib/Base64"; +import {toHexFast} from "../lib/Hex"; import CryptoJS from "crypto-js"; import forge from "imports-loader?jQuery=>null!node-forge/dist/forge.min.js"; import {blowfish as Blowfish} from "sladex-blowfish"; @@ -366,7 +368,7 @@ DES uses a key length of 8 bytes (64 bits).`; input = Utils.convertToByteString(input, inputType); - Blowfish.setIV(Utils.toBase64(iv), 0); + Blowfish.setIV(toBase64(iv), 0); const enc = Blowfish.encrypt(input, key, { outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType], @@ -395,14 +397,14 @@ DES uses a key length of 8 bytes (64 bits).`; input = inputType === "Raw" ? Utils.strToByteArray(input) : input; - Blowfish.setIV(Utils.toBase64(iv), 0); + Blowfish.setIV(toBase64(iv), 0); const result = Blowfish.decrypt(input, key, { outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[inputType], // This actually means inputType. The library is weird. cipherMode: Cipher._BLOWFISH_MODE_LOOKUP[mode] }); - return outputType === "Hex" ? Utils.toHexFast(Utils.strToByteArray(result)) : result; + return outputType === "Hex" ? toHexFast(Utils.strToByteArray(result)) : result; }, diff --git a/src/core/operations/Code.js b/src/core/operations/legacy/Code.js similarity index 99% rename from src/core/operations/Code.js rename to src/core/operations/legacy/Code.js index 42a9bbb4..ad777d01 100755 --- a/src/core/operations/Code.js +++ b/src/core/operations/legacy/Code.js @@ -483,12 +483,11 @@ const Code = { /** - * Converts to snake_case. + * To Snake Case operation. * * @param {string} input * @param {Object[]} args * @returns {string} - * */ runToSnakeCase(input, args) { const smart = args[0]; @@ -502,12 +501,11 @@ const Code = { /** - * Converts to camelCase. + * To Camel Case operation. * * @param {string} input * @param {Object[]} args * @returns {string} - * */ runToCamelCase(input, args) { const smart = args[0]; @@ -521,12 +519,11 @@ const Code = { /** - * Converts to kebab-case. + * To Kebab Case operation. * * @param {string} input * @param {Object[]} args * @returns {string} - * */ runToKebabCase(input, args) { const smart = args[0]; @@ -537,6 +534,7 @@ const Code = { return kebabCase(input); } }, + }; export default Code; diff --git a/src/core/operations/legacy/Compress.js b/src/core/operations/legacy/Compress.js new file mode 100755 index 00000000..2313541d --- /dev/null +++ b/src/core/operations/legacy/Compress.js @@ -0,0 +1,243 @@ +import Utils from "../Utils.js"; +import bzip2 from "exports-loader?bzip2!../vendor/bzip2.js"; + + +/** + * Compression operations. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @namespace + */ +const Compress = { + + /** + * Bzip2 Decompress operation. + * + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + runBzip2Decompress: function(input, args) { + let compressed = new Uint8Array(input), + bzip2Reader, + plain = ""; + + bzip2Reader = bzip2.array(compressed); + plain = bzip2.simple(bzip2Reader); + return plain; + }, + + + /** + * @constant + * @default + */ + TAR_FILENAME: "file.txt", + + + /** + * Tar pack operation. + * + * @author tlwr [toby@toby.codes] + * + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + runTar: function(input, args) { + const Tarball = function() { + this.bytes = new Array(512); + this.position = 0; + }; + + Tarball.prototype.addEmptyBlock = function() { + const filler = new Array(512); + filler.fill(0); + this.bytes = this.bytes.concat(filler); + }; + + Tarball.prototype.writeBytes = function(bytes) { + const self = this; + + if (this.position + bytes.length > this.bytes.length) { + this.addEmptyBlock(); + } + + Array.prototype.forEach.call(bytes, function(b, i) { + if (typeof b.charCodeAt !== "undefined") { + b = b.charCodeAt(); + } + + self.bytes[self.position] = b; + self.position += 1; + }); + }; + + Tarball.prototype.writeEndBlocks = function() { + const numEmptyBlocks = 2; + for (let i = 0; i < numEmptyBlocks; i++) { + this.addEmptyBlock(); + } + }; + + const fileSize = input.length.toString(8).padStart(11, "0"); + const currentUnixTimestamp = Math.floor(Date.now() / 1000); + const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0"); + + const file = { + fileName: Utils.padBytesRight(args[0], 100), + fileMode: Utils.padBytesRight("0000664", 8), + ownerUID: Utils.padBytesRight("0", 8), + ownerGID: Utils.padBytesRight("0", 8), + size: Utils.padBytesRight(fileSize, 12), + lastModTime: Utils.padBytesRight(lastModTime, 12), + checksum: " ", + type: "0", + linkedFileName: Utils.padBytesRight("", 100), + USTARFormat: Utils.padBytesRight("ustar", 6), + version: "00", + ownerUserName: Utils.padBytesRight("", 32), + ownerGroupName: Utils.padBytesRight("", 32), + deviceMajor: Utils.padBytesRight("", 8), + deviceMinor: Utils.padBytesRight("", 8), + fileNamePrefix: Utils.padBytesRight("", 155), + }; + + let checksum = 0; + for (const key in file) { + const bytes = file[key]; + Array.prototype.forEach.call(bytes, function(b) { + if (typeof b.charCodeAt !== "undefined") { + checksum += b.charCodeAt(); + } else { + checksum += b; + } + }); + } + checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8); + file.checksum = checksum; + + const tarball = new Tarball(); + tarball.writeBytes(file.fileName); + tarball.writeBytes(file.fileMode); + tarball.writeBytes(file.ownerUID); + tarball.writeBytes(file.ownerGID); + tarball.writeBytes(file.size); + tarball.writeBytes(file.lastModTime); + tarball.writeBytes(file.checksum); + tarball.writeBytes(file.type); + tarball.writeBytes(file.linkedFileName); + tarball.writeBytes(file.USTARFormat); + tarball.writeBytes(file.version); + tarball.writeBytes(file.ownerUserName); + tarball.writeBytes(file.ownerGroupName); + tarball.writeBytes(file.deviceMajor); + tarball.writeBytes(file.deviceMinor); + tarball.writeBytes(file.fileNamePrefix); + tarball.writeBytes(Utils.padBytesRight("", 12)); + tarball.writeBytes(input); + tarball.writeEndBlocks(); + + return tarball.bytes; + }, + + + /** + * Untar unpack operation. + * + * @author tlwr [toby@toby.codes] + * + * @param {byteArray} input + * @param {Object[]} args + * @returns {html} + */ + runUntar: function(input, args) { + const Stream = function(input) { + this.bytes = input; + this.position = 0; + }; + + Stream.prototype.getBytes = function(bytesToGet) { + const newPosition = this.position + bytesToGet; + const bytes = this.bytes.slice(this.position, newPosition); + this.position = newPosition; + return bytes; + }; + + Stream.prototype.readString = function(numBytes) { + let result = ""; + for (let i = this.position; i < this.position + numBytes; i++) { + const currentByte = this.bytes[i]; + if (currentByte === 0) break; + result += String.fromCharCode(currentByte); + } + this.position += numBytes; + return result; + }; + + Stream.prototype.readInt = function(numBytes, base) { + const string = this.readString(numBytes); + return parseInt(string, base); + }; + + Stream.prototype.hasMore = function() { + return this.position < this.bytes.length; + }; + + let stream = new Stream(input), + files = []; + + while (stream.hasMore()) { + const dataPosition = stream.position + 512; + + const file = { + fileName: stream.readString(100), + fileMode: stream.readString(8), + ownerUID: stream.readString(8), + ownerGID: stream.readString(8), + size: parseInt(stream.readString(12), 8), // Octal + lastModTime: new Date(1000 * stream.readInt(12, 8)), // Octal + checksum: stream.readString(8), + type: stream.readString(1), + linkedFileName: stream.readString(100), + USTARFormat: stream.readString(6).indexOf("ustar") >= 0, + }; + + if (file.USTARFormat) { + file.version = stream.readString(2); + file.ownerUserName = stream.readString(32); + file.ownerGroupName = stream.readString(32); + file.deviceMajor = stream.readString(8); + file.deviceMinor = stream.readString(8); + file.filenamePrefix = stream.readString(155); + } + + stream.position = dataPosition; + + if (file.type === "0") { + // File + files.push(file); + let endPosition = stream.position + file.size; + if (file.size % 512 !== 0) { + endPosition += 512 - (file.size % 512); + } + + file.bytes = stream.getBytes(file.size); + file.contents = Utils.byteArrayToUtf8(file.bytes); + stream.position = endPosition; + } else if (file.type === "5") { + // Directory + files.push(file); + } else { + // Symlink or empty bytes + } + } + + return Utils.displayFilesAsHTML(files); + }, +}; + +export default Compress; diff --git a/src/core/operations/Convert.js b/src/core/operations/legacy/Convert.js similarity index 100% rename from src/core/operations/Convert.js rename to src/core/operations/legacy/Convert.js diff --git a/src/core/operations/DateTime.js b/src/core/operations/legacy/DateTime.js similarity index 95% rename from src/core/operations/DateTime.js rename to src/core/operations/legacy/DateTime.js index 9f87939d..b117a2ca 100755 --- a/src/core/operations/DateTime.js +++ b/src/core/operations/legacy/DateTime.js @@ -1,3 +1,6 @@ +import moment from "moment-timezone"; + + /** * Date and time operations. * @@ -57,24 +60,29 @@ const DateTime = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {string} */ runToUnixTimestamp: function(input, args) { - let units = args[0], + const units = args[0], treatAsUTC = args[1], + showDateTime = args[2], d = treatAsUTC ? moment.utc(input) : moment(input); + let result = ""; + if (units === "Seconds (s)") { - return d.unix(); + result = d.unix(); } else if (units === "Milliseconds (ms)") { - return d.valueOf(); + result = d.valueOf(); } else if (units === "Microseconds (μs)") { - return d.valueOf() * 1000; + result = d.valueOf() * 1000; } else if (units === "Nanoseconds (ns)") { - return d.valueOf() * 1000000; + result = d.valueOf() * 1000000; } else { throw "Unrecognised unit"; } + + return showDateTime ? `${result} (${d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss")} UTC)` : result.toString(); }, diff --git a/src/core/operations/Diff.js b/src/core/operations/legacy/Diff.js similarity index 100% rename from src/core/operations/Diff.js rename to src/core/operations/legacy/Diff.js diff --git a/src/core/operations/Endian.js b/src/core/operations/legacy/Endian.js similarity index 94% rename from src/core/operations/Endian.js rename to src/core/operations/legacy/Endian.js index bb0f9136..a2ec7703 100755 --- a/src/core/operations/Endian.js +++ b/src/core/operations/legacy/Endian.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import {toHex, fromHex} from "../lib/Hex"; /** @@ -52,7 +53,7 @@ const Endian = { // Convert input to raw data based on specified data format switch (dataFormat) { case "Hex": - data = Utils.fromHex(input); + data = fromHex(input); break; case "Raw": data = Utils.strToByteArray(input); @@ -86,7 +87,7 @@ const Endian = { // Convert data back to specified data format switch (dataFormat) { case "Hex": - return Utils.toHex(result); + return toHex(result); case "Raw": return Utils.byteArrayToUtf8(result); default: diff --git a/src/core/operations/Entropy.js b/src/core/operations/legacy/Entropy.js similarity index 100% rename from src/core/operations/Entropy.js rename to src/core/operations/legacy/Entropy.js diff --git a/src/core/operations/Extract.js b/src/core/operations/legacy/Extract.js similarity index 100% rename from src/core/operations/Extract.js rename to src/core/operations/legacy/Extract.js diff --git a/src/core/operations/FileType.js b/src/core/operations/legacy/FileType.js similarity index 100% rename from src/core/operations/FileType.js rename to src/core/operations/legacy/FileType.js diff --git a/src/core/operations/Filetime.js b/src/core/operations/legacy/Filetime.js similarity index 100% rename from src/core/operations/Filetime.js rename to src/core/operations/legacy/Filetime.js diff --git a/src/core/operations/HTML.js b/src/core/operations/legacy/HTML.js similarity index 100% rename from src/core/operations/HTML.js rename to src/core/operations/legacy/HTML.js diff --git a/src/core/operations/HTTP.js b/src/core/operations/legacy/HTTP.js similarity index 100% rename from src/core/operations/HTTP.js rename to src/core/operations/legacy/HTTP.js diff --git a/src/core/operations/Hash.js b/src/core/operations/legacy/Hash.js similarity index 84% rename from src/core/operations/Hash.js rename to src/core/operations/legacy/Hash.js index 1d125aa6..f09f7c75 100755 --- a/src/core/operations/Hash.js +++ b/src/core/operations/legacy/Hash.js @@ -5,6 +5,8 @@ import * as SHA3 from "js-sha3"; import Checksum from "./Checksum.js"; import ctph from "ctph.js"; import ssdeep from "ssdeep.js"; +import bcrypt from "bcryptjs"; +import scrypt from "scryptsy"; /** @@ -376,7 +378,7 @@ const Hash = { * @returns {Number} */ runCompareCTPH: function (input, args) { - const samples = input.split(Utils.charRep[args[0]]); + const samples = input.split(Utils.charRep(args[0])); if (samples.length !== 2) throw "Incorrect number of samples."; return ctph.similarity(samples[0], samples[1]); }, @@ -390,7 +392,7 @@ const Hash = { * @returns {Number} */ runCompareSSDEEP: function (input, args) { - const samples = input.split(Utils.charRep[args[0]]); + const samples = input.split(Utils.charRep(args[0])); if (samples.length !== 2) throw "Incorrect number of samples."; return ssdeep.similarity(samples[0], samples[1]); }, @@ -449,6 +451,127 @@ const Hash = { }, + /** + * @constant + * @default + */ + BCRYPT_ROUNDS: 10, + + /** + * Bcrypt operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runBcrypt: async function (input, args) { + const rounds = args[0]; + const salt = await bcrypt.genSalt(rounds); + + return await bcrypt.hash(input, salt, null, p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`); + }); + }, + + + /** + * Bcrypt compare operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runBcryptCompare: async function (input, args) { + const hash = args[0]; + + const match = await bcrypt.compare(input, hash, null, p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`); + }); + + return match ? "Match: " + input : "No match"; + }, + + + /** + * Bcrypt parse operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runBcryptParse: async function (input, args) { + try { + return `Rounds: ${bcrypt.getRounds(input)} +Salt: ${bcrypt.getSalt(input)} +Password hash: ${input.split(bcrypt.getSalt(input))[1]} +Full hash: ${input}`; + } catch (err) { + return "Error: " + err.toString(); + } + }, + + + /** + * @constant + * @default + */ + KEY_FORMAT: ["Hex", "Base64", "UTF8", "Latin1"], + /** + * @constant + * @default + */ + SCRYPT_ITERATIONS: 16384, + /** + * @constant + * @default + */ + SCRYPT_MEM_FACTOR: 8, + /** + * @constant + * @default + */ + SCRYPT_PARALLEL_FACTOR: 1, + /** + * @constant + * @default + */ + SCRYPT_KEY_LENGTH: 64, + + /** + * Scrypt operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runScrypt: function (input, args) { + const salt = Utils.convertToByteString(args[0].string || "", args[0].option), + iterations = args[1], + memFactor = args[2], + parallelFactor = args[3], + keyLength = args[4]; + + try { + const data = scrypt( + input, salt, iterations, memFactor, parallelFactor, keyLength, + p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${p.percent.toFixed(0)}%`); + } + ); + + return data.toString("hex"); + } catch (err) { + return "Error: " + err.toString(); + } + }, + + /** * Generate all hashes operation. * diff --git a/src/core/operations/Hexdump.js b/src/core/operations/legacy/Hexdump.js similarity index 95% rename from src/core/operations/Hexdump.js rename to src/core/operations/legacy/Hexdump.js index fc907d9e..2626217a 100755 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/legacy/Hexdump.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import {fromHex} from "../lib/Hex"; /** @@ -78,11 +79,11 @@ const Hexdump = { */ runFrom: function(input, args) { let output = [], - regex = /^\s*(?:[\dA-F]{4,16}:?)?\s*((?:[\dA-F]{2}\s){1,8}(?:\s|[\dA-F]{2}-)(?:[\dA-F]{2}\s){1,8}|(?:[\dA-F]{2}\s|[\dA-F]{4}\s)+)/igm, + regex = /^\s*(?:[\dA-F]{4,16}h?:?)?\s*((?:[\dA-F]{2}\s){1,8}(?:\s|[\dA-F]{2}-)(?:[\dA-F]{2}\s){1,8}|(?:[\dA-F]{2}\s|[\dA-F]{4}\s)+)/igm, block, line; while ((block = regex.exec(input))) { - line = Utils.fromHex(block[1].replace(/-/g, " ")); + line = fromHex(block[1].replace(/-/g, " ")); for (let i = 0; i < line.length; i++) { output.push(line[i]); } diff --git a/src/core/operations/IP.js b/src/core/operations/legacy/IP.js similarity index 99% rename from src/core/operations/IP.js rename to src/core/operations/legacy/IP.js index 4c3eb5de..c4c3c18f 100755 --- a/src/core/operations/IP.js +++ b/src/core/operations/legacy/IP.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import {toHex, fromHex} from "../lib/Hex"; import Checksum from "./Checksum.js"; import {BigInteger} from "jsbn"; @@ -283,7 +284,7 @@ const IP = { baIp.push(decimal & 255); break; case "Hex": - baIp = Utils.fromHex(lines[i]); + baIp = fromHex(lines[i]); break; default: throw "Unsupported input IP format"; @@ -346,7 +347,7 @@ const IP = { * @returns {string} */ runGroupIps: function(input, args) { - let delim = Utils.charRep[args[0]], + let delim = Utils.charRep(args[0]), cidr = args[1], onlySubnets = args[2], ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF, @@ -445,7 +446,7 @@ const IP = { output; if (format === "Hex") { - input = Utils.fromHex(input); + input = fromHex(input); } else if (format === "Raw") { input = Utils.strToByteArray(input); } else { @@ -516,7 +517,7 @@ const IP = { "Destination IP address" + IP._ipv4ToStr(dstIP) + ""; if (ihl > 5) { - output += "Options" + Utils.toHex(options) + ""; + output += "Options" + toHex(options) + ""; } return output + ""; diff --git a/src/core/operations/Image.js b/src/core/operations/legacy/Image.js similarity index 91% rename from src/core/operations/Image.js rename to src/core/operations/legacy/Image.js index afab21c1..23581382 100644 --- a/src/core/operations/Image.js +++ b/src/core/operations/legacy/Image.js @@ -1,7 +1,9 @@ import * as ExifParser from "exif-parser"; -import removeEXIF from "../lib/remove-exif.js"; +import removeEXIF from "../vendor/remove-exif.js"; import Utils from "../Utils.js"; import FileType from "./FileType.js"; +import {fromBase64, toBase64} from "../lib/Base64"; +import {fromHex} from "../lib/Hex"; /** @@ -91,12 +93,12 @@ const Image = { // Convert input to raw bytes switch (inputFormat) { case "Hex": - input = Utils.fromHex(input); + input = fromHex(input); break; case "Base64": // Don't trust the Base64 entered by the user. // Unwrap it first, then re-encode later. - input = Utils.fromBase64(input, null, "byteArray"); + input = fromBase64(input, null, "byteArray"); break; case "Raw": default: @@ -113,7 +115,7 @@ const Image = { } // Add image data to URI - dataURI += "base64," + Utils.toBase64(input); + dataURI += "base64," + toBase64(input); return ""; }, diff --git a/src/core/operations/JS.js b/src/core/operations/legacy/JS.js similarity index 100% rename from src/core/operations/JS.js rename to src/core/operations/legacy/JS.js diff --git a/src/core/operations/MAC.js b/src/core/operations/legacy/MAC.js similarity index 100% rename from src/core/operations/MAC.js rename to src/core/operations/legacy/MAC.js diff --git a/src/core/operations/MS.js b/src/core/operations/legacy/MS.js similarity index 100% rename from src/core/operations/MS.js rename to src/core/operations/legacy/MS.js diff --git a/src/core/operations/MorseCode.js b/src/core/operations/legacy/MorseCode.js similarity index 96% rename from src/core/operations/MorseCode.js rename to src/core/operations/legacy/MorseCode.js index ae5d091b..de81ce89 100644 --- a/src/core/operations/MorseCode.js +++ b/src/core/operations/legacy/MorseCode.js @@ -101,8 +101,8 @@ const MorseCode = { const dash = format[0]; const dot = format[1]; - const letterDelim = Utils.charRep[args[1]]; - const wordDelim = Utils.charRep[args[2]]; + const letterDelim = Utils.charRep(args[1]); + const wordDelim = Utils.charRep(args[2]); input = input.split(/\r?\n/); input = Array.prototype.map.call(input, function(line) { @@ -163,8 +163,8 @@ const MorseCode = { reverseTable(); } - const letterDelim = Utils.charRep[args[0]]; - const wordDelim = Utils.charRep[args[1]]; + const letterDelim = Utils.charRep(args[0]); + const wordDelim = Utils.charRep(args[1]); input = input.replace(/-|‐|−|_|–|—|dash/ig, ""); //hyphen-minus|hyphen|minus-sign|undersore|en-dash|em-dash input = input.replace(/\.|·|dot/ig, ""); diff --git a/src/core/operations/NetBIOS.js b/src/core/operations/legacy/NetBIOS.js similarity index 100% rename from src/core/operations/NetBIOS.js rename to src/core/operations/legacy/NetBIOS.js diff --git a/src/core/operations/Numberwang.js b/src/core/operations/legacy/Numberwang.js similarity index 100% rename from src/core/operations/Numberwang.js rename to src/core/operations/legacy/Numberwang.js diff --git a/src/core/operations/OS.js b/src/core/operations/legacy/OS.js similarity index 100% rename from src/core/operations/OS.js rename to src/core/operations/legacy/OS.js diff --git a/src/core/operations/OTP.js b/src/core/operations/legacy/OTP.js similarity index 100% rename from src/core/operations/OTP.js rename to src/core/operations/legacy/OTP.js diff --git a/src/core/operations/PHP.js b/src/core/operations/legacy/PHP.js similarity index 100% rename from src/core/operations/PHP.js rename to src/core/operations/legacy/PHP.js diff --git a/src/core/operations/PublicKey.js b/src/core/operations/legacy/PublicKey.js similarity index 96% rename from src/core/operations/PublicKey.js rename to src/core/operations/legacy/PublicKey.js index 66b177a5..80c35196 100755 --- a/src/core/operations/PublicKey.js +++ b/src/core/operations/legacy/PublicKey.js @@ -1,4 +1,6 @@ import Utils from "../Utils.js"; +import {fromBase64} from "../lib/Base64"; +import {toHex, fromHex} from "../lib/Hex"; import * as r from "jsrsasign"; @@ -43,10 +45,10 @@ const PublicKey = { cert.readCertPEM(input); break; case "Base64": - cert.readCertHex(Utils.toHex(Utils.fromBase64(input, null, "byteArray"), "")); + cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), "")); break; case "Raw": - cert.readCertHex(Utils.toHex(Utils.strToByteArray(input), "")); + cert.readCertHex(toHex(Utils.strToByteArray(input), "")); break; default: throw "Undefined input format"; @@ -304,7 +306,7 @@ ${extensions}`; * @returns {string} */ _formatByteStr: function(byteStr, length, indent) { - byteStr = Utils.toHex(Utils.fromHex(byteStr), ":"); + byteStr = toHex(fromHex(byteStr), ":"); length = length * 3; let output = ""; diff --git a/src/core/operations/Punycode.js b/src/core/operations/legacy/Punycode.js similarity index 100% rename from src/core/operations/Punycode.js rename to src/core/operations/legacy/Punycode.js diff --git a/src/core/operations/QuotedPrintable.js b/src/core/operations/legacy/QuotedPrintable.js similarity index 100% rename from src/core/operations/QuotedPrintable.js rename to src/core/operations/legacy/QuotedPrintable.js diff --git a/src/core/operations/Regex.js b/src/core/operations/legacy/Regex.js similarity index 100% rename from src/core/operations/Regex.js rename to src/core/operations/legacy/Regex.js diff --git a/src/core/operations/Rotate.js b/src/core/operations/legacy/Rotate.js similarity index 100% rename from src/core/operations/Rotate.js rename to src/core/operations/legacy/Rotate.js diff --git a/src/core/operations/SeqUtils.js b/src/core/operations/legacy/SeqUtils.js similarity index 98% rename from src/core/operations/SeqUtils.js rename to src/core/operations/legacy/SeqUtils.js index fa900cf9..4080528a 100755 --- a/src/core/operations/SeqUtils.js +++ b/src/core/operations/legacy/SeqUtils.js @@ -36,7 +36,7 @@ const SeqUtils = { * @returns {string} */ runSort: function (input, args) { - let delim = Utils.charRep[args[0]], + let delim = Utils.charRep(args[0]), sortReverse = args[1], order = args[2], sorted = input.split(delim); @@ -64,7 +64,7 @@ const SeqUtils = { * @returns {string} */ runUnique: function (input, args) { - const delim = Utils.charRep[args[0]]; + const delim = Utils.charRep(args[0]); return input.split(delim).unique().join(delim); }, diff --git a/src/core/operations/Shellcode.js b/src/core/operations/legacy/Shellcode.js similarity index 97% rename from src/core/operations/Shellcode.js rename to src/core/operations/legacy/Shellcode.js index 2fb6baeb..920b12c6 100644 --- a/src/core/operations/Shellcode.js +++ b/src/core/operations/legacy/Shellcode.js @@ -1,4 +1,4 @@ -import disassemble from "../lib/DisassembleX86-64.js"; +import disassemble from "../vendor/DisassembleX86-64.js"; /** diff --git a/src/core/operations/StrUtils.js b/src/core/operations/legacy/StrUtils.js similarity index 85% rename from src/core/operations/StrUtils.js rename to src/core/operations/legacy/StrUtils.js index 7e7f8b33..9a5c010e 100755 --- a/src/core/operations/StrUtils.js +++ b/src/core/operations/legacy/StrUtils.js @@ -1,4 +1,6 @@ import Utils from "../Utils.js"; +import {fromHex} from "../lib/Hex"; +import jsesc from "jsesc"; /** @@ -119,7 +121,7 @@ const StrUtils = { * @returns {string} */ runFilter: function(input, args) { - let delim = Utils.charRep[args[0]], + let delim = Utils.charRep(args[0]), regex, reverse = args[2]; @@ -219,35 +221,45 @@ const StrUtils = { * @constant * @default */ - ESCAPE_REPLACEMENTS: [ - {"escaped": "\\\\", "unescaped": "\\"}, // Must be first - {"escaped": "\\'", "unescaped": "'"}, - {"escaped": "\\\"", "unescaped": "\""}, - {"escaped": "\\n", "unescaped": "\n"}, - {"escaped": "\\r", "unescaped": "\r"}, - {"escaped": "\\t", "unescaped": "\t"}, - {"escaped": "\\b", "unescaped": "\b"}, - {"escaped": "\\f", "unescaped": "\f"}, - ], + QUOTE_TYPES: ["Single", "Double", "Backtick"], + /** + * @constant + * @default + */ + ESCAPE_LEVEL: ["Special chars", "Everything", "Minimal"], /** * Escape string operation. * * @author Vel0x [dalemy@microsoft.com] + * @author n1474335 [n1474335@gmail.com] * * @param {string} input * @param {Object[]} args * @returns {string} * * @example - * StrUtils.runUnescape("Don't do that", []) + * StrUtils.runEscape("Don't do that", []) * > "Don\'t do that" - * StrUtils.runUnescape(`Hello + * StrUtils.runEscape(`Hello * World`, []) * > "Hello\nWorld" */ runEscape: function(input, args) { - return StrUtils._replaceByKeys(input, "unescaped", "escaped"); + const level = args[0], + quotes = args[1], + jsonCompat = args[2], + es6Compat = args[3], + lowercaseHex = !args[4]; + + return jsesc(input, { + quotes: quotes.toLowerCase(), + es6: es6Compat, + escapeEverything: level === "Everything", + minimal: level === "Minimal", + json: jsonCompat, + lowercaseHex: lowercaseHex, + }); }, @@ -255,6 +267,7 @@ const StrUtils = { * Unescape string operation. * * @author Vel0x [dalemy@microsoft.com] + * @author n1474335 [n1474335@gmail.com] * * @param {string} input * @param {Object[]} args @@ -268,32 +281,7 @@ const StrUtils = { * World` */ runUnescape: function(input, args) { - return StrUtils._replaceByKeys(input, "escaped", "unescaped"); - }, - - - /** - * Replaces all matching tokens in ESCAPE_REPLACEMENTS with the correction. The - * ordering is determined by the patternKey and the replacementKey. - * - * @author Vel0x [dalemy@microsoft.com] - * @author Matt C [matt@artemisbot.uk] - * - * @param {string} input - * @param {string} pattern_key - * @param {string} replacement_key - * @returns {string} - */ - _replaceByKeys: function(input, patternKey, replacementKey) { - let output = input; - - // Catch the \\x encoded characters - if (patternKey === "escaped") output = Utils.parseEscapedChars(input); - - StrUtils.ESCAPE_REPLACEMENTS.forEach(replacement => { - output = output.split(replacement[patternKey]).join(replacement[replacementKey]); - }); - return output; + return Utils.parseEscapedChars(input); }, @@ -308,7 +296,7 @@ const StrUtils = { let delimiter = args[0], number = args[1]; - delimiter = Utils.charRep[delimiter]; + delimiter = Utils.charRep(delimiter); const splitInput = input.split(delimiter); return splitInput @@ -336,7 +324,7 @@ const StrUtils = { let delimiter = args[0], number = args[1]; - delimiter = Utils.charRep[delimiter]; + delimiter = Utils.charRep(delimiter); const splitInput = input.split(delimiter); return splitInput @@ -393,8 +381,8 @@ const StrUtils = { } if (inputType === "Hex") { - samples[0] = Utils.fromHex(samples[0]); - samples[1] = Utils.fromHex(samples[1]); + samples[0] = fromHex(samples[0]); + samples[1] = fromHex(samples[1]); } else { samples[0] = Utils.strToByteArray(samples[0]); samples[1] = Utils.strToByteArray(samples[1]); diff --git a/src/core/operations/Tidy.js b/src/core/operations/legacy/Tidy.js similarity index 100% rename from src/core/operations/Tidy.js rename to src/core/operations/legacy/Tidy.js diff --git a/src/core/operations/URL.js b/src/core/operations/legacy/URL.js similarity index 100% rename from src/core/operations/URL.js rename to src/core/operations/legacy/URL.js diff --git a/src/core/operations/UUID.js b/src/core/operations/legacy/UUID.js similarity index 100% rename from src/core/operations/UUID.js rename to src/core/operations/legacy/UUID.js diff --git a/src/core/operations/Unicode.js b/src/core/operations/legacy/Unicode.js similarity index 58% rename from src/core/operations/Unicode.js rename to src/core/operations/legacy/Unicode.js index 16e9357f..104ef07e 100755 --- a/src/core/operations/Unicode.js +++ b/src/core/operations/legacy/Unicode.js @@ -50,6 +50,40 @@ const Unicode = { }, + /** + * Escape Unicode Characters operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runEscape: function(input, args) { + const regexWhitelist = /[ -~]/i, + prefix = args[0], + encodeAll = args[1], + padding = args[2], + uppercaseHex = args[3]; + + let output = "", + character = ""; + + for (let i = 0; i < input.length; i++) { + character = input[i]; + if (!encodeAll && regexWhitelist.test(character)) { + // It’s a printable ASCII character so don’t escape it. + output += character; + continue; + } + + let cp = character.codePointAt(0).toString(16); + if (uppercaseHex) cp = cp.toUpperCase(); + output += prefix + cp.padStart(padding, "0"); + } + + return output; + }, + + /** * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex. * diff --git a/src/core/operations/XKCD.js b/src/core/operations/legacy/XKCD.js similarity index 100% rename from src/core/operations/XKCD.js rename to src/core/operations/legacy/XKCD.js diff --git a/src/core/lib/DisassembleX86-64.js b/src/core/vendor/DisassembleX86-64.js similarity index 99% rename from src/core/lib/DisassembleX86-64.js rename to src/core/vendor/DisassembleX86-64.js index e350bd16..e320c6c2 100644 --- a/src/core/lib/DisassembleX86-64.js +++ b/src/core/vendor/DisassembleX86-64.js @@ -1443,8 +1443,8 @@ const Operands = [ ------------------------------------------------------------------------------------------------------------------------*/ "10000004","10000004","10000004","10000004", "16000C00","170E0C00","0C001600","0C00170E", - "10020008", - "10020008", + "110E0008", + "110E0008", "0D060C01", //JMP Ap (w:z). "100000040004", "16001A01","170E1A01", @@ -5703,7 +5703,6 @@ function LDisassemble() } - //////////////////////////////////////////////////////////////////////////////////////////////// /* diff --git a/src/core/lib/bzip2.js b/src/core/vendor/bzip2.js similarity index 100% rename from src/core/lib/bzip2.js rename to src/core/vendor/bzip2.js diff --git a/src/core/lib/canvascomponents.js b/src/core/vendor/canvascomponents.js similarity index 100% rename from src/core/lib/canvascomponents.js rename to src/core/vendor/canvascomponents.js diff --git a/src/core/lib/js-codepage/cptable.js b/src/core/vendor/js-codepage/cptable.js similarity index 100% rename from src/core/lib/js-codepage/cptable.js rename to src/core/vendor/js-codepage/cptable.js diff --git a/src/core/lib/js-codepage/cputils.js b/src/core/vendor/js-codepage/cputils.js similarity index 100% rename from src/core/lib/js-codepage/cputils.js rename to src/core/vendor/js-codepage/cputils.js diff --git a/src/core/lib/remove-exif.js b/src/core/vendor/remove-exif.js similarity index 100% rename from src/core/lib/remove-exif.js rename to src/core/vendor/remove-exif.js diff --git a/src/node/index.js b/src/node/index.js deleted file mode 100644 index ffab80b9..00000000 --- a/src/node/index.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Node view for CyberChef. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - */ -require("babel-polyfill"); - -const Chef = require("../core/Chef.js").default; - -const CyberChef = { - - bake: function(input, recipeConfig) { - this.chef = new Chef(); - return this.chef.bake( - input, - recipeConfig, - {}, - 0, - false - ); - } - -}; - -module.exports = CyberChef; diff --git a/src/node/index.mjs b/src/node/index.mjs new file mode 100644 index 00000000..c6e86c68 --- /dev/null +++ b/src/node/index.mjs @@ -0,0 +1,39 @@ +/** + * Node view for CyberChef. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ +import "babel-polyfill"; + +// Define global environment functions +global.ENVIRONMENT_IS_WORKER = function() { + return typeof importScripts === "function"; +}; +global.ENVIRONMENT_IS_NODE = function() { + return typeof process === "object" && typeof require === "function"; +}; +global.ENVIRONMENT_IS_WEB = function() { + return typeof window === "object"; +}; + +import Chef from "../core/Chef"; + +const CyberChef = { + + bake: function(input, recipeConfig) { + this.chef = new Chef(); + return this.chef.bake( + input, + recipeConfig, + {}, + 0, + false + ); + } + +}; + +export default CyberChef; +export {CyberChef}; diff --git a/src/web/App.js b/src/web/App.js index 0c2bb2ea..29b858f2 100755 --- a/src/web/App.js +++ b/src/web/App.js @@ -1,4 +1,5 @@ -import Utils from "../core/Utils.js"; +import Utils from "../core/Utils"; +import {fromBase64} from "../core/lib/Base64"; import Manager from "./Manager.js"; import HTMLCategory from "./HTMLCategory.js"; import HTMLOperation from "./HTMLOperation.js"; @@ -24,7 +25,7 @@ const App = function(categories, operations, defaultFavourites, defaultOptions) this.operations = operations; this.dfavourites = defaultFavourites; this.doptions = defaultOptions; - this.options = Utils.extend({}, defaultOptions); + this.options = Object.assign({}, defaultOptions); this.manager = new Manager(this); @@ -193,12 +194,12 @@ App.prototype.populateOperationsList = function() { let i; for (i = 0; i < this.categories.length; i++) { - let catConf = this.categories[i], + const catConf = this.categories[i], selected = i === 0, cat = new HTMLCategory(catConf.name, selected); for (let j = 0; j < catConf.ops.length; j++) { - let opName = catConf.ops[j], + const opName = catConf.ops[j], op = new HTMLOperation(opName, this.operations[opName], this, this.manager); cat.addOperation(op); } @@ -405,7 +406,7 @@ App.prototype.loadURIParams = function() { // Read in input data from URI params if (this.uriParams.input) { try { - const inputData = Utils.fromBase64(this.uriParams.input); + const inputData = fromBase64(this.uriParams.input); this.setInput(inputData); } catch (err) {} } @@ -503,11 +504,11 @@ App.prototype.resetLayout = function() { */ App.prototype.setCompileMessage = function() { // Display time since last build and compile message - let now = new Date(), + const now = new Date(), timeSinceCompile = Utils.fuzzyTime(now.getTime() - window.compileTime); // Calculate previous version to compare to - let prev = PKG_VERSION.split(".").map(n => { + const prev = PKG_VERSION.split(".").map(n => { return parseInt(n, 10); }); if (prev[2] > 0) prev[2]--; @@ -574,7 +575,7 @@ App.prototype.alert = function(str, style, timeout, silent) { style = style || "danger"; timeout = timeout || 0; - let alertEl = document.getElementById("alert"), + const alertEl = document.getElementById("alert"), alertContent = document.getElementById("alert-content"); alertEl.classList.remove("alert-danger"); diff --git a/src/web/BindingsWaiter.js b/src/web/BindingsWaiter.js old mode 100644 new mode 100755 diff --git a/src/web/ControlsWaiter.js b/src/web/ControlsWaiter.js index a9b7490c..bf3082cd 100755 --- a/src/web/ControlsWaiter.js +++ b/src/web/ControlsWaiter.js @@ -1,4 +1,5 @@ -import Utils from "../core/Utils.js"; +import Utils from "../core/Utils"; +import {toBase64} from "../core/lib/Base64"; /** @@ -169,7 +170,7 @@ ControlsWaiter.prototype.generateStateUrl = function(includeRecipe, includeInput window.location.host + window.location.pathname; const recipeStr = Utils.generatePrettyRecipe(recipeConfig); - const inputStr = Utils.toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding + const inputStr = toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding includeRecipe = includeRecipe && (recipeConfig.length > 0); // Only inlcude input if it is less than 50KB (51200 * 4/3 as it is Base64 encoded) @@ -271,9 +272,9 @@ ControlsWaiter.prototype.saveButtonClick = function() { return; } - let savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : [], - recipeId = localStorage.recipeId || 0; + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + let recipeId = localStorage.recipeId || 0; savedRecipes.push({ id: ++recipeId, diff --git a/src/web/HTMLIngredient.js b/src/web/HTMLIngredient.js index 3f360f04..ca28ab01 100755 --- a/src/web/HTMLIngredient.js +++ b/src/web/HTMLIngredient.js @@ -32,12 +32,14 @@ const HTMLIngredient = function(config, app, manager) { * @returns {string} */ HTMLIngredient.prototype.toHtml = function() { - let inline = (this.type === "boolean" || - this.type === "number" || - this.type === "option" || - this.type === "shortString" || - this.type === "binaryShortString"), - html = inline ? "" : "
 
", + const inline = ( + this.type === "boolean" || + this.type === "number" || + this.type === "option" || + this.type === "shortString" || + this.type === "binaryShortString" + ); + let html = inline ? "" : "
 
", i, m; html += "