diff --git a/.babelrc b/.babelrc index 094c0592..08b4065a 100644 --- a/.babelrc +++ b/.babelrc @@ -10,5 +10,10 @@ "modules": false, "useBuiltIns": true }] + ], + "plugins": [ + ["babel-plugin-transform-builtin-extend", { + "globals": ["Error"] + }] ] } 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..529676da 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1 @@ -src/core/lib/** -src/core/config/MetaConfig.js \ No newline at end of file +src/core/vendor/** 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..9ea869e3 100755 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ docs/* !docs/*.conf.json !docs/*.ico .vscode -src/core/config/MetaConfig.js \ No newline at end of file +src/core/config/modules/* +src/core/config/OperationConfig.json +src/core/operations/index.mjs + 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/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ad6e8be9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog +All notable changes to this project will be documented in this file. + +## [8.0.0] - 2018-08-05 +- Codebase rewritten using [ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) and [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) #284 +- Operation architecture restructured to make adding new operations a lot simpler #284 +- A script has been added to aid in the creation of new operations by running `npm run newop` @n1474335 #284 +- 'Magic' operation added - [automated detection of encoded data](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) @n1474335 #239 +- UI updated to use [Bootstrap Material Design](https://fezvrasta.github.io/bootstrap-material-design/) @n1474335 #248 +- `JSON`, `File` and `List` Dish types added @n1474335 #284 +- `OperationError` type added for better handling of errors thrown by operations @d98762625 #296 +- A `present()` method has been added, allowing operations to pass machine-friendly data to subsequent operations whilst presenting human-friendly data to the user @n1474335 #284 +- Set operations added @d98762625 #281 +- 'To Table' operation added @JustAnotherMark #294 +- 'Haversine distance' operation added @Dachande663 #325 +- Started keeping a changelog @n1474335 + +## [7.0.0] - 2017-12-28 +- Added support for loading, processing and downloading files up to 500MB @n1474335 #224 + +## [6.0.0] - 2017-09-19 +- Added threading support, moving all recipe processing into a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to increase performance and allow long-running operations to be cancelled @n1474335 #173 +- Created modules so that operations relying on large libraries can be downloaded separately as required, reducing the initial loading time for the app @n1474335 #173 + +## [5.0.0] - 2017-03-30 +- Configured Webpack build process, Babel transpilation and ES6 imports and exports @n1474335 #95 + +## [4.0.0] - 2016-11-28 +- Initial open source commit @n1474335 + + +[8.0.0]: https://github.com/gchq/CyberChef/releases/tag/v8.0.0 +[7.0.0]: https://github.com/gchq/CyberChef/releases/tag/v7.0.0 +[6.0.0]: https://github.com/gchq/CyberChef/releases/tag/v6.0.0 +[5.0.0]: https://github.com/gchq/CyberChef/releases/tag/v5.0.0 +[4.0.0]: https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306 diff --git a/Gruntfile.js b/Gruntfile.js index b767a092..81d838b5 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", "exec:generateConfig", "concurrent:dev"]); grunt.registerTask("node", "Compiles CyberChef into a single NodeJS module.", - ["clean:node", "webpack:metaConf", "webpack:node", "chmod:build"]); + ["clean:node", "clean:config", "exec:generateConfig", "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:generateConfig", "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", "exec:generateConfig", "webpack:web", "inline", "chmod"]); grunt.registerTask("default", "Lints the code base", @@ -45,7 +46,7 @@ module.exports = function (grunt) { grunt.registerTask("inline", "Compiles a production build of CyberChef into a single, portable web page.", - ["webpack:webInline", "runInliner", "clean:inlineScripts"]); + ["exec:generateConfig", "webpack:webInline", "runInliner", "clean:inlineScripts"]); grunt.registerTask("runInliner", runInliner); @@ -60,9 +61,9 @@ module.exports = function (grunt) { grunt.loadNpmTasks("grunt-jsdoc"); grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks("grunt-contrib-watch"); grunt.loadNpmTasks("grunt-chmod"); grunt.loadNpmTasks("grunt-exec"); - grunt.loadNpmTasks("grunt-execute"); grunt.loadNpmTasks("grunt-accessibility"); grunt.loadNpmTasks("grunt-concurrent"); @@ -118,12 +119,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; @@ -132,9 +133,9 @@ module.exports = function (grunt) { grunt.initConfig({ clean: { dev: ["build/dev/*"], - prod: ["build/prod/*", "src/core/config/MetaConfig.js"], - test: ["build/test/*", "src/core/config/MetaConfig.js"], - node: ["build/node/*", "src/core/config/MetaConfig.js"], + 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 +144,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/**/*", "!src/core/operations/legacy/**/*"], + web: ["src/web/**/*.{js,mjs}"], + node: ["src/node/**/*.{js,mjs}"], + tests: ["test/**/*.{js,mjs}"], }, jsdoc: { options: { @@ -159,17 +160,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 +179,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 +191,7 @@ module.exports = function (grunt) { }, resolve: { alias: { - "./config/modules/OpModules.js": "./config/modules/Default.js" + "./config/modules/OpModules": "./config/modules/Default" } }, plugins: [ @@ -279,7 +241,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 +254,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 +292,7 @@ module.exports = function (grunt) { }, moduleEntryPoints), resolve: { alias: { - "./config/modules/OpModules.js": "./config/modules/Default.js" + "./config/modules/OpModules": "./config/modules/Default" } }, plugins: [ @@ -388,6 +350,18 @@ module.exports = function (grunt) { src: ["docs/**/*", "docs/"] } }, + watch: { + config: { + files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"], + tasks: ["exec:generateConfig"] + } + }, + concurrent: { + dev: ["watch:config", "webpack-dev-server:start"], + options: { + logConcurrentOutput: true + } + }, exec: { repoSize: { command: [ @@ -401,10 +375,21 @@ module.exports = function (grunt) { }, sitemap: { command: "node build/prod/sitemap.js > build/prod/sitemap.xml" + }, + generateConfig: { + command: [ + "echo '\n--- Regenerating config files. ---'", + "mkdir -p src/core/config/modules", + "echo 'export default {};\n' > src/core/config/modules/OpModules.mjs", + "echo '[]\n' > src/core/config/OperationConfig.json", + "node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs", + "node --experimental-modules src/core/config/scripts/generateConfig.mjs", + "echo '--- Config scripts finished. ---\n'" + ].join(";") + }, + tests: { + command: "node --experimental-modules test/index.mjs" } }, - execute: { - test: "build/test/index.js" - }, }); }; diff --git a/README.md b/README.md index 36f83407..d1960abb 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more. -The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years. Every effort has been made to structure the code in a readable and extendable format, however it should be noted that the analyst is not a professional developer. +The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years. ## Live demo @@ -43,16 +43,19 @@ You can use as many operations as you like in simple or complex ways. Some examp - [Carry out different operations on data of different types][8] - [Use parts of the input as arguments to operations][9] - [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10] + - [Automagically detect several layers of nested encoding][12] ## Features - Drag and drop - Operations can be dragged in and out of the recipe list, or reorganised. - - Files can be dragged over the input box to load them directly into the browser. + - Files up to 500MB can be dragged over the input box to load them directly into the browser. - Auto Bake - Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately. - This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance). + - Automated encoding detection + - CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation which can make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data. - Breakpoints - You can set breakpoints on any operation in your recipe to pause execution before running it. - You can also step through the recipe one operation at a time to see what the data looks like at each stage. @@ -81,6 +84,8 @@ CyberChef is built to support ## Contributing +Contributing a new operation to CyberChef is super easy! There is a quickstart script which will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation. + An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki). - Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0) @@ -104,3 +109,4 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice [9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ [10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg [11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4 + [12]: https://gchq.github.io/CyberChef/#recipe=Magic(3,false,false)&input=V1VhZ3dzaWFlNm1QOGdOdENDTFVGcENwQ0IyNlJtQkRvREQ4UGFjZEFtekF6QlZqa0syUXN0RlhhS2hwQzZpVVM3UkhxWHJKdEZpc29SU2dvSjR3aGptMWFybTg2NHFhTnE0UmNmVW1MSHJjc0FhWmM1VFhDWWlmTmRnUzgzZ0RlZWpHWDQ2Z2FpTXl1QlY2RXNrSHQxc2NnSjg4eDJ0TlNvdFFEd2JHWTFtbUNvYjJBUkdGdkNLWU5xaU45aXBNcTFaVTFtZ2tkYk51R2NiNzZhUnRZV2hDR1VjOGc5M1VKdWRoYjhodHNoZVpud1RwZ3FoeDgzU1ZKU1pYTVhVakpUMnptcEM3dVhXdHVtcW9rYmRTaTg4WXRrV0RBYzFUb291aDJvSDRENGRkbU5LSldVRHBNd21uZ1VtSzE0eHdtb21jY1BRRTloTTE3MkFQblNxd3hkS1ExNzJSa2NBc3lzbm1qNWdHdFJtVk5OaDJzMzU5d3I2bVMyUVJQ diff --git a/package-lock.json b/package-lock.json index edd01bd1..5a77acfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,15 +4,274 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@webassemblyjs/ast": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", + "integrity": "sha512-49nwvW/Hx9i+OYHg+mRhKZfAlqThr11Dqz8TsrvqGKMhdI2ijy3KBJOun2Z4770TPjrIJhR6KxChQIDaz8clDA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "debug": "^3.1.0", + "mamacro": "^0.0.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz", + "integrity": "sha512-vrvvB18Kh4uyghSKb0NTv+2WZx871WL2NzwMj61jcq2bXkyhRC+8Q0oD7JGVf0+5i/fKQYQSBCNMMsDMRVAMqA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz", + "integrity": "sha512-dBh2CWYqjaDlvMmRP/kudxpdh30uXjIbpkLj9HQe+qtYlwvYjPRjdQXrq1cTAAOUSMTtzqbXIxEdEZmyKfcwsg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz", + "integrity": "sha512-v7igWf1mHcpJNbn4m7e77XOAWXCDT76Xe7Is1VQFXc4K5jRcFrl9D0NrqM4XifQ0bXiuTSkTKMYqDxu5MhNljA==", + "dev": true, + "requires": { + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz", + "integrity": "sha512-yN6ScQQDFCiAXnVctdVO/J5NQRbwyTbQzsGzEgXsAnrxhjp0xihh+nNHQTMrq5UhOqTb5LykpJAvEv9AT0jnAQ==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.5.13" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz", + "integrity": "sha512-hSIKzbXjVMRvy3Jzhgu+vDd/aswJ+UMEnLRCkZDdknZO3Z9e6rp1DAs0tdLItjCFqkz9+0BeOPK/mk3eYvVzZg==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz", + "integrity": "sha512-zxJXULGPLB7r+k+wIlvGlXpT4CYppRz8fLUM/xobGHc9Z3T6qlmJD9ySJ2jknuktuuiR9AjnNpKYDECyaiX+QQ==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "mamacro": "^0.0.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz", + "integrity": "sha512-0n3SoNGLvbJIZPhtMFq0XmmnA/YmQBXaZKQZcW8maGKwLpVcgjNrxpFZHEOLKjXJYVN5Il8vSfG7nRX50Zn+aw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz", + "integrity": "sha512-IJ/goicOZ5TT1axZFSnlAtz4m8KEjYr12BNOANAwGFPKXM4byEDaMNXYowHMG0yKV9a397eU/NlibFaLwr1fbw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/ieee754": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz", + "integrity": "sha512-TseswvXEPpG5TCBKoLx9tT7+/GMACjC1ruo09j46ULRZWYm8XHpDWaosOjTnI7kr4SRJFzA6MWoUkAB+YCGKKg==", + "dev": true, + "requires": { + "ieee754": "^1.1.11" + } + }, + "@webassemblyjs/leb128": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.5.13.tgz", + "integrity": "sha512-0NRMxrL+GG3eISGZBmLBLAVjphbN8Si15s7jzThaw1UE9e5BY1oH49/+MA1xBzxpf1OW5sf9OrPDOclk9wj2yg==", + "dev": true, + "requires": { + "long": "4.0.0" + }, + "dependencies": { + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + } + } + }, + "@webassemblyjs/utf8": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.5.13.tgz", + "integrity": "sha512-Ve1ilU2N48Ew0lVGB8FqY7V7hXjaC4+PeZM+vDYxEd+R2iQ0q+Wb3Rw8v0Ri0+rxhoz6gVGsnQNb4FjRiEH/Ng==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz", + "integrity": "sha512-X7ZNW4+Hga4f2NmqENnHke2V/mGYK/xnybJSIXImt1ulxbCOEs/A+ZK/Km2jgihjyVxp/0z0hwIcxC6PrkWtgw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/helper-wasm-section": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "@webassemblyjs/wast-printer": "1.5.13", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz", + "integrity": "sha512-yfv94Se8R73zmr8GAYzezFHc3lDwE/lBXQddSiIZEKZFuqy7yWtm3KMwA1uGbv5G1WphimJxboXHR80IgX1hQA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz", + "integrity": "sha512-IkXSkgzVhQ0QYAdIayuCWMmXSYx0dHGU8Ah/AxJf1gBvstMWVnzJnBwLsXLyD87VSBIcsqkmZ28dVb0mOC3oBg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz", + "integrity": "sha512-XnYoIcu2iqq8/LrtmdnN3T+bRjqYFjRHqWbqK3osD/0r/Fcv4d9ecRzjVtC29ENEuNTK4mQ9yyxCBCbK8S/cpg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz", + "integrity": "sha512-Lbz65T0LQ1LgzKiUytl34CwuhMNhaCLgrh0JW4rJBN6INnBB8NMwUfQM+FxTnLY9qJ+lHJL/gCM5xYhB9oWi4A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/floating-point-hex-parser": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-code-frame": "1.5.13", + "@webassemblyjs/helper-fsm": "1.5.13", + "long": "^3.2.0", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz", + "integrity": "sha512-QcwogrdqcBh8Z+eUF8SG+ag5iwQSXxQJELBEHmLkk790wgQgnIMmntT2sMAMw53GiFNckArf5X0bsCA44j3lWQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "long": "^3.2.0" + } + }, "JSONSelect": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz", "integrity": "sha1-oI7cxn6z/L6Z7WMIVTRKDPKCu40=" }, "abab": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", - "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", "dev": true }, "abbrev": { @@ -27,25 +286,8 @@ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "dev": true, "requires": { - "mime-types": "2.1.18", + "mime-types": "~2.1.18", "negotiator": "0.6.1" - }, - "dependencies": { - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "requires": { - "mime-db": "1.33.0" - } - } } }, "access-sniff": { @@ -54,88 +296,62 @@ "integrity": "sha512-HLvH8e5g312urx6ZRo+nxSHjhVHEcuUxbpjFaFQ1LZOtN19L0CSb5ppwxtxy0QZ05zYAcWmXH6lVurdb+mGuUw==", "dev": true, "requires": { - "axios": "0.18.0", - "bluebird": "3.5.1", - "chalk": "2.3.1", - "commander": "2.14.1", - "glob": "7.1.2", - "html_codesniffer": "2.1.1", - "jsdom": "11.6.2", - "mkdirp": "0.5.1", - "phantomjs-prebuilt": "2.1.16", - "rc": "1.2.5", - "underscore": "1.8.3", - "unixify": "1.0.0", - "validator": "9.4.1" + "axios": "^0.18.0", + "bluebird": "^3.5.1", + "chalk": "^2.3.1", + "commander": "^2.14.1", + "glob": "^7.1.2", + "html_codesniffer": "^2.1.1", + "jsdom": "^11.6.2", + "mkdirp": "^0.5.1", + "phantomjs-prebuilt": "^2.1.16", + "rc": "^1.2.5", + "underscore": "^1.8.3", + "unixify": "^1.0.0", + "validator": "^9.4.1" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true - }, "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "5.2.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", "dev": true } } }, "acorn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.0.tgz", - "integrity": "sha512-arn53F07VXmls4o4pUhSzBa4fvaagPRe7AVZ8l7NHxFWUie2DsuFSBMMNAkgzRlOhEhzAnxeKyaWVzOH4xqp/g==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", "dev": true }, "acorn-dynamic-import": { @@ -144,7 +360,7 @@ "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", "dev": true, "requires": { - "acorn": "5.5.0" + "acorn": "^5.0.0" } }, "acorn-globals": { @@ -153,59 +369,33 @@ "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", "dev": true, "requires": { - "acorn": "5.5.0" + "acorn": "^5.0.0" } }, "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { - "acorn": "3.3.0" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } + "acorn": "^5.0.3" } }, "ajv": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", - "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", - "dev": true, + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "ajv-keywords": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", - "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, "amdefine": { @@ -228,14 +418,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "anymatch": { "version": "2.0.0", @@ -243,8 +431,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, "aproba": { @@ -253,13 +441,23 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { @@ -310,8 +508,8 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, "array-union": { @@ -320,7 +518,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "1.0.3" + "array-uniq": "^1.0.1" } }, "array-uniq": { @@ -341,18 +539,18 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true, - "optional": true + "arrive": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/arrive/-/arrive-2.4.1.tgz", + "integrity": "sha1-VkyH8gvAm4DeeBEk2UMWlQBLgCA=" }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "asn1.js": { "version": "4.10.1", @@ -360,9 +558,9 @@ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "assert": { @@ -372,13 +570,29 @@ "dev": true, "requires": { "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } } }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assign-symbols": { "version": "1.0.0", @@ -387,12 +601,12 @@ "dev": true }, "async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", - "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lodash": "4.17.10" + "lodash": "^4.17.10" } }, "async-each": { @@ -401,6 +615,12 @@ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true + }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", @@ -410,8 +630,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.1", @@ -420,27 +639,68 @@ "dev": true }, "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.1.0.tgz", + "integrity": "sha512-BbAIdxNdptG/x4DiGGfpkDVYyqu4nUyNdBB0Utr49Gn3+0RERV1MdHik2FSbbWwhMAuk1KrfVJHe7nEMheGdBA==", "dev": true, "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000821", - "normalize-range": "0.1.2", - "num2fraction": "1.2.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" + "browserslist": "^4.0.1", + "caniuse-lite": "^1.0.30000872", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.2", + "postcss-value-parser": "^3.2.3" }, "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "caniuse-db": "1.0.30000821", - "electron-to-chromium": "1.3.41" + "color-convert": "^1.9.0" + } + }, + "browserslist": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.0.1.tgz", + "integrity": "sha512-QqiiIWchEIkney3wY53/huI7ZErouNAdvOkjorUALAwRcu3tEwOV3Sh6He0DnP38mz1JjBpCBb50jQBmaYuHPw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000865", + "electron-to-chromium": "^1.3.52", + "node-releases": "^1.0.0-alpha.10" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "postcss": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.2.tgz", + "integrity": "sha512-fmaUY5370keLUTx+CnwRxtGiuFTcNBLQBqr1oE3WZ/euIYmGAo0OAgOhVJ3ByDnVmOR3PK+0V9VebzfjRIUcqw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } @@ -448,14 +708,12 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "dev": true + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, "axios": { "version": "0.18.0", @@ -463,19 +721,18 @@ "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "dev": true, "requires": { - "follow-redirects": "1.4.1", - "is-buffer": "1.1.6" + "follow-redirects": "^1.3.0", + "is-buffer": "^1.1.5" } }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" } }, "babel-core": { @@ -484,25 +741,33 @@ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.1", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.5.1", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.10", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "babel-generator": { @@ -511,14 +776,14 @@ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.10", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" }, "dependencies": { "jsesc": { @@ -526,6 +791,12 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -535,9 +806,9 @@ "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", "dev": true, "requires": { - "babel-helper-explode-assignable-expression": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-call-delegate": { @@ -546,10 +817,10 @@ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-define-map": { @@ -558,10 +829,10 @@ "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.10" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, "babel-helper-explode-assignable-expression": { @@ -570,9 +841,9 @@ "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-function-name": { @@ -581,11 +852,11 @@ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-get-function-arity": { @@ -594,8 +865,8 @@ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-hoist-variables": { @@ -604,8 +875,8 @@ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-optimise-call-expression": { @@ -614,8 +885,8 @@ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-regex": { @@ -624,9 +895,9 @@ "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.10" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, "babel-helper-remap-async-to-generator": { @@ -635,11 +906,11 @@ "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-replace-supers": { @@ -648,12 +919,12 @@ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { - "babel-helper-optimise-call-expression": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helpers": { @@ -662,28 +933,27 @@ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-loader": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.4.tgz", - "integrity": "sha512-/hbyEvPzBJuGpk9o80R0ZyTej6heEOr59GoEUtn8qFKbnx4cJm9FWES6J/iv644sYgrtVw9JJQkjaLW/bqb5gw==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.5.tgz", + "integrity": "sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw==", "dev": true, "requires": { - "find-cache-dir": "1.0.0", - "loader-utils": "1.1.0", - "mkdirp": "0.5.1" + "find-cache-dir": "^1.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1" } }, "babel-messages": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-check-es2015-constants": { @@ -692,7 +962,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-syntax-async-functions": { @@ -719,9 +989,18 @@ "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", "dev": true, "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-functions": "6.13.0", - "babel-runtime": "6.26.0" + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-builtin-extend": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.2.tgz", + "integrity": "sha1-Xpb+z1i4+h7XTvytiEdbKvPJEW4=", + "requires": { + "babel-runtime": "^6.2.0", + "babel-template": "^6.3.0" } }, "babel-plugin-transform-es2015-arrow-functions": { @@ -730,7 +1009,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-block-scoped-functions": { @@ -739,7 +1018,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-block-scoping": { @@ -748,11 +1027,11 @@ "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.10" + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, "babel-plugin-transform-es2015-classes": { @@ -761,15 +1040,15 @@ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "babel-helper-define-map": "6.26.0", - "babel-helper-function-name": "6.24.1", - "babel-helper-optimise-call-expression": "6.24.1", - "babel-helper-replace-supers": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-computed-properties": { @@ -778,8 +1057,8 @@ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-destructuring": { @@ -788,7 +1067,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-duplicate-keys": { @@ -797,8 +1076,8 @@ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-for-of": { @@ -807,7 +1086,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-function-name": { @@ -816,9 +1095,9 @@ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-literals": { @@ -827,7 +1106,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-modules-amd": { @@ -836,21 +1115,21 @@ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", - "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", "dev": true, "requires": { - "babel-plugin-transform-strict-mode": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" } }, "babel-plugin-transform-es2015-modules-systemjs": { @@ -859,9 +1138,9 @@ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-modules-umd": { @@ -870,9 +1149,9 @@ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-object-super": { @@ -881,8 +1160,8 @@ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.26.0" + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-parameters": { @@ -891,12 +1170,12 @@ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", "dev": true, "requires": { - "babel-helper-call-delegate": "6.24.1", - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-shorthand-properties": { @@ -905,8 +1184,8 @@ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-spread": { @@ -915,7 +1194,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-sticky-regex": { @@ -924,9 +1203,9 @@ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", "dev": true, "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-template-literals": { @@ -935,7 +1214,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-typeof-symbol": { @@ -944,7 +1223,7 @@ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-unicode-regex": { @@ -953,9 +1232,9 @@ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", "dev": true, "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "regexpu-core": "2.0.0" + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" } }, "babel-plugin-transform-exponentiation-operator": { @@ -964,9 +1243,9 @@ "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", "dev": true, "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", - "babel-plugin-syntax-exponentiation-operator": "6.13.0", - "babel-runtime": "6.26.0" + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-regenerator": { @@ -975,7 +1254,7 @@ "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", "dev": true, "requires": { - "regenerator-transform": "0.10.1" + "regenerator-transform": "^0.10.0" } }, "babel-plugin-transform-strict-mode": { @@ -984,8 +1263,8 @@ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-polyfill": { @@ -993,9 +1272,9 @@ "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "requires": { - "babel-runtime": "6.26.0", - "core-js": "2.5.3", - "regenerator-runtime": "0.10.5" + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" }, "dependencies": { "regenerator-runtime": { @@ -1006,41 +1285,41 @@ } }, "babel-preset-env": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", - "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0", - "browserslist": "2.11.3", - "invariant": "2.2.3", - "semver": "5.4.1" + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.23.0", + "babel-plugin-transform-es2015-classes": "^6.23.0", + "babel-plugin-transform-es2015-computed-properties": "^6.22.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", + "babel-plugin-transform-es2015-for-of": "^6.23.0", + "babel-plugin-transform-es2015-function-name": "^6.22.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-umd": "^6.23.0", + "babel-plugin-transform-es2015-object-super": "^6.22.0", + "babel-plugin-transform-es2015-parameters": "^6.23.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", + "babel-plugin-transform-exponentiation-operator": "^6.22.0", + "babel-plugin-transform-regenerator": "^6.22.0", + "browserslist": "^3.2.6", + "invariant": "^2.2.2", + "semver": "^5.3.0" } }, "babel-register": { @@ -1049,13 +1328,13 @@ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "6.26.3", - "babel-runtime": "6.26.0", - "core-js": "2.5.3", - "home-or-tmp": "2.0.0", - "lodash": "4.17.10", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" } }, "babel-runtime": { @@ -1063,57 +1342,53 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.3", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-template": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.10" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" } }, "babel-traverse": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.3", - "lodash": "4.17.10" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" } }, "babel-types": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.10", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" }, "balanced-match": { "version": "1.0.0", @@ -1127,13 +1402,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "dependencies": { "define-property": { @@ -1142,7 +1417,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -1151,7 +1426,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -1160,7 +1435,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -1169,16 +1444,10 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -1195,13 +1464,12 @@ "dev": true }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bcryptjs": { @@ -1216,9 +1484,9 @@ "dev": true }, "bignumber.js": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.0.1.tgz", - "integrity": "sha512-orXkDA6dhvrCTxYkWMDLIu8R1XWKfPWoJCkFeXOi/Rybl0FVUGHvgDYgUkWVn8fGa5mw2xy25VQGPPmrxfoZkQ==" + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" }, "binary-extensions": { "version": "1.11.0", @@ -1226,10 +1494,19 @@ "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, "bluebird": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", - "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", "dev": true }, "bn": { @@ -1243,49 +1520,90 @@ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true }, - "body-parser": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", - "integrity": "sha1-EBXLH+LEQ4WCWVgdtTMy+NDPUPk=", + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", "dev": true, "requires": { - "bytes": "2.2.0", - "content-type": "1.0.4", - "debug": "2.2.0", - "depd": "1.1.2", - "http-errors": "1.3.1", - "iconv-lite": "0.4.13", - "on-finished": "2.3.0", - "qs": "5.2.0", - "raw-body": "2.1.7", - "type-is": "1.6.16" + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" }, "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "requires": { - "ms": "0.7.1" - } - }, - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", "dev": true }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", "dev": true }, "qs": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", - "integrity": "sha1-qfMRQq9GjLcrJbMBNrokVoNJFr4=", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + } + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", "dev": true } } @@ -1296,12 +1614,12 @@ "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", "dev": true, "requires": { - "array-flatten": "2.1.1", - "deep-equal": "1.0.1", - "dns-equal": "1.0.0", - "dns-txt": "2.0.2", - "multicast-dns": "6.2.3", - "multicast-dns-service-types": "1.1.0" + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" } }, "boolbase": { @@ -1311,39 +1629,40 @@ "dev": true }, "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true, "requires": { - "hoek": "4.2.0" + "hoek": "2.x.x" } }, "bootstrap": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", - "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.3.tgz", + "integrity": "sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w==", + "dev": true }, "bootstrap-colorpicker": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/bootstrap-colorpicker/-/bootstrap-colorpicker-2.5.2.tgz", - "integrity": "sha512-krzBno9AMUwI2+IDwMvjnpqpa2f8womW0CCKmEcxGzVkolCFrt22jjMjzx1NZqB8C1DUdNgZP4LfyCsgpHRiYA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/bootstrap-colorpicker/-/bootstrap-colorpicker-2.5.3.tgz", + "integrity": "sha512-xdllX8LSMvKULs3b8JrgRXTvyvjkSMHHHVuHjjN5FNMqr6kRe5NPiMHFmeAFjlgDF73MspikudLuEwR28LbzLw==", "requires": { - "jquery": "3.3.1" + "jquery": ">=1.10" } }, - "bootstrap-switch": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/bootstrap-switch/-/bootstrap-switch-3.3.4.tgz", - "integrity": "sha1-cOCusqh3wNx2aZHeEI4hcPwpov8=" + "bootstrap-material-design": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bootstrap-material-design/-/bootstrap-material-design-4.1.1.tgz", + "integrity": "sha1-h0M9sL9k1qCvsPX6qoYGE0ydJtI=" }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -1353,16 +1672,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -1371,7 +1690,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -1394,12 +1713,12 @@ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "browserify-cipher": { @@ -1408,20 +1727,21 @@ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "dev": true, "requires": { - "browserify-aes": "1.2.0", - "browserify-des": "1.0.1", - "evp_bytestokey": "1.0.3" + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, "browserify-des": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.1.tgz", - "integrity": "sha512-zy0Cobe3hhgpiOM32Tj7KQ3Vl91m0njwsjzZQK1L+JDf11dzP9qIvjreVinsvXrgfjhStXwUWAEpB9D7Gwmayw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "browserify-rsa": { @@ -1430,8 +1750,8 @@ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" } }, "browserify-sign": { @@ -1440,46 +1760,38 @@ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "dev": true, "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.1" + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" } }, "browserify-zlib": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", - "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "pako": "0.2.9" + "pako": "~1.0.5" } }, "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000810", - "electron-to-chromium": "1.3.34" - }, - "dependencies": { - "electron-to-chromium": { - "version": "1.3.34", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.34.tgz", - "integrity": "sha1-2TSY9AORuwwWpgPYJBuZUUBBV+0=", - "dev": true - } + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" } }, "bson": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-2.0.6.tgz", - "integrity": "sha512-DH9Xvo+zN7PnS6rmQauNWLZqICiwOXygoh0nppbJrcpGv6XUyon6S/rGSvUkR3SdyqHQ6YjSkxmSnmqOhUZNew==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-3.0.2.tgz", + "integrity": "sha512-HrDzr7y/ZkgyEVancPVDmfbaD8j81GzSNr6h6yUd/yZfavkrlrqI8aUZMCHrhyMoCW2/I+vEJDat1xDWRwVR6A==" }, "buffer": { "version": "4.9.1", @@ -1487,11 +1799,16 @@ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "base64-js": "1.3.0", - "ieee754": "1.1.11", - "isarray": "1.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, "buffer-indexof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", @@ -1517,9 +1834,9 @@ "dev": true }, "bytes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", - "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", "dev": true }, "bzip-deflate": { @@ -1533,49 +1850,26 @@ "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", "dev": true, "requires": { - "bluebird": "3.5.1", - "chownr": "1.0.1", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "lru-cache": "4.1.1", - "mississippi": "2.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.6.2", - "ssri": "5.3.0", - "unique-filename": "1.1.0", - "y18n": "4.0.0" + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" }, "dependencies": { - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.1.2" - } } } }, @@ -1585,15 +1879,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.2.1", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" } }, "caller-path": { @@ -1602,7 +1896,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "0.2.0" + "callsites": "^0.2.0" } }, "callsites": { @@ -1617,8 +1911,8 @@ "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", "dev": true, "requires": { - "no-case": "2.3.2", - "upper-case": "1.1.3" + "no-case": "^2.2.0", + "upper-case": "^1.1.1" } }, "camelcase": { @@ -1633,51 +1927,20 @@ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" } }, - "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000821", - "lodash.memoize": "4.1.2", - "lodash.uniq": "4.5.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000821", - "electron-to-chromium": "1.3.41" - } - } - } - }, - "caniuse-db": { - "version": "1.0.30000821", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000821.tgz", - "integrity": "sha1-P83GfERqlKnN2Egkik4+VLLadBk=", - "dev": true - }, "caniuse-lite": { - "version": "1.0.30000810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000810.tgz", - "integrity": "sha512-/0Q00Oie9C72P8zQHtFvzmkrMC3oOFUnMWjCy5F2+BE8lzICm91hQPhh0+XIsAFPKOe2Dh3pKgbRmU3EKxfldA==", + "version": "1.0.30000874", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000874.tgz", + "integrity": "sha512-29nr1EPiHwrJTAHHsEmTt2h+55L8j2GNFdAcYPlRy2NX6iFz7ZZiepVI7kP/QqlnHLq3KvfWpbmGa0d063U09w==", "dev": true }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "catharsis": { "version": "0.8.9", @@ -1685,30 +1948,19 @@ "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", "dev": true, "requires": { - "underscore-contrib": "0.3.0" - } - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "underscore-contrib": "~0.3.0" } }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "chardet": { @@ -1717,24 +1969,33 @@ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", "dev": true }, + "chi-squared": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chi-squared/-/chi-squared-1.1.0.tgz", + "integrity": "sha1-iShlz/qOCnIPkhv8nGNcGawqNG0=", + "requires": { + "gamma": "^1.0.0" + } + }, "chokidar": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", - "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.2", - "fsevents": "1.2.3", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.0.5" + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" } }, "chownr": { @@ -1744,10 +2005,13 @@ "dev": true }, "chrome-trace-event": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-0.1.3.tgz", - "integrity": "sha512-sjndyZHrrWiu4RY7AkHgjn80GfAM2ZSzUkZLV/Js59Ldmh6JDThf0SUmOHU53rFu2rVxxfCzJ30Ukcfch3Gb/A==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", + "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } }, "cipher-base": { "version": "1.0.4", @@ -1755,8 +2019,8 @@ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "circular-json": { @@ -1770,25 +2034,16 @@ "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.2.1.tgz", "integrity": "sha1-c82KrWXZ4VBfmvF0TTt5wVJ2gqU=" }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "1.1.3" - } - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "dependencies": { "define-property": { @@ -1797,7 +2052,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -1808,7 +2063,15 @@ "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "0.5.x" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "cli": { @@ -1818,23 +2081,7 @@ "dev": true, "requires": { "exit": "0.1.2", - "glob": "7.1.2" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } + "glob": "^7.1.1" } }, "cli-cursor": { @@ -1843,7 +2090,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "2.0.0" + "restore-cursor": "^2.0.0" } }, "cli-width": { @@ -1853,44 +2100,54 @@ "dev": true }, "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" }, "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } } } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + } }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "dev": true, - "requires": { - "q": "1.5.1" - } + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "code-point-at": { "version": "1.1.0", @@ -1910,74 +2167,43 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" - } - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "1.0.4", - "color-convert": "1.9.0", - "color-string": "0.3.0" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" } }, "color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "1.1.1" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", "dev": true }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "0.11.4", - "css-color-names": "0.0.4", - "has": "1.0.1" - } - }, "colors": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", - "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.1.tgz", + "integrity": "sha512-jg/vxRmv430jixZrC+La5kMbUWqIg32/JsYNZb94+JEmzceYbWKTsv1OuTp+7EaqiaWRR2tPcykibwCRgclIsw==", + "dev": true }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "dev": true, + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", - "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.0.tgz", + "integrity": "sha512-477o1hdVORiFlZxw8wgsXYCef3lh0zl/OV0FTftqiDxJSWw6dPQ2ipS4k20J2qBcsmsmLKSyr2iFrf9e3JGi4w==", "dev": true }, "commondir": { @@ -1993,35 +2219,27 @@ "dev": true }, "compressible": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", - "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz", + "integrity": "sha1-MmxfUH+7BV9UEWeCuWmoG2einac=", "dev": true, "requires": { - "mime-db": "1.33.0" - }, - "dependencies": { - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - } + "mime-db": ">= 1.34.0 < 2" } }, "compression": { - "version": "1.7.2", - "resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz", - "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "2.0.13", + "compressible": "~2.0.14", "debug": "2.6.9", - "on-headers": "1.0.1", - "safe-buffer": "5.1.1", - "vary": "1.1.2" + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" }, "dependencies": { "bytes": { @@ -2039,14 +2257,14 @@ "dev": true }, "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true, + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "typedarray": "0.0.6" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, "connect-history-api-fallback": { @@ -2061,9 +2279,15 @@ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "date-now": "0.1.4" + "date-now": "^0.1.4" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -2082,10 +2306,10 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, - "content-type-parser": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz", - "integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ==", + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", "dev": true }, "convert-source-map": { @@ -2112,23 +2336,12 @@ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": true, "requires": { - "aproba": "1.2.0", - "fs-write-stream-atomic": "1.0.10", - "iferr": "0.1.5", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "run-queue": "1.0.3" - }, - "dependencies": { - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.0.6" - } - } + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" } }, "copy-descriptor": { @@ -2138,47 +2351,47 @@ "dev": true }, "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz", - "integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", + "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", "dev": true, "requires": { - "is-directory": "0.3.1", - "js-yaml": "3.7.0", - "minimist": "1.2.0", - "object-assign": "4.1.1", - "os-homedir": "1.0.2", - "parse-json": "2.2.0", - "require-from-string": "1.2.1" + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0", + "require-from-string": "^2.0.1" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } } } }, "create-ecdh": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.1.tgz", - "integrity": "sha512-iZvCCg8XqHQZ1ioNBTzXS/cQSkqkqcPs8xSX4upNB+DAk9Ht3uzQf2J32uAHNCne8LDmKr29AgZrEs4oIrwLuQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "dev": true, "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" } }, "create-hash": { @@ -2187,11 +2400,11 @@ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "md5.js": "1.3.4", - "ripemd160": "2.0.2", - "sha.js": "2.4.11" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" } }, "create-hmac": { @@ -2200,43 +2413,34 @@ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "inherits": "2.0.3", - "ripemd160": "2.0.2", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.2.14" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "dev": true, "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - } + "boom": "2.x.x" } }, "crypto-api": { @@ -2250,17 +2454,17 @@ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { - "browserify-cipher": "1.0.1", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "diffie-hellman": "5.0.3", - "inherits": "2.0.3", - "pbkdf2": "3.0.16", - "public-encrypt": "4.0.2", - "randombytes": "2.0.6", - "randomfill": "1.0.4" + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" } }, "crypto-js": { @@ -2268,32 +2472,24 @@ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true - }, "css-loader": { - "version": "0.28.11", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", - "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.0.tgz", + "integrity": "sha512-tMXlTYf3mIMt3b0dDCOQFJiVvxbocJ5Ho577WiGPYPZcqVEO218L2iU22pDXzkTZCLDE+9AmGSUkWxeh/nZReA==", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "css-selector-tokenizer": "0.7.0", - "cssnano": "3.10.0", - "icss-utils": "2.1.0", - "loader-utils": "1.1.0", - "lodash.camelcase": "4.3.0", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-modules-extract-imports": "1.2.0", - "postcss-modules-local-by-default": "1.2.0", - "postcss-modules-scope": "1.1.0", - "postcss-modules-values": "1.3.0", - "postcss-value-parser": "3.3.0", - "source-list-map": "2.0.0" + "babel-code-frame": "^6.26.0", + "css-selector-tokenizer": "^0.7.0", + "icss-utils": "^2.1.0", + "loader-utils": "^1.0.2", + "lodash.camelcase": "^4.3.0", + "postcss": "^6.0.23", + "postcss-modules-extract-imports": "^1.2.0", + "postcss-modules-local-by-default": "^1.2.0", + "postcss-modules-scope": "^1.1.0", + "postcss-modules-values": "^1.3.0", + "postcss-value-parser": "^3.3.0", + "source-list-map": "^2.0.0" } }, "css-select": { @@ -2302,10 +2498,10 @@ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", + "boolbase": "~1.0.0", + "css-what": "2.1", "domutils": "1.5.1", - "nth-check": "1.0.1" + "nth-check": "~1.0.1" } }, "css-selector-tokenizer": { @@ -2314,9 +2510,9 @@ "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", "dev": true, "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" }, "dependencies": { "regexpu-core": { @@ -2325,9 +2521,9 @@ "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "dev": true, "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" } } } @@ -2344,69 +2540,19 @@ "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", "dev": true }, - "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", - "dev": true, - "requires": { - "autoprefixer": "6.7.7", - "decamelize": "1.2.0", - "defined": "1.0.0", - "has": "1.0.1", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-calc": "5.3.1", - "postcss-colormin": "2.2.2", - "postcss-convert-values": "2.6.1", - "postcss-discard-comments": "2.0.4", - "postcss-discard-duplicates": "2.1.0", - "postcss-discard-empty": "2.1.0", - "postcss-discard-overridden": "0.1.1", - "postcss-discard-unused": "2.2.3", - "postcss-filter-plugins": "2.0.2", - "postcss-merge-idents": "2.1.7", - "postcss-merge-longhand": "2.0.2", - "postcss-merge-rules": "2.1.2", - "postcss-minify-font-values": "1.0.5", - "postcss-minify-gradients": "1.0.5", - "postcss-minify-params": "1.2.2", - "postcss-minify-selectors": "2.1.1", - "postcss-normalize-charset": "1.1.1", - "postcss-normalize-url": "3.0.8", - "postcss-ordered-values": "2.2.3", - "postcss-reduce-idents": "2.4.0", - "postcss-reduce-initial": "1.0.1", - "postcss-reduce-transforms": "1.0.4", - "postcss-svgo": "2.1.6", - "postcss-unique-selectors": "2.0.2", - "postcss-value-parser": "3.3.0", - "postcss-zindex": "2.2.0" - } - }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", - "dev": true, - "requires": { - "clap": "1.2.3", - "source-map": "0.5.7" - } - }, "cssom": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", + "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", "dev": true }, "cssstyle": { - "version": "0.2.37", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", - "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.0.0.tgz", + "integrity": "sha512-Bpuh47j2mRMY60X90mXaJAEtJwxvA2roZzbgwAXYhMbmwmakdRr4Cq9L5SkleKJNLOKqHIa2YWyOXDX3VgggSQ==", "dev": true, "requires": { - "cssom": "0.3.2" + "cssom": "0.3.x" } }, "ctph.js": { @@ -2420,9 +2566,15 @@ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "array-find-index": "1.0.2" + "array-find-index": "^1.0.1" } }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", + "dev": true + }, "cyclist": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", @@ -2435,35 +2587,45 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.42" + "es5-ext": "^0.10.9" } }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.0.tgz", + "integrity": "sha512-ai40PPQR0Fn1lD2PPie79CibnlMN2AYiDhwFX/rZHVsxbs5kNJSjegqXIprhouGXlRdEnfybva7kqRGnB6mypA==", "dev": true, "requires": { - "assert-plus": "1.0.0" + "abab": "^1.0.4", + "whatwg-mimetype": "^2.0.0", + "whatwg-url": "^6.4.0" + }, + "dependencies": { + "abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", + "dev": true + } } }, "datauri": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/datauri/-/datauri-1.0.5.tgz", - "integrity": "sha1-0JddGrbI8uDOPKQ7qkU5vhLSiaA=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/datauri/-/datauri-1.1.0.tgz", + "integrity": "sha512-0q+cTTKx7q8eDteZRIQLTFJuiIsVing17UbWTPssY4JLSMaYsk/VKpNulBDo9NSgQWcvlPrkEHW8kUO67T/7mQ==", "dev": true, "requires": { - "image-size": "0.3.5", - "mimer": "0.2.3", - "semver": "5.4.1" - }, - "dependencies": { - "image-size": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.3.5.tgz", - "integrity": "sha1-gyQOqy+1sAsEqrjHSwRx6cunrYw=", - "dev": true - } + "image-size": "^0.6.2", + "mimer": "^0.3.2", + "semver": "^5.5.0" } }, "date-now": { @@ -2478,15 +2640,14 @@ "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", "dev": true, "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" + "get-stdin": "^4.0.1", + "meow": "^3.3.0" } }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -2509,9 +2670,9 @@ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "deep-for-each": { @@ -2520,7 +2681,7 @@ "integrity": "sha512-Y9mu+rplGcNZ7veer+5rqcdI9w3aPb7/WyE/nYnsuPevaE2z5YuC2u7/Gz/hIKsa0zo8sE8gKoBimSNsO/sr+A==", "dev": true, "requires": { - "lodash.isplainobject": "4.0.6" + "lodash.isplainobject": "^4.0.6" } }, "deep-is": { @@ -2534,8 +2695,8 @@ "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", "dev": true, "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" + "foreach": "^2.0.5", + "object-keys": "^1.0.8" } }, "define-property": { @@ -2544,8 +2705,8 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -2554,7 +2715,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -2563,7 +2724,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -2572,44 +2733,45 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, "del": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.2.8" + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, "depd": { @@ -2624,8 +2786,8 @@ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "destroy": { @@ -2640,7 +2802,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "detect-node": { @@ -2660,9 +2822,9 @@ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, "dns-equal": { @@ -2677,8 +2839,8 @@ "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", "dev": true, "requires": { - "ip": "1.1.5", - "safe-buffer": "5.1.1" + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" } }, "dns-txt": { @@ -2687,7 +2849,7 @@ "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", "dev": true, "requires": { - "buffer-indexof": "1.1.1" + "buffer-indexof": "^1.0.0" } }, "doctrine": { @@ -2696,7 +2858,7 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "2.0.2" + "esutils": "^2.0.2" } }, "dom-converter": { @@ -2705,7 +2867,7 @@ "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", "dev": true, "requires": { - "utila": "0.3.3" + "utila": "~0.3" }, "dependencies": { "utila": { @@ -2722,8 +2884,8 @@ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "dev": true, "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" + "domelementtype": "~1.1.1", + "entities": "~1.1.1" }, "dependencies": { "domelementtype": { @@ -2731,6 +2893,12 @@ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", "dev": true + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true } } }, @@ -2752,7 +2920,7 @@ "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", "dev": true, "requires": { - "webidl-conversions": "4.0.2" + "webidl-conversions": "^4.0.2" } }, "domhandler": { @@ -2761,7 +2929,7 @@ "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", "dev": true, "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } }, "domutils": { @@ -2770,20 +2938,20 @@ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" + "dom-serializer": "0", + "domelementtype": "1" } }, "duplexify": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz", - "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", + "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "dev": true, "requires": { - "end-of-stream": "1.4.0", - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "stream-shift": "1.0.0" + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" } }, "ebnf-parser": { @@ -2792,20 +2960,19 @@ "integrity": "sha1-zR9rpHfFY4xAyX7ZtXLbW6tdgzE=" }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" }, "dependencies": { "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true } } @@ -2817,9 +2984,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.41.tgz", - "integrity": "sha1-fjNkPgDNhe39F+BBlPbQDnNzcjU=", + "version": "1.3.55", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.55.tgz", + "integrity": "sha1-8VDhCyC3fZ1Br8yjEu/gw7Gn/c4=", "dev": true }, "elliptic": { @@ -2828,13 +2995,13 @@ "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", "dev": true, "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" } }, "emojis-list": { @@ -2850,29 +3017,29 @@ "dev": true }, "end-of-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", - "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "enhanced-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.0.0.tgz", - "integrity": "sha512-jox/62b2GofV1qTUQTMPEJSDIGycS43evqYzD/KVtEb9OCoki9cnacUPxCrZa7JfPzZSYOCZhu9O9luaMxAX8g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "tapable": "1.0.0" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" } }, "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", "dev": true }, "errno": { @@ -2881,29 +3048,39 @@ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { - "prr": "1.0.1" + "prr": "~1.0.1" + } + }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "dev": true, + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" } }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es-abstract": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", - "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "dev": true, "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.1", - "is-callable": "1.1.3", - "is-regex": "1.0.4" + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" } }, "es-to-primitive": { @@ -2912,20 +3089,20 @@ "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", "dev": true, "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" } }, "es5-ext": { - "version": "0.10.42", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", - "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", + "version": "0.10.45", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz", + "integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==", "dev": true, "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" } }, "es6-iterator": { @@ -2934,9 +3111,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, "es6-object-assign": { @@ -2949,15 +3126,14 @@ "resolved": "https://registry.npmjs.org/es6-polyfills/-/es6-polyfills-2.0.0.tgz", "integrity": "sha1-fzWP04jYyIjQDPyaHuqJ+XFoOTE=", "requires": { - "es6-object-assign": "1.1.0", - "es6-promise-polyfill": "1.2.0" + "es6-object-assign": "^1.0.3", + "es6-promise-polyfill": "^1.2.0" } }, "es6-promise": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", - "integrity": "sha1-eILzCt3lskDM+n99eMVIMwlRrkI=", - "dev": true + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" }, "es6-promise-polyfill": { "version": "1.2.0", @@ -2975,8 +3151,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42" + "d": "1", + "es5-ext": "~0.10.14" } }, "escape-html": { @@ -2988,31 +3164,24 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", - "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", + "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", "requires": { - "esprima": "3.1.3", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.6.1" + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, "dependencies": { "esprima": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true } } }, @@ -3021,7 +3190,7 @@ "resolved": "https://registry.npmjs.org/escope/-/escope-1.0.3.tgz", "integrity": "sha1-dZ3OhJbEJI/sLQyq9BCLzz8af10=", "requires": { - "estraverse": "2.0.0" + "estraverse": "^2.0.0" }, "dependencies": { "estraverse": { @@ -3032,61 +3201,62 @@ } }, "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.3.0.tgz", + "integrity": "sha512-N/tCqlMKkyNvAvLu+zI9AqDasnSLt00K+Hu8kdsERliC9jYEc8ck12XtjvOXrBKu8fK6RrBcN9bat6Xk++9jAg==", "dev": true, "requires": { - "ajv": "5.5.2", - "babel-code-frame": "6.26.0", - "chalk": "2.3.2", - "concat-stream": "1.6.0", - "cross-spawn": "5.1.0", - "debug": "3.1.0", - "doctrine": "2.1.0", - "eslint-scope": "3.7.1", - "eslint-visitor-keys": "1.0.0", - "espree": "3.5.4", - "esquery": "1.0.0", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "functional-red-black-tree": "1.0.1", - "glob": "7.1.2", - "globals": "11.4.0", - "ignore": "3.3.7", - "imurmurhash": "0.1.4", - "inquirer": "3.3.0", - "is-resolvable": "1.1.0", - "js-yaml": "3.11.0", - "json-stable-stringify-without-jsonify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.10", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "pluralize": "7.0.0", - "progress": "2.0.0", - "regexpp": "1.0.1", - "require-uncached": "1.0.3", - "semver": "5.4.1", - "strip-ansi": "4.0.0", - "strip-json-comments": "2.0.1", - "table": "4.0.2", - "text-table": "0.2.0" + "ajv": "^6.5.0", + "babel-code-frame": "^6.26.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.2", + "imurmurhash": "^0.1.4", + "inquirer": "^5.2.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.11.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.0", + "require-uncached": "^1.0.3", + "semver": "^5.5.0", + "string.prototype.matchall": "^2.0.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^4.0.3", + "text-table": "^0.2.0" }, "dependencies": { "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", + "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" } }, "ansi-regex": { @@ -3101,18 +3271,18 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "debug": { @@ -3124,42 +3294,24 @@ "ms": "2.0.0" } }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "globals": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.4.0.tgz", - "integrity": "sha512-Dyzmifil8n/TmSqYDEXbm+C8yitzJQqQIlJQLNRMwa+BOUJpRC19pyVeN12JAjt61xonvXjtff+hJruTRXn5HA==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", "dev": true }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", - "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" - } - }, "progress": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", @@ -3172,30 +3324,36 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } }, "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -3207,14 +3365,14 @@ "resolved": "https://registry.npmjs.org/esmangle/-/esmangle-1.0.1.tgz", "integrity": "sha1-2bs3uPjq+/Tm1O1reqKVarvTxMI=", "requires": { - "escodegen": "1.3.3", - "escope": "1.0.3", - "esprima": "1.1.1", - "esshorten": "1.1.1", - "estraverse": "1.5.1", - "esutils": "1.0.0", - "optionator": "0.3.0", - "source-map": "0.1.43" + "escodegen": "~1.3.2", + "escope": "~1.0.1", + "esprima": "~1.1.1", + "esshorten": "~1.1.0", + "estraverse": "~1.5.0", + "esutils": "~ 1.0.0", + "optionator": "~0.3.0", + "source-map": "~0.1.33" }, "dependencies": { "escodegen": { @@ -3222,10 +3380,10 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", "integrity": "sha1-8CQBb1qI4Eb9EgBQVek5gC5sXyM=", "requires": { - "esprima": "1.1.1", - "estraverse": "1.5.1", - "esutils": "1.0.0", - "source-map": "0.1.43" + "esprima": "~1.1.1", + "estraverse": "~1.5.0", + "esutils": "~1.0.0", + "source-map": "~0.1.33" } }, "esprima": { @@ -3253,8 +3411,8 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz", "integrity": "sha1-uo0znQykphDjo/FFucr0iAcVUFQ=", "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.0", + "type-check": "~0.3.1" } }, "optionator": { @@ -3262,12 +3420,12 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.3.0.tgz", "integrity": "sha1-lxWotfXnWGz/BsgkngOc1zZNP1Q=", "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "1.0.7", - "levn": "0.2.5", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "0.0.3" + "deep-is": "~0.1.2", + "fast-levenshtein": "~1.0.0", + "levn": "~0.2.4", + "prelude-ls": "~1.1.0", + "type-check": "~0.3.1", + "wordwrap": "~0.0.2" } }, "source-map": { @@ -3275,7 +3433,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } }, "wordwrap": { @@ -3286,27 +3444,27 @@ } }, "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", + "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", "dev": true, "requires": { - "acorn": "5.5.0", - "acorn-jsx": "3.0.1" + "acorn": "^5.6.0", + "acorn-jsx": "^4.1.1" } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.0.0" } }, "esrecurse": { @@ -3315,7 +3473,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.1.0" } }, "esshorten": { @@ -3323,9 +3481,9 @@ "resolved": "https://registry.npmjs.org/esshorten/-/esshorten-1.1.1.tgz", "integrity": "sha1-F0+Wt8wmfkaHLYFOfbfCkL3/Yak=", "requires": { - "escope": "1.0.3", - "estraverse": "4.1.1", - "esutils": "2.0.2" + "escope": "~1.0.1", + "estraverse": "~4.1.1", + "esutils": "~2.0.2" }, "dependencies": { "estraverse": { @@ -3375,7 +3533,7 @@ "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", "dev": true, "requires": { - "original": "1.0.0" + "original": ">=0.0.5" } }, "evp_bytestokey": { @@ -3384,8 +3542,8 @@ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "md5.js": "1.3.4", - "safe-buffer": "5.1.1" + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" } }, "execa": { @@ -3394,13 +3552,26 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } } }, "exif-parser": { @@ -3420,13 +3591,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -3435,7 +3606,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -3444,7 +3615,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -3455,7 +3626,7 @@ "integrity": "sha512-RKwCrO4A6IiKm0pG3c9V46JxIHcDplwwGJn6+JJ1RcVnh/WSGJa0xkmk5cRVtgOPzCAtTMGj2F7nluh9L0vpSA==", "dev": true, "requires": { - "loader-utils": "1.1.0", + "loader-utils": "^1.1.0", "source-map": "0.5.0" }, "dependencies": { @@ -3473,36 +3644,36 @@ "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.5", "array-flatten": "1.1.1", "body-parser": "1.18.2", "content-disposition": "0.5.2", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.3", + "proxy-addr": "~2.0.3", "qs": "6.5.1", - "range-parser": "1.2.0", + "range-parser": "~1.2.0", "safe-buffer": "5.1.1", "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "1.4.0", - "type-is": "1.6.16", + "statuses": "~1.4.0", + "type-is": "~1.6.16", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" }, "dependencies": { "array-flatten": { @@ -3511,87 +3682,24 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "1.0.4", - "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.3", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.16" - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", "dev": true }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": "1.4.0" - } - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.4.0" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true - } - } + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true } } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -3599,8 +3707,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -3609,20 +3717,20 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } }, "external-editor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", - "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { - "chardet": "0.4.2", - "iconv-lite": "0.4.19", - "tmp": "0.0.33" + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" } }, "extglob": { @@ -3631,14 +3739,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -3647,7 +3755,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -3656,7 +3764,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -3665,7 +3773,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -3674,7 +3782,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -3683,16 +3791,10 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -3702,91 +3804,43 @@ "integrity": "sha512-Hypkn9jUTnFr0DpekNam53X47tXn3ucY08BQumv7kdGgeVUBLq3DJHJTi6HNxv4jl9W+Skxjz9+RnK0sJyqqjA==", "dev": true, "requires": { - "async": "2.5.0", - "loader-utils": "1.1.0", - "schema-utils": "0.4.5", - "webpack-sources": "1.1.0" - }, - "dependencies": { - "ajv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.2.0.tgz", - "integrity": "sha1-r6wpW7qgFSRJ5SJ0LkVHwa6TKNI=", - "dev": true, - "requires": { - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", - "dev": true, - "requires": { - "ajv": "6.2.0", - "ajv-keywords": "3.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", - "dev": true, - "requires": { - "source-list-map": "2.0.0", - "source-map": "0.6.1" - } - } + "async": "^2.4.1", + "loader-utils": "^1.1.0", + "schema-utils": "^0.4.5", + "webpack-sources": "^1.1.0" } }, "extract-zip": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz", - "integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=", - "dev": true, + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", "requires": { - "concat-stream": "1.6.0", + "concat-stream": "1.6.2", "debug": "2.6.9", - "mkdirp": "0.5.0", + "mkdirp": "0.5.1", "yauzl": "2.4.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", - "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } } }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", "dev": true }, "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", @@ -3805,16 +3859,15 @@ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", "dev": true, "requires": { - "websocket-driver": "0.7.0" + "websocket-driver": ">=0.5.1" } }, "fd-slicer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, "requires": { - "pend": "1.2.0" + "pend": "~1.2.0" } }, "figures": { @@ -3823,7 +3876,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { @@ -3832,8 +3885,8 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" } }, "file-loader": { @@ -3842,32 +3895,8 @@ "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" - }, - "dependencies": { - "ajv": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", - "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", - "dev": true, - "requires": { - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1", - "uri-js": "3.0.2" - } - }, - "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", - "dev": true, - "requires": { - "ajv": "6.4.0", - "ajv-keywords": "3.1.0" - } - } + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" } }, "file-saver": { @@ -3887,10 +3916,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -3899,7 +3928,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -3911,12 +3940,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.4.0", - "unpipe": "1.0.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" } }, "find-cache-dir": { @@ -3925,9 +3954,9 @@ "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", "dev": true, "requires": { - "commondir": "1.0.1", - "make-dir": "1.2.0", - "pkg-dir": "2.0.0" + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" } }, "find-up": { @@ -3936,7 +3965,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "findup-sync": { @@ -3945,7 +3974,7 @@ "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", "dev": true, "requires": { - "glob": "5.0.15" + "glob": "~5.0.0" }, "dependencies": { "glob": { @@ -3954,11 +3983,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } } } @@ -3969,35 +3998,29 @@ "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" } }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", - "dev": true - }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3" + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" } }, "follow-redirects": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", - "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.2.tgz", + "integrity": "sha512-kssLorP/9acIdpQ2udQVTiCS5LQmdEz9mvdIfDcl1gYX2tPKFADHSyFdvJS040XdFsPzemWtgI3q8mFVCxtX8A==", "dev": true, "requires": { - "debug": "3.1.0" + "debug": "^3.1.0" }, "dependencies": { "debug": { @@ -4017,6 +4040,15 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -4026,18 +4058,16 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", - "dev": true, + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" } }, "forwarded": { @@ -4052,7 +4082,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "0.2.2" + "map-cache": "^0.2.2" } }, "fresh": { @@ -4067,19 +4097,18 @@ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, "fs-extra": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", - "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "2.4.0", - "klaw": "1.3.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" } }, "fs-write-stream-atomic": { @@ -4088,10 +4117,10 @@ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "2.3.3" + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" } }, "fs.realpath": { @@ -4101,14 +4130,14 @@ "dev": true }, "fsevents": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.3.tgz", - "integrity": "sha512-X+57O5YkDTiEQGiw8i7wYc2nQgweIekqkepI8Q3y4wVlurgBt2SuwxTeYUYMZIGpLZH3r/TsMjczCMXE5ZOt7Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", "dev": true, "optional": true, "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.9.1" + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" }, "dependencies": { "abbrev": { @@ -4134,8 +4163,8 @@ "dev": true, "optional": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "balanced-match": { @@ -4148,7 +4177,7 @@ "bundled": true, "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -4189,7 +4218,7 @@ } }, "deep-extend": { - "version": "0.4.2", + "version": "0.5.1", "bundled": true, "dev": true, "optional": true @@ -4212,7 +4241,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "fs.realpath": { @@ -4227,14 +4256,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "glob": { @@ -4243,12 +4272,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-unicode": { @@ -4263,7 +4292,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "^2.1.0" } }, "ignore-walk": { @@ -4272,7 +4301,7 @@ "dev": true, "optional": true, "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "inflight": { @@ -4281,8 +4310,8 @@ "dev": true, "optional": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -4301,7 +4330,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "isarray": { @@ -4315,7 +4344,7 @@ "bundled": true, "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -4328,8 +4357,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" } }, "minizlib": { @@ -4338,7 +4367,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "mkdirp": { @@ -4361,27 +4390,27 @@ "dev": true, "optional": true, "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.21", - "sax": "1.2.4" + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.9.1", + "version": "0.10.0", "bundled": true, "dev": true, "optional": true, "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.0", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.6", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.1" + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" } }, "nopt": { @@ -4390,8 +4419,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "npm-bundled": { @@ -4406,8 +4435,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npmlog": { @@ -4416,10 +4445,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { @@ -4438,7 +4467,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -4459,8 +4488,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { @@ -4476,15 +4505,15 @@ "optional": true }, "rc": { - "version": "1.2.6", + "version": "1.2.7", "bundled": true, "dev": true, "optional": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -4501,13 +4530,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "rimraf": { @@ -4516,7 +4545,7 @@ "dev": true, "optional": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { @@ -4559,9 +4588,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { @@ -4570,7 +4599,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -4578,7 +4607,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -4593,13 +4622,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.2.4", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" } }, "util-deprecate": { @@ -4614,7 +4643,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { @@ -4629,6 +4658,18 @@ } } }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4641,19 +4682,62 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "gaze": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", - "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", + "gamma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gamma/-/gamma-1.0.0.tgz", + "integrity": "sha1-mDwck5/iPZMnAVhXEeHZpDDLdMs=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "requires": { - "globule": "1.2.0" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "^1.0.0" } }, "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, "get-stdin": { @@ -4684,23 +4768,22 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "glob-parent": { @@ -4709,8 +4792,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { "is-glob": { @@ -4719,7 +4802,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -4727,8 +4810,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" }, "globby": { "version": "5.0.0", @@ -4736,69 +4818,61 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.0.6", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "globule": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", - "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", "dev": true, "requires": { - "glob": "7.1.2", - "lodash": "4.17.10", - "minimatch": "3.0.4" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" } }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "grunt": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.2.tgz", - "integrity": "sha1-TmpeaVtwRy/VME9fqeNCNoNqc7w=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.3.tgz", + "integrity": "sha512-/JzmZNPfKorlCrrmxWqQO4JVodO+DVd5XX4DkocL/1WlLlKVLE9+SdEIempOAxDhWPysLle6afvn/hg7Ck2k9g==", "dev": true, "requires": { - "coffeescript": "1.10.0", - "dateformat": "1.0.12", - "eventemitter2": "0.4.14", - "exit": "0.1.2", - "findup-sync": "0.3.0", - "glob": "7.0.6", - "grunt-cli": "1.2.0", - "grunt-known-options": "1.1.0", - "grunt-legacy-log": "1.0.0", - "grunt-legacy-util": "1.0.0", - "iconv-lite": "0.4.19", - "js-yaml": "3.5.5", - "minimatch": "3.0.4", - "nopt": "3.0.6", - "path-is-absolute": "1.0.1", - "rimraf": "2.2.8" + "coffeescript": "~1.10.0", + "dateformat": "~1.0.12", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.3.0", + "glob": "~7.0.0", + "grunt-cli": "~1.2.0", + "grunt-known-options": "~1.1.0", + "grunt-legacy-log": "~2.0.0", + "grunt-legacy-util": "~1.1.1", + "iconv-lite": "~0.4.13", + "js-yaml": "~3.5.2", + "minimatch": "~3.0.2", + "mkdirp": "~0.5.1", + "nopt": "~3.0.6", + "path-is-absolute": "~1.0.0", + "rimraf": "~2.6.2" }, "dependencies": { "esprima": { @@ -4807,16 +4881,30 @@ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "dev": true }, + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "grunt-cli": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", "dev": true, "requires": { - "findup-sync": "0.3.0", - "grunt-known-options": "1.1.0", - "nopt": "3.0.6", - "resolve": "1.1.7" + "findup-sync": "~0.3.0", + "grunt-known-options": "~1.1.0", + "nopt": "~3.0.6", + "resolve": "~1.1.0" } }, "js-yaml": { @@ -4825,8 +4913,8 @@ "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "2.7.3" + "argparse": "^1.0.2", + "esprima": "^2.6.0" } } } @@ -4837,7 +4925,7 @@ "integrity": "sha512-5Y7MMYzpzMICkspvmUOU+YC/VE5eiB5TV8k9u43ZFrzLIoYDulKce8KX0fyi2EXYEDKlUEyaVI/W4rLDqqy3/Q==", "dev": true, "requires": { - "access-sniff": "3.2.0" + "access-sniff": "^3.2.0" } }, "grunt-chmod": { @@ -4846,7 +4934,15 @@ "integrity": "sha1-0YZcWoTn7Zrv5Qn/v1KQ+XoleEA=", "dev": true, "requires": { - "shelljs": "0.5.3" + "shelljs": "^0.5.3" + }, + "dependencies": { + "shelljs": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", + "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=", + "dev": true + } } }, "grunt-concurrent": { @@ -4855,10 +4951,10 @@ "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" + "arrify": "^1.0.1", + "async": "^1.2.1", + "indent-string": "^2.0.0", + "pad-stream": "^1.0.0" }, "dependencies": { "async": { @@ -4875,8 +4971,8 @@ "integrity": "sha1-Vkq/LQN4qYOhW54/MO51tzjEBjg=", "dev": true, "requires": { - "async": "1.5.2", - "rimraf": "2.6.2" + "async": "^1.5.2", + "rimraf": "^2.5.1" }, "dependencies": { "async": { @@ -4884,15 +4980,6 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.0.6" - } } } }, @@ -4902,8 +4989,8 @@ "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", "dev": true, "requires": { - "chalk": "1.1.3", - "file-sync-cmp": "0.1.1" + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" } }, "grunt-contrib-jshint": { @@ -4912,93 +4999,60 @@ "integrity": "sha1-Np2QmyWTxA6L55lAshNAhQx5Oaw=", "dev": true, "requires": { - "chalk": "1.1.3", - "hooker": "0.2.3", - "jshint": "2.9.5" - } - }, - "grunt-contrib-uglify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-2.3.0.tgz", - "integrity": "sha1-s9AmDr3WzvoS/y+Onh4ln33kIW8=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "maxmin": "1.1.0", - "object.assign": "4.1.0", - "uglify-js": "2.8.29", - "uri-path": "1.0.0" + "chalk": "^1.1.1", + "hooker": "^0.2.3", + "jshint": "~2.9.4" } }, "grunt-contrib-watch": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", - "integrity": "sha1-hKGnodar0m7VaEE0lscxM+mQAY8=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", + "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", "dev": true, "requires": { - "async": "1.5.2", - "gaze": "1.1.2", - "lodash": "3.10.1", - "tiny-lr": "0.2.1" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - } + "async": "^2.6.0", + "gaze": "^1.1.0", + "lodash": "^4.17.10", + "tiny-lr": "^1.1.1" } }, "grunt-eslint": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-20.1.0.tgz", - "integrity": "sha512-VZlDOLrB2KKefDDcx/wR8rEEz7smDwDKVblmooa+itdt/2jWw3ee2AiZB5Ap4s4AoRY0pbHRjZ3HHwY8uKR9Rw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-21.0.0.tgz", + "integrity": "sha512-HJocD9P35lpCvy6pPPCTgzBavzckrT1nt7lpqV55Vy8E6LQJv4RortXoH1jJTYhO5DYY7RPATv7Uc4383PUYqQ==", "dev": true, "requires": { - "chalk": "2.1.0", - "eslint": "4.19.1" + "chalk": "^2.1.0", + "eslint": "^5.0.0" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "^3.0.0" } } } @@ -5009,21 +5063,15 @@ "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", "integrity": "sha512-33QZYBYjv2Ph3H2ygqXHn/o0ttfptw1f9QciOTgvzhzUeiPrnvzMNUApTPtw22T6zgReE5FZ1RR58U2wnK/l+w==", "dev": true, "requires": { - "cross-spawn": "3.0.1", - "jsdoc": "3.5.5", - "marked": "0.3.12" + "cross-spawn": "^3.0.1", + "jsdoc": "~3.5.5", + "marked": "^0.3.9" }, "dependencies": { "cross-spawn": { @@ -5032,8 +5080,8 @@ "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "dev": true, "requires": { - "lru-cache": "4.1.1", - "which": "1.2.14" + "lru-cache": "^4.0.1", + "which": "^1.2.9" } } } @@ -5045,16 +5093,15 @@ "dev": true }, "grunt-legacy-log": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", - "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz", + "integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==", "dev": true, "requires": { - "colors": "1.1.2", - "grunt-legacy-log-utils": "1.0.0", - "hooker": "0.2.3", - "lodash": "3.10.1", - "underscore.string": "3.2.3" + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.5" }, "dependencies": { "colors": { @@ -5062,46 +5109,63 @@ "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true } } }, "grunt-legacy-log-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", - "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz", + "integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==", "dev": true, "requires": { - "chalk": "1.1.3", - "lodash": "4.3.0" + "chalk": "~2.4.1", + "lodash": "~4.17.10" }, "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", - "dev": true + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "grunt-legacy-util": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", - "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz", + "integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==", "dev": true, "requires": { - "async": "1.5.2", - "exit": "0.1.2", - "getobject": "0.1.0", - "hooker": "0.2.3", - "lodash": "4.3.0", - "underscore.string": "3.2.3", - "which": "1.2.14" + "async": "~1.5.2", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.10", + "underscore.string": "~3.3.4", + "which": "~1.3.0" }, "dependencies": { "async": { @@ -5109,33 +5173,17 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true - }, - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", - "dev": true } } }, "grunt-webpack": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-3.1.1.tgz", - "integrity": "sha512-K7yi4rLx/Tvr0rcgaPW80EJu5EbTtzWlNabR9jemmHnbkmgbkMPqohPcLiEtbi+DOREMpJy8dpnYvtwPdv+SyQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/grunt-webpack/-/grunt-webpack-3.1.2.tgz", + "integrity": "sha512-Ngixl4W/mNJYyghyXJ+JzJ7pUaVRcVKgvC+74ePXPglAEodc9jMlBrGszZAzmspCuvo5dkhbBIcuBHn+Wv1pOQ==", "dev": true, "requires": { - "deep-for-each": "2.0.3", - "lodash": "4.17.10" - } - }, - "gzip-size": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz", - "integrity": "sha1-Zs+LEBBHInuVus5uodoMF37Vwi8=", - "dev": true, - "requires": { - "browserify-zlib": "0.1.4", - "concat-stream": "1.6.0" + "deep-for-each": "^2.0.2", + "lodash": "^4.7.0" } }, "handle-thing": { @@ -5147,41 +5195,38 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, "requires": { - "ajv": "5.2.3", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "har-schema": "^2.0.0" } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { @@ -5190,15 +5235,21 @@ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" } }, "has-values": { @@ -5207,8 +5258,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "kind-of": { @@ -5217,7 +5268,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -5228,40 +5279,39 @@ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", + "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" } }, "hasha": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", - "dev": true, "requires": { - "is-stream": "1.1.0", - "pinkie-promise": "2.0.1" + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" } }, "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "dev": true, "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.0.2" + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" } }, "he": { @@ -5281,15 +5331,15 @@ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", "dev": true }, "home-or-tmp": { @@ -5298,8 +5348,8 @@ "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "dev": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" } }, "hooker": { @@ -5309,9 +5359,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "hpack.js": { @@ -5320,25 +5370,19 @@ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, "requires": { - "inherits": "2.0.3", - "obuf": "1.1.2", - "readable-stream": "2.3.3", - "wbuf": "1.7.3" + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" } }, - "html-comment-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", - "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", - "dev": true - }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", "dev": true, "requires": { - "whatwg-encoding": "1.0.3" + "whatwg-encoding": "^1.0.1" } }, "html-entities": { @@ -5348,41 +5392,25 @@ "dev": true }, "html-minifier": { - "version": "3.5.15", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.15.tgz", - "integrity": "sha512-OZa4rfb6tZOZ3Z8Xf0jKxXkiDcFWldQePGYFDcgKqES2sXeWaEv9y6QQvWUtX3ySI3feApQi5uCsHLINQ6NoAw==", + "version": "3.5.19", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.19.tgz", + "integrity": "sha512-Qr2JC9nsjK8oCrEmuB430ZIA8YWbF3D5LSjywD75FTuXmeqacwHgIM8wp3vHYzzPbklSjp53RdmDuzR4ub2HzA==", "dev": true, "requires": { - "camel-case": "3.0.0", - "clean-css": "4.1.11", - "commander": "2.15.1", - "he": "1.1.1", - "param-case": "2.1.1", - "relateurl": "0.2.7", - "uglify-js": "3.3.23" + "camel-case": "3.0.x", + "clean-css": "4.1.x", + "commander": "2.16.x", + "he": "1.1.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" }, "dependencies": { "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "uglify-js": { - "version": "3.3.23", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.23.tgz", - "integrity": "sha512-Ks+KqLGDsYn4z+pU7JsKCzC0T3mPYl+rU+VcPZiQOazjE4Uqi4UCRY3qPMDbJi7ze37n1lDXj3biz1ik93vqvw==", - "dev": true, - "requires": { - "commander": "2.15.1", - "source-map": "0.6.1" - } } } }, @@ -5392,12 +5420,12 @@ "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { - "html-minifier": "3.5.15", - "loader-utils": "0.2.17", - "lodash": "4.17.10", - "pretty-error": "2.1.1", - "tapable": "1.0.0", - "toposort": "1.0.7", + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", "util.promisify": "1.0.0" }, "dependencies": { @@ -5407,26 +5435,25 @@ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", "dev": true, "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1", - "object-assign": "4.1.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" } } } }, "html_codesniffer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/html_codesniffer/-/html_codesniffer-2.1.1.tgz", - "integrity": "sha512-ms7RxMbIPunkyyZIsU22HV1lTBRrmDov+FlCYTu9ONTSyP5Yj2V8cg27p1e2qL1zxhMl/yLx8P9/b7a9O8gcXQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/html_codesniffer/-/html_codesniffer-2.2.0.tgz", + "integrity": "sha512-xG6E3g2V/huu/WwRcrX3AFRmAUFkU7PWlgF/QFLtuRitI+NxvJcYTFthb24rB7/mhKE3khSIxB11A8IQTJ2N9w==", "dev": true, "requires": { - "grunt": "1.0.2", - "grunt-contrib-copy": "1.0.0", - "grunt-contrib-jshint": "1.1.0", - "grunt-contrib-uglify": "2.3.0", - "grunt-contrib-watch": "1.0.0", - "load-grunt-tasks": "3.5.2" + "grunt": "^1.0.0", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-jshint": "^1.0.0", + "grunt-contrib-watch": "^1.0.0", + "load-grunt-tasks": "^3.5.2" } }, "htmlparser2": { @@ -5435,19 +5462,13 @@ "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", "dev": true, "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.3.0", - "domutils": "1.5.1", - "entities": "1.0.0", - "readable-stream": "1.1.14" + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" }, "dependencies": { - "entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", - "dev": true - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -5460,10 +5481,10 @@ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -5481,19 +5502,21 @@ "dev": true }, "http-errors": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", - "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { + "depd": "~1.1.2", "inherits": "2.0.3", - "statuses": "1.4.0" + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } }, "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", + "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=", "dev": true }, "http-proxy": { @@ -5502,9 +5525,9 @@ "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { - "eventemitter3": "3.1.0", - "follow-redirects": "1.4.1", - "requires-port": "1.0.0" + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" } }, "http-proxy-middleware": { @@ -5513,21 +5536,20 @@ "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { - "http-proxy": "1.17.0", - "is-glob": "4.0.0", - "lodash": "4.17.10", - "micromatch": "3.1.10" + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" } }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "https-browserify": { @@ -5536,6 +5558,12 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "i": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", + "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=", + "dev": true + }, "iced-error": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/iced-error/-/iced-error-0.0.12.tgz", @@ -5546,7 +5574,7 @@ "resolved": "https://registry.npmjs.org/iced-lock/-/iced-lock-1.1.0.tgz", "integrity": "sha1-YRbvHKs6zW5rEIk7snumIv0/3nI=", "requires": { - "iced-runtime": "1.0.3" + "iced-runtime": "^1.0.0" } }, "iced-runtime": { @@ -5555,10 +5583,13 @@ "integrity": "sha1-LU9PuZmreqVDCxk8d6f85BGDGc4=" }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "icss-replace-symbols": { "version": "1.1.0", @@ -5572,67 +5603,13 @@ "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", "dev": true, "requires": { - "postcss": "6.0.21" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } + "postcss": "^6.0.1" } }, "ieee754": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz", - "integrity": "sha512-VhDzCKN7K8ufStx/CLj5/PDTMgph+qwN5Pkd5i0sGnVwk56zJ0lkT8Qzi1xqWLS0Wp29DgDtNeS7v8/wMoZeHg==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", "dev": true }, "iferr": { @@ -5642,17 +5619,42 @@ "dev": true }, "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.3.tgz", + "integrity": "sha512-Z/vAH2GGIEATQnBVXMclE2IGV6i0GyVngKThcGZ5kHgHMxLo9Ow2+XHRq1aEKEej5vOF1TPJNbvX6J/anT0M7A==", "dev": true }, "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", + "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", "dev": true, - "optional": true + "requires": { + "import-from": "^2.1.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } }, "import-local": { "version": "1.0.0", @@ -5660,8 +5662,8 @@ "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", "dev": true, "requires": { - "pkg-dir": "2.0.0", - "resolve-cwd": "2.0.0" + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" } }, "imports-loader": { @@ -5670,16 +5672,8 @@ "integrity": "sha512-kXWL7Scp8KQ4552ZcdVTeaQCZSLW+e6nJfp3cwUMB673T7Hr98Xjx5JK+ql7ADlJUvj1JS5O01RLbKoutN5QDQ==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "loader-utils": "^1.0.2", + "source-map": "^0.6.1" } }, "imurmurhash": { @@ -5688,21 +5682,21 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true + }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", @@ -5715,15 +5709,14 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -5737,30 +5730,29 @@ "integrity": "sha512-STx5orGQU1gfrkoI/fMU7lX6CSP7LBGO10gXNgOZhwKhUqbtNjCkYSewJtNnLmWP1tAGN6oyEpG1HFPw5vpa5Q==", "dev": true, "requires": { - "moment": "2.22.1", - "sanitize-html": "1.17.0" + "moment": "^2.14.1", + "sanitize-html": "^1.13.0" } }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", "dev": true, "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.3.2", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "2.1.0", - "figures": "2.0.0", - "lodash": "4.17.10", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.1.0", + "figures": "^2.0.0", + "lodash": "^4.3.0", "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rx-lite": "4.0.8", - "rx-lite-aggregates": "4.0.8", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" + "run-async": "^2.2.0", + "rxjs": "^5.5.2", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" }, "dependencies": { "ansi-regex": { @@ -5775,42 +5767,36 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -5821,16 +5807,15 @@ "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", "dev": true, "requires": { - "meow": "3.7.0" + "meow": "^3.3.0" } }, "invariant": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.3.tgz", - "integrity": "sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==", - "dev": true, + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "invert-kv": { @@ -5846,15 +5831,9 @@ "dev": true }, "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", - "dev": true - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", "dev": true }, "is-accessor-descriptor": { @@ -5863,7 +5842,18 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-arrayish": { @@ -5878,7 +5868,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "1.11.0" + "binary-extensions": "^1.0.0" } }, "is-buffer": { @@ -5893,13 +5883,13 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-data-descriptor": { @@ -5908,7 +5898,18 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-date-object": { @@ -5923,9 +5924,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" }, "dependencies": { "kind-of": { @@ -5960,7 +5961,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -5975,7 +5976,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -5984,23 +5985,17 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" - } - }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "4.0.0" + "kind-of": "^3.0.2" }, "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } } } }, @@ -6016,7 +6011,7 @@ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "1.0.1" + "is-path-inside": "^1.0.0" } }, "is-path-inside": { @@ -6025,22 +6020,16 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" } }, "is-promise": { @@ -6055,7 +6044,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.1" + "has": "^1.0.1" } }, "is-resolvable": { @@ -6067,17 +6056,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "dev": true, - "requires": { - "html-comment-regex": "1.1.1" - } + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.1", @@ -6088,8 +6067,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-utf8": { "version": "0.2.1", @@ -6112,14 +6090,12 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -6130,8 +6106,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jison": { "version": "0.4.13", @@ -6139,12 +6114,12 @@ "integrity": "sha1-kEFwfWIkE2f1iDRTK58ZwsNvrHg=", "requires": { "JSONSelect": "0.4.0", - "cjson": "0.2.1", - "ebnf-parser": "0.1.10", + "cjson": "~0.2.1", + "ebnf-parser": "~0.1.9", "escodegen": "0.0.21", - "esprima": "1.0.4", - "jison-lex": "0.2.1", - "lex-parser": "0.1.4", + "esprima": "1.0.x", + "jison-lex": "0.2.x", + "lex-parser": "~0.1.3", "nomnom": "1.5.2" }, "dependencies": { @@ -6153,9 +6128,9 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.21.tgz", "integrity": "sha1-U9ZSz6EDA4gnlFilJmxf/HCcY8M=", "requires": { - "esprima": "1.0.4", - "estraverse": "0.0.4", - "source-map": "0.5.7" + "esprima": "~1.0.2", + "estraverse": "~0.0.4", + "source-map": ">= 0.1.2" } }, "esprima": { @@ -6175,7 +6150,7 @@ "resolved": "https://registry.npmjs.org/jison-lex/-/jison-lex-0.2.1.tgz", "integrity": "sha1-rEuBXozOUTLrErXfz+jXB7iETf4=", "requires": { - "lex-parser": "0.1.4", + "lex-parser": "0.1.x", "nomnom": "1.5.2" } }, @@ -6185,9 +6160,9 @@ "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" }, "js-base64": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", - "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==", + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.8.tgz", + "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==", "dev": true }, "js-crc": { @@ -6200,28 +6175,60 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.7.0.tgz", "integrity": "sha512-Wpks3yBDm0UcL5qlVhwW9Jr9n9i4FfeWBFOOXP5puDS/SiudJGhw7DPyBqn3487qD4F0lsC0q3zxink37f7zeA==" }, + "js-to-mjs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/js-to-mjs/-/js-to-mjs-0.2.0.tgz", + "integrity": "sha512-5OlmInr6FuKAEAqNi0Ag8ErS8LWCp53w/vHSc6Ndm8aTckuOKI/uiil/ltP/xRrl+cSz8Q/oW7q6iglNQHCx+A==", + "dev": true, + "requires": { + "babylon": "^6.18.0", + "chalk": "^2.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "2.7.3" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - } + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "js2xmlparser": { @@ -6230,7 +6237,7 @@ "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", "dev": true, "requires": { - "xmlcreate": "1.0.2" + "xmlcreate": "^1.0.1" } }, "jsbn": { @@ -6245,17 +6252,17 @@ "dev": true, "requires": { "babylon": "7.0.0-beta.19", - "bluebird": "3.5.0", - "catharsis": "0.8.9", - "escape-string-regexp": "1.0.5", - "js2xmlparser": "3.0.0", - "klaw": "2.0.0", - "marked": "0.3.12", - "mkdirp": "0.5.1", - "requizzle": "0.2.1", - "strip-json-comments": "2.0.1", + "bluebird": "~3.5.0", + "catharsis": "~0.8.9", + "escape-string-regexp": "~1.0.5", + "js2xmlparser": "~3.0.0", + "klaw": "~2.0.0", + "marked": "~0.3.6", + "mkdirp": "~0.5.1", + "requizzle": "~0.2.1", + "strip-json-comments": "~2.0.1", "taffydb": "2.6.2", - "underscore": "1.8.3" + "underscore": "~1.8.3" }, "dependencies": { "babylon": { @@ -6270,7 +6277,7 @@ "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.9" } }, "underscore": { @@ -6287,8 +6294,8 @@ "integrity": "sha512-KF3WTPvoPYc8ZyXzC1m+vvwi+2VCKkqZX/NkqcE1tFephp8RnZAxG52QB/wvz/zoDS6XU28aM8NItMPMad50PA==", "dev": true, "requires": { - "jsdoc-regex": "1.0.1", - "lodash": "4.17.10" + "jsdoc-regex": "^1.0.1", + "lodash": "^4.13.1" } }, "jsdoc-regex": { @@ -6298,37 +6305,37 @@ "dev": true }, "jsdom": { - "version": "11.6.2", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.6.2.tgz", - "integrity": "sha512-pAeZhpbSlUp5yQcS6cBQJwkbzmv4tWFaYxHbFVSxzXefqjvtRA851Z5N2P+TguVG9YeUDcgb8pdeVQRJh0XR3Q==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", "dev": true, "requires": { - "abab": "1.0.4", - "acorn": "5.5.0", - "acorn-globals": "4.1.0", - "array-equal": "1.0.0", - "browser-process-hrtime": "0.1.2", - "content-type-parser": "1.0.2", - "cssom": "0.3.2", - "cssstyle": "0.2.37", - "domexception": "1.0.1", - "escodegen": "1.9.1", - "html-encoding-sniffer": "1.0.2", - "left-pad": "1.2.0", - "nwmatcher": "1.4.4", + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", "parse5": "4.0.0", - "pn": "1.1.0", - "request": "2.83.0", - "request-promise-native": "1.0.5", - "sax": "1.2.4", - "symbol-tree": "3.2.2", - "tough-cookie": "2.3.3", - "w3c-hr-time": "1.0.1", - "webidl-conversions": "4.0.2", - "whatwg-encoding": "1.0.3", - "whatwg-url": "6.4.0", - "ws": "4.1.0", - "xml-name-validator": "3.0.0" + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" } }, "jsesc": { @@ -6337,33 +6344,24 @@ "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" }, "jshint": { - "version": "2.9.5", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz", - "integrity": "sha1-HnJSkVzmgbQIJ+4UJIxG006apiw=", + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.6.tgz", + "integrity": "sha512-KO9SIAKTlJQOM4lE64GQUtGBRpTOuvbrRrSZw3AhUxMNG266nX9hK2cKA4SBhXOj0irJGyNyGSLT62HGOVDEOA==", "dev": true, "requires": { - "cli": "1.0.1", - "console-browserify": "1.1.0", - "exit": "0.1.2", - "htmlparser2": "3.8.3", - "lodash": "3.7.0", - "minimatch": "3.0.4", - "shelljs": "0.3.0", - "strip-json-comments": "1.0.4" + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.10", + "minimatch": "~3.0.2", + "phantom": "~4.0.1", + "phantomjs-prebuilt": "~2.1.7", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x", + "unicode-5.2.0": "^0.7.5" }, "dependencies": { - "lodash": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.7.0.tgz", - "integrity": "sha1-Nni9irmVBXwHreg27S7wh9qBHUU=", - "dev": true - }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", - "dev": true - }, "strip-json-comments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", @@ -6372,17 +6370,21 @@ } } }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, "json-stable-stringify": { "version": "1.0.1", @@ -6390,7 +6392,7 @@ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stable-stringify-without-jsonify": { @@ -6402,8 +6404,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json3": { "version": "3.3.2", @@ -6421,9 +6422,8 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "jsonify": { @@ -6454,7 +6454,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -6472,19 +6471,19 @@ "resolved": "https://registry.npmjs.org/kbpgp/-/kbpgp-2.0.77.tgz", "integrity": "sha1-bp0vV5hb6VlsXqbJ5aODM/MasZk=", "requires": { - "bn": "1.0.1", - "bzip-deflate": "1.0.0", - "deep-equal": "1.0.1", - "iced-error": "0.0.12", - "iced-lock": "1.1.0", - "iced-runtime": "1.0.3", - "keybase-ecurve": "1.0.0", - "keybase-nacl": "1.0.10", - "minimist": "1.2.0", - "pgp-utils": "0.0.34", - "purepack": "1.0.4", - "triplesec": "3.0.26", - "tweetnacl": "0.13.3" + "bn": "^1.0.0", + "bzip-deflate": "^1.0.0", + "deep-equal": ">=0.2.1", + "iced-error": ">=0.0.10", + "iced-lock": "^1.0.2", + "iced-runtime": "^1.0.3", + "keybase-ecurve": "^1.0.0", + "keybase-nacl": "^1.0.0", + "minimist": "^1.2.0", + "pgp-utils": ">=0.0.34", + "purepack": ">=1.0.4", + "triplesec": ">=3.0.19", + "tweetnacl": "^0.13.1" }, "dependencies": { "minimist": { @@ -6502,15 +6501,14 @@ "kew": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", - "dev": true + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=" }, "keybase-ecurve": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/keybase-ecurve/-/keybase-ecurve-1.0.0.tgz", "integrity": "sha1-xrxyrdpGA/0xhP7n6ZaU7Y/WmtI=", "requires": { - "bn": "1.0.1" + "bn": "^1.0.0" } }, "keybase-nacl": { @@ -6518,9 +6516,9 @@ "resolved": "https://registry.npmjs.org/keybase-nacl/-/keybase-nacl-1.0.10.tgz", "integrity": "sha1-OGWDHpSBUWSI33y9mJRn6VDYeos=", "requires": { - "iced-runtime": "1.0.3", - "tweetnacl": "0.13.3", - "uint64be": "1.0.1" + "iced-runtime": "^1.0.2", + "tweetnacl": "^0.13.1", + "uint64be": "^1.0.1" }, "dependencies": { "tweetnacl": { @@ -6537,92 +6535,41 @@ "dev": true }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true }, "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.9" } }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "left-pad": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz", - "integrity": "sha1-0wpzxrggHY99jnlWupYWCHpo4O4=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", "dev": true }, - "less": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/less/-/less-3.0.2.tgz", - "integrity": "sha512-konnFwWXpUQwzuwyN3Zfw/2Ziah2BKzqTfGoHBZjJdQWCmR+yrjmIG3QLwnlXNFWz27QetOmhGNSbHgGRdqhYQ==", - "dev": true, - "requires": { - "errno": "0.1.7", - "graceful-fs": "4.1.11", - "image-size": "0.5.5", - "mime": "1.6.0", - "mkdirp": "0.5.1", - "promise": "7.3.1", - "request": "2.83.0", - "source-map": "0.5.7" - } - }, - "less-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz", - "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", - "dev": true, - "requires": { - "clone": "2.1.2", - "loader-utils": "1.1.0", - "pify": "3.0.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "lex-parser": { @@ -6642,10 +6589,10 @@ "integrity": "sha1-ByhWEYD9IP+KaSdQWFL8WKrqDIg=", "dev": true, "requires": { - "arrify": "1.0.1", - "multimatch": "2.1.0", - "pkg-up": "1.0.0", - "resolve-pkg": "0.1.0" + "arrify": "^1.0.0", + "multimatch": "^2.0.0", + "pkg-up": "^1.0.0", + "resolve-pkg": "^0.1.0" } }, "load-json-file": { @@ -6654,11 +6601,19 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "loader-runner": { @@ -6673,9 +6628,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } }, "locate-path": { @@ -6684,8 +6639,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, "lodash": { @@ -6693,6 +6648,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -6705,6 +6666,12 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, "lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", @@ -6717,16 +6684,16 @@ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", "dev": true }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", "dev": true }, "lodash.mergewith": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz", - "integrity": "sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", "dev": true }, "lodash.sortby": { @@ -6735,25 +6702,25 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, "lodash.unescape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", "dev": true }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.4.1" + "chalk": "^2.0.1" }, "dependencies": { "ansi-styles": { @@ -6762,7 +6729,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -6771,24 +6738,18 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -6803,8 +6764,8 @@ "resolved": "https://registry.npmjs.org/loglevel-message-prefix/-/loglevel-message-prefix-3.0.0.tgz", "integrity": "sha1-ER/bltlPlh2PyLiqv7ZrBqw+dq0=", "requires": { - "es6-polyfills": "2.0.0", - "loglevel": "1.6.1" + "es6-polyfills": "^2.0.0", + "loglevel": "^1.4.0" } }, "loglevelnext": { @@ -6813,23 +6774,22 @@ "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", "dev": true, "requires": { - "es6-symbol": "3.1.1", - "object.assign": "4.1.0" + "es6-symbol": "^3.1.1", + "object.assign": "^4.1.0" } }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", "dev": true }, "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true, + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0 || ^4.0.0" } }, "loud-rejection": { @@ -6838,8 +6798,8 @@ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" } }, "lower-case": { @@ -6849,38 +6809,30 @@ "dev": true }, "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, - "macaddress": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", - "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", - "dev": true - }, "make-dir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", - "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "pify": "3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "pify": "^3.0.0" } }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -6899,53 +6851,23 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "1.0.1" + "object-visit": "^1.0.0" } }, "marked": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", - "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", - "dev": true - }, - "maxmin": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-1.1.0.tgz", - "integrity": "sha1-cTZehKmd2Piz99X94vANHn9zvmE=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "figures": "1.7.0", - "gzip-size": "1.0.0", - "pretty-bytes": "1.0.4" - }, - "dependencies": { - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - } - } - }, "md5.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "dev": true, "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, "media-typer": { @@ -6960,7 +6882,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "memory-fs": { @@ -6969,8 +6891,8 @@ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "errno": "0.1.7", - "readable-stream": "2.3.3" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } }, "meow": { @@ -6979,16 +6901,16 @@ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" }, "dependencies": { "minimist": { @@ -7017,27 +6939,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "miller-rabin": { @@ -7046,36 +6960,33 @@ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" } }, "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", "dev": true }, + "mime-db": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" + }, "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "dev": true, + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "requires": { - "mime-db": "1.30.0" + "mime-db": "~1.35.0" } }, "mimer": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/mimer/-/mimer-0.2.3.tgz", - "integrity": "sha512-cICHJPMZUdZMqWaOQ+Eh0hHo1R6IUCiBee7WvIGGUJsZyjdMUInxQVmyu8hKj5uCy+Bi+Wlp/EsdUR61yOdWOw==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/mimer/-/mimer-0.3.2.tgz", + "integrity": "sha512-N6NcgDQAevhP/02DQ/epK6daLy4NKrIHyTlJcO6qBiYn98q+Y4a/knNsAATCe1xLS2F0nEmJp+QYli2s8vKwyQ==", "dev": true }, "mimic-fn": { @@ -7102,14 +7013,13 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mississippi": { "version": "2.0.0", @@ -7117,28 +7027,16 @@ "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", "dev": true, "requires": { - "concat-stream": "1.6.0", - "duplexify": "3.5.1", - "end-of-stream": "1.4.0", - "flush-write-stream": "1.0.3", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "2.0.1", - "pumpify": "1.3.5", - "stream-each": "1.2.2", - "through2": "2.0.3" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "1.4.0", - "once": "1.4.0" - } - } + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" } }, "mixin-deep": { @@ -7147,8 +7045,8 @@ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -7157,31 +7055,48 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" } }, "moment": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", - "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" }, "moment-timezone": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.16.tgz", - "integrity": "sha512-4d1l92plNNqnMkqI/7boWNVXJvwGL2WyByl1Hxp3h/ao3HZiAqaoQY+6KBkYdiN5QtNDpndq+58ozl8W4GVoNw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz", + "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==", "requires": { - "moment": "2.22.1" + "moment": ">= 2.9.0" } }, "more-entropy": { @@ -7189,7 +7104,7 @@ "resolved": "https://registry.npmjs.org/more-entropy/-/more-entropy-0.0.7.tgz", "integrity": "sha1-Z7/G96hvJvvDeqyD/UbYjGHRCbU=", "requires": { - "iced-runtime": "1.0.3" + "iced-runtime": ">=0.0.1" } }, "move-concurrently": { @@ -7198,30 +7113,18 @@ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { - "aproba": "1.2.0", - "copy-concurrently": "1.0.5", - "fs-write-stream-atomic": "1.0.10", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "run-queue": "1.0.3" - }, - "dependencies": { - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.0.6" - } - } + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" } }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multicast-dns": { "version": "6.2.3", @@ -7229,8 +7132,8 @@ "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", "dev": true, "requires": { - "dns-packet": "1.3.1", - "thunky": "1.0.2" + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" } }, "multicast-dns-service-types": { @@ -7245,10 +7148,10 @@ "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", "dev": true, "requires": { - "array-differ": "1.0.0", - "array-union": "1.0.2", - "arrify": "1.0.1", - "minimatch": "3.0.4" + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" } }, "mute-stream": { @@ -7261,35 +7164,25 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true, - "optional": true + "dev": true }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-odd": "2.0.0", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" } }, "natural-compare": { @@ -7298,6 +7191,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "ncp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", + "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=", + "dev": true + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -7316,13 +7215,19 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "nice-try": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "dev": true + }, "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { - "lower-case": "1.1.4" + "lower-case": "^1.1.1" } }, "node-forge": { @@ -7330,52 +7235,165 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==" }, + "node-gyp": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.7.0.tgz", + "integrity": "sha512-qDQE/Ft9xXP6zphwx4sD0t+VhwV7yFaloMpfbL2QnnDZcyaiakWlLdtFGGQfTAwpFHdpbRhRxVhIHN1OKAjgbg==", + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": ">=2.9.0 <2.82.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" + } + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "dev": true, + "requires": { + "ajv": "^4.9.1", + "har-schema": "^1.0.5" + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dev": true, + "requires": { + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" + } + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, "node-libs-browser": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", "dev": true, "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.2.0", - "events": "1.1.1", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.3", - "stream-browserify": "2.0.1", - "stream-http": "2.8.1", - "string_decoder": "1.0.3", - "timers-browserify": "2.0.10", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", + "url": "^0.11.0", + "util": "^0.10.3", "vm-browserify": "0.0.4" - }, - "dependencies": { - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "1.0.6" - } - }, - "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true - } } }, "node-md6": { @@ -7383,15 +7401,68 @@ "resolved": "https://registry.npmjs.org/node-md6/-/node-md6-0.1.0.tgz", "integrity": "sha1-9WH0WyszY1K4KXbFHMoRR9U5N/U=" }, + "node-releases": { + "version": "1.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.0-alpha.10.tgz", + "integrity": "sha512-BSQrRgOfN6L/MoKIa7pRUc7dHvflCXMcqyTBvphixcSsgJTuUd24vAFONuNfVsuwTyz28S1HEc9XN6ZKylk4Hg==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "node-sass": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz", + "integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==", + "dev": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash.assign": "^4.2.0", + "lodash.clonedeep": "^4.3.2", + "lodash.mergewith": "^4.6.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.10.0", + "node-gyp": "^3.3.1", + "npmlog": "^4.0.0", + "request": "2.87.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + } + } + }, "nomnom": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", "integrity": "sha1-9DRUSKhTz71cDSYyDyR3qwUm/i8=", "requires": { - "colors": "0.5.1", - "underscore": "1.1.7" + "colors": "0.5.x", + "underscore": "1.1.x" }, "dependencies": { + "colors": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" + }, "underscore": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", @@ -7405,7 +7476,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.1.1" + "abbrev": "1" } }, "normalize-package-data": { @@ -7414,10 +7485,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "validate-npm-package-license": "3.0.1" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { @@ -7426,7 +7497,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "1.1.0" + "remove-trailing-separator": "^1.0.1" } }, "normalize-range": { @@ -7435,25 +7506,25 @@ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", "dev": true }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "prepend-http": "1.0.4", - "query-string": "4.3.4", - "sort-keys": "1.1.2" - } - }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "nth-check": { @@ -7462,7 +7533,7 @@ "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", "dev": true, "requires": { - "boolbase": "1.0.0" + "boolbase": "~1.0.0" } }, "num2fraction": { @@ -7482,11 +7553,16 @@ "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==" }, + "nwsapi": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.8.tgz", + "integrity": "sha512-7RZ+qbFGiVc6v14Y8DSZjPN1wZPOaMbiiP4tzf5eNuyOITAeOIA3cMhjuKUypVIqBgCSg1KaSyAv8Ocq/0ZJ1A==", + "dev": true + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, "object-assign": { "version": "4.1.1", @@ -7500,9 +7576,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" }, "dependencies": { "define-property": { @@ -7511,15 +7587,24 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" } } } }, "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", "dev": true }, "object-visit": { @@ -7528,7 +7613,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.0" } }, "object.assign": { @@ -7537,10 +7622,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "1.1.2", - "function-bind": "1.1.1", - "has-symbols": "1.0.0", - "object-keys": "1.0.11" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, "object.getownpropertydescriptors": { @@ -7549,8 +7634,8 @@ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" } }, "object.pick": { @@ -7559,7 +7644,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" } }, "obuf": { @@ -7589,7 +7674,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "onetime": { @@ -7598,7 +7683,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "opn": { @@ -7607,7 +7692,7 @@ "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", "dev": true, "requires": { - "is-wsl": "1.1.0" + "is-wsl": "^1.1.0" } }, "optionator": { @@ -7615,33 +7700,21 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" } }, "original": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", - "integrity": "sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.1.tgz", + "integrity": "sha512-IEvtB5vM5ULvwnqMxWBLxkS13JIEXbakizMSo3yoPNPCIWzg8TG3Usn/UhXoZFM/m+FuEA20KdzPSFq/0rS+UA==", "dev": true, "requires": { - "url-parse": "1.0.5" - }, - "dependencies": { - "url-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.0.5.tgz", - "integrity": "sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=", - "dev": true, - "requires": { - "querystringify": "0.0.4", - "requires-port": "1.0.0" - } - } + "url-parse": "~1.4.0" } }, "os-browserify": { @@ -7657,14 +7730,12 @@ "dev": true }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" + "lcid": "^1.0.0" } }, "os-tmpdir": { @@ -7673,12 +7744,22 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "otp": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/otp/-/otp-0.1.3.tgz", "integrity": "sha1-wle/JdL5Anr3esUiabPBQmjSvWs=", "requires": { - "thirty-two": "0.0.2" + "thirty-two": "^0.0.2" } }, "p-finally": { @@ -7688,12 +7769,12 @@ "dev": true }, "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -7702,7 +7783,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "^1.1.0" } }, "p-map": { @@ -7723,17 +7804,17 @@ "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" + "meow": "^3.0.0", + "pumpify": "^1.3.3", + "repeating": "^2.0.0", + "split2": "^1.0.0", + "through2": "^2.0.0" } }, "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", "dev": true }, "parallel-transform": { @@ -7742,9 +7823,9 @@ "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "dev": true, "requires": { - "cyclist": "0.2.2", - "inherits": "2.0.3", - "readable-stream": "2.3.3" + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" } }, "param-case": { @@ -7753,7 +7834,7 @@ "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", "dev": true, "requires": { - "no-case": "2.3.2" + "no-case": "^2.2.0" } }, "parse-asn1": { @@ -7762,11 +7843,11 @@ "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { - "asn1.js": "4.10.1", - "browserify-aes": "1.2.0", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.16" + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" } }, "parse-json": { @@ -7775,7 +7856,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "parse5": { @@ -7844,9 +7925,17 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "pbkdf2": { @@ -7855,70 +7944,77 @@ "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", "dev": true, "requires": { - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "ripemd160": "2.0.2", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pgp-utils": { "version": "0.0.34", "resolved": "https://registry.npmjs.org/pgp-utils/-/pgp-utils-0.0.34.tgz", "integrity": "sha1-2E9J98GTteC5QV9cxcKmle15DCM=", "requires": { - "iced-error": "0.0.12", - "iced-runtime": "1.0.3" + "iced-error": ">=0.0.8", + "iced-runtime": ">=0.0.1" + } + }, + "phantom": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/phantom/-/phantom-4.0.12.tgz", + "integrity": "sha512-Tz82XhtPmwCk1FFPmecy7yRGZG2btpzY2KI9fcoPT7zT9det0CcMyfBFPp1S8DqzsnQnm8ZYEfdy528mwVtksA==", + "dev": true, + "optional": true, + "requires": { + "phantomjs-prebuilt": "^2.1.16", + "split": "^1.0.1", + "winston": "^2.4.0" } }, "phantomjs-prebuilt": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", - "dev": true, "requires": { - "es6-promise": "4.0.5", - "extract-zip": "1.6.6", - "fs-extra": "1.0.0", - "hasha": "2.2.0", - "kew": "0.7.0", - "progress": "1.1.8", - "request": "2.83.0", - "request-progress": "2.0.1", - "which": "1.2.14" + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" } }, "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "pkg-dir": { @@ -7927,7 +8023,7 @@ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "2.1.0" + "find-up": "^2.1.0" } }, "pkg-up": { @@ -7936,7 +8032,7 @@ "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", "dev": true, "requires": { - "find-up": "1.1.2" + "find-up": "^1.0.0" }, "dependencies": { "find-up": { @@ -7945,8 +8041,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "path-exists": { @@ -7955,11 +8051,17 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } } } }, + "pkginfo": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", + "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=", + "dev": true + }, "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", @@ -7972,15 +8074,20 @@ "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true }, + "popper.js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.4.tgz", + "integrity": "sha1-juwdj/AqWjoVLdQ0FKFce3n9abY=" + }, "portfinder": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz", "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", "dev": true, "requires": { - "async": "1.5.2", - "debug": "2.6.9", - "mkdirp": "0.5.1" + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" }, "dependencies": { "async": { @@ -7998,69 +8105,14 @@ "dev": true }, "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", "dev": true, "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - }, - "dependencies": { - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-message-helpers": "2.0.0", - "reduce-css-calc": "1.3.0" - } - }, - "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", - "dev": true, - "requires": { - "colormin": "1.1.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-css-variables": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/postcss-css-variables/-/postcss-css-variables-0.8.1.tgz", - "integrity": "sha1-pS5e8aLrYzqKT1/ENNbYXUD+cxA=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5", - "extend": "3.0.1", - "postcss": "6.0.21" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" }, "dependencies": { "ansi-styles": { @@ -8069,227 +8121,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "postcss-filter-plugins": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz", - "integrity": "sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqid": "4.1.1" - } - }, - "postcss-import": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-11.1.0.tgz", - "integrity": "sha512-5l327iI75POonjxkXgdRCUS+AlzAdBx4pOvMEhTKTCjb1p8IEeVR9yx3cPbmN7LIWJLbfnIXxAhoB4jpD0c/Cw==", - "dev": true, - "requires": { - "postcss": "6.0.19", - "postcss-value-parser": "3.3.0", - "read-cache": "1.0.0", - "resolve": "1.1.7" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "5.2.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.19.tgz", - "integrity": "sha512-f13HRz0HtVwVaEuW6J6cOUCBLFtymhgyLPV7t4QEk2UD3twRI9IluDcQNdzQdBpiixkXj2OmzejhhTbSbDxNTg==", - "dev": true, - "requires": { - "chalk": "2.3.1", - "source-map": "0.6.1", - "supports-color": "5.2.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "postcss-load-config": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-1.2.0.tgz", - "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1", - "postcss-load-options": "1.2.0", - "postcss-load-plugins": "2.3.0" - } - }, - "postcss-load-options": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-load-options/-/postcss-load-options-1.2.0.tgz", - "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1" - } - }, - "postcss-load-plugins": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz", - "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1" - } - }, - "postcss-loader": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.4.tgz", - "integrity": "sha512-L2p654oK945B/gDFUGgOhh7uzj19RWoY1SVMeJVoKno1H2MdbQ0RppR/28JGju4pMb22iRC7BJ9aDzbxXSLf4A==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "postcss": "6.0.22", - "postcss-load-config": "1.2.0", - "schema-utils": "0.4.5" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -8298,139 +8130,107 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", - "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", - "dev": true, - "requires": { - "chalk": "2.4.1", - "source-map": "0.6.1", - "supports-color": "5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", + "postcss-css-variables": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/postcss-css-variables/-/postcss-css-variables-0.9.0.tgz", + "integrity": "sha512-pSNj57bdBaFnyDzt96SZOidMMWY7Q5OySQdKsTxaxehYO6EtNC5SAKIwjah/poh5vUmNthkrlOd03bP+HNGuCA==", "dev": true, "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" + "escape-string-regexp": "^1.0.3", + "extend": "^3.0.1", + "postcss": "^6.0.8" } }, - "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", + "postcss-import": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.0.tgz", + "integrity": "sha512-3KqKRZcaZAvxbY8DVLdd81tG5uKzbUQuiWIvy0o0fzEC42bKacqPYFWbfCQyw6L4LWUaqPz/idvIdbhpgQ32eQ==", "dev": true, "requires": { - "postcss": "5.2.18" - } - }, - "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-api": "1.6.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3", - "vendors": "1.0.1" + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" }, "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "caniuse-db": "1.0.30000821", - "electron-to-chromium": "1.3.41" + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "postcss": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.2.tgz", + "integrity": "sha512-fmaUY5370keLUTx+CnwRxtGiuFTcNBLQBqr1oE3WZ/euIYmGAo0OAgOhVJ3ByDnVmOR3PK+0V9VebzfjRIUcqw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, - "postcss-message-helpers": { + "postcss-load-config": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", - "dev": true - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.0.0.tgz", + "integrity": "sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ==", "dev": true, "requires": { - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" + "cosmiconfig": "^4.0.0", + "import-cwd": "^2.0.0" } }, - "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", + "postcss-loader": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.6.tgz", + "integrity": "sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg==", "dev": true, "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "uniqs": "2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3" + "loader-utils": "^1.1.0", + "postcss": "^6.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^0.4.0" } }, "postcss-modules-extract-imports": { @@ -8439,61 +8239,7 @@ "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", "dev": true, "requires": { - "postcss": "6.0.21" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } + "postcss": "^6.0.1" } }, "postcss-modules-local-by-default": { @@ -8502,62 +8248,8 @@ "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", "dev": true, "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.21" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" } }, "postcss-modules-scope": { @@ -8566,62 +8258,8 @@ "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", "dev": true, "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.21" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" } }, "postcss-modules-values": { @@ -8630,157 +8268,8 @@ "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", "dev": true, "requires": { - "icss-replace-symbols": "1.1.0", - "postcss": "6.0.21" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", - "dev": true, - "requires": { - "is-absolute-url": "2.1.0", - "normalize-url": "1.9.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "1.0.2", - "indexes-of": "1.0.1", - "uniq": "1.0.1" - } - }, - "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", - "dev": true, - "requires": { - "is-svg": "2.1.0", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "svgo": "0.7.2" - } - }, - "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "uniqs": "2.0.0" + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" } }, "postcss-value-parser": { @@ -8789,46 +8278,19 @@ "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", "dev": true }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "pretty-bytes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", - "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", - "dev": true, - "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" - } - }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", "dev": true, "requires": { - "renderkid": "2.0.1", - "utila": "0.4.0" + "renderkid": "^2.0.1", + "utila": "~0.4" } }, "private": { @@ -8844,40 +8306,80 @@ "dev": true }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "optional": true, - "requires": { - "asap": "2.0.6" - } - }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "prompt": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", + "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", "dev": true, "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.6.0" + "colors": "^1.1.2", + "pkginfo": "0.x.x", + "read": "1.0.x", + "revalidator": "0.1.x", + "utile": "0.3.x", + "winston": "2.1.x" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", + "dev": true + }, + "winston": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", + "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", + "dev": true, + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "pkginfo": "0.3.x", + "stack-trace": "0.0.x" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "pkginfo": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", + "dev": true + } + } + } + } + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" } }, "prr": { @@ -8898,66 +8400,48 @@ "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", "dev": true, "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "parse-asn1": "5.1.1", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1" } }, "pump": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz", - "integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "1.4.0", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "pumpify": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.3.5.tgz", - "integrity": "sha1-G2ccYZlAq8rqwK0OOjwWS+dgmTs=", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { - "duplexify": "3.5.1", - "inherits": "2.0.3", - "pump": "1.0.2" + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" } }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "purepack": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/purepack/-/purepack-1.0.4.tgz", "integrity": "sha1-CGKC/ZOShfWGZLqam7oxzbFlzNI=" }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" - } + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -8972,9 +8456,9 @@ "dev": true }, "querystringify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz", - "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", + "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", "dev": true }, "randombytes": { @@ -8983,7 +8467,7 @@ "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.1.0" } }, "randomfill": { @@ -8992,8 +8476,8 @@ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "randombytes": "2.0.6", - "safe-buffer": "5.1.1" + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" } }, "range-parser": { @@ -9003,40 +8487,33 @@ "dev": true }, "raw-body": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", - "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", "dev": true, "requires": { - "bytes": "2.4.0", - "iconv-lite": "0.4.13", - "unpipe": "1.0.0" + "bytes": "1", + "string_decoder": "0.10" }, "dependencies": { - "bytes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", - "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", - "dev": true - }, - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true } } }, "rc": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.5.tgz", - "integrity": "sha1-J1zWh/bjs2zHVrqibf7oCnkDAf0=", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -9047,13 +8524,30 @@ } } }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", "dev": true, "requires": { - "pify": "2.3.0" + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "read-pkg": { @@ -9062,9 +8556,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -9073,8 +8567,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" }, "dependencies": { "find-up": { @@ -9083,8 +8577,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "path-exists": { @@ -9093,24 +8587,23 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } } } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "readdirp": { @@ -9119,10 +8612,10 @@ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.3", - "set-immediate-shim": "1.0.1" + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" } }, "redent": { @@ -9131,50 +8624,14 @@ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "math-expression-evaluator": "1.2.17", - "reduce-function-call": "1.0.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", - "dev": true, - "requires": { - "balanced-match": "0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" } }, "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", "dev": true }, "regenerator-runtime": { @@ -9188,9 +8645,9 @@ "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "private": "0.1.8" + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" } }, "regex-not": { @@ -9199,14 +8656,23 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp.prototype.flags": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", + "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2" } }, "regexpp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.0.1.tgz", - "integrity": "sha512-8Ph721maXiOYSLtaDGKVmDn5wdsNaF6Px85qFNeMPQq0r8K5Y10tgP6YuR65Ws35n4DvzFcCxEnRNBIXQunzLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", + "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", "dev": true }, "regexpu-core": { @@ -9215,9 +8681,9 @@ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", "dev": true, "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" } }, "regjsgen": { @@ -9232,7 +8698,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "0.5.0" + "jsesc": "~0.5.0" }, "dependencies": { "jsesc": { @@ -9261,11 +8727,11 @@ "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-converter": "0.1.4", - "htmlparser2": "3.3.0", - "strip-ansi": "3.0.1", - "utila": "0.3.3" + "css-select": "^1.1.0", + "dom-converter": "~0.1", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "~0.3" }, "dependencies": { "domhandler": { @@ -9274,7 +8740,7 @@ "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", "dev": true, "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } }, "domutils": { @@ -9283,7 +8749,7 @@ "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", "dev": true, "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } }, "htmlparser2": { @@ -9292,10 +8758,10 @@ "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.1.0", - "domutils": "1.1.6", - "readable-stream": "1.0.34" + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" } }, "isarray": { @@ -9310,10 +8776,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -9348,46 +8814,42 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.0.2" + "is-finite": "^1.0.0" } }, "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", - "dev": true, + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.1", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" } }, "request-progress": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", - "dev": true, "requires": { - "throttleit": "1.0.0" + "throttleit": "^1.0.0" } }, "request-promise-core": { @@ -9396,7 +8858,7 @@ "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", "dev": true, "requires": { - "lodash": "4.17.10" + "lodash": "^4.13.1" } }, "request-promise-native": { @@ -9406,8 +8868,8 @@ "dev": true, "requires": { "request-promise-core": "1.1.1", - "stealthy-require": "1.1.1", - "tough-cookie": "2.3.3" + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" } }, "require-directory": { @@ -9417,9 +8879,9 @@ "dev": true }, "require-from-string": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "require-main-filename": { @@ -9434,8 +8896,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" } }, "requires-port": { @@ -9450,7 +8912,7 @@ "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", "dev": true, "requires": { - "underscore": "1.6.0" + "underscore": "~1.6.0" }, "dependencies": { "underscore": { @@ -9473,7 +8935,7 @@ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "dev": true, "requires": { - "resolve-from": "3.0.0" + "resolve-from": "^3.0.0" }, "dependencies": { "resolve-from": { @@ -9496,7 +8958,7 @@ "integrity": "sha1-AsyZNBDik2livZcWahsHfalyVTE=", "dev": true, "requires": { - "resolve-from": "2.0.0" + "resolve-from": "^2.0.0" }, "dependencies": { "resolve-from": { @@ -9519,8 +8981,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, "ret": { @@ -9529,20 +8991,20 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "requires": { - "align-text": "0.1.4" - } + "revalidator": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", + "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=", + "dev": true }, "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } }, "ripemd160": { "version": "2.0.2", @@ -9550,8 +9012,8 @@ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, "run-async": { @@ -9560,7 +9022,7 @@ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "is-promise": "2.1.0" + "is-promise": "^2.1.0" } }, "run-queue": { @@ -9569,28 +9031,27 @@ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { - "aproba": "1.2.0" + "aproba": "^1.1.1" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "rxjs": { + "version": "5.5.11", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", + "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", "dev": true, "requires": { - "rx-lite": "4.0.8" + "symbol-observable": "1.0.1" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", "dev": true }, "safe-regex": { @@ -9599,58 +9060,56 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "0.1.15" + "ret": "~0.1.10" } }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "sanitize-html": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.17.0.tgz", - "integrity": "sha512-5r265ukJgS+MXVMK0OxXLn7iBqRTIxYK0m6Bc+/gFhCY20Vr/KFp/ZTKu9hyB3tKkiGPiQ08aGDPUbjbBhRpXw==", + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.18.4.tgz", + "integrity": "sha512-hjyDYCYrQuhnEjq+5lenLlIfdPBtnZ7z0DkQOC8YGxvkuOInH+1SrkNTj30t4f2/SSv9c5kLniB+uCIpBvYuew==", "dev": true, "requires": { - "chalk": "2.3.0", - "htmlparser2": "3.9.2", - "lodash.clonedeep": "4.5.0", - "lodash.escaperegexp": "4.1.2", - "lodash.mergewith": "4.6.0", - "postcss": "6.0.16", - "srcset": "1.0.0", - "xtend": "4.0.1" + "chalk": "^2.3.0", + "htmlparser2": "^3.9.0", + "lodash.clonedeep": "^4.5.0", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.mergewith": "^4.6.0", + "postcss": "^6.0.14", + "srcset": "^1.0.0", + "xtend": "^4.0.0" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "domhandler": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", - "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "domelementtype": "1.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", "dev": true }, "htmlparser2": { @@ -9659,53 +9118,51 @@ "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", "dev": true, "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.4.1", - "domutils": "1.5.1", - "entities": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.3" + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" } }, - "postcss": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.16.tgz", - "integrity": "sha512-m758RWPmSjFH/2MyyG3UOW1fgYbR9rtdzz5UNJnlm7OLtu4B2h9C6gi+bE4qFKghsBRFfZT8NzoQBs6JhLotoA==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "5.1.0" - }, - "dependencies": { - "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "^3.0.0" } } } }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "dev": true, + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -9718,21 +9175,33 @@ "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", "dev": true, "requires": { - "ajv": "6.4.0", - "ajv-keywords": "3.1.0" + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" }, "dependencies": { "ajv": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", - "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", + "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", "dev": true, "requires": { - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1", - "uri-js": "3.0.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true } } }, @@ -9741,6 +9210,27 @@ "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.0.0.tgz", "integrity": "sha1-Jiw28CMc+nZU4jY/o5TNLexm83g=" }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -9748,26 +9238,18 @@ "dev": true }, "selfsigned": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.2.tgz", - "integrity": "sha1-tESVgNmZKbZbEKSDiTAaZZIIh1g=", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.3.tgz", + "integrity": "sha512-vmZenZ+8Al3NLHkWnhBQ0x6BkML1eCP2xEi3JE+f3D9wW9fipD9NNJHYtE9XJM4TsPaHGZJIamrSI6MTg1dU2Q==", "dev": true, "requires": { - "node-forge": "0.7.1" - }, - "dependencies": { - "node-forge": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", - "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=", - "dev": true - } + "node-forge": "0.7.5" } }, "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, "send": { @@ -9777,32 +9259,20 @@ "dev": true, "requires": { "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.6.3", + "http-errors": "~1.6.2", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" }, "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": "1.4.0" - } - }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", @@ -9823,27 +9293,13 @@ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.4", "batch": "0.6.1", "debug": "2.6.9", - "escape-html": "1.0.3", - "http-errors": "1.6.3", - "mime-types": "2.1.17", - "parseurl": "1.3.2" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": "1.4.0" - } - } + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" } }, "serve-static": { @@ -9852,9 +9308,9 @@ "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "dev": true, "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", "send": "0.16.2" } }, @@ -9876,10 +9332,10 @@ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -9888,7 +9344,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -9911,8 +9367,27 @@ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, "shebang-command": { @@ -9921,7 +9396,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -9931,9 +9406,9 @@ "dev": true }, "shelljs": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", - "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", "dev": true }, "signal-exit": { @@ -9948,15 +9423,10 @@ "integrity": "sha1-Vpy+IYAgKSamKiZs094Jyc60P4M=", "dev": true, "requires": { - "underscore": "1.7.0", - "url-join": "1.1.0" + "underscore": "^1.7.0", + "url-join": "^1.1.0" } }, - "sladex-blowfish": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/sladex-blowfish/-/sladex-blowfish-0.8.1.tgz", - "integrity": "sha1-y431Dra7sJgchCjGzl6B8H5kZrE=" - }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -9969,23 +9439,28 @@ "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0" + "is-fullwidth-code-point": "^2.0.0" } }, + "snackbarjs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/snackbarjs/-/snackbarjs-1.1.0.tgz", + "integrity": "sha1-pont9ExxEEdzvPIhxGk3ZosLvNY=" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.1", - "use": "3.1.0" + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" }, "dependencies": { "define-property": { @@ -9994,7 +9469,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -10003,8 +9478,14 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -10014,9 +9495,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" }, "dependencies": { "define-property": { @@ -10025,7 +9506,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -10034,7 +9515,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -10043,7 +9524,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -10052,16 +9533,10 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -10071,16 +9546,27 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "sntp": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz", - "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "dev": true, "requires": { - "hoek": "4.2.0" + "hoek": "2.x.x" } }, "sockjs": { @@ -10089,22 +9575,22 @@ "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", "dev": true, "requires": { - "faye-websocket": "0.10.0", - "uuid": "3.1.0" + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" } }, "sockjs-client": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz", - "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.5.tgz", + "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", "dev": true, "requires": { - "debug": "2.6.9", + "debug": "^2.6.6", "eventsource": "0.1.6", - "faye-websocket": "0.11.1", - "inherits": "2.0.3", - "json3": "3.3.2", - "url-parse": "1.4.0" + "faye-websocket": "~0.11.0", + "inherits": "^2.0.1", + "json3": "^3.3.2", + "url-parse": "^1.1.8" }, "dependencies": { "faye-websocket": { @@ -10113,20 +9599,11 @@ "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", "dev": true, "requires": { - "websocket-driver": "0.7.0" + "websocket-driver": ">=0.5.1" } } } }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "1.1.0" - } - }, "sortablejs": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.7.0.tgz", @@ -10139,21 +9616,21 @@ "dev": true }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "atob": "2.1.1", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" } }, "source-map-support": { @@ -10162,7 +9639,15 @@ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "source-map-url": { @@ -10172,24 +9657,35 @@ "dev": true }, "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-license-ids": "1.2.2" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", "dev": true }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, "spdy": { @@ -10198,12 +9694,12 @@ "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", "dev": true, "requires": { - "debug": "2.6.9", - "handle-thing": "1.2.5", - "http-deceiver": "1.2.7", - "safe-buffer": "5.1.1", - "select-hose": "2.0.0", - "spdy-transport": "2.1.0" + "debug": "^2.6.8", + "handle-thing": "^1.2.5", + "http-deceiver": "^1.2.7", + "safe-buffer": "^5.0.1", + "select-hose": "^2.0.0", + "spdy-transport": "^2.0.18" } }, "spdy-transport": { @@ -10212,13 +9708,23 @@ "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", "dev": true, "requires": { - "debug": "2.6.9", - "detect-node": "2.0.3", - "hpack.js": "2.1.6", - "obuf": "1.1.2", - "readable-stream": "2.3.3", - "safe-buffer": "5.1.1", - "wbuf": "1.7.3" + "debug": "^2.6.8", + "detect-node": "^2.0.3", + "hpack.js": "^2.1.6", + "obuf": "^1.1.1", + "readable-stream": "^2.2.9", + "safe-buffer": "^5.0.1", + "wbuf": "^1.7.2" + } + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "optional": true, + "requires": { + "through": "2" } }, "split-string": { @@ -10227,7 +9733,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "3.0.2" + "extend-shallow": "^3.0.0" } }, "split.js": { @@ -10241,7 +9747,7 @@ "integrity": "sha1-Fi2bGIZfAqsvKtlYVSLbm1TEgfk=", "dev": true, "requires": { - "through2": "2.0.3" + "through2": "~2.0.0" } }, "sprintf-js": { @@ -10256,8 +9762,8 @@ "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", "dev": true, "requires": { - "array-uniq": "1.0.3", - "number-is-nan": "1.0.1" + "array-uniq": "^1.0.2", + "number-is-nan": "^1.0.0" } }, "ssdeep.js": { @@ -10266,26 +9772,25 @@ "integrity": "sha1-mItJTQ3JwxkAX9rJZj1jOO/tHyI=" }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "dev": true, + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" }, "dependencies": { "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true } } @@ -10296,15 +9801,21 @@ "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.1.1" } }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, "static-eval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.0.tgz", "integrity": "sha512-6flshd3F1Gwm+Ksxq463LtFd1liC77N/PX1FVVc3OzL3hAmo2fwHFbuArkcfi7s9rTNsLEhcRmXGFZhlgy40uw==", "requires": { - "escodegen": "1.9.1" + "escodegen": "^1.8.1" } }, "static-extend": { @@ -10313,8 +9824,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "dependencies": { "define-property": { @@ -10323,7 +9834,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -10334,6 +9845,15 @@ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", "dev": true }, + "stdout-stream": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz", + "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -10346,31 +9866,31 @@ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3" + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" } }, "stream-each": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", "dev": true, "requires": { - "end-of-stream": "1.4.0", - "stream-shift": "1.0.0" + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" } }, "stream-http": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.1.tgz", - "integrity": "sha512-cQ0jo17BLca2r0GfRdZKYAGLU6JRoIWxqSOakUMuKOT6MOK7AAlE856L33QuDmAy/eeOrhLee3dZKX0Uadu93A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", "dev": true, "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" } }, "stream-shift": { @@ -10379,10 +9899,10 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", "dev": true }, "string-width": { @@ -10391,8 +9911,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "dependencies": { "ansi-regex": { @@ -10407,33 +9927,44 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "string.prototype.matchall": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz", + "integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.10.0", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "regexp.prototype.flags": "^1.2.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" } }, "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", "dev": true }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -10442,7 +9973,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } }, "strip-eof": { @@ -10457,7 +9988,7 @@ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { - "get-stdin": "4.0.1" + "get-stdin": "^4.0.1" } }, "strip-json-comments": { @@ -10472,38 +10003,20 @@ "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" + "loader-utils": "^1.1.0", + "schema-utils": "^0.4.5" } }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, - "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", - "dev": true, - "requires": { - "coa": "1.0.4", - "colors": "1.1.2", - "csso": "2.3.2", - "js-yaml": "3.7.0", - "mkdirp": "0.5.1", - "sax": "1.2.4", - "whet.extend": "0.9.9" - }, - "dependencies": { - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - } - } + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true }, "symbol-tree": { "version": "3.2.2", @@ -10512,24 +10025,30 @@ "dev": true }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { - "ajv": "5.2.3", - "ajv-keywords": "2.1.1", - "chalk": "2.3.2", - "lodash": "4.17.10", + "ajv": "^6.0.1", + "ajv-keywords": "^3.0.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", "slice-ansi": "1.0.0", - "string-width": "2.1.1" + "string-width": "^2.1.1" }, "dependencies": { - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true + "ajv": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", + "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" + } }, "ansi-styles": { "version": "3.2.1", @@ -10537,33 +10056,39 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -10580,6 +10105,17 @@ "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", "dev": true }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10594,8 +10130,7 @@ "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", - "dev": true + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" }, "through": { "version": "2.3.8", @@ -10609,8 +10144,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "2.3.3", - "xtend": "4.0.1" + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" } }, "thunky": { @@ -10625,43 +10160,31 @@ "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", "dev": true, "requires": { - "setimmediate": "1.0.5" + "setimmediate": "^1.0.4" } }, "tiny-lr": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", - "integrity": "sha1-s/26gC5dVqM8L28QeUsy5Hescp0=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", "dev": true, "requires": { - "body-parser": "1.14.2", - "debug": "2.2.0", - "faye-websocket": "0.10.0", - "livereload-js": "2.3.0", - "parseurl": "1.3.2", - "qs": "5.1.0" + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" }, "dependencies": { "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { - "ms": "0.7.1" + "ms": "2.0.0" } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - }, - "qs": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", - "integrity": "sha1-TZMuXH6kEcynajEtOaYGIA/VDNk=", - "dev": true } } }, @@ -10671,7 +10194,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } }, "to-arraybuffer": { @@ -10683,8 +10206,7 @@ "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" }, "to-object-path": { "version": "0.3.0", @@ -10692,7 +10214,18 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "to-regex": { @@ -10701,10 +10234,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" } }, "to-regex-range": { @@ -10713,8 +10246,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } }, "toposort": { @@ -10724,12 +10257,11 @@ "dev": true }, "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "dev": true, + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "tr46": { @@ -10738,13 +10270,13 @@ "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", "dev": true, "requires": { - "punycode": "2.1.0" + "punycode": "^2.1.0" }, "dependencies": { "punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true } } @@ -10766,13 +10298,43 @@ "resolved": "https://registry.npmjs.org/triplesec/-/triplesec-3.0.26.tgz", "integrity": "sha1-3/K7R1ikIzcuc5o5fYmR8Fl9CsE=", "requires": { - "iced-error": "0.0.12", - "iced-lock": "1.1.0", - "iced-runtime": "1.0.3", - "more-entropy": "0.0.7", - "progress": "1.1.8" + "iced-error": ">=0.0.9", + "iced-lock": "^1.0.1", + "iced-runtime": "^1.0.2", + "more-entropy": ">=0.0.7", + "progress": "~1.1.2" } }, + "true-case-path": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz", + "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=", + "dev": true, + "requires": { + "glob": "^6.0.4" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -10783,16 +10345,14 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, "optional": true }, "type-check": { @@ -10800,7 +10360,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-is": { @@ -10810,69 +10370,51 @@ "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.18" - }, - "dependencies": { - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "requires": { - "mime-db": "1.33.0" - } - } + "mime-types": "~2.1.18" } }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "ua-parser-js": { - "version": "0.7.17", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", - "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==" + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", + "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" }, "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.6.tgz", + "integrity": "sha512-O1D7L6WcOzS1qW2ehopEm4cWm5yA6bQBozlks8jO8ODxYCy4zv+bR/la4Lwp01tpkYGNonnpXvUpYtrvSu8Yzg==", "dev": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "commander": "~2.16.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", + "dev": true + } } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, "uglifyjs-webpack-plugin": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.5.tgz", - "integrity": "sha512-hIQJ1yxAPhEA2yW/i7Fr+SXZVMp+VEI3d42RTHBgQd2yhp/1UdBcR3QEWPV5ahBxlqQDMEMTuTEvDHSFINfwSw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz", + "integrity": "sha512-1VicfKhCYHLS8m1DCApqBhoulnASsEoJ/BvpUpP4zoNAPpKzdH+ghk0olGJMmwX2/jprK2j3hAHdUbczBSy2FA==", "dev": true, "requires": { - "cacache": "10.0.4", - "find-cache-dir": "1.0.0", - "schema-utils": "0.4.5", - "serialize-javascript": "1.5.0", - "source-map": "0.6.1", - "uglify-es": "3.3.9", - "webpack-sources": "1.1.0", - "worker-farm": "1.6.0" + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" }, "dependencies": { "commander": { @@ -10881,20 +10423,14 @@ "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "uglify-es": { "version": "3.3.9", "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", "dev": true, "requires": { - "commander": "2.13.0", - "source-map": "0.6.1" + "commander": "~2.13.0", + "source-map": "~0.6.1" } } } @@ -10927,9 +10463,19 @@ } }, "underscore.string": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", - "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz", + "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=", + "dev": true, + "requires": { + "sprintf-js": "^1.0.3", + "util-deprecate": "^1.0.2" + } + }, + "unicode-5.2.0": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unicode-5.2.0/-/unicode-5.2.0-0.7.5.tgz", + "integrity": "sha512-KVGLW1Bri30x00yv4HNM8kBxoqFXr0Sbo55735nvrlsx4PYBZol3UtoWgO492fSwmsetzPEZzy73rbU8OGXJcA==", "dev": true }, "union-value": { @@ -10938,10 +10484,10 @@ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" }, "dependencies": { "extend-shallow": { @@ -10950,7 +10496,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "set-value": { @@ -10959,42 +10505,21 @@ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" } } } }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqid": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz", - "integrity": "sha1-iSIN32t1GuUrX3JISGNShZa7hME=", - "dev": true, - "requires": { - "macaddress": "0.2.8" - } - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, "unique-filename": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", "dev": true, "requires": { - "unique-slug": "2.0.0" + "unique-slug": "^2.0.0" } }, "unique-slug": { @@ -11003,7 +10528,7 @@ "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", "dev": true, "requires": { - "imurmurhash": "0.1.4" + "imurmurhash": "^0.1.4" } }, "unixify": { @@ -11012,7 +10537,7 @@ "integrity": "sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=", "dev": true, "requires": { - "normalize-path": "2.1.1" + "normalize-path": "^2.1.1" } }, "unpipe": { @@ -11027,8 +10552,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" + "has-value": "^0.3.1", + "isobject": "^3.0.0" }, "dependencies": { "has-value": { @@ -11037,9 +10562,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" }, "dependencies": { "isobject": { @@ -11062,9 +10587,9 @@ } }, "upath": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.5.tgz", - "integrity": "sha512-qbKn90aDQ0YEwvXoLqj0oiuUYroLX2lVHZ+b+xwjozFasAOC4GneDq5+OaIG5Zj+jFmbz/uO+f7a9qxjktJQww==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, "upper-case": { @@ -11074,28 +10599,22 @@ "dev": true }, "uri-js": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", - "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "punycode": "2.1.0" + "punycode": "^2.1.0" }, "dependencies": { "punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true } } }, - "uri-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", - "integrity": "sha1-l0fwGDWJM8Md4PzP2C0TjmcmLjI=", - "dev": true - }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -11132,53 +10651,26 @@ "integrity": "sha512-rAonpHy7231fmweBKUFe0bYnlGDty77E+fm53NZdij7j/YOpyGzc7ttqG1nAXl3aRs0k41o0PC3TvGXQiw2Zvw==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "mime": "2.2.0", - "schema-utils": "0.4.5" - }, - "dependencies": { - "mime": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz", - "integrity": "sha512-0Qz9uF1ATtl8RKJG4VRfOymh7PyEor6NbrI/61lRfuRe4vx9SNATrvAeTj2EWVRKjEQGskrzWkJBBY5NbaVHIA==", - "dev": true - } + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^0.4.3" } }, "url-parse": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.0.tgz", - "integrity": "sha512-ERuGxDiQ6Xw/agN4tuoCRbmwRuZP0cJ1lJxJubXr5Q/5cDa78+Dc4wfvtxzhzhkm5VvmW6Mf8EVj9SPGN4l8Lg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", + "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", "dev": true, "requires": { - "querystringify": "2.0.0", - "requires-port": "1.0.0" - }, - "dependencies": { - "querystringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", - "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", - "dev": true - } + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" } }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "utf8": { "version": "3.0.0", @@ -11186,27 +10678,18 @@ "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" }, "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", "dev": true, "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } + "inherits": "2.0.3" } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -11214,8 +10697,8 @@ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", "dev": true, "requires": { - "define-properties": "1.1.2", - "object.getownpropertydescriptors": "2.0.3" + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" } }, "utila": { @@ -11224,6 +10707,34 @@ "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", "dev": true }, + "utile": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", + "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", + "dev": true, + "requires": { + "async": "~0.9.0", + "deep-equal": "~0.2.1", + "i": "0.3.x", + "mkdirp": "0.x.x", + "ncp": "1.0.x", + "rimraf": "2.x.x" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "deep-equal": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", + "dev": true + } + } + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -11231,34 +10742,24 @@ "dev": true }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "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" - } + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "valid-data-url": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.4.tgz", - "integrity": "sha512-p3bCVl3Vrz42TV37a1OjagyLLd6qQAXBDWarIazuo7NQzCt8Kw8ZZwSAbUVPGlz5ubgbgJmgT0KRjLeCFNrfoQ==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz", + "integrity": "sha512-FXg2qXMzfAhZc0y2HzELNfUeiOjPr+52hU1DNBWiJJ2luXD+dD1R9NA48Ug5aj0ibbxroeGDc/RJv6ThiGgkDw==", "dev": true }, "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "validator": { @@ -11273,21 +10774,14 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, - "vendors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz", - "integrity": "sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI=", - "dev": true - }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "vkbeautify": { @@ -11310,7 +10804,7 @@ "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", "dev": true, "requires": { - "browser-process-hrtime": "0.1.2" + "browser-process-hrtime": "^0.1.2" } }, "watchpack": { @@ -11319,9 +10813,9 @@ "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", "dev": true, "requires": { - "chokidar": "2.0.3", - "graceful-fs": "4.1.11", - "neo-async": "2.5.1" + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" } }, "wbuf": { @@ -11330,7 +10824,7 @@ "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, "requires": { - "minimalistic-assert": "1.0.1" + "minimalistic-assert": "^1.0.0" } }, "web-resource-inliner": { @@ -11339,24 +10833,21 @@ "integrity": "sha512-fOWnBQHVX8zHvEbECDTxtYL0FXIIZZ5H3LWoez8mGopYJK7inEru1kVMDzM1lVdeJBNEqUnNP5FBGxvzuMcwwQ==", "dev": true, "requires": { - "async": "2.5.0", - "chalk": "1.1.3", - "datauri": "1.0.5", - "htmlparser2": "3.9.2", - "lodash.unescape": "4.0.1", - "request": "2.83.0", - "valid-data-url": "0.1.4", - "xtend": "4.0.1" + "async": "^2.1.2", + "chalk": "^1.1.3", + "datauri": "^1.0.4", + "htmlparser2": "^3.9.2", + "lodash.unescape": "^4.0.1", + "request": "^2.78.0", + "valid-data-url": "^0.1.4", + "xtend": "^4.0.0" }, "dependencies": { - "domhandler": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", - "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", - "dev": true, - "requires": { - "domelementtype": "1.3.0" - } + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true }, "htmlparser2": { "version": "3.9.2", @@ -11364,12 +10855,12 @@ "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", "dev": true, "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.4.1", - "domutils": "1.5.1", - "entities": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.3" + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" } } } @@ -11381,67 +10872,79 @@ "dev": true }, "webpack": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.6.0.tgz", - "integrity": "sha512-Fu/k/3fZeGtIhuFkiYpIy1UDHhMiGKjG4FFPVuvG+5Os2lWA1ttWpmi9Qnn6AgfZqj9MvhZW/rmj/ip+nHr06g==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.4.tgz", + "integrity": "sha512-RqUfwp4qMqv3oFwBQQOoK69C2tdu2FHJEqPABPqgjGDvOIOLqkTOhmmdJjpiRabzNAAH1ahmkA3z4xowlHN+VA==", "dev": true, "requires": { - "acorn": "5.5.0", - "acorn-dynamic-import": "3.0.0", - "ajv": "6.4.0", - "ajv-keywords": "3.1.0", - "chrome-trace-event": "0.1.3", - "enhanced-resolve": "4.0.0", - "eslint-scope": "3.7.1", - "loader-runner": "2.3.0", - "loader-utils": "1.1.0", - "memory-fs": "0.4.1", - "micromatch": "3.1.10", - "mkdirp": "0.5.1", - "neo-async": "2.5.1", - "node-libs-browser": "2.1.0", - "schema-utils": "0.4.5", - "tapable": "1.0.0", - "uglifyjs-webpack-plugin": "1.2.5", - "watchpack": "1.6.0", - "webpack-sources": "1.1.0" + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/wasm-edit": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.0.0", + "uglifyjs-webpack-plugin": "^1.2.4", + "watchpack": "^1.5.0", + "webpack-sources": "^1.0.1" }, "dependencies": { "ajv": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", - "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", + "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", "dev": true, "requires": { - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1", - "uri-js": "3.0.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true } } }, "webpack-dev-middleware": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.1.2.tgz", - "integrity": "sha512-Z11Zp3GTvCe6mGbbtma+lMB9xRfJcNtupXfmvFBujyXqLNms6onDnSi9f/Cb2rw6KkD5kgibOfxhN7npZwTiGA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.1.3.tgz", + "integrity": "sha512-I6Mmy/QjWU/kXwCSFGaiOoL5YEQIVmbb0o45xMoCyQAg/mClqZVTcsX327sPfekDyJWpCxb+04whNyLOIxpJdQ==", "dev": true, "requires": { - "loud-rejection": "1.6.0", - "memory-fs": "0.4.1", - "mime": "2.3.1", - "path-is-absolute": "1.0.1", - "range-parser": "1.2.0", - "url-join": "4.0.0", - "webpack-log": "1.2.0" + "loud-rejection": "^1.6.0", + "memory-fs": "~0.4.1", + "mime": "^2.1.0", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "url-join": "^4.0.0", + "webpack-log": "^1.0.1" }, "dependencies": { - "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", - "dev": true - }, "url-join": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", @@ -11451,38 +10954,38 @@ } }, "webpack-dev-server": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.3.tgz", - "integrity": "sha512-UXfgQIPpdw2rByoUnCrMAIXCS7IJJMp5N0MDQNk9CuQvirCkuWlu7gQpCS8Kaiz4kogC4TdAQHG3jzh/DdqEWg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.5.tgz", + "integrity": "sha512-LVHg+EPwZLHIlfvokSTgtJqO/vI5CQi89fASb5JEDtVMDjY0yuIEqPPdMiKaBJIB/Ab7v/UN/sYZ7WsZvntQKw==", "dev": true, "requires": { "ansi-html": "0.0.7", - "array-includes": "3.0.3", - "bonjour": "3.5.0", - "chokidar": "2.0.3", - "compression": "1.7.2", - "connect-history-api-fallback": "1.5.0", - "debug": "3.1.0", - "del": "3.0.0", - "express": "4.16.3", - "html-entities": "1.2.1", - "http-proxy-middleware": "0.18.0", - "import-local": "1.0.0", + "array-includes": "^3.0.3", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^1.0.0", "internal-ip": "1.2.0", - "ip": "1.1.5", - "killable": "1.0.0", - "loglevel": "1.6.1", - "opn": "5.3.0", - "portfinder": "1.0.13", - "selfsigned": "1.10.2", - "serve-index": "1.9.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "selfsigned": "^1.9.1", + "serve-index": "^1.7.2", "sockjs": "0.3.19", - "sockjs-client": "1.1.4", - "spdy": "3.4.7", - "strip-ansi": "3.0.1", - "supports-color": "5.4.0", - "webpack-dev-middleware": "3.1.2", - "webpack-log": "1.2.0", + "sockjs-client": "1.1.5", + "spdy": "^3.4.1", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "webpack-dev-middleware": "3.1.3", + "webpack-log": "^1.1.2", "yargs": "11.0.0" }, "dependencies": { @@ -11492,15 +10995,21 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" }, "dependencies": { "strip-ansi": { @@ -11509,7 +11018,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -11529,12 +11038,12 @@ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", "dev": true, "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.2.8" + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" } }, "globby": { @@ -11543,11 +11052,11 @@ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { - "array-union": "1.0.2", - "glob": "7.0.6", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" }, "dependencies": { "pify": { @@ -11558,17 +11067,16 @@ } } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } }, "supports-color": { "version": "5.4.0", @@ -11576,13 +11084,13 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "yargs": { @@ -11591,18 +11099,27 @@ "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", "dev": true, "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" } } } @@ -11613,10 +11130,10 @@ "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", "dev": true, "requires": { - "chalk": "2.4.1", - "log-symbols": "2.2.0", - "loglevelnext": "1.0.5", - "uuid": "3.1.0" + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "loglevelnext": "^1.0.1", + "uuid": "^3.1.0" }, "dependencies": { "ansi-styles": { @@ -11625,7 +11142,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -11634,24 +11151,18 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -11668,16 +11179,8 @@ "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", "dev": true, "requires": { - "source-list-map": "2.0.0", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" } }, "websocket-driver": { @@ -11686,8 +11189,8 @@ "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", "dev": true, "requires": { - "http-parser-js": "0.4.10", - "websocket-extensions": "0.1.3" + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" } }, "websocket-extensions": { @@ -11703,45 +11206,86 @@ "dev": true, "requires": { "iconv-lite": "0.4.19" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + } } }, - "whatwg-url": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.4.0.tgz", - "integrity": "sha512-Z0CVh/YE217Foyb488eo+iBv+r7eAQ0wSTyApi9n06jhcA3z6Nidg/EGvl0UFkg7kMdKxfBzzr+o9JF+cevgMg==", - "dev": true, - "requires": { - "lodash.sortby": "4.7.0", - "tr46": "1.0.1", - "webidl-conversions": "4.0.2" - } - }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "whatwg-mimetype": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz", + "integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew==", "dev": true }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", "dev": true, "requires": { - "isexe": "2.0.0" + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" } }, "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "winston": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.3.tgz", + "integrity": "sha512-GYKuysPz2pxYAVJD2NPsDLP5Z79SDEzPm9/j4tCjkF/n89iBNGBMJcR+dMUqxgPNgoSs6fVygPi+Vl2oxIpBuw==", + "dev": true, + "optional": true, + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", + "dev": true, + "optional": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true, + "optional": true + } + } }, "wordwrap": { "version": "1.0.0", @@ -11754,46 +11298,17 @@ "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", "dev": true, "requires": { - "errno": "0.1.7" + "errno": "~0.1.7" } }, "worker-loader": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-1.1.1.tgz", - "integrity": "sha512-qJZLVS/jMCBITDzPo/RuweYSIG8VJP5P67mP/71alGyTZRe1LYJFdwLjLalY3T5ifx0bMDRD3OB6P2p1escvlg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", + "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" - }, - "dependencies": { - "ajv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.2.0.tgz", - "integrity": "sha1-r6wpW7qgFSRJ5SJ0LkVHwa6TKNI=", - "dev": true, - "requires": { - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "ajv-keywords": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", - "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=", - "dev": true - }, - "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", - "dev": true, - "requires": { - "ajv": "6.2.0", - "ajv-keywords": "3.1.0" - } - } + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" } }, "wrap-ansi": { @@ -11802,8 +11317,8 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { "is-fullwidth-code-point": { @@ -11812,7 +11327,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "string-width": { @@ -11821,9 +11336,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -11840,17 +11355,16 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "0.5.1" + "mkdirp": "^0.5.1" } }, "ws": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", - "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", "dev": true, "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1" + "async-limiter": "~1.0.0" } }, "xml-name-validator": { @@ -11876,9 +11390,9 @@ "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" }, "xregexp": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.1.1.tgz", - "integrity": "sha512-QJ1gfSUV7kEOLfpKFCjBJRnfPErUzkNKFMso4kDSmGpp3x6ZgkyKf74inxI7PnnQCFYq5TqYJCd7DrgDN8Q05A==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.0.tgz", + "integrity": "sha512-IyMa7SVe9FyT4WbQVW3b95mTLVceHhLEezQ02+QMvmIqDnKTxk0MLWIQPSW2MXAr1zQb+9yvwYhcyQULneh3wA==" }, "xtend": { "version": "4.0.1", @@ -11887,9 +11401,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true }, "yallist": { @@ -11899,38 +11413,67 @@ "dev": true }, "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", "dev": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" }, "dependencies": { "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } } } }, "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^3.0.0" }, "dependencies": { "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true } } @@ -11939,9 +11482,8 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "dev": true, "requires": { - "fd-slicer": "1.0.1" + "fd-slicer": "~1.0.1" } }, "zlibjs": { diff --git a/package.json b/package.json index ca8bd22c..0d643f21 100644 --- a/package.json +++ b/package.json @@ -30,61 +30,66 @@ "main": "build/node/CyberChef.js", "bugs": "https://github.com/gchq/CyberChef/issues", "devDependencies": { + "autoprefixer": "^9.1.0", "babel-core": "^6.26.3", - "babel-loader": "^7.1.4", - "babel-polyfill": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "^0.28.11", - "eslint": "^4.19.1", + "babel-loader": "^7.1.5", + "babel-preset-env": "^1.7.0", + "bootstrap": "^4.1.3", + "colors": "^1.3.1", + "css-loader": "^1.0.0", + "eslint": "^5.3.0", "exports-loader": "^0.7.0", "extract-text-webpack-plugin": "^4.0.0-alpha0", "file-loader": "^1.1.11", - "grunt": ">=1.0.2", + "grunt": "^1.0.3", "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-contrib-watch": "^1.1.0", + "grunt-eslint": "^21.0.0", "grunt-exec": "~3.0.0", - "grunt-execute": "^0.2.2", "grunt-jsdoc": "^2.2.1", - "grunt-webpack": "^3.1.1", + "grunt-webpack": "^3.1.2", "html-webpack-plugin": "^3.2.0", "imports-loader": "^0.8.0", "ink-docstrap": "^1.3.2", + "js-to-mjs": "^0.2.0", "jsdoc-babel": "^0.4.0", - "less": "^3.0.2", - "less-loader": "^4.1.0", - "postcss-css-variables": "^0.8.1", - "postcss-import": "^11.1.0", - "postcss-loader": "^2.1.4", + "node-sass": "^4.9.2", + "postcss-css-variables": "^0.9.0", + "postcss-import": "^12.0.0", + "postcss-loader": "^2.1.6", + "prompt": "^1.0.0", + "sass-loader": "^7.1.0", "sitemap": "^1.13.0", "style-loader": "^0.21.0", "url-loader": "^1.0.1", - "val-loader": "^1.1.0", "web-resource-inliner": "^4.2.1", - "webpack": "^4.6.0", - "webpack-dev-server": "^3.1.3", + "webpack": "^4.16.4", + "webpack-dev-server": "^3.1.5", "webpack-node-externals": "^1.7.2", - "worker-loader": "^1.1.1" + "worker-loader": "^2.0.0" }, "dependencies": { + "arrive": "^2.4.1", + "babel-plugin-transform-builtin-extend": "1.1.2", "babel-polyfill": "^6.26.0", "bcryptjs": "^2.4.3", - "bignumber.js": "^7.0.1", - "bootstrap": "^3.3.7", - "bootstrap-colorpicker": "^2.5.2", - "bootstrap-switch": "^3.3.4", - "bson": "^2.0.6", + "bignumber.js": "^7.2.1", + "bootstrap-colorpicker": "^2.5.3", + "bootstrap-material-design": "^4.1.1", + "bson": "^3.0.2", + "chi-squared": "^1.1.0", "crypto-api": "^0.8.0", "crypto-js": "^3.1.9-1", "ctph.js": "0.0.5", "diff": "^3.5.0", - "escodegen": "^1.9.1", "es6-promisify": "^6.0.0", + "escodegen": "^1.11.0", "esmangle": "^1.0.1", - "esprima": "^4.0.0", + "esprima": "^4.0.1", "exif-parser": "^0.1.12", "file-saver": "^1.3.8", "highlight.js": "^9.12.0", @@ -95,33 +100,37 @@ "jsesc": "^2.5.1", "jsonpath": "^1.0.0", "jsrsasign": "8.0.12", + "kbpgp": "^2.0.77", "lodash": "^4.17.10", "loglevel": "^1.6.1", - "kbpgp": "^2.0.77", "loglevel-message-prefix": "^3.0.0", - "moment": "^2.22.1", - "moment-timezone": "^0.5.16", + "moment": "^2.22.2", + "moment-timezone": "^0.5.21", "node-forge": "^0.7.5", "node-md6": "^0.1.0", "nwmatcher": "^1.4.4", "otp": "^0.1.3", + "popper.js": "^1.14.4", "scryptsy": "^2.0.0", - "sladex-blowfish": "^0.8.1", + "snackbarjs": "^1.1.0", "sortablejs": "^1.7.0", "split.js": "^1.3.5", "ssdeep.js": "0.0.2", - "ua-parser-js": "^0.7.17", + "ua-parser-js": "^0.7.18", "utf8": "^3.0.0", "vkbeautify": "^0.99.3", "xmldom": "^0.1.27", "xpath": "0.0.27", - "xregexp": "^4.1.1", + "xregexp": "^4.2.0", "zlibjs": "^0.3.1" }, "scripts": { "start": "grunt dev", "build": "grunt prod", "test": "grunt test", - "docs": "grunt docs" + "docs": "grunt docs", + "lint": "grunt lint", + "newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs", + "postinstall": "[ -f node_modules/crypto-api/src/crypto-api.mjs ] || npx j2m node_modules/crypto-api/src/crypto-api.js" } } 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..a7302377 --- /dev/null +++ b/src/core/Chef.mjs @@ -0,0 +1,198 @@ +/** + * @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 the previously run operation presented a different value to its + // normal output, we need to recalculate it. + if (recipe.lastOpPresented(progress)) { + progress = 0; + } + + // 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; + + // Create a raw version of the dish, unpresented + const rawDish = new Dish(this.dish); + + // Present the raw result + await recipe.present(this.dish); + + return { + dish: rawDish, + result: this.dish.type === Dish.HTML ? + await this.dish.get(Dish.HTML, notUTF8) : + await 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(); + + 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 + }; + } + + + /** + * Translates the dish to a specified type and returns it. + * + * @param {Dish} dish + * @param {string} type + * @returns {Dish} + */ + async getDishAs(dish, type) { + const newDish = new Dish(dish); + return await newDish.get(type); + } + +} + +export default Chef; diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index a091e09c..91f9955d 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/OpModules"; // Add ">" to the start of all log messages in the Chef Worker import loglevelMessagePrefix from "loglevel-message-prefix"; @@ -60,6 +60,9 @@ self.addEventListener("message", function(e) { case "silentBake": silentBake(r.data); break; + case "getDishAs": + getDishAs(r.data); + break; case "docURL": // Used to set the URL of the current document so that scripts can be // imported into an inline worker. @@ -88,7 +91,7 @@ self.addEventListener("message", function(e) { */ async function bake(data) { // Ensure the relevant modules are loaded - loadRequiredModules(data.recipeConfig); + self.loadRequiredModules(data.recipeConfig); try { const response = await self.chef.bake( @@ -101,12 +104,16 @@ async function bake(data) { self.postMessage({ action: "bakeComplete", - data: response + data: Object.assign(response, { + id: data.id + }) }); } catch (err) { self.postMessage({ action: "bakeError", - data: err + data: Object.assign(err, { + id: data.id + }) }); } } @@ -126,19 +133,16 @@ function silentBake(data) { /** - * Checks that all required modules are loaded and loads them if not. - * - * @param {Object} recipeConfig + * Translates the dish to a given type. */ -function loadRequiredModules(recipeConfig) { - recipeConfig.forEach(op => { - let module = self.OperationConfig[op.op].module; +async function getDishAs(data) { + const value = await self.chef.getDishAs(data.dish, data.type); - if (!OpModules.hasOwnProperty(module)) { - log.info("Loading module " + module); - self.sendStatusMessage("Loading module " + module); - self.importScripts(self.docURL + "/" + module + ".js"); - self.sendStatusMessage(""); + self.postMessage({ + action: "dishReturned", + data: { + value: value, + id: data.id } }); } @@ -163,6 +167,25 @@ function calculateHighlights(recipeConfig, direction, pos) { } +/** + * Checks that all required modules are loaded and loads them if not. + * + * @param {Object} recipeConfig + */ +self.loadRequiredModules = function(recipeConfig) { + recipeConfig.forEach(op => { + const module = self.OperationConfig[op.op].module; + + if (!OpModules.hasOwnProperty(module)) { + log.info(`Loading ${module} module`); + self.sendStatusMessage(`Loading ${module} module`); + self.importScripts(`${self.docURL}/${module}.js`); + self.sendStatusMessage(""); + } + }); +}; + + /** * Send status update to the app. * 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..eee404fc --- /dev/null +++ b/src/core/Dish.mjs @@ -0,0 +1,362 @@ +/** + * @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 {Dish} [dish=null] - A dish to clone + */ + constructor(dish=null) { + this.value = []; + this.type = Dish.BYTE_ARRAY; + + if (dish && + dish.hasOwnProperty("value") && + dish.hasOwnProperty("type")) { + this.set(dish.value, dish.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; + case "json": + return Dish.JSON; + case "file": + return Dish.FILE; + case "list": + return Dish.LIST_FILE; + 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"; + case Dish.JSON: + return "JSON"; + case Dish.FILE: + return "File"; + case Dish.LIST_FILE: + return "List"; + default: + throw "Invalid data type enum. No matching type."; + } + } + + + /** + * Sets the data value and type and then validates them. + * + * @param {*} 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 {*} - The value of the output data. + */ + async get(type, notUTF8=false) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + if (this.type !== type) { + await 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. + */ + async _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; + case Dish.JSON: + this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value)) : []; + break; + case Dish.FILE: + this.value = await Utils.readFile(this.value); + this.value = Array.prototype.slice.call(this.value); + break; + case Dish.LIST_FILE: + this.value = await Promise.all(this.value.map(async f => Utils.readFile(f))); + this.value = this.value.map(b => Array.prototype.slice.call(b)); + this.value = [].concat.apply([], this.value); + 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; + case Dish.JSON: + this.value = JSON.parse(byteArrayToStr(this.value)); + this.type = Dish.JSON; + break; + case Dish.FILE: + this.value = new File(this.value, "unknown"); + break; + case Dish.LIST_FILE: + this.value = [new File(this.value, "unknown")]; + this.type = Dish.LIST_FILE; + 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; + case Dish.JSON: + // All values can be serialised in some manner, so we return true in all cases + return true; + case Dish.FILE: + return this.value instanceof File; + case Dish.LIST_FILE: + return this.value instanceof Array && + this.value.reduce((acc, curr) => acc && curr instanceof File, true); + 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; + case Dish.JSON: + return JSON.stringify(this.value).length; + case Dish.FILE: + return this.value.size; + case Dish.LIST_FILE: + return this.value.reduce((acc, curr) => acc + curr.size, 0); + 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; +/** + * Dish data type enum for JSON. + * @readonly + * @enum + */ +Dish.JSON = 6; +/** + * Dish data type enum for lists of files. + * @readonly + * @enum + */ +Dish.FILE = 7; +/** +* Dish data type enum for lists of files. +* @readonly +* @enum +*/ +Dish.LIST_FILE = 8; + + +export default Dish; diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js deleted file mode 100755 index fd2c0785..00000000 --- a/src/core/FlowControl.js +++ /dev/null @@ -1,289 +0,0 @@ -import Recipe from "./Recipe.js"; -import Dish from "./Dish.js"; - - -/** - * Flow Control operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const FlowControl = { - - /** - * Fork operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runFork: async function(state) { - let opList = state.opList, - inputType = opList[state.progress].inputType, - outputType = opList[state.progress].outputType, - input = state.dish.get(inputType), - ings = opList[state.progress].getIngValues(), - splitDelim = ings[0], - mergeDelim = ings[1], - ignoreErrors = ings[2], - subOpList = [], - inputs = [], - i; - - if (input) - inputs = input.split(splitDelim); - - // 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()) { - break; - } else { - subOpList.push(opList[i]); - } - } - - let recipe = new Recipe(), - output = "", - progress = 0; - - state.forkOffset += state.progress + 1; - - recipe.addOperations(subOpList); - - // Take a deep(ish) copy of the ingredient values - const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.getIngValues()))); - - // Run recipe over each tranche - for (i = 0; i < inputs.length; i++) { - log.debug(`Entering tranche ${i + 1} of ${inputs.length}`); - - // Baseline ing values for each tranche so that registers are reset - subOpList.forEach((op, i) => { - op.setIngValues(JSON.parse(JSON.stringify(ingValues[i]))); - }); - - const dish = new Dish(inputs[i], inputType); - try { - progress = await recipe.execute(dish, 0, state); - } catch (err) { - if (!ignoreErrors) { - throw err; - } - progress = err.progress + 1; - } - output += dish.get(outputType) + mergeDelim; - } - - state.dish.set(output, outputType); - state.progress += progress; - return state; - }, - - - /** - * Merge operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runMerge: function(state) { - // No need to actually do anything here. The fork operation will - // merge when it sees this operation. - return state; - }, - - - /** - * Register operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runRegister: function(state) { - const ings = state.opList[state.progress].getIngValues(), - extractorStr = ings[0], - i = ings[1], - m = ings[2]; - - let modifiers = ""; - if (i) modifiers += "i"; - if (m) modifiers += "m"; - - const extractor = new RegExp(extractorStr, modifiers), - input = state.dish.get(Dish.STRING), - registers = input.match(extractor); - - if (!registers) return state; - - if (ENVIRONMENT_IS_WORKER()) { - self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1)); - } - - /** - * Replaces references to registers (e.g. $R0) with the contents of those registers. - * - * @param {string} str - * @returns {string} - */ - function replaceRegister(str) { - // Replace references to registers ($Rn) with contents of registers - return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => { - const index = parseInt(regNum, 10) + 1; - if (index <= state.numRegisters || index >= state.numRegisters + registers.length) - return match; - if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape - return slashes + registers[index - state.numRegisters]; - }); - } - - // 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; - - let args = state.opList[i].getIngValues(); - args = args.map(arg => { - if (typeof arg !== "string" && typeof arg !== "object") return arg; - - if (typeof arg === "object" && arg.hasOwnProperty("string")) { - arg.string = replaceRegister(arg.string); - return arg; - } - return replaceRegister(arg); - }); - state.opList[i].setIngValues(args); - } - - state.numRegisters += registers.length - 1; - return state; - }, - - - /** - * Jump operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @param {number} state.numJumps - The number of jumps taken so far. - * @returns {Object} The updated state of the recipe. - */ - runJump: function(state) { - const ings = state.opList[state.progress].getIngValues(), - label = ings[0], - maxJumps = ings[1], - jmpIndex = FlowControl._getLabelIndex(label, state); - - if (state.numJumps >= maxJumps || jmpIndex === -1) { - log.debug("Maximum jumps reached or label cannot be found"); - return state; - } - - state.progress = jmpIndex; - state.numJumps++; - log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`); - return state; - }, - - - /** - * Conditional Jump operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @param {number} state.numJumps - The number of jumps taken so far. - * @returns {Object} The updated state of the recipe. - */ - runCondJump: function(state) { - const ings = state.opList[state.progress].getIngValues(), - dish = state.dish, - regexStr = ings[0], - invert = ings[1], - label = ings[2], - maxJumps = ings[3], - jmpIndex = FlowControl._getLabelIndex(label, state); - - if (state.numJumps >= maxJumps || jmpIndex === -1) { - log.debug("Maximum jumps reached or label cannot be found"); - return state; - } - - if (regexStr !== "") { - let strMatch = dish.get(Dish.STRING).search(regexStr) > -1; - if (!invert && strMatch || invert && !strMatch) { - state.progress = jmpIndex; - state.numJumps++; - log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`); - } - } - - return state; - }, - - - /** - * Return operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runReturn: function(state) { - state.progress = state.opList.length; - return state; - }, - - - /** - * Comment operation. - * - * @param {Object} state - The current state of the recipe. - * @param {number} state.progress - The current position in the recipe. - * @param {Dish} state.dish - The Dish being operated on. - * @param {Operation[]} state.opList - The list of operations in the recipe. - * @returns {Object} The updated state of the recipe. - */ - runComment: function(state) { - return state; - }, - - - /** - * Returns the index of a label. - * - * @private - * @param {Object} state - * @param {string} name - * @returns {number} - */ - _getLabelIndex: function(name, state) { - for (let o = 0; o < state.opList.length; o++) { - let operation = state.opList[o]; - if (operation.name === "Label"){ - let ings = operation.getIngValues(); - if (name === ings[0]) { - return o; - } - } - } - return -1; - }, -}; - -export default FlowControl; 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..02359014 --- /dev/null +++ b/src/core/Ingredient.mjs @@ -0,0 +1,118 @@ +/** + * @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; + this.disabled = false; + this.hint = ""; + this.toggleValues = []; + this.target = 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; + this.disabled = !!ingredientConfig.disabled; + this.hint = ingredientConfig.hint || false; + this.toggleValues = ingredientConfig.toggleValues; + this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null; + } + + + /** + * 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..cdeb9a73 --- /dev/null +++ b/src/core/Operation.mjs @@ -0,0 +1,293 @@ +/** + * @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._presentType = -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; + } + + + /** + * Method to be called when displaying the result of an operation in a human-readable + * format. This allows operations to return usable data from their run() method and + * only format them when this method is called. + * + * The default action is to return the data unchanged, but child classes can override + * this behaviour. + * + * @param {*} data - The result of the run() function + * @param {Object[]} args - The operation's arguments + * @returns {*} - A human-readable version of the data + */ + present(data, args) { + return data; + } + + + /** + * 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); + if (this._presentType < 0) this._presentType = this._outputType; + } + + + /** + * Gets the output type as a readable string. + * + * @returns {string} + */ + get outputType() { + return Dish.enumLookup(this._outputType); + } + + + /** + * Sets the presentation type as a Dish enum. + * + * @param {string} typeStr + */ + set presentType(typeStr) { + this._presentType = Dish.typeEnum(typeStr); + } + + + /** + * Gets the presentation type as a readable string. + * + * @returns {string} + */ + get presentType() { + return Dish.enumLookup(this._presentType); + } + + + /** + * 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 => { + const conf = { + name: ing.name, + type: ing.type, + value: ing.defaultValue + }; + + if (ing.toggleValues) conf.toggleValues = ing.toggleValues; + if (ing.hint) conf.hint = ing.hint; + if (ing.disabled) conf.disabled = ing.disabled; + if (ing.target) conf.target = ing.target; + return conf; + }); + } + + + /** + * 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.config) + }; + } + + + /** + * 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; + } + + /** + * Set whether this Operation is a flowcontrol op. + * + * @param {boolean} value + */ + set flowControl(value) { + this._flowControl = !!value; + } + +} + +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..a5b2e81f --- /dev/null +++ b/src/core/Recipe.mjs @@ -0,0 +1,290 @@ +/** + * @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 OperationError from "./errors/OperationError"; +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; + + if (startFrom === 0) this.lastRunOp = null; + + 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 = await 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); + } + this.lastRunOp = op; + } catch (err) { + // Return expected errors as output + if (err instanceof OperationError || + (err.type && err.type === "OperationError")) { + // Cannot rely on `err instanceof OperationError` here as extending + // native types is not fully supported yet. + dish.set(err.message, "string"); + return i; + } else { + 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; + } + + + /** + * Present the results of the final operation. + * + * @param {Dish} dish + */ + async present(dish) { + if (!this.lastRunOp) return; + + const output = await this.lastRunOp.present( + await dish.get(this.lastRunOp.outputType), + this.lastRunOp.ingValues + ); + dish.set(output, this.lastRunOp.presentType); + } + + + /** + * 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; + } + + + /** + * Determines whether the previous operation has a different presentation type to its normal output. + * + * @param {number} progress + * @returns {boolean} + */ + lastOpPresented(progress) { + if (progress < 1) return false; + return this.opList[progress-1].presentType !== this.opList[progress-1].outputType; + } + +} + +export default Recipe; diff --git a/src/core/Utils.js b/src/core/Utils.mjs similarity index 72% rename from src/core/Utils.js rename to src/core/Utils.mjs index 4b20208b..78d4d97d 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.mjs @@ -1,17 +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 {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. @@ -23,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 @@ -35,7 +37,7 @@ const Utils = { } return String.fromCharCode(o); - }, + } /** @@ -48,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 @@ -62,7 +64,7 @@ const Utils = { } return c.charCodeAt(0); - }, + } /** @@ -88,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); @@ -98,7 +99,7 @@ const Utils = { }); return paddedBytes; - }, + } /** @@ -116,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; - }, + } /** @@ -139,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"); - }, + } /** @@ -160,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"); - }, + } /** @@ -174,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)); } @@ -185,7 +183,7 @@ const Utils = { str = str.replace(re, "."); if (!preserveWs) str = str.replace(wsRe, "."); return str; - }, + } /** @@ -201,7 +199,7 @@ const Utils = { * // returns "\n" * Utils.parseEscapedChars("\\n"); */ - parseEscapedChars: function(str) { + 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]) { @@ -232,7 +230,7 @@ const Utils = { return String.fromCharCode(parseInt(b.substr(1), 16)); } }); - }, + } /** @@ -246,9 +244,9 @@ const Utils = { * // returns "\[example\]" * Utils.escapeRegex("[example]"); */ - escapeRegex: function(str) { + static escapeRegex(str) { return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1"); - }, + } /** @@ -267,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++) { @@ -291,7 +289,7 @@ const Utils = { } } return alphArr; - }, + } /** @@ -312,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); } - }, + } /** @@ -345,19 +343,19 @@ const Utils = { * // 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; } - }, + } /** @@ -374,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--) { @@ -384,7 +382,7 @@ const Utils = { if (b > 255) return Utils.strToUtf8ByteArray(str); } return byteArray; - }, + } /** @@ -400,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) { @@ -412,7 +410,7 @@ const Utils = { } return Utils.strToByteArray(utf8Str); - }, + } /** @@ -428,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++) { @@ -446,7 +444,7 @@ const Utils = { } return charcode; - }, + } /** @@ -462,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); @@ -479,7 +477,7 @@ const Utils = { // If it fails, treat it as ANSI return str; } - }, + } /** @@ -495,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; - }, + } /** @@ -516,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 || "Auto"; - byteLen = byteLen || 2; - if (delim !== "None") { - const delimRegex = delim === "Auto" ? /[^a-f\d]/gi : 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; - }, + } /** @@ -745,14 +532,14 @@ const Utils = { * // returns [["head1", "head2"], ["data1", "data2"]] * Utils.parseCSV("head1,head2\ndata1,data2"); */ - parseCSV: function(data, cellDelims=[","], lineDelims=["\n", "\r"]) { + static parseCSV(data, cellDelims=[","], lineDelims=["\n", "\r"]) { let b, next, renderNext = false, inString = false, cell = "", - line = [], - lines = []; + line = []; + const lines = []; // Remove BOM, often present in Excel CSV files if (data.length && data[0] === "\uFEFF") data = data.substr(1); @@ -789,26 +576,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, ""); - }, + } /** @@ -822,7 +610,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/Bcrypt.mjs b/src/core/operations/Bcrypt.mjs new file mode 100644 index 00000000..cccf4131 --- /dev/null +++ b/src/core/operations/Bcrypt.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import bcrypt from "bcryptjs"; + +/** + * Bcrypt operation + */ +class Bcrypt extends Operation { + + /** + * Bcrypt constructor + */ + constructor() { + super(); + + this.name = "Bcrypt"; + this.module = "Hashing"; + this.description = "bcrypt is a password hashing function designed by Niels Provos and David Mazi\xe8res, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count (rounds) can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.

Enter the password in the input to generate its hash."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Rounds", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(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)}%`); + }); + + } + +} + +export default Bcrypt; diff --git a/src/core/operations/BcryptCompare.mjs b/src/core/operations/BcryptCompare.mjs new file mode 100644 index 00000000..32f275b5 --- /dev/null +++ b/src/core/operations/BcryptCompare.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import bcrypt from "bcryptjs"; + +/** + * Bcrypt compare operation + */ +class BcryptCompare extends Operation { + + /** + * BcryptCompare constructor + */ + constructor() { + super(); + + this.name = "Bcrypt compare"; + this.module = "Hashing"; + this.description = "Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Hash", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(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"; + + } + +} + +export default BcryptCompare; diff --git a/src/core/operations/BcryptParse.mjs b/src/core/operations/BcryptParse.mjs new file mode 100644 index 00000000..149bdaa0 --- /dev/null +++ b/src/core/operations/BcryptParse.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import bcrypt from "bcryptjs"; + +/** + * Bcrypt parse operation + */ +class BcryptParse extends Operation { + + /** + * BcryptParse constructor + */ + constructor() { + super(); + + this.name = "Bcrypt parse"; + this.module = "Hashing"; + this.description = "Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(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) { + throw new OperationError("Error: " + err.toString()); + } + } + +} + +export default BcryptParse; diff --git a/src/core/operations/BifidCipherDecode.mjs b/src/core/operations/BifidCipherDecode.mjs new file mode 100644 index 00000000..f5e2ad47 --- /dev/null +++ b/src/core/operations/BifidCipherDecode.mjs @@ -0,0 +1,124 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { genPolybiusSquare } from "../lib/Ciphers"; +import OperationError from "../errors/OperationError"; + +/** + * Bifid Cipher Decode operation + */ +class BifidCipherDecode extends Operation { + + /** + * BifidCipherDecode constructor + */ + constructor() { + super(); + + this.name = "Bifid Cipher Decode"; + this.module = "Ciphers"; + this.description = "The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Keyword", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid key + */ + run(input, args) { + const keywordStr = args[0].toUpperCase().replace("J", "I"), + keyword = keywordStr.split("").unique(), + alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ", + structure = []; + + let output = "", + count = 0, + trans = ""; + + if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0) + throw new OperationError("The key must consist only of letters in the English alphabet"); + + const polybius = genPolybiusSquare(keywordStr); + + input.replace("J", "I").split("").forEach((letter) => { + const alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0; + let polInd; + + if (alpInd) { + for (let i = 0; i < 5; i++) { + polInd = polybius[i].indexOf(letter.toLocaleUpperCase()); + if (polInd >= 0) { + trans += `${i}${polInd}`; + } + } + + if (alpha.split("").indexOf(letter) >= 0) { + structure.push(true); + } else if (alpInd) { + structure.push(false); + } + } else { + structure.push(letter); + } + }); + + structure.forEach(pos => { + if (typeof pos === "boolean") { + const coords = [trans[count], trans[count+trans.length/2]]; + + output += pos ? + polybius[coords[0]][coords[1]] : + polybius[coords[0]][coords[1]].toLocaleLowerCase(); + count++; + } else { + output += pos; + } + }); + + return output; + } + + /** + * Highlight Bifid Cipher Decode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Bifid Cipher Decode in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default BifidCipherDecode; diff --git a/src/core/operations/BifidCipherEncode.mjs b/src/core/operations/BifidCipherEncode.mjs new file mode 100644 index 00000000..41c29db0 --- /dev/null +++ b/src/core/operations/BifidCipherEncode.mjs @@ -0,0 +1,129 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import { genPolybiusSquare } from "../lib/Ciphers"; + +/** + * Bifid Cipher Encode operation + */ +class BifidCipherEncode extends Operation { + + /** + * BifidCipherEncode constructor + */ + constructor() { + super(); + + this.name = "Bifid Cipher Encode"; + this.module = "Ciphers"; + this.description = "The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Keyword", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if key is invalid + */ + run(input, args) { + const keywordStr = args[0].toUpperCase().replace("J", "I"), + keyword = keywordStr.split("").unique(), + alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ", + xCo = [], + yCo = [], + structure = []; + + let output = "", + count = 0; + + + if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0) + throw new OperationError("The key must consist only of letters in the English alphabet"); + + const polybius = genPolybiusSquare(keywordStr); + + input.replace("J", "I").split("").forEach(letter => { + const alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0; + let polInd; + + if (alpInd) { + for (let i = 0; i < 5; i++) { + polInd = polybius[i].indexOf(letter.toLocaleUpperCase()); + if (polInd >= 0) { + xCo.push(polInd); + yCo.push(i); + } + } + + if (alpha.split("").indexOf(letter) >= 0) { + structure.push(true); + } else if (alpInd) { + structure.push(false); + } + } else { + structure.push(letter); + } + }); + + const trans = `${yCo.join("")}${xCo.join("")}`; + + structure.forEach(pos => { + if (typeof pos === "boolean") { + const coords = trans.substr(2*count, 2).split(""); + + output += pos ? + polybius[coords[0]][coords[1]] : + polybius[coords[0]][coords[1]].toLocaleLowerCase(); + count++; + } else { + output += pos; + } + }); + + return output; + } + + /** + * Highlight Bifid Cipher Encode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Bifid Cipher Encode in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default BifidCipherEncode; diff --git a/src/core/operations/BitShiftLeft.mjs b/src/core/operations/BitShiftLeft.mjs new file mode 100644 index 00000000..59c740f2 --- /dev/null +++ b/src/core/operations/BitShiftLeft.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Bit shift left operation + */ +class BitShiftLeft extends Operation { + + /** + * BitShiftLeft constructor + */ + constructor() { + super(); + + this.name = "Bit shift left"; + this.module = "Default"; + this.description = "Shifts the bits in each byte towards the left by the specified amount."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Amount", + "type": "number", + "value": 1 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const amount = args[0]; + + return input.map(b => { + return (b << amount) & 0xff; + }); + } + + /** + * Highlight Bit shift left + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Bit shift left in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default BitShiftLeft; diff --git a/src/core/operations/BitShiftRight.mjs b/src/core/operations/BitShiftRight.mjs new file mode 100644 index 00000000..8207f353 --- /dev/null +++ b/src/core/operations/BitShiftRight.mjs @@ -0,0 +1,82 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Bit shift right operation + */ +class BitShiftRight extends Operation { + + /** + * BitShiftRight constructor + */ + constructor() { + super(); + + this.name = "Bit shift right"; + this.module = "Default"; + this.description = "Shifts the bits in each byte towards the right by the specified amount.

Logical shifts replace the leftmost bits with zeros.
Arithmetic shifts preserve the most significant bit (MSB) of the original byte keeping the sign the same (positive or negative)."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Amount", + "type": "number", + "value": 1 + }, + { + "name": "Type", + "type": "option", + "value": ["Logical shift", "Arithmetic shift"] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const amount = args[0], + type = args[1], + mask = type === "Logical shift" ? 0 : 0x80; + + return input.map(b => { + return (b >>> amount) ^ (b & mask); + }); + } + + /** + * Highlight Bit shift right + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Bit shift right in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default BitShiftRight; diff --git a/src/core/operations/BitwiseOp.js b/src/core/operations/BitwiseOp.js deleted file mode 100755 index f2551cba..00000000 --- a/src/core/operations/BitwiseOp.js +++ /dev/null @@ -1,368 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Bitwise operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const BitwiseOp = { - - /** - * Runs bitwise operations across the input data. - * - * @private - * @param {byteArray} input - * @param {byteArray} key - * @param {function} func - The bitwise calculation to carry out - * @param {boolean} nullPreserving - * @param {string} scheme - * @returns {byteArray} - */ - _bitOp: function (input, key, func, nullPreserving, scheme) { - if (!key || !key.length) key = [0]; - let result = [], - x = null, - k = null, - o = null; - - for (let i = 0; i < input.length; i++) { - k = key[i % key.length]; - o = input[i]; - x = nullPreserving && (o === 0 || o === k) ? o : func(o, k); - result.push(x); - if (scheme && - scheme !== "Standard" && - !(nullPreserving && (o === 0 || o === k))) { - switch (scheme) { - case "Input differential": - key[i % key.length] = x; - break; - case "Output differential": - key[i % key.length] = o; - break; - } - } - } - - return result; - }, - - - /** - * @constant - * @default - */ - XOR_PRESERVE_NULLS: false, - /** - * @constant - * @default - */ - XOR_SCHEME: ["Standard", "Input differential", "Output differential"], - /** - * @constant - * @default - */ - KEY_FORMAT: ["Hex", "Base64", "UTF8", "Latin1"], - - /** - * XOR operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runXor: function (input, args) { - const key = Utils.convertToByteArray(args[0].string || "", args[0].option), - scheme = args[1], - nullPreserving = args[2]; - - return BitwiseOp._bitOp(input, key, BitwiseOp._xor, nullPreserving, scheme); - }, - - - /** - * @constant - * @default - */ - XOR_BRUTE_KEY_LENGTH: 1, - /** - * @constant - * @default - */ - XOR_BRUTE_SAMPLE_LENGTH: 100, - /** - * @constant - * @default - */ - XOR_BRUTE_SAMPLE_OFFSET: 0, - /** - * @constant - * @default - */ - XOR_BRUTE_PRINT_KEY: true, - /** - * @constant - * @default - */ - XOR_BRUTE_OUTPUT_HEX: false, - - /** - * XOR Brute Force operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runXorBrute: function (input, args) { - const keyLength = args[0], - sampleLength = args[1], - sampleOffset = args[2], - scheme = args[3], - nullPreserving = args[4], - printKey = args[5], - outputHex = args[6], - crib = args[7].toLowerCase(); - - let output = [], - result, - resultUtf8, - record = ""; - - input = input.slice(sampleOffset, sampleOffset + sampleLength); - - if (ENVIRONMENT_IS_WORKER()) - self.sendStatusMessage("Calculating " + Math.pow(256, keyLength) + " values..."); - - /** - * Converts an integer to an array of bytes expressing that number. - * - * @param {number} int - * @param {number} len - Length of the resulting array - * @returns {array} - */ - const intToByteArray = (int, len) => { - let res = Array(len).fill(0); - for (let i = len - 1; i >= 0; i--) { - res[i] = int & 0xff; - int = int >>> 8; - } - return res; - }; - - for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) { - if (key % 10000 === 0 && ENVIRONMENT_IS_WORKER()) { - self.sendStatusMessage("Calculating " + l + " values... " + Math.floor(key / l * 100) + "%"); - } - - result = BitwiseOp._bitOp(input, intToByteArray(key, keyLength), BitwiseOp._xor, nullPreserving, scheme); - resultUtf8 = Utils.byteArrayToUtf8(result); - record = ""; - - if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue; - if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; - if (outputHex) { - record += Utils.toHex(result); - } else { - record += Utils.printable(resultUtf8, false); - } - - output.push(record); - } - - return output.join("\n"); - }, - - - /** - * NOT operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runNot: function (input, args) { - return BitwiseOp._bitOp(input, null, BitwiseOp._not); - }, - - - /** - * AND operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runAnd: function (input, args) { - const key = Utils.convertToByteArray(args[0].string || "", args[0].option); - - return BitwiseOp._bitOp(input, key, BitwiseOp._and); - }, - - - /** - * OR operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runOr: function (input, args) { - const key = Utils.convertToByteArray(args[0].string || "", args[0].option); - - return BitwiseOp._bitOp(input, key, BitwiseOp._or); - }, - - - /** - * ADD operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runAdd: function (input, args) { - const key = Utils.convertToByteArray(args[0].string || "", args[0].option); - - return BitwiseOp._bitOp(input, key, BitwiseOp._add); - }, - - - /** - * SUB operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runSub: function (input, args) { - const key = Utils.convertToByteArray(args[0].string || "", args[0].option); - - return BitwiseOp._bitOp(input, key, BitwiseOp._sub); - }, - - - /** - * Bit shift left operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runBitShiftLeft: function(input, args) { - const amount = args[0]; - - return input.map(b => { - return (b << amount) & 0xff; - }); - }, - - - /** - * @constant - * @default - */ - BIT_SHIFT_TYPE: ["Logical shift", "Arithmetic shift"], - - /** - * Bit shift right operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runBitShiftRight: function(input, args) { - const amount = args[0], - type = args[1], - mask = type === "Logical shift" ? 0 : 0x80; - - return input.map(b => { - return (b >>> amount) ^ (b & mask); - }); - }, - - - /** - * XOR bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _xor: function (operand, key) { - return operand ^ key; - }, - - - /** - * NOT bitwise calculation. - * - * @private - * @param {number} operand - * @returns {number} - */ - _not: function (operand, _) { - return ~operand & 0xff; - }, - - - /** - * AND bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _and: function (operand, key) { - return operand & key; - }, - - - /** - * OR bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _or: function (operand, key) { - return operand | key; - }, - - - /** - * ADD bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _add: function (operand, key) { - return (operand + key) % 256; - }, - - - /** - * SUB bitwise calculation. - * - * @private - * @param {number} operand - * @param {number} key - * @returns {number} - */ - _sub: function (operand, key) { - const result = operand - key; - return (result < 0) ? 256 + result : result; - }, - -}; - -export default BitwiseOp; diff --git a/src/core/operations/BlowfishDecrypt.mjs b/src/core/operations/BlowfishDecrypt.mjs new file mode 100644 index 00000000..d349d3c4 --- /dev/null +++ b/src/core/operations/BlowfishDecrypt.mjs @@ -0,0 +1,100 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import { Blowfish } from "../vendor/Blowfish"; +import { toBase64 } from "../lib/Base64"; +import { toHexFast } from "../lib/Hex"; + +/** + * Lookup table for Blowfish output types. + */ +const BLOWFISH_OUTPUT_TYPE_LOOKUP = { + Base64: 0, Hex: 1, String: 2, Raw: 3 +}; +/** + * Lookup table for Blowfish modes. + */ +const BLOWFISH_MODE_LOOKUP = { + ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5 +}; + +/** + * Blowfish Decrypt operation + */ +class BlowfishDecrypt extends Operation { + + /** + * BlowfishDecrypt constructor + */ + constructor() { + super(); + + this.name = "Blowfish Decrypt"; + this.module = "Ciphers"; + this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Base64", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length === 0) throw new OperationError("Enter a key"); + + input = inputType === "Raw" ? Utils.strToByteArray(input) : input; + + Blowfish.setIV(toBase64(iv), 0); + + const result = Blowfish.decrypt(input, key, { + outputType: BLOWFISH_OUTPUT_TYPE_LOOKUP[inputType], // This actually means inputType. The library is weird. + cipherMode: BLOWFISH_MODE_LOOKUP[mode] + }); + + return outputType === "Hex" ? toHexFast(Utils.strToByteArray(result)) : result; + } + +} + +export default BlowfishDecrypt; diff --git a/src/core/operations/BlowfishEncrypt.mjs b/src/core/operations/BlowfishEncrypt.mjs new file mode 100644 index 00000000..60c45560 --- /dev/null +++ b/src/core/operations/BlowfishEncrypt.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 OperationError from "../errors/OperationError"; +import { Blowfish } from "../vendor/Blowfish"; +import { toBase64 } from "../lib/Base64"; + +/** + * Lookup table for Blowfish output types. + */ +const BLOWFISH_OUTPUT_TYPE_LOOKUP = { + Base64: 0, Hex: 1, String: 2, Raw: 3 +}; + +/** + * Lookup table for Blowfish modes. + */ +const BLOWFISH_MODE_LOOKUP = { + ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5 +}; + + +/** + * Blowfish Encrypt operation + */ +class BlowfishEncrypt extends Operation { + + /** + * BlowfishEncrypt constructor + */ + constructor() { + super(); + + this.name = "Blowfish Encrypt"; + this.module = "Ciphers"; + this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Base64", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length === 0) throw new OperationError("Enter a key"); + + input = Utils.convertToByteString(input, inputType); + + Blowfish.setIV(toBase64(iv), 0); + + const enc = Blowfish.encrypt(input, key, { + outputType: BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType], + cipherMode: BLOWFISH_MODE_LOOKUP[mode] + }); + + return outputType === "Raw" ? Utils.byteArrayToChars(enc) : enc; + } + +} + +export default BlowfishEncrypt; diff --git a/src/core/operations/ByteRepr.js b/src/core/operations/ByteRepr.js deleted file mode 100755 index 607e6b0e..00000000 --- a/src/core/operations/ByteRepr.js +++ /dev/null @@ -1,432 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Byte representation operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const ByteRepr = { - - /** - * @constant - * @default - */ - DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"], - /** - * @constant - * @default - */ - TO_HEX_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"], - /** - * @constant - * @default - */ - FROM_HEX_DELIM_OPTIONS: ["Auto", "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. - * - * @author Matt C [matt@artemisbot.uk] - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToOct: function(input, args) { - const delim = Utils.charRep[args[0] || "Space"]; - return input.map(val => val.toString(8)).join(delim); - }, - - - /** - * From Octal operation. - * - * @author Matt C [matt@artemisbot.uk] - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromOct: function(input, args) { - const delim = Utils.charRep[args[0] || "Space"]; - if (input.length === 0) return []; - return input.split(delim).map(val => parseInt(val, 8)); - }, - - - /** - * @constant - * @default - */ - CHARCODE_BASE: 16, - - /** - * To Charcode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToCharcode: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], - base = args[1], - output = "", - padding = 2, - ordinal; - - if (base < 2 || base > 36) { - throw "Error: Base argument must be between 2 and 36"; - } - - const charcode = Utils.strToCharcode(input); - for (let i = 0; i < charcode.length; i++) { - ordinal = charcode[i]; - - if (base === 16) { - if (ordinal < 256) padding = 2; - else if (ordinal < 65536) padding = 4; - else if (ordinal < 16777216) padding = 6; - else if (ordinal < 4294967296) padding = 8; - else padding = 2; - - if (padding > 2 && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); - - output += Utils.hex(ordinal, padding) + delim; - } else { - if (ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); - output += ordinal.toString(base) + delim; - } - } - - return output.slice(0, -delim.length); - }, - - - /** - * From Charcode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromCharcode: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], - base = args[1], - bites = input.split(delim), - i = 0; - - if (base < 2 || base > 36) { - 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 - // too long to be a single character - if (bites.length === 1 && input.length > 17) { - bites = []; - for (i = 0; i < input.length; i += 2) { - bites.push(input.slice(i, i+2)); - } - } - - let latin1 = ""; - for (i = 0; i < bites.length; i++) { - latin1 += Utils.chr(parseInt(bites[i], base)); - } - return Utils.strToByteArray(latin1); - }, - - - /** - * 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. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToDecimal: function(input, args) { - const delim = Utils.charRep[args[0]]; - return input.join(delim); - }, - - - /** - * From Decimal operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromDecimal: function(input, args) { - const delim = Utils.charRep[args[0]]; - let byteStr = input.split(delim), output = []; - if (byteStr[byteStr.length-1] === "") - byteStr = byteStr.slice(0, byteStr.length-1); - - for (let i = 0; i < byteStr.length; i++) { - output[i] = parseInt(byteStr[i], 10); - } - return output; - }, - - - /** - * To Binary operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToBinary: function(input, args) { - let delim = Utils.charRep[args[0] || "Space"], - output = "", - padding = 8; - - for (let i = 0; i < input.length; i++) { - output += input[i].toString(2).padStart(padding, "0") + delim; - } - - if (delim.length) { - return output.slice(0, -delim.length); - } else { - return output; - } - }, - - - /** - * From Binary operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromBinary: function(input, args) { - const delimRegex = Utils.regexRep[args[0] || "Space"]; - input = input.replace(delimRegex, ""); - - const output = []; - const byteLen = 8; - for (let i = 0; i < input.length; i += byteLen) { - output.push(parseInt(input.substr(i, byteLen), 2)); - } - return output; - }, - - - /** - * Highlight to binary - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightToBinary: function(pos, args) { - 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; - }, - - - /** - * Highlight from binary - * - * @param {Object[]} pos - * @param {number} pos[].start - * @param {number} pos[].end - * @param {Object[]} args - * @returns {Object[]} pos - */ - highlightFromBinary: function(pos, args) { - 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; - }, - - - /** - * @constant - * @default - */ - HEX_CONTENT_CONVERT_WHICH: ["Only special chars", "Only special chars including spaces", "All chars"], - /** - * @constant - * @default - */ - HEX_CONTENT_SPACES_BETWEEN_BYTES: false, - - /** - * To Hex Content operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runToHexContent: function(input, args) { - const convert = args[0]; - const spaces = args[1]; - if (convert === "All chars") { - let result = "|" + Utils.toHex(input) + "|"; - if (!spaces) result = result.replace(/ /g, ""); - return result; - } - - let output = "", - inHex = false, - convertSpaces = convert === "Only special chars including spaces", - b; - for (let i = 0; i < input.length; i++) { - b = input[i]; - if ((b === 32 && convertSpaces) || (b < 48 && b !== 32) || (b > 57 && b < 65) || (b > 90 && b < 97) || b > 122) { - if (!inHex) { - output += "|"; - inHex = true; - } else if (spaces) output += " "; - output += Utils.toHex([b]); - } else { - if (inHex) { - output += "|"; - inHex = false; - } - output += Utils.chr(input[i]); - } - } - if (inHex) output += "|"; - return output; - }, - - - /** - * From Hex Content operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFromHexContent: function(input, args) { - const regex = /\|([a-f\d ]{2,})\|/gi; - let output = [], m, i = 0; - while ((m = regex.exec(input))) { - // Add up to match - for (; i < m.index;) - output.push(Utils.ord(input[i++])); - - // Add match - const bytes = Utils.fromHex(m[1]); - if (bytes) { - for (let a = 0; a < bytes.length;) - output.push(bytes[a++]); - } else { - // Not valid hex, print as normal - for (; i < regex.lastIndex;) - output.push(Utils.ord(input[i++])); - } - - i = regex.lastIndex; - } - // Add all after final match - for (; i < input.length;) - output.push(Utils.ord(input[i++])); - - return output; - }, - -}; - -export default ByteRepr; diff --git a/src/core/operations/Bzip2Decompress.mjs b/src/core/operations/Bzip2Decompress.mjs new file mode 100644 index 00000000..e31b3d2c --- /dev/null +++ b/src/core/operations/Bzip2Decompress.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import bzip2 from "../vendor/bzip2.js"; +import OperationError from "../errors/OperationError"; + +/** + * Bzip2 Decompress operation + */ +class Bzip2Decompress extends Operation { + + /** + * Bzip2Decompress constructor + */ + constructor() { + super(); + + this.name = "Bzip2 Decompress"; + this.module = "Compression"; + this.description = "Decompresses data using the Bzip2 algorithm."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + this.patterns = [ + { + "match": "^\\x42\\x5a\\x68", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const compressed = new Uint8Array(input); + + try { + const bzip2Reader = bzip2.array(compressed); + return bzip2.simple(bzip2Reader); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default Bzip2Decompress; diff --git a/src/core/operations/CRC16Checksum.mjs b/src/core/operations/CRC16Checksum.mjs new file mode 100644 index 00000000..c4c5d633 --- /dev/null +++ b/src/core/operations/CRC16Checksum.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import JSCRC from "js-crc"; + +/** + * CRC-16 Checksum operation + */ +class CRC16Checksum extends Operation { + + /** + * CRC16Checksum constructor + */ + constructor() { + super(); + + this.name = "CRC-16 Checksum"; + this.module = "Hashing"; + this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.

The CRC was invented by W. Wesley Peterson in 1961."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return JSCRC.crc16(input); + } + +} + +export default CRC16Checksum; diff --git a/src/core/operations/CRC32Checksum.mjs b/src/core/operations/CRC32Checksum.mjs new file mode 100644 index 00000000..5982273e --- /dev/null +++ b/src/core/operations/CRC32Checksum.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import JSCRC from "js-crc"; + +/** + * CRC-32 Checksum operation + */ +class CRC32Checksum extends Operation { + + /** + * CRC32Checksum constructor + */ + constructor() { + super(); + + this.name = "CRC-32 Checksum"; + this.module = "Hashing"; + this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.

The CRC was invented by W. Wesley Peterson in 1961; the 32-bit CRC function of Ethernet and many other standards is the work of several researchers and was published in 1975."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return JSCRC.crc32(input); + } + +} + +export default CRC32Checksum; diff --git a/src/core/operations/CSSBeautify.mjs b/src/core/operations/CSSBeautify.mjs new file mode 100644 index 00000000..d9835550 --- /dev/null +++ b/src/core/operations/CSSBeautify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * CSS Beautify operation + */ +class CSSBeautify extends Operation { + + /** + * CSSBeautify constructor + */ + constructor() { + super(); + + this.name = "CSS Beautify"; + this.module = "Code"; + this.description = "Indents and prettifies Cascading Style Sheets (CSS) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + return vkbeautify.css(input, indentStr); + } + +} + +export default CSSBeautify; diff --git a/src/core/operations/CSSMinify.mjs b/src/core/operations/CSSMinify.mjs new file mode 100644 index 00000000..2d489edc --- /dev/null +++ b/src/core/operations/CSSMinify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * CSS Minify operation + */ +class CSSMinify extends Operation { + + /** + * CSSMinify constructor + */ + constructor() { + super(); + + this.name = "CSS Minify"; + this.module = "Code"; + this.description = "Compresses Cascading Style Sheets (CSS) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Preserve comments", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preserveComments = args[0]; + return vkbeautify.cssmin(input, preserveComments); + } + +} + +export default CSSMinify; diff --git a/src/core/operations/CSSSelector.mjs b/src/core/operations/CSSSelector.mjs new file mode 100644 index 00000000..b91178cb --- /dev/null +++ b/src/core/operations/CSSSelector.mjs @@ -0,0 +1,90 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import xmldom from "xmldom"; +import nwmatcher from "nwmatcher"; + +/** + * CSS selector operation + */ +class CSSSelector extends Operation { + + /** + * CSSSelector constructor + */ + constructor() { + super(); + + this.name = "CSS selector"; + this.module = "Code"; + this.description = "Extract information from an HTML document with a CSS selector"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "CSS selector", + "type": "string", + "value": "" + }, + { + "name": "Delimiter", + "type": "binaryShortString", + "value": "\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query, delimiter] = args, + parser = new xmldom.DOMParser(); + let dom, + result; + + if (!query.length || !input.length) { + return ""; + } + + try { + dom = parser.parseFromString(input); + } catch (err) { + throw new OperationError("Invalid input HTML."); + } + + try { + const matcher = nwmatcher({document: dom}); + result = matcher.select(query, dom); + } catch (err) { + throw new OperationError("Invalid CSS Selector. Details:\n" + err.message); + } + + const nodeToString = function(node) { + return node.toString(); + /* xmldom does not return the outerHTML value. + switch (node.nodeType) { + case node.ELEMENT_NODE: return node.outerHTML; + case node.ATTRIBUTE_NODE: return node.value; + case node.TEXT_NODE: return node.wholeText; + case node.COMMENT_NODE: return node.data; + case node.DOCUMENT_NODE: return node.outerHTML; + default: throw new Error("Unknown Node Type: " + node.nodeType); + }*/ + }; + + return result + .map(nodeToString) + .join(delimiter); + } + +} + +export default CSSSelector; diff --git a/src/core/operations/CTPH.mjs b/src/core/operations/CTPH.mjs new file mode 100644 index 00000000..39f52af4 --- /dev/null +++ b/src/core/operations/CTPH.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import ctphjs from "ctph.js"; + +/** + * CTPH operation + */ +class CTPH extends Operation { + + /** + * CTPH constructor + */ + constructor() { + super(); + + this.name = "CTPH"; + this.module = "Hashing"; + this.description = "Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.

CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return ctphjs.digest(input); + } + +} + +export default CTPH; diff --git a/src/core/operations/CartesianProduct.mjs b/src/core/operations/CartesianProduct.mjs new file mode 100644 index 00000000..0d5346fb --- /dev/null +++ b/src/core/operations/CartesianProduct.mjs @@ -0,0 +1,96 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set cartesian product operation + */ +class CartesianProduct extends Operation { + + /** + * Cartesian Product constructor + */ + constructor() { + super(); + + this.name = "Cartesian Product"; + this.module = "Default"; + this.description = "Calculates the cartesian product of multiple sets of data, returning all possible combinations."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {OperationError} if fewer than 2 sets + */ + validateSampleNumbers(sets) { + if (!sets || sets.length < 2) { + throw new OperationError("Incorrect number of sets, perhaps you" + + " need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the product operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runCartesianProduct(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Return the cartesian product of the two inputted sets. + * + * @param {Object[]} a + * @param {Object[]} b + * @param {Object[]} c + * @returns {string} + */ + runCartesianProduct(a, b, ...c) { + /** + * https://stackoverflow.com/a/43053803/7200497 + * @returns {Object[]} + */ + const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e)))); + /** + * https://stackoverflow.com/a/43053803/7200497 + * @returns {Object[][]} + */ + const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a); + + return cartesian(a, b, ...c) + .map(set => `(${set.join(",")})`) + .join(this.itemDelimiter); + } +} + +export default CartesianProduct; diff --git a/src/core/operations/ChangeIPFormat.mjs b/src/core/operations/ChangeIPFormat.mjs new file mode 100644 index 00000000..b985312a --- /dev/null +++ b/src/core/operations/ChangeIPFormat.mjs @@ -0,0 +1,120 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import {fromHex} from "../lib/Hex"; + +/** + * Change IP format operation + */ +class ChangeIPFormat extends Operation { + + /** + * ChangeIPFormat constructor + */ + constructor() { + super(); + + this.name = "Change IP format"; + this.module = "JSBN"; + this.description = "Convert an IP address from one format to another, e.g. 172.20.23.54 to ac141736"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inFormat, outFormat] = args, + lines = input.split("\n"); + let output = "", + j = 0; + + for (let i = 0; i < lines.length; i++) { + if (lines[i] === "") continue; + let baIp = []; + let octets; + let decimal; + + if (inFormat === outFormat) { + output += lines[i] + "\n"; + continue; + } + + // Convert to byte array IP from input format + switch (inFormat) { + case "Dotted Decimal": + octets = lines[i].split("."); + for (j = 0; j < octets.length; j++) { + baIp.push(parseInt(octets[j], 10)); + } + break; + case "Decimal": + decimal = lines[i].toString(); + baIp.push(decimal >> 24 & 255); + baIp.push(decimal >> 16 & 255); + baIp.push(decimal >> 8 & 255); + baIp.push(decimal & 255); + break; + case "Hex": + baIp = fromHex(lines[i]); + break; + default: + throw new OperationError("Unsupported input IP format"); + } + + let ddIp; + let decIp; + let hexIp; + + // Convert byte array IP to output format + switch (outFormat) { + case "Dotted Decimal": + ddIp = ""; + for (j = 0; j < baIp.length; j++) { + ddIp += baIp[j] + "."; + } + output += ddIp.slice(0, ddIp.length-1) + "\n"; + break; + case "Decimal": + decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0; + output += decIp.toString() + "\n"; + break; + case "Hex": + hexIp = ""; + for (j = 0; j < baIp.length; j++) { + hexIp += Utils.hex(baIp[j]); + } + output += hexIp + "\n"; + break; + default: + throw new OperationError("Unsupported output IP format"); + } + } + + return output.slice(0, output.length-1); + } + +} + +export default ChangeIPFormat; diff --git a/src/core/operations/CharEnc.js b/src/core/operations/CharEnc.js deleted file mode 100755 index 8696386b..00000000 --- a/src/core/operations/CharEnc.js +++ /dev/null @@ -1,97 +0,0 @@ -import cptable from "../lib/js-codepage/cptable.js"; - - -/** - * Character encoding operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const CharEnc = { - - /** - * @constant - * @default - */ - IO_FORMAT: { - "UTF-8 (65001)": 65001, - "UTF-7 (65000)": 65000, - "UTF16LE (1200)": 1200, - "UTF16BE (1201)": 1201, - "UTF16 (1201)": 1201, - "IBM EBCDIC International (500)": 500, - "IBM EBCDIC US-Canada (37)": 37, - "Windows-874 Thai (874)": 874, - "Japanese Shift-JIS (932)": 932, - "Simplified Chinese GBK (936)": 936, - "Korean (949)": 949, - "Traditional Chinese Big5 (950)": 950, - "Windows-1250 Central European (1250)": 1250, - "Windows-1251 Cyrillic (1251)": 1251, - "Windows-1252 Latin (1252)": 1252, - "Windows-1253 Greek (1253)": 1253, - "Windows-1254 Turkish (1254)": 1254, - "Windows-1255 Hebrew (1255)": 1255, - "Windows-1256 Arabic (1256)": 1256, - "Windows-1257 Baltic (1257)": 1257, - "Windows-1258 Vietnam (1258)": 1258, - "US-ASCII (20127)": 20127, - "Simplified Chinese GB2312 (20936)": 20936, - "KOI8-R Russian Cyrillic (20866)": 20866, - "KOI8-U Ukrainian Cyrillic (21866)": 21866, - "ISO-8859-1 Latin 1 Western European (28591)": 28591, - "ISO-8859-2 Latin 2 Central European (28592)": 28592, - "ISO-8859-3 Latin 3 South European (28593)": 28593, - "ISO-8859-4 Latin 4 North European (28594)": 28594, - "ISO-8859-5 Latin/Cyrillic (28595)": 28595, - "ISO-8859-6 Latin/Arabic (28596)": 28596, - "ISO-8859-7 Latin/Greek (28597)": 28597, - "ISO-8859-8 Latin/Hebrew (28598)": 28598, - "ISO-8859-9 Latin 5 Turkish (28599)": 28599, - "ISO-8859-10 Latin 6 Nordic (28600)": 28600, - "ISO-8859-11 Latin/Thai (28601)": 28601, - "ISO-8859-13 Latin 7 Baltic Rim (28603)": 28603, - "ISO-8859-14 Latin 8 Celtic (28604)": 28604, - "ISO-8859-15 Latin 9 (28605)": 28605, - "ISO-8859-16 Latin 10 (28606)": 28606, - "ISO-2022 JIS Japanese (50222)": 50222, - "EUC Japanese (51932)": 51932, - "EUC Korean (51949)": 51949, - "Simplified Chinese GB18030 (54936)": 54936, - }, - - /** - * Encode text operation. - * @author tlwr [toby@toby.codes] - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runEncode: function(input, args) { - const format = CharEnc.IO_FORMAT[args[0]]; - let encoded = cptable.utils.encode(format, input); - encoded = Array.from(encoded); - return encoded; - }, - - - /** - * Decode text operation. - * @author tlwr [toby@toby.codes] - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runDecode: function(input, args) { - const format = CharEnc.IO_FORMAT[args[0]]; - let decoded = cptable.utils.decode(format, input); - return decoded; - }, -}; - -export default CharEnc; diff --git a/src/core/operations/Checksum.js b/src/core/operations/Checksum.js deleted file mode 100755 index 6b2cb31c..00000000 --- a/src/core/operations/Checksum.js +++ /dev/null @@ -1,179 +0,0 @@ -import * as CRC from "js-crc"; -import Utils from "../Utils.js"; - - -/** - * Checksum operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Checksum = { - - /** - * Fletcher-8 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runFletcher8: function(input, args) { - let a = 0, - b = 0; - - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xf; - b = (b + a) % 0xf; - } - - return Utils.hex(((b << 4) | a) >>> 0, 2); - }, - - - /** - * Fletcher-16 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runFletcher16: function(input, args) { - let a = 0, - b = 0; - - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xff; - b = (b + a) % 0xff; - } - - return Utils.hex(((b << 8) | a) >>> 0, 4); - }, - - - /** - * Fletcher-32 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runFletcher32: function(input, args) { - let a = 0, - b = 0; - - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xffff; - b = (b + a) % 0xffff; - } - - return Utils.hex(((b << 16) | a) >>> 0, 8); - }, - - - /** - * Fletcher-64 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runFletcher64: function(input, args) { - let a = 0, - b = 0; - - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xffffffff; - b = (b + a) % 0xffffffff; - } - - return Utils.hex(b >>> 0, 8) + Utils.hex(a >>> 0, 8); - }, - - - /** - * Adler-32 Checksum operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runAdler32: function(input, args) { - let MOD_ADLER = 65521, - a = 1, - b = 0; - - for (let i = 0; i < input.length; i++) { - a += input[i]; - b += a; - } - - a %= MOD_ADLER; - b %= MOD_ADLER; - - return Utils.hex(((b << 16) | a) >>> 0, 8); - }, - - - /** - * CRC-32 Checksum operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runCRC32: function(input, args) { - return CRC.crc32(input); - }, - - - /** - * CRC-16 Checksum operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runCRC16: function(input, args) { - return CRC.crc16(input); - }, - - - /** - * TCP/IP Checksum operation. - * - * @author GCHQ Contributor [1] - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - * - * @example - * // returns '3f2c' - * Checksum.runTcpIp([0x45,0x00,0x00,0x87,0xa3,0x1b,0x40,0x00,0x40,0x06, - * 0x00,0x00,0xac,0x11,0x00,0x04,0xac,0x11,0x00,0x03]) - * - * // returns 'a249' - * Checksum.runTcpIp([0x45,0x00,0x01,0x11,0x3f,0x74,0x40,0x00,0x40,0x06, - * 0x00,0x00,0xac,0x11,0x00,0x03,0xac,0x11,0x00,0x04]) - */ - runTCPIP: function(input, args) { - let csum = 0; - - for (let i = 0; i < input.length; i++) { - if (i % 2 === 0) { - csum += (input[i] << 8); - } else { - csum += input[i]; - } - } - - csum = (csum >> 16) + (csum & 0xffff); - - return Utils.hex(0xffff - csum); - }, - -}; - -export default Checksum; diff --git a/src/core/operations/ChiSquare.mjs b/src/core/operations/ChiSquare.mjs new file mode 100644 index 00000000..89cb2214 --- /dev/null +++ b/src/core/operations/ChiSquare.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Chi Square operation + */ +class ChiSquare extends Operation { + + /** + * ChiSquare constructor + */ + constructor() { + super(); + + this.name = "Chi Square"; + this.module = "Default"; + this.description = "Calculates the Chi Square distribution of values."; + this.inputType = "ArrayBuffer"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const data = new Uint8Array(input); + const distArray = new Array(256).fill(0); + let total = 0; + + for (let i = 0; i < data.length; i++) { + distArray[data[i]]++; + } + + for (let i = 0; i < distArray.length; i++) { + if (distArray[i] > 0) { + total += Math.pow(distArray[i] - data.length / 256, 2) / (data.length / 256); + } + } + + return total; + } + +} + +export default ChiSquare; diff --git a/src/core/operations/Cipher.js b/src/core/operations/Cipher.js deleted file mode 100755 index f44aca20..00000000 --- a/src/core/operations/Cipher.js +++ /dev/null @@ -1,1033 +0,0 @@ -import Utils from "../Utils.js"; -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"; -import BigNumber from "bignumber.js"; - - -/** - * Cipher operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Cipher = { - - /** - * @constant - * @default - */ - IO_FORMAT1: ["Hex", "UTF8", "Latin1", "Base64"], - /** - * @constant - * @default - */ - IO_FORMAT2: ["UTF8", "Latin1", "Hex", "Base64"], - /** - * @constant - * @default - */ - IO_FORMAT3: ["Raw", "Hex"], - /** - * @constant - * @default - */ - IO_FORMAT4: ["Hex", "Raw"], - /** - * @constant - * @default - */ - AES_MODES: ["CBC", "CFB", "OFB", "CTR", "GCM", "ECB"], - - /** - * AES Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAesEnc: function (input, args) { - const key = Utils.convertToByteArray(args[0].string, args[0].option), - iv = Utils.convertToByteArray(args[1].string, args[1].option), - mode = args[2], - inputType = args[3], - outputType = args[4]; - - if ([16, 24, 32].indexOf(key.length) < 0) { - return `Invalid key length: ${key.length} bytes - -The following algorithms will be used based on the size of the key: - 16 bytes = AES-128 - 24 bytes = AES-192 - 32 bytes = AES-256`; - } - - input = Utils.convertToByteString(input, inputType); - - const cipher = forge.cipher.createCipher("AES-" + mode, key); - cipher.start({iv: iv}); - cipher.update(forge.util.createBuffer(input)); - cipher.finish(); - - if (outputType === "Hex") { - if (mode === "GCM") { - return cipher.output.toHex() + "\n\n" + - "Tag: " + cipher.mode.tag.toHex(); - } - return cipher.output.toHex(); - } else { - if (mode === "GCM") { - return cipher.output.getBytes() + "\n\n" + - "Tag: " + cipher.mode.tag.getBytes(); - } - return cipher.output.getBytes(); - } - }, - - - /** - * AES Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAesDec: function (input, args) { - const key = Utils.convertToByteArray(args[0].string, args[0].option), - iv = Utils.convertToByteArray(args[1].string, args[1].option), - mode = args[2], - inputType = args[3], - outputType = args[4], - gcmTag = Utils.convertToByteString(args[5].string, args[5].option); - - if ([16, 24, 32].indexOf(key.length) < 0) { - return `Invalid key length: ${key.length} bytes - -The following algorithms will be used based on the size of the key: - 16 bytes = AES-128 - 24 bytes = AES-192 - 32 bytes = AES-256`; - } - - input = Utils.convertToByteString(input, inputType); - - const decipher = forge.cipher.createDecipher("AES-" + mode, key); - decipher.start({ - iv: iv, - tag: gcmTag - }); - decipher.update(forge.util.createBuffer(input)); - const result = decipher.finish(); - - if (result) { - return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); - } else { - return "Unable to decrypt input with these parameters."; - } - }, - - - /** - * @constant - * @default - */ - DES_MODES: ["CBC", "CFB", "OFB", "CTR", "ECB"], - - /** - * DES Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDesEnc: function (input, args) { - const key = Utils.convertToByteString(args[0].string, args[0].option), - iv = Utils.convertToByteArray(args[1].string, args[1].option), - mode = args[2], - inputType = args[3], - outputType = args[4]; - - if (key.length !== 8) { - return `Invalid key length: ${key.length} bytes - -DES uses a key length of 8 bytes (64 bits). -Triple DES uses a key length of 24 bytes (192 bits).`; - } - - input = Utils.convertToByteString(input, inputType); - - const cipher = forge.cipher.createCipher("DES-" + mode, key); - cipher.start({iv: iv}); - cipher.update(forge.util.createBuffer(input)); - cipher.finish(); - - return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); - }, - - - /** - * DES Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDesDec: function (input, args) { - const key = Utils.convertToByteString(args[0].string, args[0].option), - iv = Utils.convertToByteArray(args[1].string, args[1].option), - mode = args[2], - inputType = args[3], - outputType = args[4]; - - if (key.length !== 8) { - return `Invalid key length: ${key.length} bytes - -DES uses a key length of 8 bytes (64 bits). -Triple DES uses a key length of 24 bytes (192 bits).`; - } - - input = Utils.convertToByteString(input, inputType); - - const decipher = forge.cipher.createDecipher("DES-" + mode, key); - decipher.start({iv: iv}); - decipher.update(forge.util.createBuffer(input)); - const result = decipher.finish(); - - if (result) { - return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); - } else { - return "Unable to decrypt input with these parameters."; - } - }, - - - /** - * Triple DES Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTripleDesEnc: function (input, args) { - const key = Utils.convertToByteString(args[0].string, args[0].option), - iv = Utils.convertToByteArray(args[1].string, args[1].option), - mode = args[2], - inputType = args[3], - outputType = args[4]; - - if (key.length !== 24) { - return `Invalid key length: ${key.length} bytes - -Triple DES uses a key length of 24 bytes (192 bits). -DES uses a key length of 8 bytes (64 bits).`; - } - - input = Utils.convertToByteString(input, inputType); - - const cipher = forge.cipher.createCipher("3DES-" + mode, key); - cipher.start({iv: iv}); - cipher.update(forge.util.createBuffer(input)); - cipher.finish(); - - return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); - }, - - - /** - * Triple DES Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTripleDesDec: function (input, args) { - const key = Utils.convertToByteString(args[0].string, args[0].option), - iv = Utils.convertToByteArray(args[1].string, args[1].option), - mode = args[2], - inputType = args[3], - outputType = args[4]; - - if (key.length !== 24) { - return `Invalid key length: ${key.length} bytes - -Triple DES uses a key length of 24 bytes (192 bits). -DES uses a key length of 8 bytes (64 bits).`; - } - - input = Utils.convertToByteString(input, inputType); - - const decipher = forge.cipher.createDecipher("3DES-" + mode, key); - decipher.start({iv: iv}); - decipher.update(forge.util.createBuffer(input)); - const result = decipher.finish(); - - if (result) { - return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); - } else { - return "Unable to decrypt input with these parameters."; - } - }, - - - /** - * RC2 Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRc2Enc: function (input, args) { - const key = Utils.convertToByteString(args[0].string, args[0].option), - iv = Utils.convertToByteString(args[1].string, args[1].option), - inputType = args[2], - outputType = args[3], - cipher = forge.rc2.createEncryptionCipher(key); - - input = Utils.convertToByteString(input, inputType); - - cipher.start(iv || null); - cipher.update(forge.util.createBuffer(input)); - cipher.finish(); - - return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); - }, - - - /** - * RC2 Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRc2Dec: function (input, args) { - const key = Utils.convertToByteString(args[0].string, args[0].option), - iv = Utils.convertToByteString(args[1].string, args[1].option), - inputType = args[2], - outputType = args[3], - decipher = forge.rc2.createDecryptionCipher(key); - - input = Utils.convertToByteString(input, inputType); - - decipher.start(iv || null); - decipher.update(forge.util.createBuffer(input)); - decipher.finish(); - - return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); - }, - - - /** - * @constant - * @default - */ - BLOWFISH_MODES: ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"], - /** - * @constant - * @default - */ - BLOWFISH_OUTPUT_TYPES: ["Hex", "Base64", "Raw"], - - /** - * Lookup table for Blowfish output types. - * - * @private - */ - _BLOWFISH_OUTPUT_TYPE_LOOKUP: { - Base64: 0, Hex: 1, String: 2, Raw: 3 - }, - /** - * Lookup table for Blowfish modes. - * - * @private - */ - _BLOWFISH_MODE_LOOKUP: { - ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5 - }, - - /** - * Blowfish Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runBlowfishEnc: function (input, args) { - const key = Utils.convertToByteString(args[0].string, args[0].option), - iv = Utils.convertToByteArray(args[1].string, args[1].option), - mode = args[2], - inputType = args[3], - outputType = args[4]; - - if (key.length === 0) return "Enter a key"; - - input = Utils.convertToByteString(input, inputType); - - Blowfish.setIV(Utils.toBase64(iv), 0); - - const enc = Blowfish.encrypt(input, key, { - outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType], - cipherMode: Cipher._BLOWFISH_MODE_LOOKUP[mode] - }); - - return outputType === "Raw" ? Utils.byteArrayToChars(enc) : enc ; - }, - - - /** - * Blowfish Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runBlowfishDec: function (input, args) { - const key = Utils.convertToByteString(args[0].string, args[0].option), - iv = Utils.convertToByteArray(args[1].string, args[1].option), - mode = args[2], - inputType = args[3], - outputType = args[4]; - - if (key.length === 0) return "Enter a key"; - - input = inputType === "Raw" ? Utils.strToByteArray(input) : input; - - Blowfish.setIV(Utils.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; - }, - - - /** - * @constant - * @default - */ - KDF_KEY_SIZE: 128, - /** - * @constant - * @default - */ - KDF_ITERATIONS: 1, - /** - * @constant - * @default - */ - HASHERS: ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"], - - /** - * Derive PBKDF2 key operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runPbkdf2: function (input, args) { - const passphrase = Utils.convertToByteString(args[0].string, args[0].option), - keySize = args[1], - iterations = args[2], - hasher = args[3], - salt = Utils.convertToByteString(args[4].string, args[4].option) || - forge.random.getBytesSync(keySize), - derivedKey = forge.pkcs5.pbkdf2(passphrase, salt, iterations, keySize / 8, hasher.toLowerCase()); - - return forge.util.bytesToHex(derivedKey); - }, - - - /** - * Derive EVP key operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runEvpkdf: function (input, args) { - const passphrase = Utils.convertToByteString(args[0].string, args[0].option), - keySize = args[1] / 32, - iterations = args[2], - hasher = args[3], - salt = Utils.convertToByteString(args[4].string, args[4].option), - key = CryptoJS.EvpKDF(passphrase, salt, { - keySize: keySize, - hasher: CryptoJS.algo[hasher], - iterations: iterations, - }); - - return key.toString(CryptoJS.enc.Hex); - }, - - - /** - * @constant - * @default - */ - RC4_KEY_FORMAT: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"], - /** - * @constant - * @default - */ - CJS_IO_FORMAT: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"], - - - /** - * RC4 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRc4: function (input, args) { - let message = Cipher._format[args[1]].parse(input), - passphrase = Cipher._format[args[0].option].parse(args[0].string), - encrypted = CryptoJS.RC4.encrypt(message, passphrase); - - return encrypted.ciphertext.toString(Cipher._format[args[2]]); - }, - - - /** - * @constant - * @default - */ - RC4DROP_BYTES: 768, - - /** - * RC4 Drop operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRc4drop: function (input, args) { - let message = Cipher._format[args[1]].parse(input), - passphrase = Cipher._format[args[0].option].parse(args[0].string), - drop = args[3], - encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop }); - - return encrypted.ciphertext.toString(Cipher._format[args[2]]); - }, - - - /** - * @constant - * @default - */ - PRNG_BYTES: 32, - /** - * @constant - * @default - */ - PRNG_OUTPUT: ["Hex", "Integer", "Byte array", "Raw"], - - /** - * Pseudo-Random Number Generator operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runPRNG: function(input, args) { - const numBytes = args[0], - outputAs = args[1]; - - let bytes; - - if (ENVIRONMENT_IS_WORKER() && self.crypto) { - bytes = self.crypto.getRandomValues(new Uint8Array(numBytes)); - bytes = Utils.arrayBufferToStr(bytes.buffer); - } else { - bytes = forge.random.getBytesSync(numBytes); - } - - let value = new BigNumber(0), - i; - - switch (outputAs) { - case "Hex": - return forge.util.bytesToHex(bytes); - case "Integer": - for (i = bytes.length - 1; i >= 0; i--) { - value = value.times(256).plus(bytes.charCodeAt(i)); - } - return value.toFixed(); - case "Byte array": - return JSON.stringify(Utils.strToCharcode(bytes)); - case "Raw": - default: - return bytes; - } - }, - - - /** - * Vigenère Encode operation. - * - * @author Matt C [matt@artemisbot.uk] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runVigenereEnc: function (input, args) { - let alphabet = "abcdefghijklmnopqrstuvwxyz", - key = args[0].toLowerCase(), - output = "", - fail = 0, - keyIndex, - msgIndex, - chr; - - if (!key) return "No key entered"; - if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters"; - - for (let i = 0; i < input.length; i++) { - if (alphabet.indexOf(input[i]) >= 0) { - // Get the corresponding character of key for the current letter, accounting - // for chars not in alphabet - chr = key[(i - fail) % key.length]; - // Get the location in the vigenere square of the key char - keyIndex = alphabet.indexOf(chr); - // Get the location in the vigenere square of the message char - msgIndex = alphabet.indexOf(input[i]); - // Get the encoded letter by finding the sum of indexes modulo 26 and finding - // the letter corresponding to that - output += alphabet[(keyIndex + msgIndex) % 26]; - } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { - chr = key[(i - fail) % key.length].toLowerCase(); - keyIndex = alphabet.indexOf(chr); - msgIndex = alphabet.indexOf(input[i].toLowerCase()); - output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase(); - } else { - output += input[i]; - fail++; - } - } - - return output; - }, - - - /** - * Vigenère Decode operation. - * - * @author Matt C [matt@artemisbot.uk] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runVigenereDec: function (input, args) { - let alphabet = "abcdefghijklmnopqrstuvwxyz", - key = args[0].toLowerCase(), - output = "", - fail = 0, - keyIndex, - msgIndex, - chr; - - if (!key) return "No key entered"; - if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters"; - - for (let i = 0; i < input.length; i++) { - if (alphabet.indexOf(input[i]) >= 0) { - chr = key[(i - fail) % key.length]; - keyIndex = alphabet.indexOf(chr); - msgIndex = alphabet.indexOf(input[i]); - // Subtract indexes from each other, add 26 just in case the value is negative, - // modulo to remove if neccessary - output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26]; - } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { - chr = key[(i - fail) % key.length].toLowerCase(); - keyIndex = alphabet.indexOf(chr); - msgIndex = alphabet.indexOf(input[i].toLowerCase()); - output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase(); - } else { - output += input[i]; - fail++; - } - } - - return output; - }, - - - /** - * @constant - * @default - */ - AFFINE_A: 1, - /** - * @constant - * @default - */ - AFFINE_B: 0, - - /** - * Affine Cipher Encode operation. - * - * @author Matt C [matt@artemisbot.uk] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAffineEnc: function (input, args) { - let alphabet = "abcdefghijklmnopqrstuvwxyz", - a = args[0], - b = args[1], - output = ""; - - if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) { - return "The values of a and b can only be integers."; - } - - for (let i = 0; i < input.length; i++) { - if (alphabet.indexOf(input[i]) >= 0) { - // Uses the affine function ax+b % m = y (where m is length of the alphabet) - output += alphabet[((a * alphabet.indexOf(input[i])) + b) % 26]; - } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { - // Same as above, accounting for uppercase - output += alphabet[((a * alphabet.indexOf(input[i].toLowerCase())) + b) % 26].toUpperCase(); - } else { - // Non-alphabetic characters - output += input[i]; - } - } - return output; - }, - - - /** - * Affine Cipher Decode operation. - * - * @author Matt C [matt@artemisbot.uk] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAffineDec: function (input, args) { - let alphabet = "abcdefghijklmnopqrstuvwxyz", - a = args[0], - b = args[1], - output = "", - aModInv; - - if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) { - return "The values of a and b can only be integers."; - } - - if (Utils.gcd(a, 26) !== 1) { - return "The value of a must be coprime to 26."; - } - - // Calculates modular inverse of a - aModInv = Utils.modInv(a, 26); - - for (let i = 0; i < input.length; i++) { - if (alphabet.indexOf(input[i]) >= 0) { - // Uses the affine decode function (y-b * A') % m = x (where m is length of the alphabet and A' is modular inverse) - output += alphabet[Utils.mod((alphabet.indexOf(input[i]) - b) * aModInv, 26)]; - } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { - // Same as above, accounting for uppercase - output += alphabet[Utils.mod((alphabet.indexOf(input[i].toLowerCase()) - b) * aModInv, 26)].toUpperCase(); - } else { - // Non-alphabetic characters - output += input[i]; - } - } - return output; - }, - - - /** - * Atbash Cipher Encode operation. - * - * @author Matt C [matt@artemisbot.uk] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAtbash: function (input, args) { - return Cipher.runAffineEnc(input, [25, 25]); - }, - - - /** - * Generates a polybius square for the given keyword - * - * @private - * @author Matt C [matt@artemisbot.uk] - * @param {string} keyword - Must be upper case - * @returns {string} - */ - _genPolybiusSquare: function (keyword) { - const alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ"; - const polArray = `${keyword}${alpha}`.split("").unique(); - let polybius = []; - - for (let i = 0; i < 5; i++) { - polybius[i] = polArray.slice(i*5, i*5 + 5); - } - - return polybius; - }, - - /** - * Bifid Cipher Encode operation - * - * @author Matt C [matt@artemisbot.uk] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runBifidEnc: function (input, args) { - const keywordStr = args[0].toUpperCase().replace("J", "I"), - keyword = keywordStr.split("").unique(), - alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ"; - - let output = "", - xCo = [], - yCo = [], - structure = [], - count = 0; - - if (keyword.length > 25) - return "The alphabet keyword must be less than 25 characters."; - - if (!/^[a-zA-Z]+$/.test(keywordStr) && keyword.length > 0) - return "The key must consist only of letters"; - - const polybius = Cipher._genPolybiusSquare(keywordStr); - - input.replace("J", "I").split("").forEach(letter => { - let alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0, - polInd; - - if (alpInd) { - for (let i = 0; i < 5; i++) { - polInd = polybius[i].indexOf(letter.toLocaleUpperCase()); - if (polInd >= 0) { - xCo.push(polInd); - yCo.push(i); - } - } - - if (alpha.split("").indexOf(letter) >= 0) { - structure.push(true); - } else if (alpInd) { - structure.push(false); - } - } else { - structure.push(letter); - } - }); - - const trans = `${yCo.join("")}${xCo.join("")}`; - - structure.forEach(pos => { - if (typeof pos === "boolean") { - let coords = trans.substr(2*count, 2).split(""); - - output += pos ? - polybius[coords[0]][coords[1]] : - polybius[coords[0]][coords[1]].toLocaleLowerCase(); - count++; - } else { - output += pos; - } - }); - - return output; - }, - - /** - * Bifid Cipher Decode operation - * - * @author Matt C [matt@artemisbot.uk] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runBifidDec: function (input, args) { - const keywordStr = args[0].toUpperCase().replace("J", "I"), - keyword = keywordStr.split("").unique(), - alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ"; - - let output = "", - structure = [], - count = 0, - trans = ""; - - if (keyword.length > 25) - return "The alphabet keyword must be less than 25 characters."; - - if (!/^[a-zA-Z]+$/.test(keywordStr) && keyword.length > 0) - return "The key must consist only of letters"; - - const polybius = Cipher._genPolybiusSquare(keywordStr); - - input.replace("J", "I").split("").forEach((letter) => { - let alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0, - polInd; - - if (alpInd) { - for (let i = 0; i < 5; i++) { - polInd = polybius[i].indexOf(letter.toLocaleUpperCase()); - if (polInd >= 0) { - trans += `${i}${polInd}`; - } - } - - if (alpha.split("").indexOf(letter) >= 0) { - structure.push(true); - } else if (alpInd) { - structure.push(false); - } - } else { - structure.push(letter); - } - }); - - structure.forEach(pos => { - if (typeof pos === "boolean") { - let coords = [trans[count], trans[count+trans.length/2]]; - - output += pos ? - polybius[coords[0]][coords[1]] : - polybius[coords[0]][coords[1]].toLocaleLowerCase(); - count++; - } else { - output += pos; - } - }); - - return output; - }, - - - /** - * @constant - * @default - */ - SUBS_PLAINTEXT: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - /** - * @constant - * @default - */ - SUBS_CIPHERTEXT: "XYZABCDEFGHIJKLMNOPQRSTUVW", - - /** - * Substitute operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSubstitute: function (input, args) { - let plaintext = Utils.expandAlphRange(args[0]).join(""), - ciphertext = Utils.expandAlphRange(args[1]).join(""), - output = "", - index = -1; - - if (plaintext.length !== ciphertext.length) { - output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; - } - - for (let i = 0; i < input.length; i++) { - index = plaintext.indexOf(input[i]); - output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i]; - } - - return output; - }, - - - /** - * A mapping of string formats to their classes in the CryptoJS library. - * - * @private - * @constant - */ - _format: { - "Hex": CryptoJS.enc.Hex, - "Base64": CryptoJS.enc.Base64, - "UTF8": CryptoJS.enc.Utf8, - "UTF16": CryptoJS.enc.Utf16, - "UTF16LE": CryptoJS.enc.Utf16LE, - "UTF16BE": CryptoJS.enc.Utf16BE, - "Latin1": CryptoJS.enc.Latin1, - }, - -}; - -export default Cipher; - - -/** - * Overwriting the CryptoJS OpenSSL key derivation function so that it is possible to not pass a - * salt in. - - * @param {string} password - The password to derive from. - * @param {number} keySize - The size in words of the key to generate. - * @param {number} ivSize - The size in words of the IV to generate. - * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be - * generated randomly. If set to false, no salt will be added. - * - * @returns {CipherParams} A cipher params object with the key, IV, and salt. - * - * @static - * - * @example - * // Randomly generates a salt - * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); - * // Uses the salt 'saltsalt' - * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); - * // Does not use a salt - * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, false); - */ -CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) { - // Generate random salt if no salt specified and not set to false - // This line changed from `if (!salt) {` to the following - if (salt === undefined || salt === null) { - salt = CryptoJS.lib.WordArray.random(64/8); - } - - // Derive key and IV - const key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); - - // Separate key and IV - const iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4); - key.sigBytes = keySize * 4; - - // Return params - return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt }); -}; - - -/** - * Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse - * the hex string. - * - * @param {string} hexStr - * @returns {CryptoJS.lib.WordArray} - */ -CryptoJS.enc.Hex.parse = function (hexStr) { - // Remove whitespace - hexStr = hexStr.replace(/\s/g, ""); - - // Shortcut - const hexStrLength = hexStr.length; - - // Convert - const words = []; - for (let i = 0; i < hexStrLength; i += 2) { - words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); - } - - return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2); -}; diff --git a/src/core/operations/Code.js b/src/core/operations/Code.js deleted file mode 100755 index ad777d01..00000000 --- a/src/core/operations/Code.js +++ /dev/null @@ -1,540 +0,0 @@ -import {camelCase, kebabCase, snakeCase} from "lodash"; -import vkbeautify from "vkbeautify"; -import {DOMParser} from "xmldom"; -import xpath from "xpath"; -import jpath from "jsonpath"; -import nwmatcher from "nwmatcher"; -import hljs from "highlight.js"; - - -/** - * Code operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Code = { - - /** - * @constant - * @default - */ - LANGUAGES: ["auto detect"].concat(hljs.listLanguages()), - - /** - * Syntax highlighter operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runSyntaxHighlight: function(input, args) { - const language = args[0]; - - if (language === "auto detect") { - return hljs.highlightAuto(input).value; - } - - return hljs.highlight(language, input, true).value; - }, - - - /** - * @constant - * @default - */ - BEAUTIFY_INDENT: "\\t", - - /** - * XML Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runXmlBeautify: function(input, args) { - const indentStr = args[0]; - return vkbeautify.xml(input, indentStr); - }, - - - /** - * JSON Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runJsonBeautify: function(input, args) { - const indentStr = args[0]; - if (!input) return ""; - return vkbeautify.json(input, indentStr); - }, - - - /** - * CSS Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runCssBeautify: function(input, args) { - const indentStr = args[0]; - return vkbeautify.css(input, indentStr); - }, - - - /** - * SQL Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSqlBeautify: function(input, args) { - const indentStr = args[0]; - return vkbeautify.sql(input, indentStr); - }, - - - /** - * @constant - * @default - */ - PRESERVE_COMMENTS: false, - - /** - * XML Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runXmlMinify: function(input, args) { - const preserveComments = args[0]; - return vkbeautify.xmlmin(input, preserveComments); - }, - - - /** - * JSON Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runJsonMinify: function(input, args) { - if (!input) return ""; - return vkbeautify.jsonmin(input); - }, - - - /** - * CSS Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runCssMinify: function(input, args) { - const preserveComments = args[0]; - return vkbeautify.cssmin(input, preserveComments); - }, - - - /** - * SQL Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSqlMinify: function(input, args) { - return vkbeautify.sqlmin(input); - }, - - - /** - * Generic Code Beautify operation. - * - * Yeeeaaah... - * - * I'm not proud of this code, but seriously, try writing a generic lexer and parser that - * correctly generates an AST for multiple different languages. I have tried, and I can tell - * you it's pretty much impossible. - * - * This basically works. That'll have to be good enough. It's not meant to produce working code, - * just slightly more readable code. - * - * Things that don't work: - * - For loop formatting - * - Do-While loop formatting - * - Switch/Case indentation - * - Bit shift operators - * - * @author n1474335 [n1474335@gmail.com] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runGenericBeautify: function(input, args) { - let code = input, - t = 0, - preservedTokens = [], - m; - - // Remove strings - const sstrings = /'([^'\\]|\\.)*'/g; - while ((m = sstrings.exec(code))) { - code = preserveToken(code, m, t++); - sstrings.lastIndex = m.index; - } - - const dstrings = /"([^"\\]|\\.)*"/g; - while ((m = dstrings.exec(code))) { - code = preserveToken(code, m, t++); - dstrings.lastIndex = m.index; - } - - // Remove comments - const scomments = /\/\/[^\n\r]*/g; - while ((m = scomments.exec(code))) { - code = preserveToken(code, m, t++); - scomments.lastIndex = m.index; - } - - const mcomments = /\/\*[\s\S]*?\*\//gm; - while ((m = mcomments.exec(code))) { - code = preserveToken(code, m, t++); - mcomments.lastIndex = m.index; - } - - const hcomments = /(^|\n)#[^\n\r#]+/g; - while ((m = hcomments.exec(code))) { - code = preserveToken(code, m, t++); - hcomments.lastIndex = m.index; - } - - // Remove regexes - const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi; - while ((m = regexes.exec(code))) { - code = preserveToken(code, m, t++); - regexes.lastIndex = m.index; - } - - code = code - // Create newlines after ; - .replace(/;/g, ";\n") - // Create newlines after { and around } - .replace(/{/g, "{\n") - .replace(/}/g, "\n}\n") - // Remove carriage returns - .replace(/\r/g, "") - // Remove all indentation - .replace(/^\s+/g, "") - .replace(/\n\s+/g, "\n") - // Remove trailing spaces - .replace(/\s*$/g, "") - .replace(/\n{/g, "{"); - - // Indent - let i = 0, - level = 0, - indent; - while (i < code.length) { - switch (code[i]) { - case "{": - level++; - break; - case "\n": - if (i+1 >= code.length) break; - - if (code[i+1] === "}") level--; - indent = (level >= 0) ? Array(level*4+1).join(" ") : ""; - - code = code.substring(0, i+1) + indent + code.substring(i+1); - if (level > 0) i += level*4; - break; - } - i++; - } - - code = code - // Add strategic spaces - .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ") - .replace(/\s*<([=]?)\s*/g, " <$1 ") - .replace(/\s*>([=]?)\s*/g, " >$1 ") - .replace(/([^+])\+([^+=])/g, "$1 + $2") - .replace(/([^-])-([^-=])/g, "$1 - $2") - .replace(/([^*])\*([^*=])/g, "$1 * $2") - .replace(/([^/])\/([^/=])/g, "$1 / $2") - .replace(/\s*,\s*/g, ", ") - .replace(/\s*{/g, " {") - .replace(/}\n/g, "}\n\n") - // Hacky horribleness - .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3") - .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3") - .replace(/else\s*\n([^{])/gim, "else\n $1") - .replace(/else\s+([^{])/gim, "else $1") - // Remove strategic spaces - .replace(/\s+;/g, ";") - .replace(/\{\s+\}/g, "{}") - .replace(/\[\s+\]/g, "[]") - .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1"); - - // Replace preserved tokens - const ptokens = /###preservedToken(\d+)###/g; - while ((m = ptokens.exec(code))) { - const ti = parseInt(m[1], 10); - code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length); - ptokens.lastIndex = m.index; - } - - return code; - - /** - * Replaces a matched token with a placeholder value. - */ - function preserveToken(str, match, t) { - preservedTokens[t] = match[0]; - return str.substring(0, match.index) + - "###preservedToken" + t + "###" + - str.substring(match.index + match[0].length); - } - }, - - - /** - * @constant - * @default - */ - XPATH_INITIAL: "", - - /** - * @constant - * @default - */ - XPATH_DELIMITER: "\\n", - - /** - * XPath expression operation. - * - * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runXpath: function(input, args) { - let query = args[0], - delimiter = args[1]; - - let doc; - try { - doc = new DOMParser().parseFromString(input, "application/xml"); - } catch (err) { - return "Invalid input XML."; - } - - let nodes; - try { - nodes = xpath.select(query, doc); - } catch (err) { - return "Invalid XPath. Details:\n" + err.message; - } - - const nodeToString = function(node) { - return node.toString(); - }; - - return nodes.map(nodeToString).join(delimiter); - }, - - - /** - * @constant - * @default - */ - JPATH_INITIAL: "", - - /** - * @constant - * @default - */ - JPATH_DELIMITER: "\\n", - - /** - * JPath expression operation. - * - * @author Matt C (matt@artemisbot.uk) - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runJpath: function(input, args) { - let query = args[0], - delimiter = args[1], - results, - obj; - - try { - obj = JSON.parse(input); - } catch (err) { - return "Invalid input JSON: " + err.message; - } - - try { - results = jpath.query(obj, query); - } catch (err) { - return "Invalid JPath expression: " + err.message; - } - - return results.map(result => JSON.stringify(result)).join(delimiter); - }, - - - /** - * @constant - * @default - */ - CSS_SELECTOR_INITIAL: "", - - /** - * @constant - * @default - */ - CSS_QUERY_DELIMITER: "\\n", - - /** - * CSS selector operation. - * - * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) - * @author n1474335 [n1474335@gmail.com] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runCSSQuery: function(input, args) { - let query = args[0], - delimiter = args[1], - parser = new DOMParser(), - dom, - result; - - if (!query.length || !input.length) { - return ""; - } - - try { - dom = parser.parseFromString(input); - } catch (err) { - return "Invalid input HTML."; - } - - try { - const matcher = nwmatcher({document: dom}); - result = matcher.select(query, dom); - } catch (err) { - return "Invalid CSS Selector. Details:\n" + err.message; - } - - const nodeToString = function(node) { - return node.toString(); - /* xmldom does not return the outerHTML value. - switch (node.nodeType) { - case node.ELEMENT_NODE: return node.outerHTML; - case node.ATTRIBUTE_NODE: return node.value; - case node.TEXT_NODE: return node.wholeText; - case node.COMMENT_NODE: return node.data; - case node.DOCUMENT_NODE: return node.outerHTML; - default: throw new Error("Unknown Node Type: " + node.nodeType); - }*/ - }; - - return result - .map(nodeToString) - .join(delimiter); - }, - - /** - * This tries to rename variable names in a code snippet according to a function. - * - * @param {string} input - * @param {function} replacer - this function will be fed the token which should be renamed. - * @returns {string} - */ - _replaceVariableNames(input, replacer) { - const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig; - - return input.replace(tokenRegex, (...args) => { - let match = args[0], - quotes = args[1]; - - if (!quotes) { - return match; - } else { - return replacer(match); - } - }); - }, - - - /** - * To Snake Case operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToSnakeCase(input, args) { - const smart = args[0]; - - if (smart) { - return Code._replaceVariableNames(input, snakeCase); - } else { - return snakeCase(input); - } - }, - - - /** - * To Camel Case operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToCamelCase(input, args) { - const smart = args[0]; - - if (smart) { - return Code._replaceVariableNames(input, camelCase); - } else { - return camelCase(input); - } - }, - - - /** - * To Kebab Case operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToKebabCase(input, args) { - const smart = args[0]; - - if (smart) { - return Code._replaceVariableNames(input, kebabCase); - } else { - return kebabCase(input); - } - }, - -}; - -export default Code; diff --git a/src/core/operations/Comment.mjs b/src/core/operations/Comment.mjs new file mode 100644 index 00000000..2c941089 --- /dev/null +++ b/src/core/operations/Comment.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Comment operation + */ +class Comment extends Operation { + + /** + * Comment constructor + */ + constructor() { + super(); + + this.name = "Comment"; + this.flowControl = true; + this.module = "Default"; + this.description = "Provides a place to write comments within the flow of the recipe. This operation has no computational effect."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + return state; + } + +} + +export default Comment; diff --git a/src/core/operations/CompareCTPHHashes.mjs b/src/core/operations/CompareCTPHHashes.mjs new file mode 100644 index 00000000..7194d89f --- /dev/null +++ b/src/core/operations/CompareCTPHHashes.mjs @@ -0,0 +1,51 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {HASH_DELIM_OPTIONS} from "../lib/Delim"; +import ctphjs from "ctph.js"; +import OperationError from "../errors/OperationError"; + +/** + * Compare CTPH hashes operation + */ +class CompareCTPHHashes extends Operation { + + /** + * CompareCTPHHashes constructor + */ + constructor() { + super(); + + this.name = "Compare CTPH hashes"; + this.module = "Hashing"; + this.description = "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100."; + this.inputType = "string"; + this.outputType = "Number"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": HASH_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Number} + */ + run(input, args) { + const samples = input.split(Utils.charRep(args[0])); + if (samples.length !== 2) throw new OperationError("Incorrect number of samples."); + return ctphjs.similarity(samples[0], samples[1]); + } + +} + +export default CompareCTPHHashes; diff --git a/src/core/operations/CompareSSDEEPHashes.mjs b/src/core/operations/CompareSSDEEPHashes.mjs new file mode 100644 index 00000000..aa39d5cf --- /dev/null +++ b/src/core/operations/CompareSSDEEPHashes.mjs @@ -0,0 +1,51 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {HASH_DELIM_OPTIONS} from "../lib/Delim"; +import ssdeepjs from "ssdeep.js"; +import OperationError from "../errors/OperationError"; + +/** + * Compare SSDEEP hashes operation + */ +class CompareSSDEEPHashes extends Operation { + + /** + * CompareSSDEEPHashes constructor + */ + constructor() { + super(); + + this.name = "Compare SSDEEP hashes"; + this.module = "Hashing"; + this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100."; + this.inputType = "string"; + this.outputType = "Number"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": HASH_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Number} + */ + run(input, args) { + const samples = input.split(Utils.charRep(args[0])); + if (samples.length !== 2) throw new OperationError("Incorrect number of samples."); + return ssdeepjs.similarity(samples[0], samples[1]); + } + +} + +export default CompareSSDEEPHashes; 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/ConditionalJump.mjs b/src/core/operations/ConditionalJump.mjs new file mode 100644 index 00000000..d102ea72 --- /dev/null +++ b/src/core/operations/ConditionalJump.mjs @@ -0,0 +1,84 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Dish from "../Dish"; +import { getLabelIndex } from "../lib/FlowControl"; + +/** + * Conditional Jump operation + */ +class ConditionalJump extends Operation { + + /** + * ConditionalJump constructor + */ + constructor() { + super(); + + this.name = "Conditional Jump"; + this.flowControl = true; + this.module = "Default"; + this.description = "Conditionally jump forwards or backwards to the specified Label based on whether the data matches the specified regular expression."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Match (regex)", + "type": "string", + "value": "" + }, + { + "name": "Invert match", + "type": "boolean", + "value": false + }, + { + "name": "Label name", + "type": "shortString", + "value": "" + }, + { + "name": "Maximum jumps (if jumping backwards)", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @param {number} state.numJumps - The number of jumps taken so far. + * @returns {Object} The updated state of the recipe. + */ + async run(state) { + const ings = state.opList[state.progress].ingValues, + dish = state.dish, + [regexStr, invert, label, maxJumps] = ings, + jmpIndex = getLabelIndex(label, state); + + if (state.numJumps >= maxJumps || jmpIndex === -1) { + return state; + } + + if (regexStr !== "") { + const str = await dish.get(Dish.STRING); + const strMatch = str.search(regexStr) > -1; + if (!invert && strMatch || invert && !strMatch) { + state.progress = jmpIndex; + state.numJumps++; + } + } + + return state; + } + +} + +export default ConditionalJump; diff --git a/src/core/operations/Convert.js b/src/core/operations/Convert.js deleted file mode 100755 index a79a5c3f..00000000 --- a/src/core/operations/Convert.js +++ /dev/null @@ -1,413 +0,0 @@ -/** - * Unit conversion operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Convert = { - - /** - * @constant - * @default - */ - DISTANCE_UNITS: [ - "[Metric]", "Nanometres (nm)", "Micrometres (µm)", "Millimetres (mm)", "Centimetres (cm)", "Metres (m)", "Kilometers (km)", "[/Metric]", - "[Imperial]", "Thou (th)", "Inches (in)", "Feet (ft)", "Yards (yd)", "Chains (ch)", "Furlongs (fur)", "Miles (mi)", "Leagues (lea)", "[/Imperial]", - "[Maritime]", "Fathoms (ftm)", "Cables", "Nautical miles", "[/Maritime]", - "[Comparisons]", "Cars (4m)", "Buses (8.4m)", "American football fields (91m)", "Football pitches (105m)", "[/Comparisons]", - "[Astronomical]", "Earth-to-Moons", "Earth's equators", "Astronomical units (au)", "Light-years (ly)", "Parsecs (pc)", "[/Astronomical]", - ], - /** - * @constant - * @default - */ - DISTANCE_FACTOR: { // Multiples of a metre - "Nanometres (nm)": 1e-9, - "Micrometres (µm)": 1e-6, - "Millimetres (mm)": 1e-3, - "Centimetres (cm)": 1e-2, - "Metres (m)": 1, - "Kilometers (km)": 1e3, - - "Thou (th)": 0.0000254, - "Inches (in)": 0.0254, - "Feet (ft)": 0.3048, - "Yards (yd)": 0.9144, - "Chains (ch)": 20.1168, - "Furlongs (fur)": 201.168, - "Miles (mi)": 1609.344, - "Leagues (lea)": 4828.032, - - "Fathoms (ftm)": 1.853184, - "Cables": 185.3184, - "Nautical miles": 1853.184, - - "Cars (4m)": 4, - "Buses (8.4m)": 8.4, - "American football fields (91m)": 91, - "Football pitches (105m)": 105, - - "Earth-to-Moons": 380000000, - "Earth's equators": 40075016.686, - "Astronomical units (au)": 149597870700, - "Light-years (ly)": 9460730472580800, - "Parsecs (pc)": 3.0856776e16 - }, - - /** - * Convert distance operation. - * - * @param {BigNumber} input - * @param {Object[]} args - * @returns {BigNumber} - */ - runDistance: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input.times(Convert.DISTANCE_FACTOR[inputUnits]); - return input.div(Convert.DISTANCE_FACTOR[outputUnits]); - }, - - - /** - * @constant - * @default - */ - DATA_UNITS: [ - "Bits (b)", "Nibbles", "Octets", "Bytes (B)", - "[Binary bits (2^n)]", "Kibibits (Kib)", "Mebibits (Mib)", "Gibibits (Gib)", "Tebibits (Tib)", "Pebibits (Pib)", "Exbibits (Eib)", "Zebibits (Zib)", "Yobibits (Yib)", "[/Binary bits (2^n)]", - "[Decimal bits (10^n)]", "Decabits", "Hectobits", "Kilobits (kb)", "Megabits (Mb)", "Gigabits (Gb)", "Terabits (Tb)", "Petabits (Pb)", "Exabits (Eb)", "Zettabits (Zb)", "Yottabits (Yb)", "[/Decimal bits (10^n)]", - "[Binary bytes (8 x 2^n)]", "Kibibytes (KiB)", "Mebibytes (MiB)", "Gibibytes (GiB)", "Tebibytes (TiB)", "Pebibytes (PiB)", "Exbibytes (EiB)", "Zebibytes (ZiB)", "Yobibytes (YiB)", "[/Binary bytes (8 x 2^n)]", - "[Decimal bytes (8 x 10^n)]", "Kilobytes (KB)", "Megabytes (MB)", "Gigabytes (GB)", "Terabytes (TB)", "Petabytes (PB)", "Exabytes (EB)", "Zettabytes (ZB)", "Yottabytes (YB)", "[/Decimal bytes (8 x 10^n)]" - ], - /** - * @constant - * @default - */ - DATA_FACTOR: { // Multiples of a bit - "Bits (b)": 1, - "Nibbles": 4, - "Octets": 8, - "Bytes (B)": 8, - - // Binary bits (2^n) - "Kibibits (Kib)": 1024, - "Mebibits (Mib)": 1048576, - "Gibibits (Gib)": 1073741824, - "Tebibits (Tib)": 1099511627776, - "Pebibits (Pib)": 1125899906842624, - "Exbibits (Eib)": 1152921504606846976, - "Zebibits (Zib)": 1180591620717411303424, - "Yobibits (Yib)": 1208925819614629174706176, - - // Decimal bits (10^n) - "Decabits": 10, - "Hectobits": 100, - "Kilobits (Kb)": 1e3, - "Megabits (Mb)": 1e6, - "Gigabits (Gb)": 1e9, - "Terabits (Tb)": 1e12, - "Petabits (Pb)": 1e15, - "Exabits (Eb)": 1e18, - "Zettabits (Zb)": 1e21, - "Yottabits (Yb)": 1e24, - - // Binary bytes (8 x 2^n) - "Kibibytes (KiB)": 8192, - "Mebibytes (MiB)": 8388608, - "Gibibytes (GiB)": 8589934592, - "Tebibytes (TiB)": 8796093022208, - "Pebibytes (PiB)": 9007199254740992, - "Exbibytes (EiB)": 9223372036854775808, - "Zebibytes (ZiB)": 9444732965739290427392, - "Yobibytes (YiB)": 9671406556917033397649408, - - // Decimal bytes (8 x 10^n) - "Kilobytes (KB)": 8e3, - "Megabytes (MB)": 8e6, - "Gigabytes (GB)": 8e9, - "Terabytes (TB)": 8e12, - "Petabytes (PB)": 8e15, - "Exabytes (EB)": 8e18, - "Zettabytes (ZB)": 8e21, - "Yottabytes (YB)": 8e24, - }, - - /** - * Convert data units operation. - * - * @param {BigNumber} input - * @param {Object[]} args - * @returns {BigNumber} - */ - runDataSize: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input.times(Convert.DATA_FACTOR[inputUnits]); - return input.div(Convert.DATA_FACTOR[outputUnits]); - }, - - - /** - * @constant - * @default - */ - AREA_UNITS: [ - "[Metric]", "Square metre (sq m)", "Square kilometre (sq km)", "Centiare (ca)", "Deciare (da)", "Are (a)", "Decare (daa)", "Hectare (ha)", "[/Metric]", - "[Imperial]", "Square inch (sq in)", "Square foot (sq ft)", "Square yard (sq yd)", "Square mile (sq mi)", "Perch (sq per)", "Rood (ro)", "International acre (ac)", "[/Imperial]", - "[US customary units]", "US survey acre (ac)", "US survey square mile (sq mi)", "US survey township", "[/US customary units]", - "[Nuclear physics]", "Yoctobarn (yb)", "Zeptobarn (zb)", "Attobarn (ab)", "Femtobarn (fb)", "Picobarn (pb)", "Nanobarn (nb)", "Microbarn (μb)", "Millibarn (mb)", "Barn (b)", "Kilobarn (kb)", "Megabarn (Mb)", "Outhouse", "Shed", "Planck area", "[/Nuclear physics]", - "[Comparisons]", "Washington D.C.", "Isle of Wight", "Wales", "Texas", "[/Comparisons]", - ], - /** - * @constant - * @default - */ - AREA_FACTOR: { // Multiples of a square metre - // Metric - "Square metre (sq m)": 1, - "Square kilometre (sq km)": 1e6, - - "Centiare (ca)": 1, - "Deciare (da)": 10, - "Are (a)": 100, - "Decare (daa)": 1e3, - "Hectare (ha)": 1e4, - - // Imperial - "Square inch (sq in)": 0.00064516, - "Square foot (sq ft)": 0.09290304, - "Square yard (sq yd)": 0.83612736, - "Square mile (sq mi)": 2589988.110336, - "Perch (sq per)": 42.21, - "Rood (ro)": 1011, - "International acre (ac)": 4046.8564224, - - // US customary units - "US survey acre (ac)": 4046.87261, - "US survey square mile (sq mi)": 2589998.470305239, - "US survey township": 93239944.9309886, - - // Nuclear physics - "Yoctobarn (yb)": 1e-52, - "Zeptobarn (zb)": 1e-49, - "Attobarn (ab)": 1e-46, - "Femtobarn (fb)": 1e-43, - "Picobarn (pb)": 1e-40, - "Nanobarn (nb)": 1e-37, - "Microbarn (μb)": 1e-34, - "Millibarn (mb)": 1e-31, - "Barn (b)": 1e-28, - "Kilobarn (kb)": 1e-25, - "Megabarn (Mb)": 1e-22, - - "Planck area": 2.6e-70, - "Shed": 1e-52, - "Outhouse": 1e-34, - - // Comparisons - "Washington D.C.": 176119191.502848, - "Isle of Wight": 380000000, - "Wales": 20779000000, - "Texas": 696241000000, - }, - - /** - * Convert area operation. - * - * @param {BigNumber} input - * @param {Object[]} args - * @returns {BigNumber} - */ - runArea: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input.times(Convert.AREA_FACTOR[inputUnits]); - return input.div(Convert.AREA_FACTOR[outputUnits]); - }, - - - /** - * @constant - * @default - */ - MASS_UNITS: [ - "[Metric]", "Yoctogram (yg)", "Zeptogram (zg)", "Attogram (ag)", "Femtogram (fg)", "Picogram (pg)", "Nanogram (ng)", "Microgram (μg)", "Milligram (mg)", "Centigram (cg)", "Decigram (dg)", "Gram (g)", "Decagram (dag)", "Hectogram (hg)", "Kilogram (kg)", "Megagram (Mg)", "Tonne (t)", "Gigagram (Gg)", "Teragram (Tg)", "Petagram (Pg)", "Exagram (Eg)", "Zettagram (Zg)", "Yottagram (Yg)", "[/Metric]", - "[Imperial Avoirdupois]", "Grain (gr)", "Dram (dr)", "Ounce (oz)", "Pound (lb)", "Nail", "Stone (st)", "Quarter (gr)", "Tod", "US hundredweight (cwt)", "Imperial hundredweight (cwt)", "US ton (t)", "Imperial ton (t)", "[/Imperial Avoirdupois]", - "[Imperial Troy]", "Grain (gr)", "Pennyweight (dwt)", "Troy dram (dr t)", "Troy ounce (oz t)", "Troy pound (lb t)", "Mark", "[/Imperial Troy]", - "[Archaic]", "Wey", "Wool wey", "Suffolk wey", "Wool sack", "Coal sack", "Load", "Last", "Flax or feather last", "Gunpowder last", "Picul", "Rice last", "[/Archaic]", - "[Comparisons]", "Big Ben (14 tonnes)", "Blue whale (180 tonnes)", "International Space Station (417 tonnes)", "Space Shuttle (2,041 tonnes)", "RMS Titanic (52,000 tonnes)", "Great Pyramid of Giza (6,000,000 tonnes)", "Earth's oceans (1.4 yottagrams)", "[/Comparisons]", - "[Astronomical]", "A teaspoon of neutron star (5,500 million tonnes)", "Lunar mass (ML)", "Earth mass (M⊕)", "Jupiter mass (MJ)", "Solar mass (M☉)", "Sagittarius A* (7.5 x 10^36 kgs-ish)", "Milky Way galaxy (1.2 x 10^42 kgs)", "The observable universe (1.45 x 10^53 kgs)", "[/Astronomical]", - ], - /** - * @constant - * @default - */ - MASS_FACTOR: { // Multiples of a gram - // Metric - "Yoctogram (yg)": 1e-24, - "Zeptogram (zg)": 1e-21, - "Attogram (ag)": 1e-18, - "Femtogram (fg)": 1e-15, - "Picogram (pg)": 1e-12, - "Nanogram (ng)": 1e-9, - "Microgram (μg)": 1e-6, - "Milligram (mg)": 1e-3, - "Centigram (cg)": 1e-2, - "Decigram (dg)": 1e-1, - "Gram (g)": 1, - "Decagram (dag)": 10, - "Hectogram (hg)": 100, - "Kilogram (kg)": 1000, - "Megagram (Mg)": 1e6, - "Tonne (t)": 1e6, - "Gigagram (Gg)": 1e9, - "Teragram (Tg)": 1e12, - "Petagram (Pg)": 1e15, - "Exagram (Eg)": 1e18, - "Zettagram (Zg)": 1e21, - "Yottagram (Yg)": 1e24, - - // Imperial Avoirdupois - "Grain (gr)": 64.79891e-3, - "Dram (dr)": 1.7718451953125, - "Ounce (oz)": 28.349523125, - "Pound (lb)": 453.59237, - "Nail": 3175.14659, - "Stone (st)": 6.35029318e3, - "Quarter (gr)": 12700.58636, - "Tod": 12700.58636, - "US hundredweight (cwt)": 45.359237e3, - "Imperial hundredweight (cwt)": 50.80234544e3, - "US ton (t)": 907.18474e3, - "Imperial ton (t)": 1016.0469088e3, - - // Imperial Troy - "Pennyweight (dwt)": 1.55517384, - "Troy dram (dr t)": 3.8879346, - "Troy ounce (oz t)": 31.1034768, - "Troy pound (lb t)": 373.2417216, - "Mark": 248.8278144, - - // Archaic - "Wey": 76.5e3, - "Wool wey": 101.7e3, - "Suffolk wey": 161.5e3, - "Wool sack": 153000, - "Coal sack": 50.80234544e3, - "Load": 918000, - "Last": 1836000, - "Flax or feather last": 770e3, - "Gunpowder last": 1090e3, - "Picul": 60.478982e3, - "Rice last": 1200e3, - - // Comparisons - "Big Ben (14 tonnes)": 14e6, - "Blue whale (180 tonnes)": 180e6, - "International Space Station (417 tonnes)": 417e6, - "Space Shuttle (2,041 tonnes)": 2041e6, - "RMS Titanic (52,000 tonnes)": 52000e6, - "Great Pyramid of Giza (6,000,000 tonnes)": 6e12, - "Earth's oceans (1.4 yottagrams)": 1.4e24, - - // Astronomical - "A teaspoon of neutron star (5,500 million tonnes)": 5.5e15, - "Lunar mass (ML)": 7.342e25, - "Earth mass (M⊕)": 5.97219e27, - "Jupiter mass (MJ)": 1.8981411476999997e30, - "Solar mass (M☉)": 1.98855e33, - "Sagittarius A* (7.5 x 10^36 kgs-ish)": 7.5e39, - "Milky Way galaxy (1.2 x 10^42 kgs)": 1.2e45, - "The observable universe (1.45 x 10^53 kgs)": 1.45e56, - }, - - /** - * Convert mass operation. - * - * @param {BigNumber} input - * @param {Object[]} args - * @returns {BigNumber} - */ - runMass: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input.times(Convert.MASS_FACTOR[inputUnits]); - return input.div(Convert.MASS_FACTOR[outputUnits]); - }, - - - /** - * @constant - * @default - */ - SPEED_UNITS: [ - "[Metric]", "Metres per second (m/s)", "Kilometres per hour (km/h)", "[/Metric]", - "[Imperial]", "Miles per hour (mph)", "Knots (kn)", "[/Imperial]", - "[Comparisons]", "Human hair growth rate", "Bamboo growth rate", "World's fastest snail", "Usain Bolt's top speed", "Jet airliner cruising speed", "Concorde", "SR-71 Blackbird", "Space Shuttle", "International Space Station", "[/Comparisons]", - "[Scientific]", "Sound in standard atmosphere", "Sound in water", "Lunar escape velocity", "Earth escape velocity", "Earth's solar orbit", "Solar system's Milky Way orbit", "Milky Way relative to the cosmic microwave background", "Solar escape velocity", "Neutron star escape velocity (0.3c)", "Light in a diamond (0.4136c)", "Signal in an optical fibre (0.667c)", "Light (c)", "[/Scientific]", - ], - /** - * @constant - * @default - */ - SPEED_FACTOR: { // Multiples of m/s - // Metric - "Metres per second (m/s)": 1, - "Kilometres per hour (km/h)": 0.2778, - - // Imperial - "Miles per hour (mph)": 0.44704, - "Knots (kn)": 0.5144, - - // Comparisons - "Human hair growth rate": 4.8e-9, - "Bamboo growth rate": 1.4e-5, - "World's fastest snail": 0.00275, - "Usain Bolt's top speed": 12.42, - "Jet airliner cruising speed": 250, - "Concorde": 603, - "SR-71 Blackbird": 981, - "Space Shuttle": 1400, - "International Space Station": 7700, - - // Scientific - "Sound in standard atmosphere": 340.3, - "Sound in water": 1500, - "Lunar escape velocity": 2375, - "Earth escape velocity": 11200, - "Earth's solar orbit": 29800, - "Solar system's Milky Way orbit": 200000, - "Milky Way relative to the cosmic microwave background": 552000, - "Solar escape velocity": 617700, - "Neutron star escape velocity (0.3c)": 100000000, - "Light in a diamond (0.4136c)": 124000000, - "Signal in an optical fibre (0.667c)": 200000000, - "Light (c)": 299792458, - }, - - /** - * Convert speed operation. - * - * @param {BigNumber} input - * @param {Object[]} args - * @returns {BigNumber} - */ - runSpeed: function (input, args) { - let inputUnits = args[0], - outputUnits = args[1]; - - input = input.times(Convert.SPEED_FACTOR[inputUnits]); - return input.div(Convert.SPEED_FACTOR[outputUnits]); - }, - -}; - -export default Convert; diff --git a/src/core/operations/ConvertArea.mjs b/src/core/operations/ConvertArea.mjs new file mode 100644 index 00000000..dd88f52e --- /dev/null +++ b/src/core/operations/ConvertArea.mjs @@ -0,0 +1,112 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert area operation + */ +class ConvertArea extends Operation { + + /** + * ConvertArea constructor + */ + constructor() { + super(); + + this.name = "Convert area"; + this.module = "Default"; + this.description = "Converts a unit of area to another format."; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": AREA_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": AREA_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(AREA_FACTOR[inputUnits]); + return input.div(AREA_FACTOR[outputUnits]); + } + +} + + +const AREA_UNITS = [ + "[Metric]", "Square metre (sq m)", "Square kilometre (sq km)", "Centiare (ca)", "Deciare (da)", "Are (a)", "Decare (daa)", "Hectare (ha)", "[/Metric]", + "[Imperial]", "Square inch (sq in)", "Square foot (sq ft)", "Square yard (sq yd)", "Square mile (sq mi)", "Perch (sq per)", "Rood (ro)", "International acre (ac)", "[/Imperial]", + "[US customary units]", "US survey acre (ac)", "US survey square mile (sq mi)", "US survey township", "[/US customary units]", + "[Nuclear physics]", "Yoctobarn (yb)", "Zeptobarn (zb)", "Attobarn (ab)", "Femtobarn (fb)", "Picobarn (pb)", "Nanobarn (nb)", "Microbarn (μb)", "Millibarn (mb)", "Barn (b)", "Kilobarn (kb)", "Megabarn (Mb)", "Outhouse", "Shed", "Planck area", "[/Nuclear physics]", + "[Comparisons]", "Washington D.C.", "Isle of Wight", "Wales", "Texas", "[/Comparisons]", +]; + +const AREA_FACTOR = { // Multiples of a square metre + // Metric + "Square metre (sq m)": 1, + "Square kilometre (sq km)": 1e6, + + "Centiare (ca)": 1, + "Deciare (da)": 10, + "Are (a)": 100, + "Decare (daa)": 1e3, + "Hectare (ha)": 1e4, + + // Imperial + "Square inch (sq in)": 0.00064516, + "Square foot (sq ft)": 0.09290304, + "Square yard (sq yd)": 0.83612736, + "Square mile (sq mi)": 2589988.110336, + "Perch (sq per)": 42.21, + "Rood (ro)": 1011, + "International acre (ac)": 4046.8564224, + + // US customary units + "US survey acre (ac)": 4046.87261, + "US survey square mile (sq mi)": 2589998.470305239, + "US survey township": 93239944.9309886, + + // Nuclear physics + "Yoctobarn (yb)": 1e-52, + "Zeptobarn (zb)": 1e-49, + "Attobarn (ab)": 1e-46, + "Femtobarn (fb)": 1e-43, + "Picobarn (pb)": 1e-40, + "Nanobarn (nb)": 1e-37, + "Microbarn (μb)": 1e-34, + "Millibarn (mb)": 1e-31, + "Barn (b)": 1e-28, + "Kilobarn (kb)": 1e-25, + "Megabarn (Mb)": 1e-22, + + "Planck area": 2.6e-70, + "Shed": 1e-52, + "Outhouse": 1e-34, + + // Comparisons + "Washington D.C.": 176119191.502848, + "Isle of Wight": 380000000, + "Wales": 20779000000, + "Texas": 696241000000, +}; + + +export default ConvertArea; diff --git a/src/core/operations/ConvertDataUnits.mjs b/src/core/operations/ConvertDataUnits.mjs new file mode 100644 index 00000000..dc22e351 --- /dev/null +++ b/src/core/operations/ConvertDataUnits.mjs @@ -0,0 +1,111 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert data units operation + */ +class ConvertDataUnits extends Operation { + + /** + * ConvertDataUnits constructor + */ + constructor() { + super(); + + this.name = "Convert data units"; + this.module = "Default"; + this.description = "Converts a unit of data to another format."; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": DATA_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": DATA_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(DATA_FACTOR[inputUnits]); + return input.div(DATA_FACTOR[outputUnits]); + } + +} + +const DATA_UNITS = [ + "Bits (b)", "Nibbles", "Octets", "Bytes (B)", + "[Binary bits (2^n)]", "Kibibits (Kib)", "Mebibits (Mib)", "Gibibits (Gib)", "Tebibits (Tib)", "Pebibits (Pib)", "Exbibits (Eib)", "Zebibits (Zib)", "Yobibits (Yib)", "[/Binary bits (2^n)]", + "[Decimal bits (10^n)]", "Decabits", "Hectobits", "Kilobits (kb)", "Megabits (Mb)", "Gigabits (Gb)", "Terabits (Tb)", "Petabits (Pb)", "Exabits (Eb)", "Zettabits (Zb)", "Yottabits (Yb)", "[/Decimal bits (10^n)]", + "[Binary bytes (8 x 2^n)]", "Kibibytes (KiB)", "Mebibytes (MiB)", "Gibibytes (GiB)", "Tebibytes (TiB)", "Pebibytes (PiB)", "Exbibytes (EiB)", "Zebibytes (ZiB)", "Yobibytes (YiB)", "[/Binary bytes (8 x 2^n)]", + "[Decimal bytes (8 x 10^n)]", "Kilobytes (KB)", "Megabytes (MB)", "Gigabytes (GB)", "Terabytes (TB)", "Petabytes (PB)", "Exabytes (EB)", "Zettabytes (ZB)", "Yottabytes (YB)", "[/Decimal bytes (8 x 10^n)]" +]; + +const DATA_FACTOR = { // Multiples of a bit + "Bits (b)": 1, + "Nibbles": 4, + "Octets": 8, + "Bytes (B)": 8, + + // Binary bits (2^n) + "Kibibits (Kib)": 1024, + "Mebibits (Mib)": 1048576, + "Gibibits (Gib)": 1073741824, + "Tebibits (Tib)": 1099511627776, + "Pebibits (Pib)": 1125899906842624, + "Exbibits (Eib)": 1152921504606846976, + "Zebibits (Zib)": 1180591620717411303424, + "Yobibits (Yib)": 1208925819614629174706176, + + // Decimal bits (10^n) + "Decabits": 10, + "Hectobits": 100, + "Kilobits (Kb)": 1e3, + "Megabits (Mb)": 1e6, + "Gigabits (Gb)": 1e9, + "Terabits (Tb)": 1e12, + "Petabits (Pb)": 1e15, + "Exabits (Eb)": 1e18, + "Zettabits (Zb)": 1e21, + "Yottabits (Yb)": 1e24, + + // Binary bytes (8 x 2^n) + "Kibibytes (KiB)": 8192, + "Mebibytes (MiB)": 8388608, + "Gibibytes (GiB)": 8589934592, + "Tebibytes (TiB)": 8796093022208, + "Pebibytes (PiB)": 9007199254740992, + "Exbibytes (EiB)": 9223372036854775808, + "Zebibytes (ZiB)": 9444732965739290427392, + "Yobibytes (YiB)": 9671406556917033397649408, + + // Decimal bytes (8 x 10^n) + "Kilobytes (KB)": 8e3, + "Megabytes (MB)": 8e6, + "Gigabytes (GB)": 8e9, + "Terabytes (TB)": 8e12, + "Petabytes (PB)": 8e15, + "Exabytes (EB)": 8e18, + "Zettabytes (ZB)": 8e21, + "Yottabytes (YB)": 8e24, +}; + + +export default ConvertDataUnits; diff --git a/src/core/operations/ConvertDistance.mjs b/src/core/operations/ConvertDistance.mjs new file mode 100644 index 00000000..278a1c6d --- /dev/null +++ b/src/core/operations/ConvertDistance.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert distance operation + */ +class ConvertDistance extends Operation { + + /** + * ConvertDistance constructor + */ + constructor() { + super(); + + this.name = "Convert distance"; + this.module = "Default"; + this.description = "Converts a unit of distance to another format."; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": DISTANCE_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": DISTANCE_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(DISTANCE_FACTOR[inputUnits]); + return input.div(DISTANCE_FACTOR[outputUnits]); + } + +} + +const DISTANCE_UNITS = [ + "[Metric]", "Nanometres (nm)", "Micrometres (µm)", "Millimetres (mm)", "Centimetres (cm)", "Metres (m)", "Kilometers (km)", "[/Metric]", + "[Imperial]", "Thou (th)", "Inches (in)", "Feet (ft)", "Yards (yd)", "Chains (ch)", "Furlongs (fur)", "Miles (mi)", "Leagues (lea)", "[/Imperial]", + "[Maritime]", "Fathoms (ftm)", "Cables", "Nautical miles", "[/Maritime]", + "[Comparisons]", "Cars (4m)", "Buses (8.4m)", "American football fields (91m)", "Football pitches (105m)", "[/Comparisons]", + "[Astronomical]", "Earth-to-Moons", "Earth's equators", "Astronomical units (au)", "Light-years (ly)", "Parsecs (pc)", "[/Astronomical]", +]; + +const DISTANCE_FACTOR = { // Multiples of a metre + "Nanometres (nm)": 1e-9, + "Micrometres (µm)": 1e-6, + "Millimetres (mm)": 1e-3, + "Centimetres (cm)": 1e-2, + "Metres (m)": 1, + "Kilometers (km)": 1e3, + + "Thou (th)": 0.0000254, + "Inches (in)": 0.0254, + "Feet (ft)": 0.3048, + "Yards (yd)": 0.9144, + "Chains (ch)": 20.1168, + "Furlongs (fur)": 201.168, + "Miles (mi)": 1609.344, + "Leagues (lea)": 4828.032, + + "Fathoms (ftm)": 1.853184, + "Cables": 185.3184, + "Nautical miles": 1853.184, + + "Cars (4m)": 4, + "Buses (8.4m)": 8.4, + "American football fields (91m)": 91, + "Football pitches (105m)": 105, + + "Earth-to-Moons": 380000000, + "Earth's equators": 40075016.686, + "Astronomical units (au)": 149597870700, + "Light-years (ly)": 9460730472580800, + "Parsecs (pc)": 3.0856776e16 +}; + + +export default ConvertDistance; diff --git a/src/core/operations/ConvertMass.mjs b/src/core/operations/ConvertMass.mjs new file mode 100644 index 00000000..18c0c84a --- /dev/null +++ b/src/core/operations/ConvertMass.mjs @@ -0,0 +1,143 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert mass operation + */ +class ConvertMass extends Operation { + + /** + * ConvertMass constructor + */ + constructor() { + super(); + + this.name = "Convert mass"; + this.module = "Default"; + this.description = "Converts a unit of mass to another format."; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": MASS_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": MASS_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(MASS_FACTOR[inputUnits]); + return input.div(MASS_FACTOR[outputUnits]); + } + +} + + +const MASS_UNITS = [ + "[Metric]", "Yoctogram (yg)", "Zeptogram (zg)", "Attogram (ag)", "Femtogram (fg)", "Picogram (pg)", "Nanogram (ng)", "Microgram (μg)", "Milligram (mg)", "Centigram (cg)", "Decigram (dg)", "Gram (g)", "Decagram (dag)", "Hectogram (hg)", "Kilogram (kg)", "Megagram (Mg)", "Tonne (t)", "Gigagram (Gg)", "Teragram (Tg)", "Petagram (Pg)", "Exagram (Eg)", "Zettagram (Zg)", "Yottagram (Yg)", "[/Metric]", + "[Imperial Avoirdupois]", "Grain (gr)", "Dram (dr)", "Ounce (oz)", "Pound (lb)", "Nail", "Stone (st)", "Quarter (gr)", "Tod", "US hundredweight (cwt)", "Imperial hundredweight (cwt)", "US ton (t)", "Imperial ton (t)", "[/Imperial Avoirdupois]", + "[Imperial Troy]", "Grain (gr)", "Pennyweight (dwt)", "Troy dram (dr t)", "Troy ounce (oz t)", "Troy pound (lb t)", "Mark", "[/Imperial Troy]", + "[Archaic]", "Wey", "Wool wey", "Suffolk wey", "Wool sack", "Coal sack", "Load", "Last", "Flax or feather last", "Gunpowder last", "Picul", "Rice last", "[/Archaic]", + "[Comparisons]", "Big Ben (14 tonnes)", "Blue whale (180 tonnes)", "International Space Station (417 tonnes)", "Space Shuttle (2,041 tonnes)", "RMS Titanic (52,000 tonnes)", "Great Pyramid of Giza (6,000,000 tonnes)", "Earth's oceans (1.4 yottagrams)", "[/Comparisons]", + "[Astronomical]", "A teaspoon of neutron star (5,500 million tonnes)", "Lunar mass (ML)", "Earth mass (M⊕)", "Jupiter mass (MJ)", "Solar mass (M☉)", "Sagittarius A* (7.5 x 10^36 kgs-ish)", "Milky Way galaxy (1.2 x 10^42 kgs)", "The observable universe (1.45 x 10^53 kgs)", "[/Astronomical]", +]; + +const MASS_FACTOR = { // Multiples of a gram + // Metric + "Yoctogram (yg)": 1e-24, + "Zeptogram (zg)": 1e-21, + "Attogram (ag)": 1e-18, + "Femtogram (fg)": 1e-15, + "Picogram (pg)": 1e-12, + "Nanogram (ng)": 1e-9, + "Microgram (μg)": 1e-6, + "Milligram (mg)": 1e-3, + "Centigram (cg)": 1e-2, + "Decigram (dg)": 1e-1, + "Gram (g)": 1, + "Decagram (dag)": 10, + "Hectogram (hg)": 100, + "Kilogram (kg)": 1000, + "Megagram (Mg)": 1e6, + "Tonne (t)": 1e6, + "Gigagram (Gg)": 1e9, + "Teragram (Tg)": 1e12, + "Petagram (Pg)": 1e15, + "Exagram (Eg)": 1e18, + "Zettagram (Zg)": 1e21, + "Yottagram (Yg)": 1e24, + + // Imperial Avoirdupois + "Grain (gr)": 64.79891e-3, + "Dram (dr)": 1.7718451953125, + "Ounce (oz)": 28.349523125, + "Pound (lb)": 453.59237, + "Nail": 3175.14659, + "Stone (st)": 6.35029318e3, + "Quarter (gr)": 12700.58636, + "Tod": 12700.58636, + "US hundredweight (cwt)": 45.359237e3, + "Imperial hundredweight (cwt)": 50.80234544e3, + "US ton (t)": 907.18474e3, + "Imperial ton (t)": 1016.0469088e3, + + // Imperial Troy + "Pennyweight (dwt)": 1.55517384, + "Troy dram (dr t)": 3.8879346, + "Troy ounce (oz t)": 31.1034768, + "Troy pound (lb t)": 373.2417216, + "Mark": 248.8278144, + + // Archaic + "Wey": 76.5e3, + "Wool wey": 101.7e3, + "Suffolk wey": 161.5e3, + "Wool sack": 153000, + "Coal sack": 50.80234544e3, + "Load": 918000, + "Last": 1836000, + "Flax or feather last": 770e3, + "Gunpowder last": 1090e3, + "Picul": 60.478982e3, + "Rice last": 1200e3, + + // Comparisons + "Big Ben (14 tonnes)": 14e6, + "Blue whale (180 tonnes)": 180e6, + "International Space Station (417 tonnes)": 417e6, + "Space Shuttle (2,041 tonnes)": 2041e6, + "RMS Titanic (52,000 tonnes)": 52000e6, + "Great Pyramid of Giza (6,000,000 tonnes)": 6e12, + "Earth's oceans (1.4 yottagrams)": 1.4e24, + + // Astronomical + "A teaspoon of neutron star (5,500 million tonnes)": 5.5e15, + "Lunar mass (ML)": 7.342e25, + "Earth mass (M⊕)": 5.97219e27, + "Jupiter mass (MJ)": 1.8981411476999997e30, + "Solar mass (M☉)": 1.98855e33, + "Sagittarius A* (7.5 x 10^36 kgs-ish)": 7.5e39, + "Milky Way galaxy (1.2 x 10^42 kgs)": 1.2e45, + "The observable universe (1.45 x 10^53 kgs)": 1.45e56, +}; + + +export default ConvertMass; diff --git a/src/core/operations/ConvertSpeed.mjs b/src/core/operations/ConvertSpeed.mjs new file mode 100644 index 00000000..71c43a54 --- /dev/null +++ b/src/core/operations/ConvertSpeed.mjs @@ -0,0 +1,96 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Convert speed operation + */ +class ConvertSpeed extends Operation { + + /** + * ConvertSpeed constructor + */ + constructor() { + super(); + + this.name = "Convert speed"; + this.module = "Default"; + this.description = "Converts a unit of speed to another format."; + this.inputType = "BigNumber"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": SPEED_UNITS + }, + { + "name": "Output units", + "type": "option", + "value": SPEED_UNITS + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const [inputUnits, outputUnits] = args; + + input = input.times(SPEED_FACTOR[inputUnits]); + return input.div(SPEED_FACTOR[outputUnits]); + } + +} + +const SPEED_UNITS = [ + "[Metric]", "Metres per second (m/s)", "Kilometres per hour (km/h)", "[/Metric]", + "[Imperial]", "Miles per hour (mph)", "Knots (kn)", "[/Imperial]", + "[Comparisons]", "Human hair growth rate", "Bamboo growth rate", "World's fastest snail", "Usain Bolt's top speed", "Jet airliner cruising speed", "Concorde", "SR-71 Blackbird", "Space Shuttle", "International Space Station", "[/Comparisons]", + "[Scientific]", "Sound in standard atmosphere", "Sound in water", "Lunar escape velocity", "Earth escape velocity", "Earth's solar orbit", "Solar system's Milky Way orbit", "Milky Way relative to the cosmic microwave background", "Solar escape velocity", "Neutron star escape velocity (0.3c)", "Light in a diamond (0.4136c)", "Signal in an optical fibre (0.667c)", "Light (c)", "[/Scientific]", +]; + +const SPEED_FACTOR = { // Multiples of m/s + // Metric + "Metres per second (m/s)": 1, + "Kilometres per hour (km/h)": 0.2778, + + // Imperial + "Miles per hour (mph)": 0.44704, + "Knots (kn)": 0.5144, + + // Comparisons + "Human hair growth rate": 4.8e-9, + "Bamboo growth rate": 1.4e-5, + "World's fastest snail": 0.00275, + "Usain Bolt's top speed": 12.42, + "Jet airliner cruising speed": 250, + "Concorde": 603, + "SR-71 Blackbird": 981, + "Space Shuttle": 1400, + "International Space Station": 7700, + + // Scientific + "Sound in standard atmosphere": 340.3, + "Sound in water": 1500, + "Lunar escape velocity": 2375, + "Earth escape velocity": 11200, + "Earth's solar orbit": 29800, + "Solar system's Milky Way orbit": 200000, + "Milky Way relative to the cosmic microwave background": 552000, + "Solar escape velocity": 617700, + "Neutron star escape velocity (0.3c)": 100000000, + "Light in a diamond (0.4136c)": 124000000, + "Signal in an optical fibre (0.667c)": 200000000, + "Light (c)": 299792458, +}; + + +export default ConvertSpeed; diff --git a/src/core/operations/CountOccurrences.mjs b/src/core/operations/CountOccurrences.mjs new file mode 100644 index 00000000..5027a0f0 --- /dev/null +++ b/src/core/operations/CountOccurrences.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Count occurrences operation + */ +class CountOccurrences extends Operation { + + /** + * CountOccurrences constructor + */ + constructor() { + super(); + + this.name = "Count occurrences"; + this.module = "Default"; + this.description = "Counts the number of times the provided string occurs in the input."; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + "name": "Search string", + "type": "toggleString", + "value": "", + "toggleValues": ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + let search = args[0].string; + const type = args[0].option; + + if (type === "Regex" && search) { + try { + const regex = new RegExp(search, "gi"), + matches = input.match(regex); + return matches.length; + } catch (err) { + return 0; + } + } else if (search) { + if (type.indexOf("Extended") === 0) { + search = Utils.parseEscapedChars(search); + } + return input.count(search); + } else { + return 0; + } + } + +} + +export default CountOccurrences; diff --git a/src/core/operations/DESDecrypt.mjs b/src/core/operations/DESDecrypt.mjs new file mode 100644 index 00000000..b1d0d8c7 --- /dev/null +++ b/src/core/operations/DESDecrypt.mjs @@ -0,0 +1,92 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * DES Decrypt operation + */ +class DESDecrypt extends Operation { + + /** + * DESDecrypt constructor + */ + constructor() { + super(); + + this.name = "DES Decrypt"; + this.module = "Ciphers"; + this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.

Key: DES uses a key length of 8 bytes (64 bits).
Triple DES uses a key length of 24 bytes (192 bits).

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length !== 8) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +DES uses a key length of 8 bytes (64 bits). +Triple DES uses a key length of 24 bytes (192 bits).`); + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("DES-" + mode, key); + decipher.start({iv: iv}); + decipher.update(forge.util.createBuffer(input)); + const result = decipher.finish(); + + if (result) { + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } else { + throw new OperationError("Unable to decrypt input with these parameters."); + } + } + +} + +export default DESDecrypt; diff --git a/src/core/operations/DESEncrypt.mjs b/src/core/operations/DESEncrypt.mjs new file mode 100644 index 00000000..17a2abcf --- /dev/null +++ b/src/core/operations/DESEncrypt.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * DES Encrypt operation + */ +class DESEncrypt extends Operation { + + /** + * DESEncrypt constructor + */ + constructor() { + super(); + + this.name = "DES Encrypt"; + this.module = "Ciphers"; + this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.

Key: DES uses a key length of 8 bytes (64 bits).
Triple DES uses a key length of 24 bytes (192 bits).

You can generate a password-based key using one of the KDF operations.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + [,, mode, inputType, outputType] = args; + + if (key.length !== 8) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +DES uses a key length of 8 bytes (64 bits). +Triple DES uses a key length of 24 bytes (192 bits).`); + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("DES-" + mode, key); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); + } + +} + +export default DESEncrypt; diff --git a/src/core/operations/DateTime.js b/src/core/operations/DateTime.js deleted file mode 100755 index b117a2ca..00000000 --- a/src/core/operations/DateTime.js +++ /dev/null @@ -1,484 +0,0 @@ -import moment from "moment-timezone"; - - -/** - * Date and time operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const DateTime = { - - /** - * @constant - * @default - */ - UNITS: ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"], - - /** - * From UNIX Timestamp operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {string} - */ - runFromUnixTimestamp: function(input, args) { - let units = args[0], - d; - - input = parseFloat(input); - - if (units === "Seconds (s)") { - d = moment.unix(input); - return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss") + " UTC"; - } else if (units === "Milliseconds (ms)") { - d = moment(input); - return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; - } else if (units === "Microseconds (μs)") { - d = moment(input / 1000); - return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; - } else if (units === "Nanoseconds (ns)") { - d = moment(input / 1000000); - return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; - } else { - throw "Unrecognised unit"; - } - }, - - - /** - * @constant - * @default - */ - TREAT_AS_UTC: true, - - /** - * To UNIX Timestamp operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToUnixTimestamp: function(input, args) { - const units = args[0], - treatAsUTC = args[1], - showDateTime = args[2], - d = treatAsUTC ? moment.utc(input) : moment(input); - - let result = ""; - - if (units === "Seconds (s)") { - result = d.unix(); - } else if (units === "Milliseconds (ms)") { - result = d.valueOf(); - } else if (units === "Microseconds (μs)") { - result = d.valueOf() * 1000; - } else if (units === "Nanoseconds (ns)") { - 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(); - }, - - - /** - * @constant - * @default - */ - DATETIME_FORMATS: [ - { - name: "Standard date and time", - value: "DD/MM/YYYY HH:mm:ss" - }, - { - name: "American-style date and time", - value: "MM/DD/YYYY HH:mm:ss" - }, - { - name: "International date and time", - value: "YYYY-MM-DD HH:mm:ss" - }, - { - name: "Verbose date and time", - value: "dddd Do MMMM YYYY HH:mm:ss Z z" - }, - { - name: "UNIX timestamp (seconds)", - value: "X" - }, - { - name: "UNIX timestamp offset (milliseconds)", - value: "x" - }, - { - name: "Automatic", - value: "" - }, - ], - /** - * @constant - * @default - */ - INPUT_FORMAT_STRING: "DD/MM/YYYY HH:mm:ss", - /** - * @constant - * @default - */ - OUTPUT_FORMAT_STRING: "dddd Do MMMM YYYY HH:mm:ss Z z", - /** - * @constant - * @default - */ - TIMEZONES: ["UTC"].concat(moment.tz.names()), - - /** - * Translate DateTime Format operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runTranslateFormat: function(input, args) { - let inputFormat = args[1], - inputTimezone = args[2], - outputFormat = args[3], - outputTimezone = args[4], - date; - - try { - date = moment.tz(input, inputFormat, inputTimezone); - if (!date || date.format() === "Invalid date") throw Error; - } catch (err) { - return "Invalid format.\n\n" + DateTime.FORMAT_EXAMPLES; - } - - return date.tz(outputTimezone).format(outputFormat); - }, - - - /** - * Parse DateTime operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runParse: function(input, args) { - let inputFormat = args[1], - inputTimezone = args[2], - date, - output = ""; - - try { - date = moment.tz(input, inputFormat, inputTimezone); - if (!date || date.format() === "Invalid date") throw Error; - } catch (err) { - return "Invalid format.\n\n" + DateTime.FORMAT_EXAMPLES; - } - - output += "Date: " + date.format("dddd Do MMMM YYYY") + - "\nTime: " + date.format("HH:mm:ss") + - "\nPeriod: " + date.format("A") + - "\nTimezone: " + date.format("z") + - "\nUTC offset: " + date.format("ZZ") + - "\n\nDaylight Saving Time: " + date.isDST() + - "\nLeap year: " + date.isLeapYear() + - "\nDays in this month: " + date.daysInMonth() + - "\n\nDay of year: " + date.dayOfYear() + - "\nWeek number: " + date.weekYear() + - "\nQuarter: " + date.quarter(); - - return output; - }, - - - /** - * Sleep operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {ArrayBuffer} - */ - runSleep: async function(input, args) { - const ms = args[0]; - await new Promise(r => setTimeout(r, ms)); - return input; - }, - - - /** - * @constant - */ - FORMAT_EXAMPLES: `Format string tokens: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryTokenOutput
MonthM1 2 ... 11 12
Mo1st 2nd ... 11th 12th
MM01 02 ... 11 12
MMMJan Feb ... Nov Dec
MMMMJanuary February ... November December
QuarterQ1 2 3 4
Day of MonthD1 2 ... 30 31
Do1st 2nd ... 30th 31st
DD01 02 ... 30 31
Day of YearDDD1 2 ... 364 365
DDDo1st 2nd ... 364th 365th
DDDD001 002 ... 364 365
Day of Weekd0 1 ... 5 6
do0th 1st ... 5th 6th
ddSu Mo ... Fr Sa
dddSun Mon ... Fri Sat
ddddSunday Monday ... Friday Saturday
Day of Week (Locale)e0 1 ... 5 6
Day of Week (ISO)E1 2 ... 6 7
Week of Yearw1 2 ... 52 53
wo1st 2nd ... 52nd 53rd
ww01 02 ... 52 53
Week of Year (ISO)W1 2 ... 52 53
Wo1st 2nd ... 52nd 53rd
WW01 02 ... 52 53
YearYY70 71 ... 29 30
YYYY1970 1971 ... 2029 2030
Week Yeargg70 71 ... 29 30
gggg1970 1971 ... 2029 2030
Week Year (ISO)GG70 71 ... 29 30
GGGG1970 1971 ... 2029 2030
AM/PMAAM PM
aam pm
HourH0 1 ... 22 23
HH00 01 ... 22 23
h1 2 ... 11 12
hh01 02 ... 11 12
Minutem0 1 ... 58 59
mm00 01 ... 58 59
Seconds0 1 ... 58 59
ss00 01 ... 58 59
Fractional SecondS0 1 ... 8 9
SS00 01 ... 98 99
SSS000 001 ... 998 999
SSSS ... SSSSSSSSS000[0..] 001[0..] ... 998[0..] 999[0..]
Timezonez or zzEST CST ... MST PST
Z-07:00 -06:00 ... +06:00 +07:00
ZZ-0700 -0600 ... +0600 +0700
Unix TimestampX1360013296
Unix Millisecond Timestampx1360013296123
`, - -}; - -export default DateTime; diff --git a/src/core/operations/DecodeNetBIOSName.mjs b/src/core/operations/DecodeNetBIOSName.mjs new file mode 100644 index 00000000..19624889 --- /dev/null +++ b/src/core/operations/DecodeNetBIOSName.mjs @@ -0,0 +1,59 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Decode NetBIOS Name operation + */ +class DecodeNetBIOSName extends Operation { + + /** + * DecodeNetBIOSName constructor + */ + constructor() { + super(); + + this.name = "Decode NetBIOS Name"; + this.module = "Default"; + this.description = "NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.

There are two levels of encoding. The first level maps a NetBIOS name into a domain system name. The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.

This operation decodes the first level of encoding. See RFC 1001 for full details."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Offset", + "type": "number", + "value": 65 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = [], + offset = args[0]; + + if (input.length <= 32 && (input.length % 2) === 0) { + for (let i = 0; i < input.length; i += 2) { + output.push((((input[i] & 0xff) - offset) << 4) | + (((input[i + 1] & 0xff) - offset) & 0xf)); + } + for (let i = output.length - 1; i > 0; i--) { + if (output[i] === 32) output.splice(i, i); + else break; + } + } + + return output; + } + +} + +export default DecodeNetBIOSName; diff --git a/src/core/operations/DecodeText.mjs b/src/core/operations/DecodeText.mjs new file mode 100644 index 00000000..9e51199f --- /dev/null +++ b/src/core/operations/DecodeText.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import cptable from "../vendor/js-codepage/cptable.js"; +import {IO_FORMAT} from "../lib/ChrEnc"; + +/** + * Decode text operation + */ +class DecodeText extends Operation { + + /** + * DecodeText constructor + */ + constructor() { + super(); + + this.name = "Decode text"; + this.module = "CharEnc"; + this.description = [ + "Decodes text from the chosen character encoding.", + "

", + "Supported charsets are:", + "
    ", + Object.keys(IO_FORMAT).map(e => `
  • ${e}
  • `).join("\n"), + "
", + ].join("\n"); + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": Object.keys(IO_FORMAT) + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const format = IO_FORMAT[args[0]]; + return cptable.utils.decode(format, input); + } + +} + +export default DecodeText; diff --git a/src/core/operations/DeriveEVPKey.mjs b/src/core/operations/DeriveEVPKey.mjs new file mode 100644 index 00000000..ad17f0b1 --- /dev/null +++ b/src/core/operations/DeriveEVPKey.mjs @@ -0,0 +1,144 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import CryptoJS from "crypto-js"; + +/** + * Derive EVP key operation + */ +class DeriveEVPKey extends Operation { + + /** + * DeriveEVPKey constructor + */ + constructor() { + super(); + + this.name = "Derive EVP key"; + this.module = "Ciphers"; + this.description = "EVP is a password-based key derivation function (PBKDF) used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

If you leave the salt argument empty, a random salt will be generated."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Passphrase", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "Latin1", "Hex", "Base64"] + }, + { + "name": "Key size", + "type": "number", + "value": 128 + }, + { + "name": "Iterations", + "type": "number", + "value": 1 + }, + { + "name": "Hashing function", + "type": "option", + "value": ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"] + }, + { + "name": "Salt", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const passphrase = Utils.convertToByteString(args[0].string, args[0].option), + keySize = args[1] / 32, + iterations = args[2], + hasher = args[3], + salt = Utils.convertToByteString(args[4].string, args[4].option), + key = CryptoJS.EvpKDF(passphrase, salt, { + keySize: keySize, + hasher: CryptoJS.algo[hasher], + iterations: iterations, + }); + + return key.toString(CryptoJS.enc.Hex); + } + +} + +export default DeriveEVPKey; + +/** + * Overwriting the CryptoJS OpenSSL key derivation function so that it is possible to not pass a + * salt in. + + * @param {string} password - The password to derive from. + * @param {number} keySize - The size in words of the key to generate. + * @param {number} ivSize - The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be + * generated randomly. If set to false, no salt will be added. + * + * @returns {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * // Randomly generates a salt + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * // Uses the salt 'saltsalt' + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + * // Does not use a salt + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, false); + */ +CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) { + // Generate random salt if no salt specified and not set to false + // This line changed from `if (!salt) {` to the following + if (salt === undefined || salt === null) { + salt = CryptoJS.lib.WordArray.random(64/8); + } + + // Derive key and IV + const key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + const iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt }); +}; + + +/** + * Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse + * the hex string. + * + * @param {string} hexStr + * @returns {CryptoJS.lib.WordArray} + */ +CryptoJS.enc.Hex.parse = function (hexStr) { + // Remove whitespace + hexStr = hexStr.replace(/\s/g, ""); + + // Shortcut + const hexStrLength = hexStr.length; + + // Convert + const words = []; + for (let i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2); +}; diff --git a/src/core/operations/DerivePBKDF2Key.mjs b/src/core/operations/DerivePBKDF2Key.mjs new file mode 100644 index 00000000..d3f7fe9a --- /dev/null +++ b/src/core/operations/DerivePBKDF2Key.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * Derive PBKDF2 key operation + */ +class DerivePBKDF2Key extends Operation { + + /** + * DerivePBKDF2Key constructor + */ + constructor() { + super(); + + this.name = "Derive PBKDF2 key"; + this.module = "Ciphers"; + this.description = "PBKDF2 is a password-based key derivation function. It is part of RSA Laboratories' Public-Key Cryptography Standards (PKCS) series, specifically PKCS #5 v2.0, also published as Internet Engineering Task Force's RFC 2898.

In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

If you leave the salt argument empty, a random salt will be generated."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Passphrase", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "Latin1", "Hex", "Base64"] + }, + { + "name": "Key size", + "type": "number", + "value": 128 + }, + { + "name": "Iterations", + "type": "number", + "value": 1 + }, + { + "name": "Hashing function", + "type": "option", + "value": ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"] + }, + { + "name": "Salt", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const passphrase = Utils.convertToByteString(args[0].string, args[0].option), + keySize = args[1], + iterations = args[2], + hasher = args[3], + salt = Utils.convertToByteString(args[4].string, args[4].option) || + forge.random.getBytesSync(keySize), + derivedKey = forge.pkcs5.pbkdf2(passphrase, salt, iterations, keySize / 8, hasher.toLowerCase()); + + return forge.util.bytesToHex(derivedKey); + } + +} + +export default DerivePBKDF2Key; diff --git a/src/core/operations/DetectFileType.mjs b/src/core/operations/DetectFileType.mjs new file mode 100644 index 00000000..ddf9a2de --- /dev/null +++ b/src/core/operations/DetectFileType.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Magic from "../lib/Magic"; + +/** + * Detect File Type operation + */ +class DetectFileType extends Operation { + + /** + * DetectFileType constructor + */ + constructor() { + super(); + + this.name = "Detect File Type"; + this.module = "Default"; + this.description = "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.

Currently supports the following file types: 7z, amr, avi, bmp, bz2, class, cr2, crx, dex, dmg, doc, elf, eot, epub, exe, flac, flv, gif, gz, ico, iso, jpg, jxr, m4a, m4v, mid, mkv, mov, mp3, mp4, mpg, ogg, otf, pdf, png, ppt, ps, psd, rar, rtf, sqlite, swf, tar, tar.z, tif, ttf, utf8, vmdk, wav, webm, webp, wmv, woff, woff2, xls, xz, zip."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = new Uint8Array(input), + type = Magic.magicFileType(data); + + if (!type) { + return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; + } else { + let output = "File extension: " + type.ext + "\n" + + "MIME type: " + type.mime; + + if (type.desc && type.desc.length) { + output += "\nDescription: " + type.desc; + } + + return output; + } + } + +} + +export default DetectFileType; diff --git a/src/core/operations/Diff.js b/src/core/operations/Diff.js deleted file mode 100644 index afa00c37..00000000 --- a/src/core/operations/Diff.js +++ /dev/null @@ -1,94 +0,0 @@ -import Utils from "../Utils.js"; -import * as JsDiff from "diff"; - - -/** - * Diff operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Diff = { - - /** - * @constant - * @default - */ - DIFF_SAMPLE_DELIMITER: "\\n\\n", - /** - * @constant - * @default - */ - DIFF_BY: ["Character", "Word", "Line", "Sentence", "CSS", "JSON"], - - /** - * Diff operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runDiff: function(input, args) { - let sampleDelim = args[0], - diffBy = args[1], - showAdded = args[2], - showRemoved = args[3], - ignoreWhitespace = args[4], - samples = input.split(sampleDelim), - output = "", - diff; - - if (!samples || samples.length !== 2) { - return "Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?"; - } - - switch (diffBy) { - case "Character": - diff = JsDiff.diffChars(samples[0], samples[1]); - break; - case "Word": - if (ignoreWhitespace) { - diff = JsDiff.diffWords(samples[0], samples[1]); - } else { - diff = JsDiff.diffWordsWithSpace(samples[0], samples[1]); - } - break; - case "Line": - if (ignoreWhitespace) { - diff = JsDiff.diffTrimmedLines(samples[0], samples[1]); - } else { - diff = JsDiff.diffLines(samples[0], samples[1]); - } - break; - case "Sentence": - diff = JsDiff.diffSentences(samples[0], samples[1]); - break; - case "CSS": - diff = JsDiff.diffCss(samples[0], samples[1]); - break; - case "JSON": - diff = JsDiff.diffJson(samples[0], samples[1]); - break; - default: - return "Invalid 'Diff by' option."; - } - - for (let i = 0; i < diff.length; i++) { - if (diff[i].added) { - if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; - } else if (diff[i].removed) { - if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; - } else { - output += Utils.escapeHtml(diff[i].value); - } - } - - return output; - }, - -}; - -export default Diff; diff --git a/src/core/operations/Diff.mjs b/src/core/operations/Diff.mjs new file mode 100644 index 00000000..1fd6e5d8 --- /dev/null +++ b/src/core/operations/Diff.mjs @@ -0,0 +1,128 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import * as JsDiff from "diff"; +import OperationError from "../errors/OperationError"; + +/** + * Diff operation + */ +class Diff extends Operation { + + /** + * Diff constructor + */ + constructor() { + super(); + + this.name = "Diff"; + this.module = "Diff"; + this.description = "Compares two inputs (separated by the specified delimiter) and highlights the differences between them."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Sample delimiter", + "type": "binaryString", + "value": "\\n\\n" + }, + { + "name": "Diff by", + "type": "option", + "value": ["Character", "Word", "Line", "Sentence", "CSS", "JSON"] + }, + { + "name": "Show added", + "type": "boolean", + "value": true + }, + { + "name": "Show removed", + "type": "boolean", + "value": true + }, + { + "name": "Ignore whitespace", + "type": "boolean", + "value": false, + "hint": "Relevant for word and line" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [ + sampleDelim, + diffBy, + showAdded, + showRemoved, + ignoreWhitespace + ] = args, + samples = input.split(sampleDelim); + let output = "", + diff; + + // Node and Webpack load modules slightly differently + const jsdiff = JsDiff.default ? JsDiff.default : JsDiff; + + if (!samples || samples.length !== 2) { + throw new OperationError("Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?"); + } + + switch (diffBy) { + case "Character": + diff = jsdiff.diffChars(samples[0], samples[1]); + break; + case "Word": + if (ignoreWhitespace) { + diff = jsdiff.diffWords(samples[0], samples[1]); + } else { + diff = jsdiff.diffWordsWithSpace(samples[0], samples[1]); + } + break; + case "Line": + if (ignoreWhitespace) { + diff = jsdiff.diffTrimmedLines(samples[0], samples[1]); + } else { + diff = jsdiff.diffLines(samples[0], samples[1]); + } + break; + case "Sentence": + diff = jsdiff.diffSentences(samples[0], samples[1]); + break; + case "CSS": + diff = jsdiff.diffCss(samples[0], samples[1]); + break; + case "JSON": + diff = jsdiff.diffJson(samples[0], samples[1]); + break; + default: + throw new OperationError("Invalid 'Diff by' option."); + } + + for (let i = 0; i < diff.length; i++) { + if (diff[i].added) { + if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; + } else if (diff[i].removed) { + if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; + } else { + output += Utils.escapeHtml(diff[i].value); + } + } + + return output; + } + +} + +export default Diff; diff --git a/src/core/operations/DisassembleX86.mjs b/src/core/operations/DisassembleX86.mjs new file mode 100644 index 00000000..c64039bb --- /dev/null +++ b/src/core/operations/DisassembleX86.mjs @@ -0,0 +1,133 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import * as disassemble from "../vendor/DisassembleX86-64"; +import OperationError from "../errors/OperationError"; + +/** + * Disassemble x86 operation + */ +class DisassembleX86 extends Operation { + + /** + * DisassembleX86 constructor + */ + constructor() { + super(); + + this.name = "Disassemble x86"; + this.module = "Shellcode"; + this.description = "Disassembly is the process of translating machine language into assembly language.

This operation supports 64-bit, 32-bit and 16-bit code written for Intel or AMD x86 processors. It is particularly useful for reverse engineering shellcode.

Input should be in hexadecimal."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Bit mode", + "type": "option", + "value": ["64", "32", "16"] + }, + { + "name": "Compatibility", + "type": "option", + "value": [ + "Full x86 architecture", + "Knights Corner", + "Larrabee", + "Cyrix", + "Geode", + "Centaur", + "X86/486" + ] + }, + { + "name": "Code Segment (CS)", + "type": "number", + "value": 16 + }, + { + "name": "Offset (IP)", + "type": "number", + "value": 0 + }, + { + "name": "Show instruction hex", + "type": "boolean", + "value": true + }, + { + "name": "Show instruction position", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid mode value + */ + run(input, args) { + const [ + mode, + compatibility, + codeSegment, + offset, + showInstructionHex, + showInstructionPos + ] = args; + + switch (mode) { + case "64": + disassemble.setBitMode(2); + break; + case "32": + disassemble.setBitMode(1); + break; + case "16": + disassemble.setBitMode(0); + break; + default: + throw new OperationError("Invalid mode value"); + } + + switch (compatibility) { + case "Full x86 architecture": + disassemble.CompatibilityMode(0); + break; + case "Knights Corner": + disassemble.CompatibilityMode(1); + break; + case "Larrabee": + disassemble.CompatibilityMode(2); + break; + case "Cyrix": + disassemble.CompatibilityMode(3); + break; + case "Geode": + disassemble.CompatibilityMode(4); + break; + case "Centaur": + disassemble.CompatibilityMode(5); + break; + case "X86/486": + disassemble.CompatibilityMode(6); + break; + } + + disassemble.SetBasePosition(codeSegment + ":" + offset); + disassemble.setShowInstructionHex(showInstructionHex); + disassemble.setShowInstructionPos(showInstructionPos); + disassemble.LoadBinCode(input.replace(/\s/g, "")); + return disassemble.LDisassemble(); + } + +} + +export default DisassembleX86; diff --git a/src/core/operations/Divide.mjs b/src/core/operations/Divide.mjs new file mode 100644 index 00000000..76f50313 --- /dev/null +++ b/src/core/operations/Divide.mjs @@ -0,0 +1,51 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { div, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Divide operation + */ +class Divide extends Operation { + + /** + * Divide constructor + */ + constructor() { + super(); + + this.name = "Divide"; + this.module = "Default"; + this.description = "Divides a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 2.5"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = div(createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + } + +} + +export default Divide; diff --git a/src/core/operations/DropBytes.mjs b/src/core/operations/DropBytes.mjs new file mode 100644 index 00000000..e697f4e9 --- /dev/null +++ b/src/core/operations/DropBytes.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Drop bytes operation + */ +class DropBytes extends Operation { + + /** + * DropBytes constructor + */ + constructor() { + super(); + + this.name = "Drop bytes"; + this.module = "Default"; + this.description = "Cuts a slice of the specified number of bytes out of the data."; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Start", + "type": "number", + "value": 0 + }, + { + "name": "Length", + "type": "number", + "value": 5 + }, + { + "name": "Apply to each line", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + * + * @throws {OperationError} if invalid input + */ + run(input, args) { + const start = args[0], + length = args[1], + applyToEachLine = args[2]; + + if (start < 0 || length < 0) + throw new OperationError("Error: Invalid value"); + + if (!applyToEachLine) { + const left = input.slice(0, start), + right = input.slice(start + length, input.byteLength); + const result = new Uint8Array(left.byteLength + right.byteLength); + result.set(new Uint8Array(left), 0); + result.set(new Uint8Array(right), left.byteLength); + return result.buffer; + } + + // Split input into lines + const data = new Uint8Array(input); + const lines = []; + let line = [], + i; + + for (i = 0; i < data.length; i++) { + if (data[i] === 0x0a) { + lines.push(line); + line = []; + } else { + line.push(data[i]); + } + } + lines.push(line); + + let output = []; + for (i = 0; i < lines.length; i++) { + output = output.concat(lines[i].slice(0, start).concat(lines[i].slice(start+length, lines[i].length))); + output.push(0x0a); + } + return new Uint8Array(output.slice(0, output.length-1)).buffer; + } + +} + +export default DropBytes; diff --git a/src/core/operations/EncodeNetBIOSName.mjs b/src/core/operations/EncodeNetBIOSName.mjs new file mode 100644 index 00000000..608f4bfd --- /dev/null +++ b/src/core/operations/EncodeNetBIOSName.mjs @@ -0,0 +1,59 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Encode NetBIOS Name operation + */ +class EncodeNetBIOSName extends Operation { + + /** + * EncodeNetBIOSName constructor + */ + constructor() { + super(); + + this.name = "Encode NetBIOS Name"; + this.module = "Default"; + this.description = "NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.

There are two levels of encoding. The first level maps a NetBIOS name into a domain system name. The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.

This operation carries out the first level of encoding. See RFC 1001 for full details."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Offset", + "type": "number", + "value": 65 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = [], + offset = args[0]; + + if (input.length <= 16) { + const len = input.length; + input.length = 16; + input.fill(32, len, 16); + for (let i = 0; i < input.length; i++) { + output.push((input[i] >> 4) + offset); + output.push((input[i] & 0xf) + offset); + } + } + + return output; + + } + +} + +export default EncodeNetBIOSName; diff --git a/src/core/operations/EncodeText.mjs b/src/core/operations/EncodeText.mjs new file mode 100644 index 00000000..6786be56 --- /dev/null +++ b/src/core/operations/EncodeText.mjs @@ -0,0 +1,58 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import cptable from "../vendor/js-codepage/cptable.js"; +import {IO_FORMAT} from "../lib/ChrEnc"; + +/** + * Encode text operation + */ +class EncodeText extends Operation { + + /** + * EncodeText constructor + */ + constructor() { + super(); + + this.name = "Encode text"; + this.module = "CharEnc"; + this.description = [ + "Encodes text into the chosen character encoding.", + "

", + "Supported charsets are:", + "
    ", + Object.keys(IO_FORMAT).map(e => `
  • ${e}
  • `).join("\n"), + "
", + ].join("\n"); + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": Object.keys(IO_FORMAT) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const format = IO_FORMAT[args[0]]; + let encoded = cptable.utils.encode(format, input); + encoded = Array.from(encoded); + return encoded; + } + +} + + +export default EncodeText; diff --git a/src/core/operations/Endian.js b/src/core/operations/Endian.js deleted file mode 100755 index bb0f9136..00000000 --- a/src/core/operations/Endian.js +++ /dev/null @@ -1,99 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Endian operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Endian = { - - /** - * @constant - * @default - */ - DATA_FORMAT: ["Hex", "Raw"], - /** - * @constant - * @default - */ - WORD_LENGTH: 4, - /** - * @constant - * @default - */ - PAD_INCOMPLETE_WORDS: true, - - /** - * Swap endianness operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSwapEndianness: function(input, args) { - let dataFormat = args[0], - wordLength = args[1], - padIncompleteWords = args[2], - data = [], - result = [], - words = [], - i = 0, - j = 0; - - if (wordLength <= 0) { - return "Word length must be greater than 0"; - } - - // Convert input to raw data based on specified data format - switch (dataFormat) { - case "Hex": - data = Utils.fromHex(input); - break; - case "Raw": - data = Utils.strToByteArray(input); - break; - default: - data = input; - } - - // Split up into words - for (i = 0; i < data.length; i += wordLength) { - const word = data.slice(i, i + wordLength); - - // Pad word if too short - if (padIncompleteWords && word.length < wordLength){ - for (j = word.length; j < wordLength; j++) { - word.push(0); - } - } - - words.push(word); - } - - // Swap endianness and flatten - for (i = 0; i < words.length; i++) { - j = words[i].length; - while (j--) { - result.push(words[i][j]); - } - } - - // Convert data back to specified data format - switch (dataFormat) { - case "Hex": - return Utils.toHex(result); - case "Raw": - return Utils.byteArrayToUtf8(result); - default: - return result; - } - }, - -}; - -export default Endian; diff --git a/src/core/operations/Entropy.js b/src/core/operations/Entropy.js deleted file mode 100755 index baf9edb3..00000000 --- a/src/core/operations/Entropy.js +++ /dev/null @@ -1,195 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Entropy operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Entropy = { - - /** - * @constant - * @default - */ - CHUNK_SIZE: 1000, - - /** - * Entropy operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {html} - */ - runEntropy: function(input, args) { - let chunkSize = args[0], - output = "", - entropy = Entropy._calcEntropy(input); - - output += "Shannon entropy: " + entropy + "\n" + - "

\n" + - "- 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string.\n" + - "- Standard English text usually falls somewhere between 3.5 and 5.\n" + - "- Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5.\n\n" + - "The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections.\n\n" + - "
"; - - let chunkEntropy = 0; - if (chunkSize !== 0) { - for (let i = 0; i < input.length; i += chunkSize) { - chunkEntropy = Entropy._calcEntropy(input.slice(i, i+chunkSize)); - output += "Bytes " + i + " to " + (i+chunkSize) + ": " + chunkEntropy + "\n"; - } - } else { - output += "Chunk size cannot be 0."; - } - - return output; - }, - - - /** - * @constant - * @default - */ - FREQ_ZEROS: false, - - /** - * Frequency distribution operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {html} - */ - runFreqDistrib: function (input, args) { - const data = new Uint8Array(input); - if (!data.length) return "No data"; - - let distrib = new Array(256).fill(0), - percentages = new Array(256), - len = data.length, - showZeroes = args[0], - i; - - // Count bytes - for (i = 0; i < len; i++) { - distrib[data[i]]++; - } - - // Calculate percentages - let repr = 0; - for (i = 0; i < 256; i++) { - if (distrib[i] > 0) repr++; - percentages[i] = distrib[i] / len * 100; - } - - // Print - let output = "
" + - "Total data length: " + len + - "\nNumber of bytes represented: " + repr + - "\nNumber of bytes not represented: " + (256-repr) + - "\n\nByte Percentage\n" + - ""; - - for (i = 0; i < 256; i++) { - if (distrib[i] || showZeroes) { - output += " " + Utils.hex(i, 2) + " (" + - (percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") + - Array(Math.ceil(percentages[i])+1).join("|") + "\n"; - } - } - - return output; - }, - - - /** - * Chi Square operation. - * - * @param {ArrayBuffer} data - * @param {Object[]} args - * @returns {number} - */ - runChiSq: function(input, args) { - const data = new Uint8Array(input); - let distArray = new Array(256).fill(0), - total = 0; - - for (let i = 0; i < data.length; i++) { - distArray[data[i]]++; - } - - for (let i = 0; i < distArray.length; i++) { - if (distArray[i] > 0) { - total += Math.pow(distArray[i] - data.length / 256, 2) / (data.length / 256); - } - } - - return total; - }, - - - /** - * Calculates the Shannon entropy for a given chunk of data. - * - * @private - * @param {byteArray} data - * @returns {number} - */ - _calcEntropy: function(data) { - let prob = [], - uniques = data.unique(), - str = Utils.byteArrayToChars(data), - i; - - for (i = 0; i < uniques.length; i++) { - prob.push(str.count(Utils.chr(uniques[i])) / data.length); - } - - let entropy = 0, - p; - - for (i = 0; i < prob.length; i++) { - p = prob[i]; - entropy += p * Math.log(p) / Math.log(2); - } - - return -entropy; - }, - -}; - -export default Entropy; diff --git a/src/core/operations/Entropy.mjs b/src/core/operations/Entropy.mjs new file mode 100644 index 00000000..2f12d40f --- /dev/null +++ b/src/core/operations/Entropy.mjs @@ -0,0 +1,96 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Entropy operation + */ +class Entropy extends Operation { + + /** + * Entropy constructor + */ + constructor() { + super(); + + this.name = "Entropy"; + this.module = "Default"; + this.description = "Shannon Entropy, in the context of information theory, is a measure of the rate at which information is produced by a source of data. It can be used, in a broad sense, to detect whether data is likely to be structured or unstructured. 8 is the maximum, representing highly unstructured, 'random' data. English language text usually falls somewhere between 3.5 and 5. Properly encrypted or compressed data should have an entropy of over 7.5."; + this.inputType = "byteArray"; + this.outputType = "number"; + this.presentType = "html"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const prob = [], + uniques = input.unique(), + str = Utils.byteArrayToChars(input); + let i; + + for (i = 0; i < uniques.length; i++) { + prob.push(str.count(Utils.chr(uniques[i])) / input.length); + } + + let entropy = 0, + p; + + for (i = 0; i < prob.length; i++) { + p = prob[i]; + entropy += p * Math.log(p) / Math.log(2); + } + + return -entropy; + } + + /** + * Displays the entropy as a scale bar for web apps. + * + * @param {number} entropy + * @returns {html} + */ + present(entropy) { + return `Shannon entropy: ${entropy} +

+- 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string. +- Standard English text usually falls somewhere between 3.5 and 5. +- Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5. + +The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections. + +
`; + } + +} + +export default Entropy; diff --git a/src/core/operations/EscapeString.mjs b/src/core/operations/EscapeString.mjs new file mode 100644 index 00000000..9e0d5172 --- /dev/null +++ b/src/core/operations/EscapeString.mjs @@ -0,0 +1,87 @@ +/** + * @author Vel0x [dalemy@microsoft.com] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import jsesc from "jsesc"; + +/** + * Escape string operation + */ +class EscapeString extends Operation { + + /** + * EscapeString constructor + */ + constructor() { + super(); + + this.name = "Escape string"; + this.module = "Default"; + this.description = "Escapes special characters in a string so that they do not cause conflicts. For example, Don't stop me now becomes Don\\'t stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
  • \\unnnn (Unicode character)
  • \\u{nnnnnn} (Unicode code point)
"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Escape level", + "type": "option", + "value": ["Special chars", "Everything", "Minimal"] + }, + { + "name": "Escape quote", + "type": "option", + "value": ["Single", "Double", "Backtick"] + }, + { + "name": "JSON compatible", + "type": "boolean", + "value": false + }, + { + "name": "ES6 compatible", + "type": "boolean", + "value": true + }, + { + "name": "Uppercase hex", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @example + * EscapeString.run("Don't do that", []) + * > "Don\'t do that" + * EscapeString.run(`Hello + * World`, []) + * > "Hello\nWorld" + */ + run(input, args) { + 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, + }); + } + +} + +export default EscapeString; diff --git a/src/core/operations/EscapeUnicodeCharacters.mjs b/src/core/operations/EscapeUnicodeCharacters.mjs new file mode 100644 index 00000000..9bbf4f69 --- /dev/null +++ b/src/core/operations/EscapeUnicodeCharacters.mjs @@ -0,0 +1,96 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Escape Unicode Characters operation + */ +class EscapeUnicodeCharacters extends Operation { + + /** + * EscapeUnicodeCharacters constructor + */ + constructor() { + super(); + + this.name = "Escape Unicode Characters"; + this.module = "Default"; + this.description = "Converts characters to their unicode-escaped notations.

Supports the prefixes:
  • \\u
  • %u
  • U+
e.g. σου becomes \\u03C3\\u03BF\\u03C5"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Prefix", + "type": "option", + "value": ["\\u", "%u", "U+"] + }, + { + "name": "Encode all chars", + "type": "boolean", + "value": false + }, + { + "name": "Padding", + "type": "number", + "value": 4 + }, + { + "name": "Uppercase hex", + "type": "boolean", + "value": true + } + ]; + this.patterns = [ + { + match: "\\\\u(?:[\\da-f]{4,6})", + flags: "i", + args: ["\\u"] + }, + { + match: "%u(?:[\\da-f]{4,6})", + flags: "i", + args: ["%u"] + }, + { + match: "U\\+(?:[\\da-f]{4,6})", + flags: "i", + args: ["U+"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const regexWhitelist = /[ -~]/i, + [prefix, encodeAll, padding, uppercaseHex] = args; + + 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; + } + +} + +export default EscapeUnicodeCharacters; diff --git a/src/core/operations/ExpandAlphabetRange.mjs b/src/core/operations/ExpandAlphabetRange.mjs new file mode 100644 index 00000000..761a7a3b --- /dev/null +++ b/src/core/operations/ExpandAlphabetRange.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"; + +/** + * Expand alphabet range operation + */ +class ExpandAlphabetRange extends Operation { + + /** + * ExpandAlphabetRange constructor + */ + constructor() { + super(); + + this.name = "Expand alphabet range"; + this.module = "Default"; + this.description = "Expand an alphabet range string into a list of the characters in that range.

e.g. a-z becomes abcdefghijklmnopqrstuvwxyz."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "binaryString", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return Utils.expandAlphRange(input).join(args[0]); + } + +} + +export default ExpandAlphabetRange; diff --git a/src/core/operations/Extract.js b/src/core/operations/Extract.js deleted file mode 100755 index 92c75a21..00000000 --- a/src/core/operations/Extract.js +++ /dev/null @@ -1,333 +0,0 @@ -import XRegExp from "xregexp"; - - -/** - * Identifier extraction operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Extract = { - - /** - * Runs search operations across the input data using regular expressions. - * - * @private - * @param {string} input - * @param {RegExp} searchRegex - * @param {RegExp} removeRegex - A regular expression defining results to remove from the - * final list - * @param {boolean} includeTotal - Whether or not to include the total number of results - * @returns {string} - */ - _search: function(input, searchRegex, removeRegex, includeTotal) { - let output = "", - total = 0, - match; - - while ((match = searchRegex.exec(input))) { - // Moves pointer when an empty string is matched (prevents infinite loop) - if (match.index === searchRegex.lastIndex) { - searchRegex.lastIndex++; - } - - if (removeRegex && removeRegex.test(match[0])) - continue; - total++; - output += match[0] + "\n"; - } - - if (includeTotal) - output = "Total found: " + total + "\n\n" + output; - - return output; - }, - - - /** - * @constant - * @default - */ - MIN_STRING_LEN: 4, - /** - * @constant - * @default - */ - STRING_MATCH_TYPE: [ - "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)", - "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)" - ], - /** - * @constant - * @default - */ - ENCODING_LIST: ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"], - /** - * @constant - * @default - */ - DISPLAY_TOTAL: false, - - /** - * Strings operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runStrings: function(input, args) { - const encoding = args[0], - minLen = args[1], - matchType = args[2], - displayTotal = args[3], - alphanumeric = "A-Z\\d", - punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@", - printable = "\x20-\x7e", - uniAlphanumeric = "\\pL\\pN", - uniPunctuation = "\\pP\\pZ", - uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP"; - - let strings = ""; - - switch (matchType) { - case "Alphanumeric + punctuation (A)": - strings = `[${alphanumeric + punctuation}]`; - break; - case "All printable chars (A)": - case "Null-terminated strings (A)": - strings = `[${printable}]`; - break; - case "Alphanumeric + punctuation (U)": - strings = `[${uniAlphanumeric + uniPunctuation}]`; - break; - case "All printable chars (U)": - case "Null-terminated strings (U)": - strings = `[${uniPrintable}]`; - break; - } - - // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars - switch (encoding) { - case "All": - strings = `(\x00?${strings}\x00?)`; - break; - case "16-bit littleendian": - strings = `(${strings}\x00)`; - break; - case "16-bit bigendian": - strings = `(\x00${strings})`; - break; - case "Single byte": - default: - break; - } - - strings = `${strings}{${minLen},}`; - - if (matchType.includes("Null-terminated")) { - strings += "\x00"; - } - - const regex = new XRegExp(strings, "ig"); - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * @constant - * @default - */ - INCLUDE_IPV4: true, - /** - * @constant - * @default - */ - INCLUDE_IPV6: false, - /** - * @constant - * @default - */ - REMOVE_LOCAL: false, - - /** - * Extract IP addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runIp: function(input, args) { - let includeIpv4 = args[0], - includeIpv6 = args[1], - removeLocal = args[2], - displayTotal = args[3], - ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", - ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})", - ips = ""; - - if (includeIpv4 && includeIpv6) { - ips = ipv4 + "|" + ipv6; - } else if (includeIpv4) { - ips = ipv4; - } else if (includeIpv6) { - ips = ipv6; - } - - if (ips) { - const regex = new RegExp(ips, "ig"); - - if (removeLocal) { - let ten = "10\\..+", - oneninetwo = "192\\.168\\..+", - oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+", - onetwoseven = "127\\..+", - removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo + - "|" + oneseventwo + "|" + onetwoseven + ")"); - - return Extract._search(input, regex, removeRegex, displayTotal); - } else { - return Extract._search(input, regex, null, displayTotal); - } - } else { - return ""; - } - }, - - - /** - * Extract email addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runEmail: function(input, args) { - let displayTotal = args[0], - regex = /\b\w[-.\w]*@[-\w]+(?:\.[-\w]+)*\.[A-Z]{2,4}\b/ig; - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract MAC addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMac: function(input, args) { - let displayTotal = args[0], - regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig; - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract URLs operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUrls: function(input, args) { - let displayTotal = args[0], - protocol = "[A-Z]+://", - hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+", - port = ":\\d+", - path = "/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*"; - - path += "(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*"; - const regex = new RegExp(protocol + hostname + "(?:" + port + - ")?(?:" + path + ")?", "ig"); - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract domains operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDomains: function(input, args) { - const displayTotal = args[0], - regex = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig; - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * @constant - * @default - */ - INCLUDE_WIN_PATH: true, - /** - * @constant - * @default - */ - INCLUDE_UNIX_PATH: true, - - /** - * Extract file paths operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFilePaths: function(input, args) { - let includeWinPath = args[0], - includeUnixPath = args[1], - displayTotal = args[2], - winDrive = "[A-Z]:\\\\", - winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}", - winExt = "[A-Z\\d]{1,6}", - winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName + - "(?:\\." + winExt + ")?", - unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+", - filePaths = ""; - - if (includeWinPath && includeUnixPath) { - filePaths = winPath + "|" + unixPath; - } else if (includeWinPath) { - filePaths = winPath; - } else if (includeUnixPath) { - filePaths = unixPath; - } - - if (filePaths) { - const regex = new RegExp(filePaths, "ig"); - return Extract._search(input, regex, null, displayTotal); - } else { - return ""; - } - }, - - - /** - * Extract dates operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDates: function(input, args) { - let displayTotal = args[0], - date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd - date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy - date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy - regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig"); - - return Extract._search(input, regex, null, displayTotal); - }, - -}; - -export default Extract; diff --git a/src/core/operations/ExtractDates.mjs b/src/core/operations/ExtractDates.mjs new file mode 100644 index 00000000..530db194 --- /dev/null +++ b/src/core/operations/ExtractDates.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract dates operation + */ +class ExtractDates extends Operation { + + /** + * ExtractDates constructor + */ + constructor() { + super(); + + this.name = "Extract dates"; + this.module = "Regex"; + this.description = "Extracts dates in the following formats
  • yyyy-mm-dd
  • dd/mm/yyyy
  • mm/dd/yyyy
Dividers can be any of /, -, . or space"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd + date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy + date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy + regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig"); + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractDates; diff --git a/src/core/operations/ExtractDomains.mjs b/src/core/operations/ExtractDomains.mjs new file mode 100644 index 00000000..8eae8064 --- /dev/null +++ b/src/core/operations/ExtractDomains.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract domains operation + */ +class ExtractDomains extends Operation { + + /** + * ExtractDomains constructor + */ + constructor() { + super(); + + this.name = "Extract domains"; + this.module = "Regex"; + this.description = "Extracts domain names.
Note that this will not include paths. Use Extract URLs to find entire URLs."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": "Extract.DISPLAY_TOTAL" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + regex = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig; + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractDomains; diff --git a/src/core/operations/ExtractEXIF.mjs b/src/core/operations/ExtractEXIF.mjs new file mode 100644 index 00000000..533fdd63 --- /dev/null +++ b/src/core/operations/ExtractEXIF.mjs @@ -0,0 +1,62 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import ExifParser from "exif-parser"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Extract EXIF operation + */ +class ExtractEXIF extends Operation { + + /** + * ExtractEXIF constructor + */ + constructor() { + super(); + + this.name = "Extract EXIF"; + this.module = "Image"; + this.description = [ + "Extracts EXIF data from an image.", + "

", + "EXIF data is metadata embedded in images (JPEG, JPG, TIFF) and audio files.", + "

", + "EXIF data from photos usually contains information about the image file itself as well as the device used to create it.", + ].join("\n"); + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + const parser = ExifParser.create(input); + const result = parser.parse(); + + const lines = []; + for (const tagName in result.tags) { + const value = result.tags[tagName]; + lines.push(`${tagName}: ${value}`); + } + + const numTags = lines.length; + lines.unshift(`Found ${numTags} tags.\n`); + return lines.join("\n"); + } catch (err) { + throw new OperationError(`Could not extract EXIF data from image: ${err}`); + } + } + +} + +export default ExtractEXIF; diff --git a/src/core/operations/ExtractEmailAddresses.mjs b/src/core/operations/ExtractEmailAddresses.mjs new file mode 100644 index 00000000..6c2dc740 --- /dev/null +++ b/src/core/operations/ExtractEmailAddresses.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract email addresses operation + */ +class ExtractEmailAddresses extends Operation { + + /** + * ExtractEmailAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract email addresses"; + this.module = "Regex"; + this.description = "Extracts all email addresses from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + regex = /\b\w[-.\w]*@[-\w]+(?:\.[-\w]+)*\.[A-Z]{2,4}\b/ig; + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractEmailAddresses; diff --git a/src/core/operations/ExtractFilePaths.mjs b/src/core/operations/ExtractFilePaths.mjs new file mode 100644 index 00000000..4b268192 --- /dev/null +++ b/src/core/operations/ExtractFilePaths.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract file paths operation + */ +class ExtractFilePaths extends Operation { + + /** + * ExtractFilePaths constructor + */ + constructor() { + super(); + + this.name = "Extract file paths"; + this.module = "Regex"; + this.description = "Extracts anything that looks like a Windows or UNIX file path.

Note that if UNIX is selected, there will likely be a lot of false positives."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Windows", + "type": "boolean", + "value": true + }, + { + "name": "UNIX", + "type": "boolean", + "value": true + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [includeWinPath, includeUnixPath, displayTotal] = args, + winDrive = "[A-Z]:\\\\", + winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}", + winExt = "[A-Z\\d]{1,6}", + winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName + + "(?:\\." + winExt + ")?", + unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+"; + let filePaths = ""; + + if (includeWinPath && includeUnixPath) { + filePaths = winPath + "|" + unixPath; + } else if (includeWinPath) { + filePaths = winPath; + } else if (includeUnixPath) { + filePaths = unixPath; + } + + if (filePaths) { + const regex = new RegExp(filePaths, "ig"); + return search(input, regex, null, displayTotal); + } else { + return ""; + } + } + +} + +export default ExtractFilePaths; diff --git a/src/core/operations/ExtractIPAddresses.mjs b/src/core/operations/ExtractIPAddresses.mjs new file mode 100644 index 00000000..1cca2098 --- /dev/null +++ b/src/core/operations/ExtractIPAddresses.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract IP addresses operation + */ +class ExtractIPAddresses extends Operation { + + /** + * ExtractIPAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract IP addresses"; + this.module = "Regex"; + this.description = "Extracts all IPv4 and IPv6 addresses.

Warning: Given a string 710.65.0.456, this will match 10.65.0.45 so always check the original input!"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "IPv4", + "type": "boolean", + "value": true + }, + { + "name": "IPv6", + "type": "boolean", + "value": false + }, + { + "name": "Remove local IPv4 addresses", + "type": "boolean", + "value": false + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [includeIpv4, includeIpv6, removeLocal, displayTotal] = args, + ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", + ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})"; + let ips = ""; + + if (includeIpv4 && includeIpv6) { + ips = ipv4 + "|" + ipv6; + } else if (includeIpv4) { + ips = ipv4; + } else if (includeIpv6) { + ips = ipv6; + } + + if (ips) { + const regex = new RegExp(ips, "ig"); + + if (removeLocal) { + const ten = "10\\..+", + oneninetwo = "192\\.168\\..+", + oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+", + onetwoseven = "127\\..+", + removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo + + "|" + oneseventwo + "|" + onetwoseven + ")"); + + return search(input, regex, removeRegex, displayTotal); + } else { + return search(input, regex, null, displayTotal); + } + } else { + return ""; + } + } + +} + +export default ExtractIPAddresses; diff --git a/src/core/operations/ExtractMACAddresses.mjs b/src/core/operations/ExtractMACAddresses.mjs new file mode 100644 index 00000000..9c3c2a5b --- /dev/null +++ b/src/core/operations/ExtractMACAddresses.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract MAC addresses operation + */ +class ExtractMACAddresses extends Operation { + + /** + * ExtractMACAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract MAC addresses"; + this.module = "Regex"; + this.description = "Extracts all Media Access Control (MAC) addresses from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig; + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractMACAddresses; diff --git a/src/core/operations/ExtractURLs.mjs b/src/core/operations/ExtractURLs.mjs new file mode 100644 index 00000000..ab306d3f --- /dev/null +++ b/src/core/operations/ExtractURLs.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract URLs operation + */ +class ExtractURLs extends Operation { + + /** + * ExtractURLs constructor + */ + constructor() { + super(); + + this.name = "Extract URLs"; + this.module = "Regex"; + this.description = "Extracts Uniform Resource Locators (URLs) from the input. The protocol (http, ftp etc.) is required otherwise there will be far too many false positives."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + protocol = "[A-Z]+://", + hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+", + port = ":\\d+"; + let path = "/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*"; + + path += "(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*"; + const regex = new RegExp(protocol + hostname + "(?:" + port + + ")?(?:" + path + ")?", "ig"); + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractURLs; diff --git a/src/core/operations/FileType.js b/src/core/operations/FileType.js deleted file mode 100755 index 715f8205..00000000 --- a/src/core/operations/FileType.js +++ /dev/null @@ -1,532 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * File type operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const FileType = { - - /** - * Detect File Type operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runDetect: function(input, args) { - const data = new Uint8Array(input), - type = FileType.magicType(data); - - if (!type) { - return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; - } else { - let output = "File extension: " + type.ext + "\n" + - "MIME type: " + type.mime; - - if (type.desc && type.desc.length) { - output += "\nDescription: " + type.desc; - } - - return output; - } - }, - - - /** - * @constant - * @default - */ - IGNORE_COMMON_BYTE_SEQUENCES: true, - - /** - * Scan for Embedded Files operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runScanForEmbeddedFiles: function(input, args) { - let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any suffiently long file is likely to contain these magic bytes coincidentally.\n", - type, - numFound = 0, - numCommonFound = 0; - const ignoreCommon = args[0], - commonExts = ["ico", "ttf", ""], - data = new Uint8Array(input); - - for (let i = 0; i < data.length; i++) { - type = FileType.magicType(data.slice(i)); - if (type) { - if (ignoreCommon && commonExts.indexOf(type.ext) > -1) { - numCommonFound++; - continue; - } - numFound++; - output += "\nOffset " + i + " (0x" + Utils.hex(i) + "):\n" + - " File extension: " + type.ext + "\n" + - " MIME type: " + type.mime + "\n"; - - if (type.desc && type.desc.length) { - output += " Description: " + type.desc + "\n"; - } - } - } - - if (numFound === 0) { - output += "\nNo embedded files were found."; - } - - if (numCommonFound > 0) { - output += "\n\n" + numCommonFound; - output += numCommonFound === 1 ? - " file type was detected that has a common byte sequence. This is likely to be a false positive." : - " file types were detected that have common byte sequences. These are likely to be false positives."; - output += " Run this operation with the 'Ignore common byte sequences' option unchecked to see details."; - } - - return output; - }, - - - /** - * Given a buffer, detects magic byte sequences at specific positions and returns the - * extension and mime type. - * - * @param {Uint8Array} buf - * @returns {Object} type - * @returns {string} type.ext - File extension - * @returns {string} type.mime - Mime type - * @returns {string} [type.desc] - Description - */ - magicType: function (buf) { - if (!(buf && buf.length > 1)) { - return null; - } - - if (buf[0] === 0xFF && buf[1] === 0xD8 && buf[2] === 0xFF) { - return { - ext: "jpg", - mime: "image/jpeg" - }; - } - - if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4E && buf[3] === 0x47) { - return { - ext: "png", - mime: "image/png" - }; - } - - if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) { - return { - ext: "gif", - mime: "image/gif" - }; - } - - if (buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) { - return { - ext: "webp", - mime: "image/webp" - }; - } - - // needs to be before `tif` check - if (((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) && buf[8] === 0x43 && buf[9] === 0x52) { - return { - ext: "cr2", - mime: "image/x-canon-cr2" - }; - } - - if ((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) { - return { - ext: "tif", - mime: "image/tiff" - }; - } - - if (buf[0] === 0x42 && buf[1] === 0x4D) { - return { - ext: "bmp", - mime: "image/bmp" - }; - } - - if (buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0xBC) { - return { - ext: "jxr", - mime: "image/vnd.ms-photo" - }; - } - - if (buf[0] === 0x38 && buf[1] === 0x42 && buf[2] === 0x50 && buf[3] === 0x53) { - return { - ext: "psd", - mime: "image/vnd.adobe.photoshop" - }; - } - - // needs to be before `zip` check - if (buf[0] === 0x50 && buf[1] === 0x4B && buf[2] === 0x3 && buf[3] === 0x4 && buf[30] === 0x6D && buf[31] === 0x69 && buf[32] === 0x6D && buf[33] === 0x65 && buf[34] === 0x74 && buf[35] === 0x79 && buf[36] === 0x70 && buf[37] === 0x65 && buf[38] === 0x61 && buf[39] === 0x70 && buf[40] === 0x70 && buf[41] === 0x6C && buf[42] === 0x69 && buf[43] === 0x63 && buf[44] === 0x61 && buf[45] === 0x74 && buf[46] === 0x69 && buf[47] === 0x6F && buf[48] === 0x6E && buf[49] === 0x2F && buf[50] === 0x65 && buf[51] === 0x70 && buf[52] === 0x75 && buf[53] === 0x62 && buf[54] === 0x2B && buf[55] === 0x7A && buf[56] === 0x69 && buf[57] === 0x70) { - return { - ext: "epub", - mime: "application/epub+zip" - }; - } - - if (buf[0] === 0x50 && buf[1] === 0x4B && (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) && (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)) { - return { - ext: "zip", - mime: "application/zip" - }; - } - - if (buf[257] === 0x75 && buf[258] === 0x73 && buf[259] === 0x74 && buf[260] === 0x61 && buf[261] === 0x72) { - return { - ext: "tar", - mime: "application/x-tar" - }; - } - - if (buf[0] === 0x52 && buf[1] === 0x61 && buf[2] === 0x72 && buf[3] === 0x21 && buf[4] === 0x1A && buf[5] === 0x7 && (buf[6] === 0x0 || buf[6] === 0x1)) { - return { - ext: "rar", - mime: "application/x-rar-compressed" - }; - } - - if (buf[0] === 0x1F && buf[1] === 0x8B && buf[2] === 0x8) { - return { - ext: "gz", - mime: "application/gzip" - }; - } - - if (buf[0] === 0x42 && buf[1] === 0x5A && buf[2] === 0x68) { - return { - ext: "bz2", - mime: "application/x-bzip2" - }; - } - - if (buf[0] === 0x37 && buf[1] === 0x7A && buf[2] === 0xBC && buf[3] === 0xAF && buf[4] === 0x27 && buf[5] === 0x1C) { - return { - ext: "7z", - mime: "application/x-7z-compressed" - }; - } - - if (buf[0] === 0x78 && buf[1] === 0x01) { - return { - ext: "dmg", - mime: "application/x-apple-diskimage" - }; - } - - if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && (buf[3] === 0x18 || buf[3] === 0x20) && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) || (buf[0] === 0x33 && buf[1] === 0x67 && buf[2] === 0x70 && buf[3] === 0x35) || (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x6D && buf[9] === 0x70 && buf[10] === 0x34 && buf[11] === 0x32 && buf[16] === 0x6D && buf[17] === 0x70 && buf[18] === 0x34 && buf[19] === 0x31 && buf[20] === 0x6D && buf[21] === 0x70 && buf[22] === 0x34 && buf[23] === 0x32 && buf[24] === 0x69 && buf[25] === 0x73 && buf[26] === 0x6F && buf[27] === 0x6D)) { - return { - ext: "mp4", - mime: "video/mp4" - }; - } - - if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x56)) { - return { - ext: "m4v", - mime: "video/x-m4v" - }; - } - - if (buf[0] === 0x4D && buf[1] === 0x54 && buf[2] === 0x68 && buf[3] === 0x64) { - return { - ext: "mid", - mime: "audio/midi" - }; - } - - // needs to be before the `webm` check - if (buf[31] === 0x6D && buf[32] === 0x61 && buf[33] === 0x74 && buf[34] === 0x72 && buf[35] === 0x6f && buf[36] === 0x73 && buf[37] === 0x6B && buf[38] === 0x61) { - return { - ext: "mkv", - mime: "video/x-matroska" - }; - } - - if (buf[0] === 0x1A && buf[1] === 0x45 && buf[2] === 0xDF && buf[3] === 0xA3) { - return { - ext: "webm", - mime: "video/webm" - }; - } - - if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x14 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) { - return { - ext: "mov", - mime: "video/quicktime" - }; - } - - if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x41 && buf[9] === 0x56 && buf[10] === 0x49) { - return { - ext: "avi", - mime: "video/x-msvideo" - }; - } - - if (buf[0] === 0x30 && buf[1] === 0x26 && buf[2] === 0xB2 && buf[3] === 0x75 && buf[4] === 0x8E && buf[5] === 0x66 && buf[6] === 0xCF && buf[7] === 0x11 && buf[8] === 0xA6 && buf[9] === 0xD9) { - return { - ext: "wmv", - mime: "video/x-ms-wmv" - }; - } - - if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x1 && buf[3].toString(16)[0] === "b") { - return { - ext: "mpg", - mime: "video/mpeg" - }; - } - - if ((buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) || (buf[0] === 0xFF && buf[1] === 0xfb)) { - return { - ext: "mp3", - mime: "audio/mpeg" - }; - } - - if ((buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x41) || (buf[0] === 0x4D && buf[1] === 0x34 && buf[2] === 0x41 && buf[3] === 0x20)) { - return { - ext: "m4a", - mime: "audio/m4a" - }; - } - - if (buf[0] === 0x4F && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53) { - return { - ext: "ogg", - mime: "audio/ogg" - }; - } - - if (buf[0] === 0x66 && buf[1] === 0x4C && buf[2] === 0x61 && buf[3] === 0x43) { - return { - ext: "flac", - mime: "audio/x-flac" - }; - } - - if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45) { - return { - ext: "wav", - mime: "audio/x-wav" - }; - } - - if (buf[0] === 0x23 && buf[1] === 0x21 && buf[2] === 0x41 && buf[3] === 0x4D && buf[4] === 0x52 && buf[5] === 0x0A) { - return { - ext: "amr", - mime: "audio/amr" - }; - } - - if (buf[0] === 0x25 && buf[1] === 0x50 && buf[2] === 0x44 && buf[3] === 0x46) { - return { - ext: "pdf", - mime: "application/pdf" - }; - } - - if (buf[0] === 0x4D && buf[1] === 0x5A) { - return { - ext: "exe", - mime: "application/x-msdownload" - }; - } - - if ((buf[0] === 0x43 || buf[0] === 0x46) && buf[1] === 0x57 && buf[2] === 0x53) { - return { - ext: "swf", - mime: "application/x-shockwave-flash" - }; - } - - if (buf[0] === 0x7B && buf[1] === 0x5C && buf[2] === 0x72 && buf[3] === 0x74 && buf[4] === 0x66) { - return { - ext: "rtf", - mime: "application/rtf" - }; - } - - if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x46 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) { - return { - ext: "woff", - mime: "application/font-woff" - }; - } - - if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x32 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) { - return { - ext: "woff2", - mime: "application/font-woff" - }; - } - - if (buf[34] === 0x4C && buf[35] === 0x50 && ((buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x01) || (buf[8] === 0x01 && buf[9] === 0x00 && buf[10] === 0x00) || (buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x02))) { - return { - ext: "eot", - mime: "application/octet-stream" - }; - } - - if (buf[0] === 0x00 && buf[1] === 0x01 && buf[2] === 0x00 && buf[3] === 0x00 && buf[4] === 0x00) { - return { - ext: "ttf", - mime: "application/font-sfnt" - }; - } - - if (buf[0] === 0x4F && buf[1] === 0x54 && buf[2] === 0x54 && buf[3] === 0x4F && buf[4] === 0x00) { - return { - ext: "otf", - mime: "application/font-sfnt" - }; - } - - if (buf[0] === 0x00 && buf[1] === 0x00 && buf[2] === 0x01 && buf[3] === 0x00) { - return { - ext: "ico", - mime: "image/x-icon" - }; - } - - if (buf[0] === 0x46 && buf[1] === 0x4C && buf[2] === 0x56 && buf[3] === 0x01) { - return { - ext: "flv", - mime: "video/x-flv" - }; - } - - if (buf[0] === 0x25 && buf[1] === 0x21) { - return { - ext: "ps", - mime: "application/postscript" - }; - } - - if (buf[0] === 0xFD && buf[1] === 0x37 && buf[2] === 0x7A && buf[3] === 0x58 && buf[4] === 0x5A && buf[5] === 0x00) { - return { - ext: "xz", - mime: "application/x-xz" - }; - } - - if (buf[0] === 0x53 && buf[1] === 0x51 && buf[2] === 0x4C && buf[3] === 0x69) { - return { - ext: "sqlite", - mime: "application/x-sqlite3" - }; - } - - // Added by n1474335 [n1474335@gmail.com] from here on - // ################################################################## // - if ((buf[0] === 0x1F && buf[1] === 0x9D) || (buf[0] === 0x1F && buf[1] === 0xA0)) { - return { - ext: "z, tar.z", - mime: "application/x-gtar" - }; - } - - if (buf[0] === 0x7F && buf[1] === 0x45 && buf[2] === 0x4C && buf[3] === 0x46) { - return { - ext: "none, axf, bin, elf, o, prx, puff, so", - mime: "application/x-executable", - desc: "Executable and Linkable Format file. No standard file extension." - }; - } - - if (buf[0] === 0xCA && buf[1] === 0xFE && buf[2] === 0xBA && buf[3] === 0xBE) { - return { - ext: "class", - mime: "application/java-vm" - }; - } - - if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) { - return { - ext: "txt", - mime: "text/plain", - desc: "UTF-8 encoded Unicode byte order mark detected, commonly but not exclusively seen in text files." - }; - } - - // Must be before Little-endian UTF-16 BOM - if (buf[0] === 0xFF && buf[1] === 0xFE && buf[2] === 0x00 && buf[3] === 0x00) { - return { - ext: "", - mime: "", - desc: "Little-endian UTF-32 encoded Unicode byte order mark detected." - }; - } - - if (buf[0] === 0xFF && buf[1] === 0xFE) { - return { - ext: "", - mime: "", - desc: "Little-endian UTF-16 encoded Unicode byte order mark detected." - }; - } - - if ((buf[0x8001] === 0x43 && buf[0x8002] === 0x44 && buf[0x8003] === 0x30 && buf[0x8004] === 0x30 && buf[0x8005] === 0x31) || - (buf[0x8801] === 0x43 && buf[0x8802] === 0x44 && buf[0x8803] === 0x30 && buf[0x8804] === 0x30 && buf[0x8805] === 0x31) || - (buf[0x9001] === 0x43 && buf[0x9002] === 0x44 && buf[0x9003] === 0x30 && buf[0x9004] === 0x30 && buf[0x9005] === 0x31)) { - return { - ext: "iso", - mime: "application/octet-stream", - desc: "ISO 9660 CD/DVD image file" - }; - } - - if (buf[0] === 0xD0 && buf[1] === 0xCF && buf[2] === 0x11 && buf[3] === 0xE0 && buf[4] === 0xA1 && buf[5] === 0xB1 && buf[6] === 0x1A && buf[7] === 0xE1) { - return { - ext: "doc, xls, ppt", - mime: "application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint", - desc: "Microsoft Office documents" - }; - } - - if (buf[0] === 0x64 && buf[1] === 0x65 && buf[2] === 0x78 && buf[3] === 0x0A && buf[4] === 0x30 && buf[5] === 0x33 && buf[6] === 0x35 && buf[7] === 0x00) { - return { - ext: "dex", - mime: "application/octet-stream", - desc: "Dalvik Executable (Android)" - }; - } - - if (buf[0] === 0x4B && buf[1] === 0x44 && buf[2] === 0x4D) { - return { - ext: "vmdk", - mime: "application/vmdk, application/x-virtualbox-vmdk" - }; - } - - if (buf[0] === 0x43 && buf[1] === 0x72 && buf[2] === 0x32 && buf[3] === 0x34) { - return { - ext: "crx", - mime: "application/crx", - desc: "Google Chrome extension or packaged app" - }; - } - - return null; - }, - -}; - -export default FileType; diff --git a/src/core/operations/Filetime.js b/src/core/operations/Filetime.js deleted file mode 100644 index 9a21088d..00000000 --- a/src/core/operations/Filetime.js +++ /dev/null @@ -1,103 +0,0 @@ -import BigNumber from "bignumber.js"; - -/** - * Windows Filetime operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const Filetime = { - - /** - * @constant - * @default - */ - UNITS: ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"], - - /** - * @constant - * @default - */ - FILETIME_FORMATS: ["Decimal", "Hex"], - - /** - * Windows Filetime to Unix Timestamp operation. - * - * @author bwhitn [brian.m.whitney@outlook.com] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFromFiletimeToUnix: function(input, args) { - let units = args[0], - format = args[1]; - - if (!input) return ""; - - if (format === "Hex") { - input = new BigNumber(input, 16); - } else { - input = new BigNumber(input); - } - - input = input.minus(new BigNumber("116444736000000000")); - - if (units === "Seconds (s)"){ - input = input.dividedBy(new BigNumber("10000000")); - } else if (units === "Milliseconds (ms)") { - input = input.dividedBy(new BigNumber("10000")); - } else if (units === "Microseconds (μs)") { - input = input.dividedBy(new BigNumber("10")); - } else if (units === "Nanoseconds (ns)") { - input = input.multipliedBy(new BigNumber("100")); - } else { - throw "Unrecognised unit"; - } - - return input.toFixed(); - }, - - - /** - * Unix Timestamp to Windows Filetime operation. - * - * @author bwhitn [brian.m.whitney@outlook.com] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToFiletimeFromUnix: function(input, args) { - let units = args[0], - format = args[1]; - - if (!input) return ""; - - input = new BigNumber(input); - - if (units === "Seconds (s)"){ - input = input.multipliedBy(new BigNumber("10000000")); - } else if (units === "Milliseconds (ms)") { - input = input.multipliedBy(new BigNumber("10000")); - } else if (units === "Microseconds (μs)") { - input = input.multiplyiedBy(new BigNumber("10")); - } else if (units === "Nanoseconds (ns)") { - input = input.dividedBy(new BigNumber("100")); - } else { - throw "Unrecognised unit"; - } - - input = input.plus(new BigNumber("116444736000000000")); - - if (format === "Hex"){ - return input.toString(16); - } else { - return input.toFixed(); - } - }, - -}; - -export default Filetime; diff --git a/src/core/operations/Filter.mjs b/src/core/operations/Filter.mjs new file mode 100644 index 00000000..aaf53f0f --- /dev/null +++ b/src/core/operations/Filter.mjs @@ -0,0 +1,72 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; + +/** + * Filter operation + */ +class Filter extends Operation { + + /** + * Filter constructor + */ + constructor() { + super(); + + this.name = "Filter"; + this.module = "Default"; + this.description = "Splits up the input using the specified delimiter and then filters each branch based on a regular expression."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "Regex", + "type": "string", + "value": "" + }, + { + "name": "Invert condition", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + reverse = args[2]; + let regex; + + try { + regex = new RegExp(args[1]); + } catch (err) { + throw new OperationError(`Invalid regex. Details: ${err.message}`); + } + + const regexFilter = function(value) { + return reverse ^ regex.test(value); + }; + + return input.split(delim).filter(regexFilter).join(delim); + } + +} + +export default Filter; diff --git a/src/core/operations/FindReplace.mjs b/src/core/operations/FindReplace.mjs new file mode 100644 index 00000000..5a971704 --- /dev/null +++ b/src/core/operations/FindReplace.mjs @@ -0,0 +1,86 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Find / Replace operation + */ +class FindReplace extends Operation { + + /** + * FindReplace constructor + */ + constructor() { + super(); + + this.name = "Find / Replace"; + this.module = "Regex"; + this.description = "Replaces all occurrences of the first string with the second.

Includes support for regular expressions (regex), simple strings and extended strings (which support \\n, \\r, \\t, \\b, \\f and escaped hex bytes using \\x notation, e.g. \\x00 for a null byte)."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Find", + "type": "toggleString", + "value": "", + "toggleValues": ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"] + }, + { + "name": "Replace", + "type": "binaryString", + "value": "" + }, + { + "name": "Global match", + "type": "boolean", + "value": true + }, + { + "name": "Case insensitive", + "type": "boolean", + "value": false + }, + { + "name": "Multiline matching", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [{option: type}, replace, g, i, m] = args; + let find = args[0].string, + modifiers = ""; + + if (g) modifiers += "g"; + if (i) modifiers += "i"; + if (m) modifiers += "m"; + + if (type === "Regex") { + find = new RegExp(find, modifiers); + return input.replace(find, replace); + } + + if (type.indexOf("Extended") === 0) { + find = Utils.parseEscapedChars(find); + } + + find = new RegExp(Utils.escapeRegex(find), modifiers); + + return input.replace(find, replace); + } + +} + +export default FindReplace; diff --git a/src/core/operations/Fletcher16Checksum.mjs b/src/core/operations/Fletcher16Checksum.mjs new file mode 100644 index 00000000..ea3fe791 --- /dev/null +++ b/src/core/operations/Fletcher16Checksum.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Fletcher-16 Checksum operation + */ +class Fletcher16Checksum extends Operation { + + /** + * Fletcher16Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-16 Checksum"; + this.module = "Hashing"; + this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.

The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xff; + b = (b + a) % 0xff; + } + + return Utils.hex(((b << 8) | a) >>> 0, 4); + } + +} + +export default Fletcher16Checksum; diff --git a/src/core/operations/Fletcher32Checksum.mjs b/src/core/operations/Fletcher32Checksum.mjs new file mode 100644 index 00000000..21c9449e --- /dev/null +++ b/src/core/operations/Fletcher32Checksum.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Fletcher-32 Checksum operation + */ +class Fletcher32Checksum extends Operation { + + /** + * Fletcher32Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-32 Checksum"; + this.module = "Hashing"; + this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.

The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xffff; + b = (b + a) % 0xffff; + } + + return Utils.hex(((b << 16) | a) >>> 0, 8); + } + +} + +export default Fletcher32Checksum; diff --git a/src/core/operations/Fletcher64Checksum.mjs b/src/core/operations/Fletcher64Checksum.mjs new file mode 100644 index 00000000..ac88a26f --- /dev/null +++ b/src/core/operations/Fletcher64Checksum.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Fletcher-64 Checksum operation + */ +class Fletcher64Checksum extends Operation { + + /** + * Fletcher64Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-64 Checksum"; + this.module = "Hashing"; + this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.

The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xffffffff; + b = (b + a) % 0xffffffff; + } + + return Utils.hex(b >>> 0, 8) + Utils.hex(a >>> 0, 8); + } + +} + +export default Fletcher64Checksum; diff --git a/src/core/operations/Fletcher8Checksum.mjs b/src/core/operations/Fletcher8Checksum.mjs new file mode 100644 index 00000000..99199449 --- /dev/null +++ b/src/core/operations/Fletcher8Checksum.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Fletcher-8 Checksum operation + */ +class Fletcher8Checksum extends Operation { + + /** + * Fletcher8Checksum constructor + */ + constructor() { + super(); + + this.name = "Fletcher-8 Checksum"; + this.module = "Hashing"; + this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.

The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let a = 0, + b = 0; + + for (let i = 0; i < input.length; i++) { + a = (a + input[i]) % 0xf; + b = (b + a) % 0xf; + } + + return Utils.hex(((b << 4) | a) >>> 0, 2); + } + +} + +export default Fletcher8Checksum; diff --git a/src/core/operations/Fork.mjs b/src/core/operations/Fork.mjs new file mode 100644 index 00000000..27a1af96 --- /dev/null +++ b/src/core/operations/Fork.mjs @@ -0,0 +1,117 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Recipe from "../Recipe"; +import Dish from "../Dish"; + +/** + * Fork operation + */ +class Fork extends Operation { + + /** + * Fork constructor + */ + constructor() { + super(); + + this.name = "Fork"; + this.flowControl = true; + this.module = "Default"; + this.description = "Split the input data up based on the specified delimiter and run all subsequent operations on each branch separately.

For example, to decode multiple Base64 strings, enter them all on separate lines then add the 'Fork' and 'From Base64' operations to the recipe. Each string will be decoded separately."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Split delimiter", + "type": "binaryShortString", + "value": "\\n" + }, + { + "name": "Merge delimiter", + "type": "binaryShortString", + "value": "\\n" + }, + { + "name": "Ignore errors", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + async run(state) { + const opList = state.opList, + inputType = opList[state.progress].inputType, + outputType = opList[state.progress].outputType, + input = await state.dish.get(inputType), + ings = opList[state.progress].ingValues, + [splitDelim, mergeDelim, ignoreErrors] = ings, + subOpList = []; + let inputs = [], + i; + + if (input) + inputs = input.split(splitDelim); + + // 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].disabled) { + break; + } else { + subOpList.push(opList[i]); + } + } + + const recipe = new Recipe(); + let output = "", + progress = 0; + + state.forkOffset += state.progress + 1; + + recipe.addOperations(subOpList); + + // Take a deep(ish) copy of the ingredient values + const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues))); + + // Run recipe over each tranche + for (i = 0; i < inputs.length; i++) { + // Baseline ing values for each tranche so that registers are reset + subOpList.forEach((op, i) => { + op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); + }); + + const dish = new Dish(); + dish.set(inputs[i], inputType); + + try { + progress = await recipe.execute(dish, 0, state); + } catch (err) { + if (!ignoreErrors) { + throw err; + } + progress = err.progress + 1; + } + output += await dish.get(outputType) + mergeDelim; + } + + state.dish.set(output, outputType); + state.progress += progress; + return state; + } + +} + +export default Fork; diff --git a/src/core/operations/MAC.js b/src/core/operations/FormatMACAddresses.mjs old mode 100755 new mode 100644 similarity index 54% rename from src/core/operations/MAC.js rename to src/core/operations/FormatMACAddresses.mjs index b909287f..b10fc15c --- a/src/core/operations/MAC.js +++ b/src/core/operations/FormatMACAddresses.mjs @@ -1,70 +1,86 @@ /** - * MAC address operations. - * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 - * - * @namespace */ -const MAC = { + +import Operation from "../Operation"; + +/** + * Format MAC addresses operation + */ +class FormatMACAddresses extends Operation { /** - * @constant - * @default + * FormatMACAddresses constructor */ - OUTPUT_CASE: ["Both", "Upper only", "Lower only"], - /** - * @constant - * @default - */ - NO_DELIM: true, - /** - * @constant - * @default - */ - DASH_DELIM: true, - /** - * @constant - * @default - */ - COLON_DELIM: true, - /** - * @constant - * @default - */ - CISCO_STYLE: false, - /** - * @constant - * @default - */ - IPV6_INTERFACE_ID: false, + constructor() { + super(); + + this.name = "Format MAC addresses"; + this.module = "Default"; + this.description = "Displays given MAC addresses in multiple different formats.

Expects addresses in a list separated by newlines, spaces or commas.

WARNING: There are no validity checks."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Output case", + "type": "option", + "value": ["Both", "Upper only", "Lower only"] + }, + { + "name": "No delimiter", + "type": "boolean", + "value": true + }, + { + "name": "Dash delimiter", + "type": "boolean", + "value": true + }, + { + "name": "Colon delimiter", + "type": "boolean", + "value": true + }, + { + "name": "Cisco style", + "type": "boolean", + "value": false + }, + { + "name": "IPv6 interface ID", + "type": "boolean", + "value": false + } + ]; + } /** - * Format MAC addresses operation. - * * @param {string} input * @param {Object[]} args * @returns {string} */ - runFormat: function(input, args) { + run(input, args) { if (!input) return ""; - let outputCase = args[0], - noDelim = args[1], - dashDelim = args[2], - colonDelim = args[3], - ciscoStyle = args[4], - ipv6IntID = args[5], + const [ + outputCase, + noDelim, + dashDelim, + colonDelim, + ciscoStyle, + ipv6IntID + ] = args, outputList = [], macs = input.toLowerCase().split(/[,\s\r\n]+/); macs.forEach(function(mac) { - let cleanMac = mac.replace(/[:.-]+/g, ""), + const cleanMac = mac.replace(/[:.-]+/g, ""), macHyphen = cleanMac.replace(/(.{2}(?=.))/g, "$1-"), macColon = cleanMac.replace(/(.{2}(?=.))/g, "$1:"), - macCisco = cleanMac.replace(/(.{4}(?=.))/g, "$1."), - macIPv6 = cleanMac.slice(0, 6) + "fffe" + cleanMac.slice(6); + macCisco = cleanMac.replace(/(.{4}(?=.))/g, "$1."); + let macIPv6 = cleanMac.slice(0, 6) + "fffe" + cleanMac.slice(6); macIPv6 = macIPv6.replace(/(.{4}(?=.))/g, "$1:"); let bite = parseInt(macIPv6.slice(0, 2), 16) ^ 2; @@ -98,8 +114,8 @@ const MAC = { // Return the data as a string return outputList.join("\n"); - }, + } -}; +} -export default MAC; +export default FormatMACAddresses; diff --git a/src/core/operations/FrequencyDistribution.mjs b/src/core/operations/FrequencyDistribution.mjs new file mode 100644 index 00000000..bf82f1d4 --- /dev/null +++ b/src/core/operations/FrequencyDistribution.mjs @@ -0,0 +1,110 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; + +/** + * Frequency distribution operation + */ +class FrequencyDistribution extends Operation { + + /** + * FrequencyDistribution constructor + */ + constructor() { + super(); + + this.name = "Frequency distribution"; + this.module = "Default"; + this.description = "Displays the distribution of bytes in the data as a graph."; + this.inputType = "ArrayBuffer"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + "name": "Show 0%s", + "type": "boolean", + "value": "Entropy.FREQ_ZEROS" + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {json} + */ + run(input, args) { + const data = new Uint8Array(input); + if (!data.length) throw new OperationError("No data"); + + const distrib = new Array(256).fill(0), + percentages = new Array(256), + len = data.length; + let i; + + // Count bytes + for (i = 0; i < len; i++) { + distrib[data[i]]++; + } + + // Calculate percentages + let repr = 0; + for (i = 0; i < 256; i++) { + if (distrib[i] > 0) repr++; + percentages[i] = distrib[i] / len * 100; + } + + return { + "dataLength": len, + "percentages": percentages, + "distribution": distrib, + "bytesRepresented": repr + }; + } + + /** + * Displays the frequency distribution as a bar chart for web apps. + * + * @param {json} freq + * @returns {html} + */ + present(freq, args) { + const showZeroes = args[0]; + // Print + let output = `
+Total data length: ${freq.dataLength} +Number of bytes represented: ${freq.bytesRepresented} +Number of bytes not represented: ${256 - freq.bytesRepresented} + +Byte Percentage +`; + + for (let i = 0; i < 256; i++) { + if (freq.distribution[i] || showZeroes) { + output += " " + Utils.hex(i, 2) + " (" + + (freq.percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") + + Array(Math.ceil(freq.percentages[i])+1).join("|") + "\n"; + } + } + + return output; + } + +} + +export default FrequencyDistribution; diff --git a/src/core/operations/FromBCD.mjs b/src/core/operations/FromBCD.mjs new file mode 100644 index 00000000..a33d739b --- /dev/null +++ b/src/core/operations/FromBCD.mjs @@ -0,0 +1,122 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD"; +import BigNumber from "bignumber.js"; + +/** + * From BCD operation + */ +class FromBCD extends Operation { + + /** + * FromBCD constructor + */ + constructor() { + super(); + + this.name = "From BCD"; + this.module = "Default"; + this.description = "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign."; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Scheme", + "type": "option", + "value": ENCODING_SCHEME + }, + { + "name": "Packed", + "type": "boolean", + "value": true + }, + { + "name": "Signed", + "type": "boolean", + "value": false + }, + { + "name": "Input format", + "type": "option", + "value": FORMAT + } + ]; + this.patterns = [ + { + match: "^(?:\\d{4} ){3,}\\d{4}$", + flags: "", + args: ["8 4 2 1", true, false, "Nibbles"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const encoding = ENCODING_LOOKUP[args[0]], + packed = args[1], + signed = args[2], + inputFormat = args[3], + nibbles = []; + + let output = "", + byteArray; + + // Normalise the input + switch (inputFormat) { + case "Nibbles": + case "Bytes": + input = input.replace(/\s/g, ""); + for (let i = 0; i < input.length; i += 4) { + nibbles.push(parseInt(input.substr(i, 4), 2)); + } + break; + case "Raw": + default: + byteArray = Utils.strToByteArray(input); + byteArray.forEach(b => { + nibbles.push(b >>> 4); + nibbles.push(b & 15); + }); + break; + } + + if (!packed) { + // Discard each high nibble + for (let i = 0; i < nibbles.length; i++) { + nibbles.splice(i, 1); + } + } + + if (signed) { + const sign = nibbles.pop(); + if (sign === 13 || + sign === 11) { + // Negative + output += "-"; + } + } + + nibbles.forEach(n => { + if (isNaN(n)) throw new OperationError("Invalid input"); + const val = encoding.indexOf(n); + if (val < 0) throw new OperationError(`Value ${Utils.bin(n, 4)} is not in the encoding scheme`); + output += val.toString(); + }); + + return new BigNumber(output); + } + +} + +export default FromBCD; diff --git a/src/core/operations/FromBase.mjs b/src/core/operations/FromBase.mjs new file mode 100644 index 00000000..4d4ca9ff --- /dev/null +++ b/src/core/operations/FromBase.mjs @@ -0,0 +1,63 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError"; + +/** + * From Base operation + */ +class FromBase extends Operation { + + /** + * FromBase constructor + */ + constructor() { + super(); + + this.name = "From Base"; + this.module = "Default"; + this.description = "Converts a number to decimal from a given numerical base."; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Radix", + "type": "number", + "value": 36 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const radix = args[0]; + if (radix < 2 || radix > 36) { + throw new OperationError("Error: Radix argument must be between 2 and 36"); + } + + const number = input.replace(/\s/g, "").split("."); + let result = new BigNumber(number[0], radix) || 0; + + if (number.length === 1) return result; + + // Fractional part + for (let i = 0; i < number[1].length; i++) { + const digit = new BigNumber(number[1][i], radix); + result += digit.div(Math.pow(radix, i+1)); + } + + return result; + } + +} + +export default FromBase; diff --git a/src/core/operations/FromBase32.mjs b/src/core/operations/FromBase32.mjs new file mode 100644 index 00000000..c49d9546 --- /dev/null +++ b/src/core/operations/FromBase32.mjs @@ -0,0 +1,97 @@ +/** + * @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 + } + ]; + this.patterns = [ + { + match: "^(?:[A-Z2-7]{8})+(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}={1})?$", + flags: "", + args: ["A-Z2-7=", false] + }, + ]; + } + + /** + * @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/FromBase58.mjs b/src/core/operations/FromBase58.mjs new file mode 100644 index 00000000..58fcab9b --- /dev/null +++ b/src/core/operations/FromBase58.mjs @@ -0,0 +1,105 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {ALPHABET_OPTIONS} from "../lib/Base58"; + +/** + * From Base58 operation + */ +class FromBase58 extends Operation { + + /** + * FromBase58 constructor + */ + constructor() { + super(); + + this.name = "From Base58"; + this.module = "Default"; + this.description = "Base58 (similar to Base64) is a notation for encoding arbitrary byte data. It differs from Base64 by removing easily misread characters (i.e. l, I, 0 and O) to improve human readability.

This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included) back into its raw form.

e.g. StV1DL6CwTryKyV becomes hello world

Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc)."; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Alphabet", + "type": "editableOption", + "value": ALPHABET_OPTIONS + }, + { + "name": "Remove non-alphabet chars", + "type": "boolean", + "value": true + } + ]; + this.patterns = [ + { + match: "^[1-9A-HJ-NP-Za-km-z]{20,}$", + flags: "", + args: ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", false] + }, + { + match: "^[1-9A-HJ-NP-Za-km-z]{20,}$", + flags: "", + args: ["rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", false] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let alphabet = args[0] || ALPHABET_OPTIONS[0].value; + const removeNonAlphaChars = args[1] === undefined ? true : args[1], + result = [0]; + + alphabet = Utils.expandAlphRange(alphabet).join(""); + + if (alphabet.length !== 58 || + [].unique.call(alphabet).length !== 58) { + throw new OperationError("Alphabet must be of length 58"); + } + + if (input.length === 0) return []; + + [].forEach.call(input, function(c, charIndex) { + const index = alphabet.indexOf(c); + + if (index === -1) { + if (removeNonAlphaChars) { + return; + } else { + throw new OperationError(`Char '${c}' at position ${charIndex} not in alphabet`); + } + } + + let carry = result[0] * 58 + index; + result[0] = carry & 0xFF; + carry = carry >> 8; + + for (let i = 1; i < result.length; i++) { + carry += result[i] * 58; + result[i] = carry & 0xFF; + carry = carry >> 8; + } + + while (carry > 0) { + result.push(carry & 0xFF); + carry = carry >> 8; + } + }); + + return result.reverse(); + } + +} + +export default FromBase58; diff --git a/src/core/operations/FromBase64.mjs b/src/core/operations/FromBase64.mjs new file mode 100644 index 00000000..34d6c6f6 --- /dev/null +++ b/src/core/operations/FromBase64.mjs @@ -0,0 +1,149 @@ +/** + * @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 + } + ]; + this.patterns = [ + { + match: "^(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$", + flags: "i", + args: ["A-Za-z0-9+/=", false] + }, + { + match: "^[A-Z\\d\\-_]{20,}$", + flags: "i", + args: ["A-Za-z0-9-_", false] + }, + { + match: "^(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?$", + flags: "i", + args: ["A-Za-z0-9+\\-=", false] + }, + { + match: "^(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?$", + flags: "i", + args: ["./0-9A-Za-z=", false] + }, + { + match: "^[A-Z\\d_.]{20,}$", + flags: "i", + args: ["A-Za-z0-9_.", false] + }, + { + match: "^(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?$", + flags: "i", + args: ["A-Za-z0-9._-", false] + }, + { + match: "^(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$", + flags: "i", + args: ["0-9a-zA-Z+/=", false] + }, + { + match: "^(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$", + flags: "i", + args: ["0-9A-Za-z+/=", false] + }, + { + match: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$", + flags: "", + args: [" -_", false] + }, + { + match: "^[A-Z\\d+\\-]{20,}$", + flags: "i", + args: ["+\\-0-9A-Za-z", false] + }, + { + match: "^[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}$", + flags: "", + args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", false] + }, + { + match: "^(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?$", + flags: "i", + args: ["N-ZA-Mn-za-m0-9+/=", false] + }, + { + match: "^[A-Z\\d./]{20,}$", + flags: "i", + args: ["./0-9A-Za-z", false] + }, + ]; + } + + /** + * @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/FromBinary.mjs b/src/core/operations/FromBinary.mjs new file mode 100644 index 00000000..207b5352 --- /dev/null +++ b/src/core/operations/FromBinary.mjs @@ -0,0 +1,124 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {BIN_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * From Binary operation + */ +class FromBinary extends Operation { + + /** + * FromBinary constructor + */ + constructor() { + super(); + + this.name = "From Binary"; + this.module = "Default"; + this.description = "Converts a binary string back into its raw form.

e.g. 01001000 01101001 becomes Hi"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": BIN_DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "^(?:[01]{8})+$", + flags: "", + args: ["None"] + }, + { + match: "^(?:[01]{8})(?: [01]{8})*$", + flags: "", + args: ["Space"] + }, + { + match: "^(?:[01]{8})(?:,[01]{8})*$", + flags: "", + args: ["Comma"] + }, + { + match: "^(?:[01]{8})(?:;[01]{8})*$", + flags: "", + args: ["Semi-colon"] + }, + { + match: "^(?:[01]{8})(?::[01]{8})*$", + flags: "", + args: ["Colon"] + }, + { + match: "^(?:[01]{8})(?:\\n[01]{8})*$", + flags: "", + args: ["Line feed"] + }, + { + match: "^(?:[01]{8})(?:\\r\\n[01]{8})*$", + flags: "", + args: ["CRLF"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delimRegex = Utils.regexRep(args[0] || "Space"); + input = input.replace(delimRegex, ""); + + const output = []; + const byteLen = 8; + for (let i = 0; i < input.length; i += byteLen) { + output.push(parseInt(input.substr(i, byteLen), 2)); + } + return output; + } + + /** + * Highlight From Binary + * + * @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"); + 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; + } + + /** + * Highlight From Binary in reverse + * + * @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"); + pos[0].start = pos[0].start * (8 + delim.length); + pos[0].end = pos[0].end * (8 + delim.length) - delim.length; + return pos; + } + +} + +export default FromBinary; diff --git a/src/core/operations/FromCharcode.mjs b/src/core/operations/FromCharcode.mjs new file mode 100644 index 00000000..2dee72f2 --- /dev/null +++ b/src/core/operations/FromCharcode.mjs @@ -0,0 +1,83 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; + +/** + * From Charcode operation + */ +class FromCharcode extends Operation { + + /** + * FromCharcode constructor + */ + constructor() { + super(); + + this.name = "From Charcode"; + this.module = "Default"; + this.description = "Converts unicode character codes back into text.

e.g. 0393 03b5 03b9 03ac 20 03c3 03bf 03c5 becomes Γειά σου"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "Base", + "type": "number", + "value": 16 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + * + * @throws {OperationError} if base out of range + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"), + base = args[1]; + let bites = input.split(delim), + i = 0; + + if (base < 2 || base > 36) { + throw new OperationError("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 + // too long to be a single character + if (bites.length === 1 && input.length > 17) { + bites = []; + for (i = 0; i < input.length; i += 2) { + bites.push(input.slice(i, i+2)); + } + } + + let latin1 = ""; + for (i = 0; i < bites.length; i++) { + latin1 += Utils.chr(parseInt(bites[i], base)); + } + return Utils.strToByteArray(latin1); + } + +} + +export default FromCharcode; diff --git a/src/core/operations/FromDecimal.mjs b/src/core/operations/FromDecimal.mjs new file mode 100644 index 00000000..48667148 --- /dev/null +++ b/src/core/operations/FromDecimal.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; + +/** + * From Decimal operation + */ +class FromDecimal extends Operation { + + /** + * FromDecimal constructor + */ + constructor() { + super(); + + this.name = "From Decimal"; + this.module = "Default"; + this.description = "Converts the data from an ordinal integer array back into its raw form.

e.g. 72 101 108 108 111 becomes Hello"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?: (?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Space"] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:,(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Comma"] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:;(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Semi-colon"] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?::(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Colon"] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:\\n(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["Line feed"] + }, + { + match: "^(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])(?:\\r\\n(?:\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5]))*$", + flags: "", + args: ["CRLF"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + output = []; + let byteStr = input.split(delim); + if (byteStr[byteStr.length-1] === "") + byteStr = byteStr.slice(0, byteStr.length-1); + + for (let i = 0; i < byteStr.length; i++) { + output[i] = parseInt(byteStr[i], 10); + } + return output; + } + +} + +export default FromDecimal; diff --git a/src/core/operations/FromHTMLEntity.mjs b/src/core/operations/FromHTMLEntity.mjs new file mode 100644 index 00000000..e9865d95 --- /dev/null +++ b/src/core/operations/FromHTMLEntity.mjs @@ -0,0 +1,342 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * From HTML Entity operation + */ +class FromHTMLEntity extends Operation { + + /** + * FromHTMLEntity constructor + */ + constructor() { + super(); + + this.name = "From HTML Entity"; + this.module = "Default"; + this.description = "Converts HTML entities back to characters

e.g. &amp; becomes &"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.patterns = [ + { + match: "&(?:#\\d{2,3}|#x[\\da-f]{2}|[a-z]{2,6});", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const regex = /&(#?x?[a-zA-Z0-9]{1,8});/g; + let output = "", + m, + i = 0; + + while ((m = regex.exec(input))) { + // Add up to match + for (; i < m.index;) + output += input[i++]; + + // Add match + const bite = entityToByte[m[1]]; + if (bite) { + output += Utils.chr(bite); + } else if (!bite && m[1][0] === "#" && m[1].length > 1 && /^#\d{1,6}$/.test(m[1])) { + // Numeric entity (e.g. ) + const num = m[1].slice(1, m[1].length); + output += Utils.chr(parseInt(num, 10)); + } else if (!bite && m[1][0] === "#" && m[1].length > 3 && /^#x[\dA-F]{2,8}$/i.test(m[1])) { + // Hex entity (e.g. :) + const hex = m[1].slice(2, m[1].length); + output += Utils.chr(parseInt(hex, 16)); + } else { + // Not a valid entity, print as normal + for (; i < regex.lastIndex;) + output += input[i++]; + } + + i = regex.lastIndex; + } + // Add all after final match + for (; i < input.length;) + output += input[i++]; + + return output; + } + +} + + +/** + * Lookup table to translate HTML entity codes to their byte values. + */ +const entityToByte = { + "quot": 34, + "amp": 38, + "apos": 39, + "lt": 60, + "gt": 62, + "nbsp": 160, + "iexcl": 161, + "cent": 162, + "pound": 163, + "curren": 164, + "yen": 165, + "brvbar": 166, + "sect": 167, + "uml": 168, + "copy": 169, + "ordf": 170, + "laquo": 171, + "not": 172, + "shy": 173, + "reg": 174, + "macr": 175, + "deg": 176, + "plusmn": 177, + "sup2": 178, + "sup3": 179, + "acute": 180, + "micro": 181, + "para": 182, + "middot": 183, + "cedil": 184, + "sup1": 185, + "ordm": 186, + "raquo": 187, + "frac14": 188, + "frac12": 189, + "frac34": 190, + "iquest": 191, + "Agrave": 192, + "Aacute": 193, + "Acirc": 194, + "Atilde": 195, + "Auml": 196, + "Aring": 197, + "AElig": 198, + "Ccedil": 199, + "Egrave": 200, + "Eacute": 201, + "Ecirc": 202, + "Euml": 203, + "Igrave": 204, + "Iacute": 205, + "Icirc": 206, + "Iuml": 207, + "ETH": 208, + "Ntilde": 209, + "Ograve": 210, + "Oacute": 211, + "Ocirc": 212, + "Otilde": 213, + "Ouml": 214, + "times": 215, + "Oslash": 216, + "Ugrave": 217, + "Uacute": 218, + "Ucirc": 219, + "Uuml": 220, + "Yacute": 221, + "THORN": 222, + "szlig": 223, + "agrave": 224, + "aacute": 225, + "acirc": 226, + "atilde": 227, + "auml": 228, + "aring": 229, + "aelig": 230, + "ccedil": 231, + "egrave": 232, + "eacute": 233, + "ecirc": 234, + "euml": 235, + "igrave": 236, + "iacute": 237, + "icirc": 238, + "iuml": 239, + "eth": 240, + "ntilde": 241, + "ograve": 242, + "oacute": 243, + "ocirc": 244, + "otilde": 245, + "ouml": 246, + "divide": 247, + "oslash": 248, + "ugrave": 249, + "uacute": 250, + "ucirc": 251, + "uuml": 252, + "yacute": 253, + "thorn": 254, + "yuml": 255, + "OElig": 338, + "oelig": 339, + "Scaron": 352, + "scaron": 353, + "Yuml": 376, + "fnof": 402, + "circ": 710, + "tilde": 732, + "Alpha": 913, + "Beta": 914, + "Gamma": 915, + "Delta": 916, + "Epsilon": 917, + "Zeta": 918, + "Eta": 919, + "Theta": 920, + "Iota": 921, + "Kappa": 922, + "Lambda": 923, + "Mu": 924, + "Nu": 925, + "Xi": 926, + "Omicron": 927, + "Pi": 928, + "Rho": 929, + "Sigma": 931, + "Tau": 932, + "Upsilon": 933, + "Phi": 934, + "Chi": 935, + "Psi": 936, + "Omega": 937, + "alpha": 945, + "beta": 946, + "gamma": 947, + "delta": 948, + "epsilon": 949, + "zeta": 950, + "eta": 951, + "theta": 952, + "iota": 953, + "kappa": 954, + "lambda": 955, + "mu": 956, + "nu": 957, + "xi": 958, + "omicron": 959, + "pi": 960, + "rho": 961, + "sigmaf": 962, + "sigma": 963, + "tau": 964, + "upsilon": 965, + "phi": 966, + "chi": 967, + "psi": 968, + "omega": 969, + "thetasym": 977, + "upsih": 978, + "piv": 982, + "ensp": 8194, + "emsp": 8195, + "thinsp": 8201, + "zwnj": 8204, + "zwj": 8205, + "lrm": 8206, + "rlm": 8207, + "ndash": 8211, + "mdash": 8212, + "lsquo": 8216, + "rsquo": 8217, + "sbquo": 8218, + "ldquo": 8220, + "rdquo": 8221, + "bdquo": 8222, + "dagger": 8224, + "Dagger": 8225, + "bull": 8226, + "hellip": 8230, + "permil": 8240, + "prime": 8242, + "Prime": 8243, + "lsaquo": 8249, + "rsaquo": 8250, + "oline": 8254, + "frasl": 8260, + "euro": 8364, + "image": 8465, + "weierp": 8472, + "real": 8476, + "trade": 8482, + "alefsym": 8501, + "larr": 8592, + "uarr": 8593, + "rarr": 8594, + "darr": 8595, + "harr": 8596, + "crarr": 8629, + "lArr": 8656, + "uArr": 8657, + "rArr": 8658, + "dArr": 8659, + "hArr": 8660, + "forall": 8704, + "part": 8706, + "exist": 8707, + "empty": 8709, + "nabla": 8711, + "isin": 8712, + "notin": 8713, + "ni": 8715, + "prod": 8719, + "sum": 8721, + "minus": 8722, + "lowast": 8727, + "radic": 8730, + "prop": 8733, + "infin": 8734, + "ang": 8736, + "and": 8743, + "or": 8744, + "cap": 8745, + "cup": 8746, + "int": 8747, + "there4": 8756, + "sim": 8764, + "cong": 8773, + "asymp": 8776, + "ne": 8800, + "equiv": 8801, + "le": 8804, + "ge": 8805, + "sub": 8834, + "sup": 8835, + "nsub": 8836, + "sube": 8838, + "supe": 8839, + "oplus": 8853, + "otimes": 8855, + "perp": 8869, + "sdot": 8901, + "vellip": 8942, + "lceil": 8968, + "rceil": 8969, + "lfloor": 8970, + "rfloor": 8971, + "lang": 9001, + "rang": 9002, + "loz": 9674, + "spades": 9824, + "clubs": 9827, + "hearts": 9829, + "diams": 9830, +}; + +export default FromHTMLEntity; diff --git a/src/core/operations/FromHex.mjs b/src/core/operations/FromHex.mjs new file mode 100644 index 00000000..45ea9955 --- /dev/null +++ b/src/core/operations/FromHex.mjs @@ -0,0 +1,146 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {fromHex, FROM_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: FROM_HEX_DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "^(?:[\\dA-F]{2})+$", + flags: "i", + args: ["None"] + }, + { + match: "^[\\dA-F]{2}(?: [\\dA-F]{2})*$", + flags: "i", + args: ["Space"] + }, + { + match: "^[\\dA-F]{2}(?:,[\\dA-F]{2})*$", + flags: "i", + args: ["Comma"] + }, + { + match: "^[\\dA-F]{2}(?:;[\\dA-F]{2})*$", + flags: "i", + args: ["Semi-colon"] + }, + { + match: "^[\\dA-F]{2}(?::[\\dA-F]{2})*$", + flags: "i", + args: ["Colon"] + }, + { + match: "^[\\dA-F]{2}(?:\\n[\\dA-F]{2})*$", + flags: "i", + args: ["Line feed"] + }, + { + match: "^[\\dA-F]{2}(?:\\r\\n[\\dA-F]{2})*$", + flags: "i", + args: ["CRLF"] + }, + { + match: "^[\\dA-F]{2}(?:0x[\\dA-F]{2})*$", + flags: "i", + args: ["0x"] + }, + { + match: "^[\\dA-F]{2}(?:\\\\x[\\dA-F]{2})*$", + flags: "i", + args: ["\\x"] + } + ]; + } + + /** + * @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) { + if (args[0] === "Auto") return false; + 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/FromHexContent.mjs b/src/core/operations/FromHexContent.mjs new file mode 100644 index 00000000..9de085db --- /dev/null +++ b/src/core/operations/FromHexContent.mjs @@ -0,0 +1,66 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {fromHex} from "../lib/Hex"; + +/** + * From Hex Content operation + */ +class FromHexContent extends Operation { + + /** + * FromHexContent constructor + */ + constructor() { + super(); + + this.name = "From Hex Content"; + this.module = "Default"; + this.description = "Translates hexadecimal bytes in text back to raw bytes.

e.g. foo|3d|bar becomes foo=bar."; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const regex = /\|([a-f\d ]{2,})\|/gi, + output = []; + let m, i = 0; + while ((m = regex.exec(input))) { + // Add up to match + for (; i < m.index;) + output.push(Utils.ord(input[i++])); + + // Add match + const bytes = fromHex(m[1]); + if (bytes) { + for (let a = 0; a < bytes.length;) + output.push(bytes[a++]); + } else { + // Not valid hex, print as normal + for (; i < regex.lastIndex;) + output.push(Utils.ord(input[i++])); + } + + i = regex.lastIndex; + } + // Add all after final match + for (; i < input.length;) + output.push(Utils.ord(input[i++])); + + return output; + } + +} + +export default FromHexContent; diff --git a/src/core/operations/FromHexdump.mjs b/src/core/operations/FromHexdump.mjs new file mode 100644 index 00000000..85d74c16 --- /dev/null +++ b/src/core/operations/FromHexdump.mjs @@ -0,0 +1,163 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {fromHex} from "../lib/Hex"; + +/** + * From Hexdump operation + */ +class FromHexdump extends Operation { + + /** + * FromHexdump constructor + */ + constructor() { + super(); + + this.name = "From Hexdump"; + this.module = "Default"; + this.description = "Attempts to convert a hexdump back into raw data. This operation supports many different hexdump variations, but probably not all. Make sure you verify that the data it gives you is correct before continuing analysis."; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + this.patterns = [ + { + match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?)+$", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = [], + regex = /^\s*(?:[\dA-F]{4,16}h?:?)?[ \t]+((?:[\dA-F]{2} ){1,8}(?:[ \t]|[\dA-F]{2}-)(?:[\dA-F]{2} ){1,8}|(?:[\dA-F]{4} )*[\dA-F]{4}|(?:[\dA-F]{2} )*[\dA-F]{2})/igm; + let block, line; + + while ((block = regex.exec(input))) { + line = fromHex(block[1].replace(/-/g, " ")); + for (let i = 0; i < line.length; i++) { + output.push(line[i]); + } + } + // Is this a CyberChef hexdump or is it from a different tool? + const width = input.indexOf("\n"); + const w = (width - 13) / 4; + // w should be the specified width of the hexdump and therefore a round number + if (Math.floor(w) !== w || input.indexOf("\r") !== -1 || output.indexOf(13) !== -1) { + if (ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + } + return output; + } + + /** + * Highlight From Hexdump + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const w = args[0] || 16; + const width = 14 + (w*4); + + let line = Math.floor(pos[0].start / width); + let offset = pos[0].start % width; + + if (offset < 10) { // In line number section + pos[0].start = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].start = (line+1)*w; + } else { // In byte section + pos[0].start = line*w + Math.floor((offset-10)/3); + } + + line = Math.floor(pos[0].end / width); + offset = pos[0].end % width; + + if (offset < 10) { // In line number section + pos[0].end = line*w; + } else if (offset > 10+(w*3)) { // In ASCII section + pos[0].end = (line+1)*w; + } else { // In byte section + pos[0].end = line*w + Math.ceil((offset-10)/3); + } + + return pos; + } + + /** + * Highlight From Hexdump in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + // Calculate overall selection + const w = args[0] || 16, + width = 14 + (w*4); + let line = Math.floor(pos[0].start / w), + offset = pos[0].start % w, + start = 0, + end = 0; + + pos[0].start = line*width + 10 + offset*3; + + line = Math.floor(pos[0].end / w); + offset = pos[0].end % w; + if (offset === 0) { + line--; + offset = w; + } + pos[0].end = line*width + 10 + offset*3 - 1; + + // Set up multiple selections for bytes + let startLineNum = Math.floor(pos[0].start / width); + const endLineNum = Math.floor(pos[0].end / width); + + if (startLineNum === endLineNum) { + pos.push(pos[0]); + } else { + start = pos[0].start; + end = (startLineNum+1) * width - w - 5; + pos.push({ start: start, end: end }); + while (end < pos[0].end) { + startLineNum++; + start = startLineNum * width + 10; + end = (startLineNum+1) * width - w - 5; + if (end > pos[0].end) end = pos[0].end; + pos.push({ start: start, end: end }); + } + } + + // Set up multiple selections for ASCII + const len = pos.length; + let lineNum = 0; + start = 0; + end = 0; + for (let i = 1; i < len; i++) { + lineNum = Math.floor(pos[i].start / width); + start = (((pos[i].start - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); + end = (((pos[i].end + 1 - (lineNum * width)) - 10) / 3) + (width - w -2) + (lineNum * width); + pos.push({ start: start, end: end }); + } + return pos; + } + +} + +export default FromHexdump; diff --git a/src/core/operations/FromMorseCode.mjs b/src/core/operations/FromMorseCode.mjs new file mode 100644 index 00000000..ecf20a08 --- /dev/null +++ b/src/core/operations/FromMorseCode.mjs @@ -0,0 +1,152 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * From Morse Code operation + */ +class FromMorseCode extends Operation { + + /** + * FromMorseCode constructor + */ + constructor() { + super(); + + this.name = "From Morse Code"; + this.module = "Default"; + this.description = "Translates Morse Code into (upper case) alphanumeric characters."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Letter delimiter", + "type": "option", + "value": LETTER_DELIM_OPTIONS + }, + { + "name": "Word delimiter", + "type": "option", + "value": WORD_DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "(?:^[-. \\n]{5,}$|^[_. \\n]{5,}$|^(?:dash|dot| |\\n){5,}$)", + flags: "i", + args: ["Space", "Line feed"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!this.reversedTable) { + this.reverseTable(); + } + + 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, ""); + + let words = input.split(wordDelim); + const self = this; + words = Array.prototype.map.call(words, function(word) { + const signals = word.split(letterDelim); + + const letters = signals.map(function(signal) { + return self.reversedTable[signal]; + }); + + return letters.join(""); + }); + words = words.join(" "); + + return words; + } + + + /** + * Reverses the Morse Code lookup table + */ + reverseTable() { + this.reversedTable = {}; + + for (const letter in MORSE_TABLE) { + const signal = MORSE_TABLE[letter]; + this.reversedTable[signal] = letter; + } + } + +} + +const MORSE_TABLE = { + "A": "", + "B": "", + "C": "", + "D": "", + "E": "", + "F": "", + "G": "", + "H": "", + "I": "", + "J": "", + "K": "", + "L": "", + "M": "", + "N": "", + "O": "", + "P": "", + "Q": "", + "R": "", + "S": "", + "T": "", + "U": "", + "V": "", + "W": "", + "X": "", + "Y": "", + "Z": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "6": "", + "7": "", + "8": "", + "9": "", + "0": "", + ".": "", + ",": "", + ":": "", + ";": "", + "!": "", + "?": "", + "'": "", + "\"": "", + "/": "", + "-": "", + "+": "", + "(": "", + ")": "", + "@": "", + "=": "", + "&": "", + "_": "", + "$": "" +}; + +export default FromMorseCode; diff --git a/src/core/operations/FromOctal.mjs b/src/core/operations/FromOctal.mjs new file mode 100644 index 00000000..efe04050 --- /dev/null +++ b/src/core/operations/FromOctal.mjs @@ -0,0 +1,81 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; + +/** + * From Octal operation + */ +class FromOctal extends Operation { + + /** + * FromOctal constructor + */ + constructor() { + super(); + + this.name = "From Octal"; + this.module = "Default"; + this.description = "Converts an octal byte string back into its raw value.

e.g. 316 223 316 265 316 271 316 254 40 317 203 316 277 317 205 becomes the UTF-8 encoded string Γειά σου"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + this.patterns = [ + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?: (?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Space"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:,(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Comma"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:;(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Semi-colon"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?::(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Colon"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["Line feed"] + }, + { + match: "^(?:[0-7]{1,2}|[123][0-7]{2})(?:\\r\\n(?:[0-7]{1,2}|[123][0-7]{2}))*$", + flags: "", + args: ["CRLF"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"); + if (input.length === 0) return []; + return input.split(delim).map(val => parseInt(val, 8)); + } + +} + +export default FromOctal; diff --git a/src/core/operations/FromPunycode.mjs b/src/core/operations/FromPunycode.mjs new file mode 100644 index 00000000..1a1cebbf --- /dev/null +++ b/src/core/operations/FromPunycode.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import punycode from "punycode"; + +/** + * From Punycode operation + */ +class FromPunycode extends Operation { + + /** + * FromPunycode constructor + */ + constructor() { + super(); + + this.name = "From Punycode"; + this.module = "Encodings"; + this.description = "Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.

e.g. mnchen-3ya decodes to m\xfcnchen"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Internationalised domain name", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const idn = args[0]; + + if (idn) { + return punycode.toUnicode(input); + } else { + return punycode.decode(input); + } + } + +} + +export default FromPunycode; diff --git a/src/core/operations/FromQuotedPrintable.mjs b/src/core/operations/FromQuotedPrintable.mjs new file mode 100644 index 00000000..06a6432c --- /dev/null +++ b/src/core/operations/FromQuotedPrintable.mjs @@ -0,0 +1,68 @@ +/** + * Some parts taken from mimelib (http://github.com/andris9/mimelib) + * @author Andris Reinman + * @license MIT + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * From Quoted Printable operation + */ +class FromQuotedPrintable extends Operation { + + /** + * FromQuotedPrintable constructor + */ + constructor() { + super(); + + this.name = "From Quoted Printable"; + this.module = "Default"; + this.description = "Converts QP-encoded text back to standard text."; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = []; + this.patterns = [ + { + match: "^[\\x21-\\x3d\\x3f-\\x7e \\t]*(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const str = input.replace(/=(?:\r?\n|$)/g, ""); + + const encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length, + bufferLength = str.length - encodedBytesCount * 2, + buffer = new Array(bufferLength); + let chr, hex, + bufferPos = 0; + + for (let i = 0, len = str.length; i < len; i++) { + chr = str.charAt(i); + if (chr === "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { + buffer[bufferPos++] = parseInt(hex, 16); + i += 2; + continue; + } + buffer[bufferPos++] = chr.charCodeAt(0); + } + + return buffer; + } + +} + +export default FromQuotedPrintable; diff --git a/src/core/operations/FromUNIXTimestamp.mjs b/src/core/operations/FromUNIXTimestamp.mjs new file mode 100644 index 00000000..c6d6af43 --- /dev/null +++ b/src/core/operations/FromUNIXTimestamp.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import moment from "moment-timezone"; +import {UNITS} from "../lib/DateTime"; +import OperationError from "../errors/OperationError"; + +/** + * From UNIX Timestamp operation + */ +class FromUNIXTimestamp extends Operation { + + /** + * FromUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "From UNIX Timestamp"; + this.module = "Default"; + this.description = "Converts a UNIX timestamp to a datetime string.

e.g. 978346800 becomes Mon 1 January 2001 11:00:00 UTC

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch)."; + this.inputType = "number"; + this.outputType = "string"; + this.args = [ + { + "name": "Units", + "type": "option", + "value": UNITS + } + ]; + this.patterns = [ + { + match: "^1?\\d{9}$", + flags: "", + args: ["Seconds (s)"] + }, + { + match: "^1?\\d{12}$", + flags: "", + args: ["Milliseconds (ms)"] + }, + { + match: "^1?\\d{15}$", + flags: "", + args: ["Microseconds (μs)"] + }, + { + match: "^1?\\d{18}$", + flags: "", + args: ["Nanoseconds (ns)"] + }, + ]; + } + + /** + * @param {number} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid unit + */ + run(input, args) { + const units = args[0]; + let d; + + input = parseFloat(input); + + if (units === "Seconds (s)") { + d = moment.unix(input); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss") + " UTC"; + } else if (units === "Milliseconds (ms)") { + d = moment(input); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else if (units === "Microseconds (μs)") { + d = moment(input / 1000); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else if (units === "Nanoseconds (ns)") { + d = moment(input / 1000000); + return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; + } else { + throw new OperationError("Unrecognised unit"); + } + } + +} + +export default FromUNIXTimestamp; diff --git a/src/core/operations/GenerateAllHashes.mjs b/src/core/operations/GenerateAllHashes.mjs new file mode 100644 index 00000000..1280249a --- /dev/null +++ b/src/core/operations/GenerateAllHashes.mjs @@ -0,0 +1,104 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import MD2 from "./MD2"; +import MD4 from "./MD4"; +import MD5 from "./MD5"; +import MD6 from "./MD6"; +import SHA0 from "./SHA0"; +import SHA1 from "./SHA1"; +import SHA2 from "./SHA2"; +import SHA3 from "./SHA3"; +import Keccak from "./Keccak"; +import Shake from "./Shake"; +import RIPEMD from "./RIPEMD"; +import HAS160 from "./HAS160"; +import Whirlpool from "./Whirlpool"; +import SSDEEP from "./SSDEEP"; +import CTPH from "./CTPH"; +import Fletcher8Checksum from "./Fletcher8Checksum"; +import Fletcher16Checksum from "./Fletcher16Checksum"; +import Fletcher32Checksum from "./Fletcher32Checksum"; +import Fletcher64Checksum from "./Fletcher64Checksum"; +import Adler32Checksum from "./Adler32Checksum"; +import CRC16Checksum from "./CRC16Checksum"; +import CRC32Checksum from "./CRC32Checksum"; + +/** + * Generate all hashes operation + */ +class GenerateAllHashes extends Operation { + + /** + * GenerateAllHashes constructor + */ + constructor() { + super(); + + this.name = "Generate all hashes"; + this.module = "Hashing"; + this.description = "Generates all available hashes and checksums for the input."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const arrayBuffer = input, + str = Utils.arrayBufferToStr(arrayBuffer, false), + byteArray = new Uint8Array(arrayBuffer), + output = "MD2: " + (new MD2()).run(arrayBuffer, []) + + "\nMD4: " + (new MD4()).run(arrayBuffer, []) + + "\nMD5: " + (new MD5()).run(arrayBuffer, []) + + "\nMD6: " + (new MD6()).run(str, []) + + "\nSHA0: " + (new SHA0()).run(arrayBuffer, []) + + "\nSHA1: " + (new SHA1()).run(arrayBuffer, []) + + "\nSHA2 224: " + (new SHA2()).run(arrayBuffer, ["224"]) + + "\nSHA2 256: " + (new SHA2()).run(arrayBuffer, ["256"]) + + "\nSHA2 384: " + (new SHA2()).run(arrayBuffer, ["384"]) + + "\nSHA2 512: " + (new SHA2()).run(arrayBuffer, ["512"]) + + "\nSHA3 224: " + (new SHA3()).run(arrayBuffer, ["224"]) + + "\nSHA3 256: " + (new SHA3()).run(arrayBuffer, ["256"]) + + "\nSHA3 384: " + (new SHA3()).run(arrayBuffer, ["384"]) + + "\nSHA3 512: " + (new SHA3()).run(arrayBuffer, ["512"]) + + "\nKeccak 224: " + (new Keccak()).run(arrayBuffer, ["224"]) + + "\nKeccak 256: " + (new Keccak()).run(arrayBuffer, ["256"]) + + "\nKeccak 384: " + (new Keccak()).run(arrayBuffer, ["384"]) + + "\nKeccak 512: " + (new Keccak()).run(arrayBuffer, ["512"]) + + "\nShake 128: " + (new Shake()).run(arrayBuffer, ["128", 256]) + + "\nShake 256: " + (new Shake()).run(arrayBuffer, ["256", 512]) + + "\nRIPEMD-128: " + (new RIPEMD()).run(arrayBuffer, ["128"]) + + "\nRIPEMD-160: " + (new RIPEMD()).run(arrayBuffer, ["160"]) + + "\nRIPEMD-256: " + (new RIPEMD()).run(arrayBuffer, ["256"]) + + "\nRIPEMD-320: " + (new RIPEMD()).run(arrayBuffer, ["320"]) + + "\nHAS-160: " + (new HAS160()).run(arrayBuffer, []) + + "\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) + + "\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) + + "\nWhirlpool: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) + + "\nSSDEEP: " + (new SSDEEP()).run(str) + + "\nCTPH: " + (new CTPH()).run(str) + + "\n\nChecksums:" + + "\nFletcher-8: " + (new Fletcher8Checksum).run(byteArray, []) + + "\nFletcher-16: " + (new Fletcher16Checksum).run(byteArray, []) + + "\nFletcher-32: " + (new Fletcher32Checksum).run(byteArray, []) + + "\nFletcher-64: " + (new Fletcher64Checksum).run(byteArray, []) + + "\nAdler-32: " + (new Adler32Checksum).run(byteArray, []) + + "\nCRC-16: " + (new CRC16Checksum).run(str, []) + + "\nCRC-32: " + (new CRC32Checksum).run(str, []); + + return output; + } + +} + +export default GenerateAllHashes; diff --git a/src/core/operations/GenerateHOTP.mjs b/src/core/operations/GenerateHOTP.mjs new file mode 100644 index 00000000..36c115c5 --- /dev/null +++ b/src/core/operations/GenerateHOTP.mjs @@ -0,0 +1,69 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import otp from "otp"; +import ToBase32 from "./ToBase32"; + +/** + * Generate HOTP operation + */ +class GenerateHOTP extends Operation { + + /** + * GenerateHOTP constructor + */ + constructor() { + super(); + + this.name = "Generate HOTP"; + this.module = "Default"; + this.description = "The HMAC-based One-Time Password algorithm (HOTP) is an algorithm that computes a one-time password from a shared secret key and an incrementing counter. It has been adopted as Internet Engineering Task Force standard RFC 4226, is the cornerstone of Initiative For Open Authentication (OATH), and is used in a number of two-factor authentication systems.

Enter the secret as the input or leave it blank for a random secret to be generated."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Name", + "type": "string", + "value": "" + }, + { + "name": "Key size", + "type": "number", + "value": 32 + }, + { + "name": "Code length", + "type": "number", + "value": 6 + }, + { + "name": "Counter", + "type": "number", + "value": 0 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const otpObj = otp({ + name: args[0], + keySize: args[1], + codeLength: args[2], + secret: (new ToBase32).run(input, []), + }); + const counter = args[3]; + return `URI: ${otpObj.hotpURL}\n\nPassword: ${otpObj.hotp(counter)}`; + } + +} + +export default GenerateHOTP; diff --git a/src/core/operations/GeneratePGPKeyPair.mjs b/src/core/operations/GeneratePGPKeyPair.mjs new file mode 100644 index 00000000..9224011e --- /dev/null +++ b/src/core/operations/GeneratePGPKeyPair.mjs @@ -0,0 +1,116 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { getSubkeySize, ASP } from "../lib/PGP"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * Generate PGP Key Pair operation + */ +class GeneratePGPKeyPair extends Operation { + + /** + * GeneratePGPKeyPair constructor + */ + constructor() { + super(); + + this.name = "Generate PGP Key Pair"; + this.module = "PGP"; + this.description = "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key type", + "type": "option", + "value": ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384"] + }, + { + "name": "Password (optional)", + "type": "string", + "value": "" + }, + { + "name": "Name (optional)", + "type": "string", + "value": "" + }, + { + "name": "Email (optional)", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [keyType, keySize] = args[0].split("-"), + password = args[1], + name = args[2], + email = args[3]; + let userIdentifier = ""; + + if (name) userIdentifier += name; + if (email) userIdentifier += ` <${email}>`; + + let flags = kbpgp.const.openpgp.certify_keys; + flags |= kbpgp.const.openpgp.sign_data; + flags |= kbpgp.const.openpgp.auth; + flags |= kbpgp.const.openpgp.encrypt_comm; + flags |= kbpgp.const.openpgp.encrypt_storage; + + const keyGenerationOptions = { + userid: userIdentifier, + ecc: keyType === "ecc", + primary: { + "nbits": keySize, + "flags": flags, + "expire_in": 0 + }, + subkeys: [{ + "nbits": getSubkeySize(keySize), + "flags": kbpgp.const.openpgp.sign_data, + "expire_in": 86400 * 365 * 8 + }, { + "nbits": getSubkeySize(keySize), + "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, + "expire_in": 86400 * 365 * 2 + }], + asp: ASP + }; + + return new Promise(async (resolve, reject) => { + try { + const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions); + await promisify(unsignedKey.sign.bind(unsignedKey))({}); + + const signedKey = unsignedKey, + privateKeyExportOptions = {}; + + if (password) privateKeyExportOptions.passphrase = password; + const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions); + const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({}); + resolve(privateKey + "\n" + publicKey.trim()); + } catch (err) { + reject(`Error whilst generating key pair: ${err}`); + } + }); + } + +} + +export default GeneratePGPKeyPair; diff --git a/src/core/operations/GenerateTOTP.mjs b/src/core/operations/GenerateTOTP.mjs new file mode 100644 index 00000000..8d53f1be --- /dev/null +++ b/src/core/operations/GenerateTOTP.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import otp from "otp"; +import ToBase32 from "./ToBase32"; + +/** + * Generate TOTP operation + */ +class GenerateTOTP extends Operation { + + /** + * GenerateTOTP constructor + */ + constructor() { + super(); + + this.name = "Generate TOTP"; + this.module = "Default"; + this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OATH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.

Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Name", + "type": "string", + "value": "" + }, + { + "name": "Key size", + "type": "number", + "value": 32 + }, + { + "name": "Code length", + "type": "number", + "value": 6 + }, + { + "name": "Epoch offset (T0)", + "type": "number", + "value": 0 + }, + { + "name": "Interval (T1)", + "type": "number", + "value": 30 + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const otpObj = otp({ + name: args[0], + keySize: args[1], + codeLength: args[2], + secret: (new ToBase32).run(input, []), + epoch: args[3], + timeSlice: args[4] + }); + return `URI: ${otpObj.totpURL}\n\nPassword: ${otpObj.totp()}`; + } + +} + +export default GenerateTOTP; diff --git a/src/core/operations/GenerateUUID.mjs b/src/core/operations/GenerateUUID.mjs new file mode 100644 index 00000000..59837b26 --- /dev/null +++ b/src/core/operations/GenerateUUID.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import crypto from "crypto"; + +/** + * Generate UUID operation + */ +class GenerateUUID extends Operation { + + /** + * GenerateUUID constructor + */ + constructor() { + super(); + + this.name = "Generate UUID"; + this.module = "Crypto"; + this.description = "Generates an RFC 4122 version 4 compliant Universally Unique Identifier (UUID), also known as a Globally Unique Identifier (GUID).

A version 4 UUID relies on random numbers, in this case generated using window.crypto if available and falling back to Math.random if not."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const buf = new Uint32Array(4).map(() => { + return crypto.randomBytes(4).readUInt32BE(0, true); + }); + let i = 0; + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + const r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, + v = c === "x" ? r : (r & 0x3 | 0x8); + i++; + return v.toString(16); + }); + } + +} + +export default GenerateUUID; diff --git a/src/core/operations/GenericCodeBeautify.mjs b/src/core/operations/GenericCodeBeautify.mjs new file mode 100644 index 00000000..5078bebc --- /dev/null +++ b/src/core/operations/GenericCodeBeautify.mjs @@ -0,0 +1,161 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Generic Code Beautify operation + */ +class GenericCodeBeautify extends Operation { + + /** + * GenericCodeBeautify constructor + */ + constructor() { + super(); + + this.name = "Generic Code Beautify"; + this.module = "Code"; + this.description = "Attempts to pretty print C-style languages such as C, C++, C#, Java, PHP, JavaScript etc.

This will not do a perfect job, and the resulting code may not work any more. This operation is designed purely to make obfuscated or minified code more easy to read and understand.

Things which will not work properly:
  • For loop formatting
  • Do-While loop formatting
  • Switch/Case indentation
  • Certain bit shift operators
"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preservedTokens = []; + let code = input, + t = 0, + m; + + // Remove strings + const sstrings = /'([^'\\]|\\.)*'/g; + while ((m = sstrings.exec(code))) { + code = preserveToken(code, m, t++); + sstrings.lastIndex = m.index; + } + + const dstrings = /"([^"\\]|\\.)*"/g; + while ((m = dstrings.exec(code))) { + code = preserveToken(code, m, t++); + dstrings.lastIndex = m.index; + } + + // Remove comments + const scomments = /\/\/[^\n\r]*/g; + while ((m = scomments.exec(code))) { + code = preserveToken(code, m, t++); + scomments.lastIndex = m.index; + } + + const mcomments = /\/\*[\s\S]*?\*\//gm; + while ((m = mcomments.exec(code))) { + code = preserveToken(code, m, t++); + mcomments.lastIndex = m.index; + } + + const hcomments = /(^|\n)#[^\n\r#]+/g; + while ((m = hcomments.exec(code))) { + code = preserveToken(code, m, t++); + hcomments.lastIndex = m.index; + } + + // Remove regexes + const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi; + while ((m = regexes.exec(code))) { + code = preserveToken(code, m, t++); + regexes.lastIndex = m.index; + } + + code = code + // Create newlines after ; + .replace(/;/g, ";\n") + // Create newlines after { and around } + .replace(/{/g, "{\n") + .replace(/}/g, "\n}\n") + // Remove carriage returns + .replace(/\r/g, "") + // Remove all indentation + .replace(/^\s+/g, "") + .replace(/\n\s+/g, "\n") + // Remove trailing spaces + .replace(/\s*$/g, "") + .replace(/\n{/g, "{"); + + // Indent + let i = 0, + level = 0, + indent; + while (i < code.length) { + switch (code[i]) { + case "{": + level++; + break; + case "\n": + if (i+1 >= code.length) break; + + if (code[i+1] === "}") level--; + indent = (level >= 0) ? Array(level*4+1).join(" ") : ""; + + code = code.substring(0, i+1) + indent + code.substring(i+1); + if (level > 0) i += level*4; + break; + } + i++; + } + + code = code + // Add strategic spaces + .replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ") + .replace(/\s*<([=]?)\s*/g, " <$1 ") + .replace(/\s*>([=]?)\s*/g, " >$1 ") + .replace(/([^+])\+([^+=])/g, "$1 + $2") + .replace(/([^-])-([^-=])/g, "$1 - $2") + .replace(/([^*])\*([^*=])/g, "$1 * $2") + .replace(/([^/])\/([^/=])/g, "$1 / $2") + .replace(/\s*,\s*/g, ", ") + .replace(/\s*{/g, " {") + .replace(/}\n/g, "}\n\n") + // Hacky horribleness + .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3") + .replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3") + .replace(/else\s*\n([^{])/gim, "else\n $1") + .replace(/else\s+([^{])/gim, "else $1") + // Remove strategic spaces + .replace(/\s+;/g, ";") + .replace(/\{\s+\}/g, "{}") + .replace(/\[\s+\]/g, "[]") + .replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1"); + + // Replace preserved tokens + const ptokens = /###preservedToken(\d+)###/g; + while ((m = ptokens.exec(code))) { + const ti = parseInt(m[1], 10); + code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length); + ptokens.lastIndex = m.index; + } + + return code; + + /** + * Replaces a matched token with a placeholder value. + */ + function preserveToken(str, match, t) { + preservedTokens[t] = match[0]; + return str.substring(0, match.index) + + "###preservedToken" + t + "###" + + str.substring(match.index + match[0].length); + } + } + +} + +export default GenericCodeBeautify; diff --git a/src/core/operations/GroupIPAddresses.mjs b/src/core/operations/GroupIPAddresses.mjs new file mode 100644 index 00000000..a5b7ea2d --- /dev/null +++ b/src/core/operations/GroupIPAddresses.mjs @@ -0,0 +1,136 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {IP_DELIM_OPTIONS} from "../lib/Delim"; +import {ipv6ToStr, genIpv6Mask, IPV4_REGEX, strToIpv6, ipv4ToStr, IPV6_REGEX, strToIpv4} from "../lib/IP"; + +/** + * Group IP addresses operation + */ +class GroupIPAddresses extends Operation { + + /** + * GroupIPAddresses constructor + */ + constructor() { + super(); + + this.name = "Group IP addresses"; + this.module = "JSBN"; + this.description = "Groups a list of IP addresses into subnets. Supports both IPv4 and IPv6 addresses."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": IP_DELIM_OPTIONS + }, + { + "name": "Subnet (CIDR)", + "type": "number", + "value": 24 + }, + { + "name": "Only show the subnets", + "type": "boolean", + "value": false, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + cidr = args[1], + onlySubnets = args[2], + ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF, + ipv6Mask = genIpv6Mask(cidr), + ips = input.split(delim), + ipv4Networks = {}, + ipv6Networks = {}; + let match = null, + output = "", + ip = null, + network = null, + networkStr = "", + i; + + if (cidr < 0 || cidr > 127) { + throw new OperationError("CIDR must be less than 32 for IPv4 or 128 for IPv6"); + } + + // Parse all IPs and add to network dictionary + for (i = 0; i < ips.length; i++) { + if ((match = IPV4_REGEX.exec(ips[i]))) { + ip = strToIpv4(match[1]) >>> 0; + network = ip & ipv4Mask; + + if (ipv4Networks.hasOwnProperty(network)) { + ipv4Networks[network].push(ip); + } else { + ipv4Networks[network] = [ip]; + } + } else if ((match = IPV6_REGEX.exec(ips[i]))) { + ip = strToIpv6(match[1]); + network = []; + networkStr = ""; + + for (let j = 0; j < 8; j++) { + network.push(ip[j] & ipv6Mask[j]); + } + + networkStr = ipv6ToStr(network, true); + + if (ipv6Networks.hasOwnProperty(networkStr)) { + ipv6Networks[networkStr].push(ip); + } else { + ipv6Networks[networkStr] = [ip]; + } + } + } + + // Sort IPv4 network dictionaries and print + for (network in ipv4Networks) { + ipv4Networks[network] = ipv4Networks[network].sort(); + + output += ipv4ToStr(network) + "/" + cidr + "\n"; + + if (!onlySubnets) { + for (i = 0; i < ipv4Networks[network].length; i++) { + output += " " + ipv4ToStr(ipv4Networks[network][i]) + "\n"; + } + output += "\n"; + } + } + + // Sort IPv6 network dictionaries and print + for (networkStr in ipv6Networks) { + //ipv6Networks[networkStr] = ipv6Networks[networkStr].sort(); TODO + + output += networkStr + "/" + cidr + "\n"; + + if (!onlySubnets) { + for (i = 0; i < ipv6Networks[networkStr].length; i++) { + output += " " + ipv6ToStr(ipv6Networks[networkStr][i], true) + "\n"; + } + output += "\n"; + } + } + + return output; + } + +} + +export default GroupIPAddresses; diff --git a/src/core/operations/Gunzip.mjs b/src/core/operations/Gunzip.mjs new file mode 100644 index 00000000..19821de6 --- /dev/null +++ b/src/core/operations/Gunzip.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + this.patterns = [ + { + match: "^\\x1f\\x8b\\x08", + flags: "", + args: [] + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File} + */ + run(input, args) { + const gunzip = new Zlib.Gunzip(new Uint8Array(input)); + return new Uint8Array(gunzip.decompress()).buffer; + } + +} + +export default Gunzip; diff --git a/src/core/operations/Gzip.mjs b/src/core/operations/Gzip.mjs new file mode 100644 index 00000000..878959ab --- /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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + 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 {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + 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(new Uint8Array(input), options); + return new Uint8Array(gzip.compress()).buffer; + } + +} + +export default Gzip; diff --git a/src/core/operations/HAS160.mjs b/src/core/operations/HAS160.mjs new file mode 100644 index 00000000..1cf1ab6e --- /dev/null +++ b/src/core/operations/HAS160.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * HAS-160 operation + */ +class HAS160 extends Operation { + + /** + * HAS-160 constructor + */ + constructor() { + super(); + + this.name = "HAS-160"; + this.module = "Hashing"; + this.description = "HAS-160 is a cryptographic hash function designed for use with the Korean KCDSA digital signature algorithm. It is derived from SHA-1, with assorted changes intended to increase its security. It produces a 160-bit output.

HAS-160 is used in the same way as SHA-1. First it divides input in blocks of 512 bits each and pads the final block. A digest function updates the intermediate hash value by processing the input blocks in turn.

The message digest algorithm consists of 80 rounds."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("has160", input); + } + +} + +export default HAS160; diff --git a/src/core/operations/HMAC.mjs b/src/core/operations/HMAC.mjs new file mode 100644 index 00000000..e29656ce --- /dev/null +++ b/src/core/operations/HMAC.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import CryptoApi from "crypto-api/src/crypto-api"; + +/** + * HMAC operation + */ +class HMAC extends Operation { + + /** + * HMAC constructor + */ + constructor() { + super(); + + this.name = "HMAC"; + this.module = "Hashing"; + this.description = "Keyed-Hash Message Authentication Codes (HMAC) are a mechanism for message authentication using cryptographic hash functions."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "binaryString", + "value": "" + }, + { + "name": "Hashing function", + "type": "option", + "value": [ + "MD2", + "MD4", + "MD5", + "SHA0", + "SHA1", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA512/224", + "SHA512/256", + "RIPEMD128", + "RIPEMD160", + "RIPEMD256", + "RIPEMD320", + "HAS160", + "Whirlpool", + "Whirlpool-0", + "Whirlpool-T", + "Snefru" + ] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = args[0], + hashFunc = args[1].toLowerCase(), + msg = Utils.arrayBufferToStr(input, false), + hasher = CryptoApi.getHasher(hashFunc); + + // Horrible shim to fix constructor bug. Reported in nf404/crypto-api#8 + hasher.reset = () => { + hasher.state = {}; + const tmp = new hasher.constructor(); + hasher.state = tmp.state; + }; + + const mac = CryptoApi.getHmac(CryptoApi.encoder.fromUtf(key), hasher); + mac.update(msg); + return CryptoApi.encoder.toHex(mac.finalize()); + } + +} + +export default HMAC; diff --git a/src/core/operations/HTML.js b/src/core/operations/HTML.js deleted file mode 100755 index b1f7e065..00000000 --- a/src/core/operations/HTML.js +++ /dev/null @@ -1,857 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * HTML operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const HTML = { - - /** - * @constant - * @default - */ - CONVERT_ALL: false, - /** - * @constant - * @default - */ - CONVERT_OPTIONS: ["Named entities where possible", "Numeric entities", "Hex entities"], - - /** - * To HTML Entity operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToEntity: function(input, args) { - let convertAll = args[0], - numeric = args[1] === "Numeric entities", - hexa = args[1] === "Hex entities"; - - const charcodes = Utils.strToCharcode(input); - let output = ""; - - for (let i = 0; i < charcodes.length; i++) { - if (convertAll && numeric) { - output += "&#" + charcodes[i] + ";"; - } else if (convertAll && hexa) { - output += "&#x" + Utils.hex(charcodes[i]) + ";"; - } else if (convertAll) { - output += HTML._byteToEntity[charcodes[i]] || "&#" + charcodes[i] + ";"; - } else if (numeric) { - if (charcodes[i] > 255 || HTML._byteToEntity.hasOwnProperty(charcodes[i])) { - output += "&#" + charcodes[i] + ";"; - } else { - output += Utils.chr(charcodes[i]); - } - } else if (hexa) { - if (charcodes[i] > 255 || HTML._byteToEntity.hasOwnProperty(charcodes[i])) { - output += "&#x" + Utils.hex(charcodes[i]) + ";"; - } else { - output += Utils.chr(charcodes[i]); - } - } else { - output += HTML._byteToEntity[charcodes[i]] || ( - charcodes[i] > 255 ? - "&#" + charcodes[i] + ";" : - Utils.chr(charcodes[i]) - ); - } - } - return output; - }, - - - /** - * From HTML Entity operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFromEntity: function(input, args) { - let regex = /&(#?x?[a-zA-Z0-9]{1,8});/g, - output = "", - m, - i = 0; - - while ((m = regex.exec(input))) { - // Add up to match - for (; i < m.index;) - output += input[i++]; - - // Add match - const bite = HTML._entityToByte[m[1]]; - if (bite) { - output += Utils.chr(bite); - } else if (!bite && m[1][0] === "#" && m[1].length > 1 && /^#\d{1,6}$/.test(m[1])) { - // Numeric entity (e.g. ) - const num = m[1].slice(1, m[1].length); - output += Utils.chr(parseInt(num, 10)); - } else if (!bite && m[1][0] === "#" && m[1].length > 3 && /^#x[\dA-F]{2,8}$/i.test(m[1])) { - // Hex entity (e.g. :) - const hex = m[1].slice(2, m[1].length); - output += Utils.chr(parseInt(hex, 16)); - } else { - // Not a valid entity, print as normal - for (; i < regex.lastIndex;) - output += input[i++]; - } - - i = regex.lastIndex; - } - // Add all after final match - for (; i < input.length;) - output += input[i++]; - - return output; - }, - - - /** - * @constant - * @default - */ - REMOVE_INDENTATION: true, - /** - * @constant - * @default - */ - REMOVE_LINE_BREAKS: true, - - /** - * Strip HTML tags operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runStripTags: function(input, args) { - let removeIndentation = args[0], - removeLineBreaks = args[1]; - - input = Utils.stripHtmlTags(input); - - if (removeIndentation) { - input = input.replace(/\n[ \f\t]+/g, "\n"); - } - - if (removeLineBreaks) { - input = input - .replace(/^\s*\n/, "") // first line - .replace(/(\n\s*){2,}/g, "\n"); // all others - } - - return input; - }, - - - /** - * Parse colour code operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runParseColourCode: function(input, args) { - let m = null, - r = 0, g = 0, b = 0, a = 1; - - // Read in the input - if ((m = input.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i))) { - // Hex - #d9edf7 - r = parseInt(m[1], 16); - g = parseInt(m[2], 16); - b = parseInt(m[3], 16); - } else if ((m = input.match(/rgba?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)(?:,\s?(\d(?:\.\d+)?))?\)/i))) { - // RGB or RGBA - rgb(217,237,247) or rgba(217,237,247,1) - r = parseFloat(m[1]); - g = parseFloat(m[2]); - b = parseFloat(m[3]); - a = m[4] ? parseFloat(m[4]) : 1; - } else if ((m = input.match(/hsla?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)%,\s?(\d{1,3}(?:\.\d+)?)%(?:,\s?(\d(?:\.\d+)?))?\)/i))) { - // HSL or HSLA - hsl(200, 65%, 91%) or hsla(200, 65%, 91%, 1) - let h_ = parseFloat(m[1]) / 360, - s_ = parseFloat(m[2]) / 100, - l_ = parseFloat(m[3]) / 100, - rgb_ = HTML._hslToRgb(h_, s_, l_); - - r = rgb_[0]; - g = rgb_[1]; - b = rgb_[2]; - a = m[4] ? parseFloat(m[4]) : 1; - } else if ((m = input.match(/cmyk\((\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?)\)/i))) { - // CMYK - cmyk(0.12, 0.04, 0.00, 0.03) - let c_ = parseFloat(m[1]), - m_ = parseFloat(m[2]), - y_ = parseFloat(m[3]), - k_ = parseFloat(m[4]); - - r = Math.round(255 * (1 - c_) * (1 - k_)); - g = Math.round(255 * (1 - m_) * (1 - k_)); - b = Math.round(255 * (1 - y_) * (1 - k_)); - } - - let hsl_ = HTML._rgbToHsl(r, g, b), - h = Math.round(hsl_[0] * 360), - s = Math.round(hsl_[1] * 100), - l = Math.round(hsl_[2] * 100), - k = 1 - Math.max(r/255, g/255, b/255), - c = (1 - r/255 - k) / (1 - k), - y = (1 - b/255 - k) / (1 - k); - - m = (1 - g/255 - k) / (1 - k); - - c = isNaN(c) ? "0" : c.toFixed(2); - m = isNaN(m) ? "0" : m.toFixed(2); - y = isNaN(y) ? "0" : y.toFixed(2); - k = k.toFixed(2); - - let hex = "#" + - Math.round(r).toString(16).padStart(2, "0") + - Math.round(g).toString(16).padStart(2, "0") + - Math.round(b).toString(16).padStart(2, "0"), - rgb = "rgb(" + r + ", " + g + ", " + b + ")", - rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")", - hsl = "hsl(" + h + ", " + s + "%, " + l + "%)", - hsla = "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")", - cmyk = "cmyk(" + c + ", " + m + ", " + y + ", " + k + ")"; - - // Generate output - return `
-Hex: ${hex} -RGB: ${rgb} -RGBA: ${rgba} -HSL: ${hsl} -HSLA: ${hsla} -CMYK: ${cmyk} -`; - }, - - - /** - * Converts an HSL color value to RGB. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. - * Assumes h, s, and l are contained in the set [0, 1] and - * returns r, g, and b in the set [0, 255]. - * - * @private - * @author Mohsen (http://stackoverflow.com/a/9493060) - * - * @param {number} h - The hue - * @param {number} s - The saturation - * @param {number} l - The lightness - * @return {Array} The RGB representation - */ - _hslToRgb: function(h, s, l){ - let r, g, b; - - if (s === 0){ - r = g = b = l; // achromatic - } else { - const hue2rgb = function hue2rgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1/6) return p + (q - p) * 6 * t; - if (t < 1/2) return q; - if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; - return p; - }; - - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; - }, - - - /** - * Converts an RGB color value to HSL. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. - * Assumes r, g, and b are contained in the set [0, 255] and - * returns h, s, and l in the set [0, 1]. - * - * @private - * @author Mohsen (http://stackoverflow.com/a/9493060) - * - * @param {number} r - The red color value - * @param {number} g - The green color value - * @param {number} b - The blue color value - * @return {Array} The HSL representation - */ - _rgbToHsl: function(r, g, b) { - r /= 255; g /= 255; b /= 255; - let max = Math.max(r, g, b), - min = Math.min(r, g, b), - h, s, l = (max + min) / 2; - - if (max === min) { - h = s = 0; // achromatic - } else { - const d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; - } - h /= 6; - } - - return [h, s, l]; - }, - - - /** - * Lookup table to translate byte values to their HTML entity codes. - * - * @private - * @constant - */ - _byteToEntity: { - 34: """, - 38: "&", - 39: "'", - 60: "<", - 62: ">", - 160: " ", - 161: "¡", - 162: "¢", - 163: "£", - 164: "¤", - 165: "¥", - 166: "¦", - 167: "§", - 168: "¨", - 169: "©", - 170: "ª", - 171: "«", - 172: "¬", - 173: "­", - 174: "®", - 175: "¯", - 176: "°", - 177: "±", - 178: "²", - 179: "³", - 180: "´", - 181: "µ", - 182: "¶", - 183: "·", - 184: "¸", - 185: "¹", - 186: "º", - 187: "»", - 188: "¼", - 189: "½", - 190: "¾", - 191: "¿", - 192: "À", - 193: "Á", - 194: "Â", - 195: "Ã", - 196: "Ä", - 197: "Å", - 198: "Æ", - 199: "Ç", - 200: "È", - 201: "É", - 202: "Ê", - 203: "Ë", - 204: "Ì", - 205: "Í", - 206: "Î", - 207: "Ï", - 208: "Ð", - 209: "Ñ", - 210: "Ò", - 211: "Ó", - 212: "Ô", - 213: "Õ", - 214: "Ö", - 215: "×", - 216: "Ø", - 217: "Ù", - 218: "Ú", - 219: "Û", - 220: "Ü", - 221: "Ý", - 222: "Þ", - 223: "ß", - 224: "à", - 225: "á", - 226: "â", - 227: "ã", - 228: "ä", - 229: "å", - 230: "æ", - 231: "ç", - 232: "è", - 233: "é", - 234: "ê", - 235: "ë", - 236: "ì", - 237: "í", - 238: "î", - 239: "ï", - 240: "ð", - 241: "ñ", - 242: "ò", - 243: "ó", - 244: "ô", - 245: "õ", - 246: "ö", - 247: "÷", - 248: "ø", - 249: "ù", - 250: "ú", - 251: "û", - 252: "ü", - 253: "ý", - 254: "þ", - 255: "ÿ", - 338: "Œ", - 339: "œ", - 352: "Š", - 353: "š", - 376: "Ÿ", - 402: "ƒ", - 710: "ˆ", - 732: "˜", - 913: "Α", - 914: "Β", - 915: "Γ", - 916: "Δ", - 917: "Ε", - 918: "Ζ", - 919: "Η", - 920: "Θ", - 921: "Ι", - 922: "Κ", - 923: "Λ", - 924: "Μ", - 925: "Ν", - 926: "Ξ", - 927: "Ο", - 928: "Π", - 929: "Ρ", - 931: "Σ", - 932: "Τ", - 933: "Υ", - 934: "Φ", - 935: "Χ", - 936: "Ψ", - 937: "Ω", - 945: "α", - 946: "β", - 947: "γ", - 948: "δ", - 949: "ε", - 950: "ζ", - 951: "η", - 952: "θ", - 953: "ι", - 954: "κ", - 955: "λ", - 956: "μ", - 957: "ν", - 958: "ξ", - 959: "ο", - 960: "π", - 961: "ρ", - 962: "ς", - 963: "σ", - 964: "τ", - 965: "υ", - 966: "φ", - 967: "χ", - 968: "ψ", - 969: "ω", - 977: "ϑ", - 978: "ϒ", - 982: "ϖ", - 8194: " ", - 8195: " ", - 8201: " ", - 8204: "‌", - 8205: "‍", - 8206: "‎", - 8207: "‏", - 8211: "–", - 8212: "—", - 8216: "‘", - 8217: "’", - 8218: "‚", - 8220: "“", - 8221: "”", - 8222: "„", - 8224: "†", - 8225: "‡", - 8226: "•", - 8230: "…", - 8240: "‰", - 8242: "′", - 8243: "″", - 8249: "‹", - 8250: "›", - 8254: "‾", - 8260: "⁄", - 8364: "€", - 8465: "ℑ", - 8472: "℘", - 8476: "ℜ", - 8482: "™", - 8501: "ℵ", - 8592: "←", - 8593: "↑", - 8594: "→", - 8595: "↓", - 8596: "↔", - 8629: "↵", - 8656: "⇐", - 8657: "⇑", - 8658: "⇒", - 8659: "⇓", - 8660: "⇔", - 8704: "∀", - 8706: "∂", - 8707: "∃", - 8709: "∅", - 8711: "∇", - 8712: "∈", - 8713: "∉", - 8715: "∋", - 8719: "∏", - 8721: "∑", - 8722: "−", - 8727: "∗", - 8730: "√", - 8733: "∝", - 8734: "∞", - 8736: "∠", - 8743: "∧", - 8744: "∨", - 8745: "∩", - 8746: "∪", - 8747: "∫", - 8756: "∴", - 8764: "∼", - 8773: "≅", - 8776: "≈", - 8800: "≠", - 8801: "≡", - 8804: "≤", - 8805: "≥", - 8834: "⊂", - 8835: "⊃", - 8836: "⊄", - 8838: "⊆", - 8839: "⊇", - 8853: "⊕", - 8855: "⊗", - 8869: "⊥", - 8901: "⋅", - 8942: "⋮", - 8968: "⌈", - 8969: "⌉", - 8970: "⌊", - 8971: "⌋", - 9001: "⟨", - 9002: "⟩", - 9674: "◊", - 9824: "♠", - 9827: "♣", - 9829: "♥", - 9830: "♦", - }, - - - /** - * Lookup table to translate HTML entity codes to their byte values. - * - * @private - * @constant - */ - _entityToByte: { - "quot": 34, - "amp": 38, - "apos": 39, - "lt": 60, - "gt": 62, - "nbsp": 160, - "iexcl": 161, - "cent": 162, - "pound": 163, - "curren": 164, - "yen": 165, - "brvbar": 166, - "sect": 167, - "uml": 168, - "copy": 169, - "ordf": 170, - "laquo": 171, - "not": 172, - "shy": 173, - "reg": 174, - "macr": 175, - "deg": 176, - "plusmn": 177, - "sup2": 178, - "sup3": 179, - "acute": 180, - "micro": 181, - "para": 182, - "middot": 183, - "cedil": 184, - "sup1": 185, - "ordm": 186, - "raquo": 187, - "frac14": 188, - "frac12": 189, - "frac34": 190, - "iquest": 191, - "Agrave": 192, - "Aacute": 193, - "Acirc": 194, - "Atilde": 195, - "Auml": 196, - "Aring": 197, - "AElig": 198, - "Ccedil": 199, - "Egrave": 200, - "Eacute": 201, - "Ecirc": 202, - "Euml": 203, - "Igrave": 204, - "Iacute": 205, - "Icirc": 206, - "Iuml": 207, - "ETH": 208, - "Ntilde": 209, - "Ograve": 210, - "Oacute": 211, - "Ocirc": 212, - "Otilde": 213, - "Ouml": 214, - "times": 215, - "Oslash": 216, - "Ugrave": 217, - "Uacute": 218, - "Ucirc": 219, - "Uuml": 220, - "Yacute": 221, - "THORN": 222, - "szlig": 223, - "agrave": 224, - "aacute": 225, - "acirc": 226, - "atilde": 227, - "auml": 228, - "aring": 229, - "aelig": 230, - "ccedil": 231, - "egrave": 232, - "eacute": 233, - "ecirc": 234, - "euml": 235, - "igrave": 236, - "iacute": 237, - "icirc": 238, - "iuml": 239, - "eth": 240, - "ntilde": 241, - "ograve": 242, - "oacute": 243, - "ocirc": 244, - "otilde": 245, - "ouml": 246, - "divide": 247, - "oslash": 248, - "ugrave": 249, - "uacute": 250, - "ucirc": 251, - "uuml": 252, - "yacute": 253, - "thorn": 254, - "yuml": 255, - "OElig": 338, - "oelig": 339, - "Scaron": 352, - "scaron": 353, - "Yuml": 376, - "fnof": 402, - "circ": 710, - "tilde": 732, - "Alpha": 913, - "Beta": 914, - "Gamma": 915, - "Delta": 916, - "Epsilon": 917, - "Zeta": 918, - "Eta": 919, - "Theta": 920, - "Iota": 921, - "Kappa": 922, - "Lambda": 923, - "Mu": 924, - "Nu": 925, - "Xi": 926, - "Omicron": 927, - "Pi": 928, - "Rho": 929, - "Sigma": 931, - "Tau": 932, - "Upsilon": 933, - "Phi": 934, - "Chi": 935, - "Psi": 936, - "Omega": 937, - "alpha": 945, - "beta": 946, - "gamma": 947, - "delta": 948, - "epsilon": 949, - "zeta": 950, - "eta": 951, - "theta": 952, - "iota": 953, - "kappa": 954, - "lambda": 955, - "mu": 956, - "nu": 957, - "xi": 958, - "omicron": 959, - "pi": 960, - "rho": 961, - "sigmaf": 962, - "sigma": 963, - "tau": 964, - "upsilon": 965, - "phi": 966, - "chi": 967, - "psi": 968, - "omega": 969, - "thetasym": 977, - "upsih": 978, - "piv": 982, - "ensp": 8194, - "emsp": 8195, - "thinsp": 8201, - "zwnj": 8204, - "zwj": 8205, - "lrm": 8206, - "rlm": 8207, - "ndash": 8211, - "mdash": 8212, - "lsquo": 8216, - "rsquo": 8217, - "sbquo": 8218, - "ldquo": 8220, - "rdquo": 8221, - "bdquo": 8222, - "dagger": 8224, - "Dagger": 8225, - "bull": 8226, - "hellip": 8230, - "permil": 8240, - "prime": 8242, - "Prime": 8243, - "lsaquo": 8249, - "rsaquo": 8250, - "oline": 8254, - "frasl": 8260, - "euro": 8364, - "image": 8465, - "weierp": 8472, - "real": 8476, - "trade": 8482, - "alefsym": 8501, - "larr": 8592, - "uarr": 8593, - "rarr": 8594, - "darr": 8595, - "harr": 8596, - "crarr": 8629, - "lArr": 8656, - "uArr": 8657, - "rArr": 8658, - "dArr": 8659, - "hArr": 8660, - "forall": 8704, - "part": 8706, - "exist": 8707, - "empty": 8709, - "nabla": 8711, - "isin": 8712, - "notin": 8713, - "ni": 8715, - "prod": 8719, - "sum": 8721, - "minus": 8722, - "lowast": 8727, - "radic": 8730, - "prop": 8733, - "infin": 8734, - "ang": 8736, - "and": 8743, - "or": 8744, - "cap": 8745, - "cup": 8746, - "int": 8747, - "there4": 8756, - "sim": 8764, - "cong": 8773, - "asymp": 8776, - "ne": 8800, - "equiv": 8801, - "le": 8804, - "ge": 8805, - "sub": 8834, - "sup": 8835, - "nsub": 8836, - "sube": 8838, - "supe": 8839, - "oplus": 8853, - "otimes": 8855, - "perp": 8869, - "sdot": 8901, - "vellip": 8942, - "lceil": 8968, - "rceil": 8969, - "lfloor": 8970, - "rfloor": 8971, - "lang": 9001, - "rang": 9002, - "loz": 9674, - "spades": 9824, - "clubs": 9827, - "hearts": 9829, - "diams": 9830, - }, - -}; - -export default HTML; diff --git a/src/core/operations/HTTP.js b/src/core/operations/HTTP.js deleted file mode 100755 index dcb1cc50..00000000 --- a/src/core/operations/HTTP.js +++ /dev/null @@ -1,158 +0,0 @@ -import UAParser from "ua-parser-js"; - - -/** - * HTTP operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const HTTP = { - - /** - * @constant - * @default - */ - METHODS: [ - "GET", "POST", "HEAD", - "PUT", "PATCH", "DELETE", - "CONNECT", "TRACE", "OPTIONS" - ], - - - /** - * Strip HTTP headers operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runStripHeaders: function(input, args) { - let headerEnd = input.indexOf("\r\n\r\n"); - headerEnd = (headerEnd < 0) ? input.indexOf("\n\n") + 2 : headerEnd + 4; - - return (headerEnd < 2) ? input : input.slice(headerEnd, input.length); - }, - - - /** - * Parse User Agent operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseUserAgent: function(input, args) { - const ua = UAParser(input); - return `Browser - Name: ${ua.browser.name || "unknown"} - Version: ${ua.browser.version || "unknown"} -Device - Model: ${ua.device.model || "unknown"} - Type: ${ua.device.type || "unknown"} - Vendor: ${ua.device.vendor || "unknown"} -Engine - Name: ${ua.engine.name || "unknown"} - Version: ${ua.engine.version || "unknown"} -OS - Name: ${ua.os.name || "unknown"} - Version: ${ua.os.version || "unknown"} -CPU - Architecture: ${ua.cpu.architecture || "unknown"}`; - }, - - - /** - * @constant - * @default - */ - MODE: [ - "Cross-Origin Resource Sharing", - "No CORS (limited to HEAD, GET or POST)", - ], - - /** - * Lookup table for HTTP modes - * - * @private - * @constant - */ - _modeLookup: { - "Cross-Origin Resource Sharing": "cors", - "No CORS (limited to HEAD, GET or POST)": "no-cors", - }, - - /** - * HTTP request operation. - * - * @author tlwr [toby@toby.codes] - * @author n1474335 [n1474335@gmail.com] - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHTTPRequest(input, args) { - const method = args[0], - url = args[1], - headersText = args[2], - mode = args[3], - showResponseMetadata = args[4]; - - if (url.length === 0) return ""; - - let headers = new Headers(); - headersText.split(/\r?\n/).forEach(line => { - line = line.trim(); - - if (line.length === 0) return; - - let split = line.split(":"); - if (split.length !== 2) throw `Could not parse header in line: ${line}`; - - headers.set(split[0].trim(), split[1].trim()); - }); - - let config = { - method: method, - headers: headers, - mode: HTTP._modeLookup[mode], - cache: "no-cache", - }; - - if (method !== "GET" && method !== "HEAD") { - config.body = input; - } - - return fetch(url, config) - .then(r => { - if (r.status === 0 && r.type === "opaque") { - return "Error: Null response. Try setting the connection mode to CORS."; - } - - if (showResponseMetadata) { - let headers = ""; - for (let pair of r.headers.entries()) { - headers += " " + pair[0] + ": " + pair[1] + "\n"; - } - return r.text().then(b => { - return "####\n Status: " + r.status + " " + r.statusText + - "\n Exposed headers:\n" + headers + "####\n\n" + b; - }); - } - return r.text(); - }) - .catch(e => { - return e.toString() + - "\n\nThis error could be caused by one of the following:\n" + - " - An invalid URL\n" + - " - Making a request to an insecure resource (HTTP) from a secure source (HTTPS)\n" + - " - Making a cross-origin request to a server which does not support CORS\n"; - }); - }, - -}; - -export default HTTP; diff --git a/src/core/operations/HTTPRequest.mjs b/src/core/operations/HTTPRequest.mjs new file mode 100644 index 00000000..6846f97a --- /dev/null +++ b/src/core/operations/HTTPRequest.mjs @@ -0,0 +1,146 @@ +/** + * @author tlwr [toby@toby.codes] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * HTTP request operation + */ +class HTTPRequest extends Operation { + + /** + * HTTPRequest constructor + */ + constructor() { + super(); + + this.name = "HTTP request"; + this.module = "Default"; + this.description = [ + "Makes an HTTP request and returns the response.", + "

", + "This operation supports different HTTP verbs like GET, POST, PUT, etc.", + "

", + "You can add headers line by line in the format Key: Value", + "

", + "The status code of the response, along with a limited selection of exposed headers, can be viewed by checking the 'Show response metadata' option. Only a limited set of response headers are exposed by the browser for security reasons.", + ].join("\n"); + this.inputType = "string"; + this.outputType = "string"; + this.manualBake = true; + this.args = [ + { + "name": "Method", + "type": "option", + "value": [ + "GET", "POST", "HEAD", + "PUT", "PATCH", "DELETE", + "CONNECT", "TRACE", "OPTIONS" + ] + }, + { + "name": "URL", + "type": "string", + "value": "" + }, + { + "name": "Headers", + "type": "text", + "value": "" + }, + { + "name": "Mode", + "type": "option", + "value": [ + "Cross-Origin Resource Sharing", + "No CORS (limited to HEAD, GET or POST)", + ] + }, + { + "name": "Show response metadata", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [method, url, headersText, mode, showResponseMetadata] = args; + + if (url.length === 0) return ""; + + const headers = new Headers(); + headersText.split(/\r?\n/).forEach(line => { + line = line.trim(); + + if (line.length === 0) return; + + const split = line.split(":"); + if (split.length !== 2) throw `Could not parse header in line: ${line}`; + + headers.set(split[0].trim(), split[1].trim()); + }); + + const config = { + method: method, + headers: headers, + mode: modeLookup[mode], + cache: "no-cache", + }; + + if (method !== "GET" && method !== "HEAD") { + config.body = input; + } + + return fetch(url, config) + .then(r => { + if (r.status === 0 && r.type === "opaque") { + throw new OperationError("Error: Null response. Try setting the connection mode to CORS."); + } + + if (showResponseMetadata) { + let headers = ""; + for (const pair of r.headers.entries()) { + headers += " " + pair[0] + ": " + pair[1] + "\n"; + } + return r.text().then(b => { + return "####\n Status: " + r.status + " " + r.statusText + + "\n Exposed headers:\n" + headers + "####\n\n" + b; + }); + } + return r.text(); + }) + .catch(e => { + throw new OperationError(e.toString() + + "\n\nThis error could be caused by one of the following:\n" + + " - An invalid URL\n" + + " - Making a request to an insecure resource (HTTP) from a secure source (HTTPS)\n" + + " - Making a cross-origin request to a server which does not support CORS\n"); + }); + } + +} + + +/** + * Lookup table for HTTP modes + * + * @private + */ +const modeLookup = { + "Cross-Origin Resource Sharing": "cors", + "No CORS (limited to HEAD, GET or POST)": "no-cors", +}; + + +export default HTTPRequest; diff --git a/src/core/operations/HammingDistance.mjs b/src/core/operations/HammingDistance.mjs new file mode 100644 index 00000000..173ca3e7 --- /dev/null +++ b/src/core/operations/HammingDistance.mjs @@ -0,0 +1,97 @@ +/** + * @author GCHQ Contributor [2] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {fromHex} from "../lib/Hex"; +import OperationError from "../errors/OperationError"; + +/** + * Hamming Distance operation + */ +class HammingDistance extends Operation { + + /** + * HammingDistance constructor + */ + constructor() { + super(); + + this.name = "Hamming Distance"; + this.module = "Default"; + this.description = "In information theory, the Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different. In other words, it measures the minimum number of substitutions required to change one string into the other, or the minimum number of errors that could have transformed one string into the other. In a more general context, the Hamming distance is one of several string metrics for measuring the edit distance between two sequences."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "binaryShortString", + "value": "\\n\\n" + }, + { + "name": "Unit", + "type": "option", + "value": ["Byte", "Bit"] + }, + { + "name": "Input type", + "type": "option", + "value": ["Raw string", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = args[0], + byByte = args[1] === "Byte", + inputType = args[2], + samples = input.split(delim); + + if (samples.length !== 2) { + throw new OperationError("Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter."); + } + + if (samples[0].length !== samples[1].length) { + throw new OperationError("Error: Both inputs must be of the same length."); + } + + if (inputType === "Hex") { + samples[0] = fromHex(samples[0]); + samples[1] = fromHex(samples[1]); + } else { + samples[0] = Utils.strToByteArray(samples[0]); + samples[1] = Utils.strToByteArray(samples[1]); + } + + let dist = 0; + + for (let i = 0; i < samples[0].length; i++) { + const lhs = samples[0][i], + rhs = samples[1][i]; + + if (byByte && lhs !== rhs) { + dist++; + } else if (!byByte) { + let xord = lhs ^ rhs; + + while (xord) { + dist++; + xord &= xord - 1; + } + } + } + + return dist.toString(); + } + +} + +export default HammingDistance; diff --git a/src/core/operations/Hash.js b/src/core/operations/Hash.js deleted file mode 100755 index 89142adb..00000000 --- a/src/core/operations/Hash.js +++ /dev/null @@ -1,785 +0,0 @@ -import Utils from "../Utils.js"; -import CryptoApi from "babel-loader!crypto-api"; -import MD6 from "node-md6"; -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"; - - -/** - * Hashing operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Hash = { - - /** - * Generic hash function. - * - * @param {string} name - * @param {ArrayBuffer} input - * @param {Object} [options={}] - * @returns {string} - */ - runHash: function(name, input, options={}) { - const msg = Utils.arrayBufferToStr(input, false), - hasher = CryptoApi.getHasher(name, options); - hasher.update(msg); - return CryptoApi.encoder.toHex(hasher.finalize()); - }, - - - /** - * MD2 operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runMD2: function (input, args) { - return Hash.runHash("md2", input); - }, - - - /** - * MD4 operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runMD4: function (input, args) { - return Hash.runHash("md4", input); - }, - - - /** - * MD5 operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runMD5: function (input, args) { - return Hash.runHash("md5", input); - }, - - - /** - * @constant - * @default - */ - MD6_SIZE: 256, - /** - * @constant - * @default - */ - MD6_LEVELS: 64, - - /** - * MD6 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMD6: function (input, args) { - const size = args[0], - levels = args[1], - key = args[2]; - - if (size < 0 || size > 512) - return "Size must be between 0 and 512"; - if (levels < 0) - return "Levels must be greater than 0"; - - return MD6.getHashOfText(input, size, key, levels); - }, - - - /** - * SHA0 operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runSHA0: function (input, args) { - return Hash.runHash("sha0", input); - }, - - - /** - * SHA1 operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runSHA1: function (input, args) { - return Hash.runHash("sha1", input); - }, - - - /** - * @constant - * @default - */ - SHA2_SIZE: ["512", "256", "384", "224", "512/256", "512/224"], - - /** - * SHA2 operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runSHA2: function (input, args) { - const size = args[0]; - return Hash.runHash("sha" + size, input); - }, - - - /** - * @constant - * @default - */ - SHA3_SIZE: ["512", "384", "256", "224"], - - /** - * SHA3 operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runSHA3: function (input, args) { - const size = parseInt(args[0], 10); - let algo; - - switch (size) { - case 224: - algo = SHA3.sha3_224; - break; - case 384: - algo = SHA3.sha3_384; - break; - case 256: - algo = SHA3.sha3_256; - break; - case 512: - algo = SHA3.sha3_512; - break; - default: - return "Invalid size"; - } - - return algo(input); - }, - - - /** - * @constant - * @default - */ - KECCAK_SIZE: ["512", "384", "256", "224"], - - /** - * Keccak operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runKeccak: function (input, args) { - const size = parseInt(args[0], 10); - let algo; - - switch (size) { - case 224: - algo = SHA3.keccak224; - break; - case 384: - algo = SHA3.keccak384; - break; - case 256: - algo = SHA3.keccak256; - break; - case 512: - algo = SHA3.keccak512; - break; - default: - return "Invalid size"; - } - - return algo(input); - }, - - - /** - * @constant - * @default - */ - SHAKE_CAPACITY: ["256", "128"], - /** - * @constant - * @default - */ - SHAKE_SIZE: 512, - - /** - * Shake operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runShake: function (input, args) { - const capacity = parseInt(args[0], 10), - size = args[1]; - let algo; - - if (size < 0) - return "Size must be greater than 0"; - - switch (capacity) { - case 128: - algo = SHA3.shake128; - break; - case 256: - algo = SHA3.shake256; - break; - default: - return "Invalid size"; - } - - return algo(input, size); - }, - - - /** - * @constant - * @default - */ - RIPEMD_SIZE: ["320", "256", "160", "128"], - - /** - * RIPEMD operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runRIPEMD: function (input, args) { - const size = args[0]; - return Hash.runHash("ripemd" + size, input); - }, - - - /** - * HAS-160 operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runHAS: function (input, args) { - return Hash.runHash("has160", input); - }, - - - /** - * @constant - * @default - */ - WHIRLPOOL_VARIANT: ["Whirlpool", "Whirlpool-T", "Whirlpool-0"], - - /** - * Whirlpool operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runWhirlpool: function (input, args) { - const variant = args[0].toLowerCase(); - return Hash.runHash(variant, input); - }, - - - /** - * @constant - * @default - */ - SNEFRU_ROUNDS: ["8", "4", "2"], - /** - * @constant - * @default - */ - SNEFRU_SIZE: ["256", "128"], - - /** - * Snefru operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runSnefru: function (input, args) { - return Hash.runHash("snefru", input, { - rounds: args[0], - length: args[1] - }); - }, - - - /** - * CTPH operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runCTPH: function (input, args) { - return ctph.digest(input); - }, - - - /** - * SSDEEP operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSSDEEP: function (input, args) { - return ssdeep.digest(input); - }, - - - /** - * @constant - * @default - */ - DELIM_OPTIONS: ["Line feed", "CRLF", "Space", "Comma"], - - /** - * Compare CTPH hashes operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {Number} - */ - runCompareCTPH: function (input, args) { - const samples = input.split(Utils.charRep[args[0]]); - if (samples.length !== 2) throw "Incorrect number of samples."; - return ctph.similarity(samples[0], samples[1]); - }, - - - /** - * Compare SSDEEP hashes operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {Number} - */ - runCompareSSDEEP: function (input, args) { - const samples = input.split(Utils.charRep[args[0]]); - if (samples.length !== 2) throw "Incorrect number of samples."; - return ssdeep.similarity(samples[0], samples[1]); - }, - - - /** - * @constant - * @default - */ - HMAC_FUNCTIONS: [ - "MD2", - "MD4", - "MD5", - "SHA0", - "SHA1", - "SHA224", - "SHA256", - "SHA384", - "SHA512", - "SHA512/224", - "SHA512/256", - "RIPEMD128", - "RIPEMD160", - "RIPEMD256", - "RIPEMD320", - "HAS160", - "Whirlpool", - "Whirlpool-0", - "Whirlpool-T", - "Snefru" - ], - - /** - * HMAC operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runHMAC: function (input, args) { - const key = args[0], - hashFunc = args[1].toLowerCase(), - msg = Utils.arrayBufferToStr(input, false), - hasher = CryptoApi.getHasher(hashFunc); - - // Horrible shim to fix constructor bug. Reported in nf404/crypto-api#8 - hasher.reset = () => { - hasher.state = {}; - const tmp = new hasher.constructor(); - hasher.state = tmp.state; - }; - - const mac = CryptoApi.getHmac(CryptoApi.encoder.fromUtf(key), hasher); - mac.update(msg); - return CryptoApi.encoder.toHex(mac.finalize()); - }, - - - /** - * @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. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runAll: function (input, args) { - const arrayBuffer = input, - str = Utils.arrayBufferToStr(arrayBuffer, false), - byteArray = new Uint8Array(arrayBuffer), - output = "MD2: " + Hash.runMD2(arrayBuffer, []) + - "\nMD4: " + Hash.runMD4(arrayBuffer, []) + - "\nMD5: " + Hash.runMD5(arrayBuffer, []) + - "\nMD6: " + Hash.runMD6(str, []) + - "\nSHA0: " + Hash.runSHA0(arrayBuffer, []) + - "\nSHA1: " + Hash.runSHA1(arrayBuffer, []) + - "\nSHA2 224: " + Hash.runSHA2(arrayBuffer, ["224"]) + - "\nSHA2 256: " + Hash.runSHA2(arrayBuffer, ["256"]) + - "\nSHA2 384: " + Hash.runSHA2(arrayBuffer, ["384"]) + - "\nSHA2 512: " + Hash.runSHA2(arrayBuffer, ["512"]) + - "\nSHA3 224: " + Hash.runSHA3(arrayBuffer, ["224"]) + - "\nSHA3 256: " + Hash.runSHA3(arrayBuffer, ["256"]) + - "\nSHA3 384: " + Hash.runSHA3(arrayBuffer, ["384"]) + - "\nSHA3 512: " + Hash.runSHA3(arrayBuffer, ["512"]) + - "\nKeccak 224: " + Hash.runKeccak(arrayBuffer, ["224"]) + - "\nKeccak 256: " + Hash.runKeccak(arrayBuffer, ["256"]) + - "\nKeccak 384: " + Hash.runKeccak(arrayBuffer, ["384"]) + - "\nKeccak 512: " + Hash.runKeccak(arrayBuffer, ["512"]) + - "\nShake 128: " + Hash.runShake(arrayBuffer, ["128", 256]) + - "\nShake 256: " + Hash.runShake(arrayBuffer, ["256", 512]) + - "\nRIPEMD-128: " + Hash.runRIPEMD(arrayBuffer, ["128"]) + - "\nRIPEMD-160: " + Hash.runRIPEMD(arrayBuffer, ["160"]) + - "\nRIPEMD-256: " + Hash.runRIPEMD(arrayBuffer, ["256"]) + - "\nRIPEMD-320: " + Hash.runRIPEMD(arrayBuffer, ["320"]) + - "\nHAS-160: " + Hash.runHAS(arrayBuffer, []) + - "\nWhirlpool-0: " + Hash.runWhirlpool(arrayBuffer, ["Whirlpool-0"]) + - "\nWhirlpool-T: " + Hash.runWhirlpool(arrayBuffer, ["Whirlpool-T"]) + - "\nWhirlpool: " + Hash.runWhirlpool(arrayBuffer, ["Whirlpool"]) + - "\nSSDEEP: " + Hash.runSSDEEP(str) + - "\nCTPH: " + Hash.runCTPH(str) + - "\n\nChecksums:" + - "\nFletcher-8: " + Checksum.runFletcher8(byteArray, []) + - "\nFletcher-16: " + Checksum.runFletcher16(byteArray, []) + - "\nFletcher-32: " + Checksum.runFletcher32(byteArray, []) + - "\nFletcher-64: " + Checksum.runFletcher64(byteArray, []) + - "\nAdler-32: " + Checksum.runAdler32(byteArray, []) + - "\nCRC-16: " + Checksum.runCRC16(str, []) + - "\nCRC-32: " + Checksum.runCRC32(str, []); - - return output; - }, - - - /** - * Analyse hash operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAnalyse: function(input, args) { - input = input.replace(/\s/g, ""); - - let output = "", - byteLength = input.length / 2, - bitLength = byteLength * 8, - possibleHashFunctions = []; - - if (!/^[a-f0-9]+$/i.test(input)) { - return "Invalid hash"; - } - - output += "Hash length: " + input.length + "\n" + - "Byte length: " + byteLength + "\n" + - "Bit length: " + bitLength + "\n\n" + - "Based on the length, this hash could have been generated by one of the following hashing functions:\n"; - - switch (bitLength) { - case 4: - possibleHashFunctions = [ - "Fletcher-4", - "Luhn algorithm", - "Verhoeff algorithm", - ]; - break; - case 8: - possibleHashFunctions = [ - "Fletcher-8", - ]; - break; - case 16: - possibleHashFunctions = [ - "BSD checksum", - "CRC-16", - "SYSV checksum", - "Fletcher-16" - ]; - break; - case 32: - possibleHashFunctions = [ - "CRC-32", - "Fletcher-32", - "Adler-32", - ]; - break; - case 64: - possibleHashFunctions = [ - "CRC-64", - "RIPEMD-64", - "SipHash", - ]; - break; - case 128: - possibleHashFunctions = [ - "MD5", - "MD4", - "MD2", - "HAVAL-128", - "RIPEMD-128", - "Snefru", - "Tiger-128", - ]; - break; - case 160: - possibleHashFunctions = [ - "SHA-1", - "SHA-0", - "FSB-160", - "HAS-160", - "HAVAL-160", - "RIPEMD-160", - "Tiger-160", - ]; - break; - case 192: - possibleHashFunctions = [ - "Tiger", - "HAVAL-192", - ]; - break; - case 224: - possibleHashFunctions = [ - "SHA-224", - "SHA3-224", - "ECOH-224", - "FSB-224", - "HAVAL-224", - ]; - break; - case 256: - possibleHashFunctions = [ - "SHA-256", - "SHA3-256", - "BLAKE-256", - "ECOH-256", - "FSB-256", - "GOST", - "Grøstl-256", - "HAVAL-256", - "PANAMA", - "RIPEMD-256", - "Snefru", - ]; - break; - case 320: - possibleHashFunctions = [ - "RIPEMD-320", - ]; - break; - case 384: - possibleHashFunctions = [ - "SHA-384", - "SHA3-384", - "ECOH-384", - "FSB-384", - ]; - break; - case 512: - possibleHashFunctions = [ - "SHA-512", - "SHA3-512", - "BLAKE-512", - "ECOH-512", - "FSB-512", - "Grøstl-512", - "JH", - "MD6", - "Spectral Hash", - "SWIFFT", - "Whirlpool", - ]; - break; - case 1024: - possibleHashFunctions = [ - "Fowler-Noll-Vo", - ]; - break; - default: - possibleHashFunctions = [ - "Unknown" - ]; - break; - } - - return output + possibleHashFunctions.join("\n"); - }, - -}; - -export default Hash; diff --git a/src/core/operations/HaversineDistance.mjs b/src/core/operations/HaversineDistance.mjs new file mode 100644 index 00000000..3e7bc506 --- /dev/null +++ b/src/core/operations/HaversineDistance.mjs @@ -0,0 +1,58 @@ +/** + * @author Dachande663 [dachande663@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * HaversineDistance operation + */ +class HaversineDistance extends Operation { + + /** + * HaversineDistance constructor + */ + constructor() { + super(); + + this.name = "Haversine distance"; + this.module = "Default"; + this.description = "Returns the distance between two pairs of GPS latitude and longitude co-ordinates in metres.

e.g. 51.487263,-0.124323, 38.9517,-77.1467"; + this.inputType = "string"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + + const values = input.match(/^(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?)$/); + if (!values) { + throw new OperationError("Input must in the format lat1, lng1, lat2, lng2"); + } + + const lat1 = parseFloat(values[1]); + const lng1 = parseFloat(values[3]); + const lat2 = parseFloat(values[6]); + const lng2 = parseFloat(values[8]); + + const TO_RAD = Math.PI / 180; + const dLat = (lat2-lat1) * TO_RAD; + const dLng = (lng2-lng1) * TO_RAD; + const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * TO_RAD) * Math.cos(lat2 * TO_RAD) * Math.sin(dLng/2) * Math.sin(dLng/2); + const metres = 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + + return metres; + + } + +} + +export default HaversineDistance; diff --git a/src/core/operations/Head.mjs b/src/core/operations/Head.mjs new file mode 100644 index 00000000..76e17c59 --- /dev/null +++ b/src/core/operations/Head.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Head operation + */ +class Head extends Operation { + + /** + * Head constructor + */ + constructor() { + super(); + + this.name = "Head"; + this.module = "Default"; + this.description = "Like the UNIX head utility.
Gets the first n lines.
You can select all but the last n lines by entering a negative value for n.
The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "Number", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let delimiter = args[0]; + const number = args[1]; + + delimiter = Utils.charRep(delimiter); + const splitInput = input.split(delimiter); + + return splitInput + .filter((line, lineIndex) => { + lineIndex += 1; + + if (number < 0) { + return lineIndex <= splitInput.length + number; + } else { + return lineIndex <= number; + } + }) + .join(delimiter); + } + +} + +export default Head; diff --git a/src/core/operations/HexToObjectIdentifier.mjs b/src/core/operations/HexToObjectIdentifier.mjs new file mode 100644 index 00000000..00512e1a --- /dev/null +++ b/src/core/operations/HexToObjectIdentifier.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Hex to Object Identifier operation + */ +class HexToObjectIdentifier extends Operation { + + /** + * HexToObjectIdentifier constructor + */ + constructor() { + super(); + + this.name = "Hex to Object Identifier"; + this.module = "PublicKey"; + this.description = "Converts a hexadecimal string into an object identifier (OID)."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\s/g, "")); + } + +} + +export default HexToObjectIdentifier; diff --git a/src/core/operations/HexToPEM.mjs b/src/core/operations/HexToPEM.mjs new file mode 100644 index 00000000..c08888d4 --- /dev/null +++ b/src/core/operations/HexToPEM.mjs @@ -0,0 +1,46 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Hex to PEM operation + */ +class HexToPEM extends Operation { + + /** + * HexToPEM constructor + */ + constructor() { + super(); + + this.name = "Hex to PEM"; + this.module = "PublicKey"; + this.description = "Converts a hexadecimal DER (Distinguished Encoding Rules) string into PEM (Privacy Enhanced Mail) format."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Header string", + "type": "string", + "value": "CERTIFICATE" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\s/g, ""), args[0]); + } + +} + +export default HexToPEM; diff --git a/src/core/operations/IP.js b/src/core/operations/IP.js deleted file mode 100755 index 4c3eb5de..00000000 --- a/src/core/operations/IP.js +++ /dev/null @@ -1,1085 +0,0 @@ -import Utils from "../Utils.js"; -import Checksum from "./Checksum.js"; -import {BigInteger} from "jsbn"; - - -/** - * Internet Protocol address operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const IP = { - - /** - * @constant - * @default - */ - INCLUDE_NETWORK_INFO: true, - /** - * @constant - * @default - */ - ENUMERATE_ADDRESSES: true, - /** - * @constant - * @default - */ - ALLOW_LARGE_LIST: false, - - /** - * Parse IP range operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseIpRange: function (input, args) { - let includeNetworkInfo = args[0], - enumerateAddresses = args[1], - allowLargeList = args[2]; - - // Check what type of input we are looking at - let ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/, - ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, - ipv6CidrRegex = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\/(\d\d?\d?)\s*$/i, - ipv6RangeRegex = /^\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*-\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\17)::|:\b|(?![\dA-F])))|(?!\16\17)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i, - match; - - if ((match = ipv4CidrRegex.exec(input))) { - return IP._ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); - } else if ((match = ipv4RangeRegex.exec(input))) { - return IP._ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); - } else if ((match = ipv6CidrRegex.exec(input))) { - return IP._ipv6CidrRange(match, includeNetworkInfo); - } else if ((match = ipv6RangeRegex.exec(input))) { - return IP._ipv6HyphenatedRange(match, includeNetworkInfo); - } else { - return "Invalid input.\n\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported."; - } - }, - - - /** - * @constant - * @default - */ - IPV4_REGEX: /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, - /** - * @constant - * @default - */ - IPV6_REGEX: /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i, - - /** - * Parse IPv6 address operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseIPv6: function (input, args) { - let match, - output = ""; - - if ((match = IP.IPV6_REGEX.exec(input))) { - let ipv6 = IP._strToIpv6(match[1]), - longhand = IP._ipv6ToStr(ipv6), - shorthand = IP._ipv6ToStr(ipv6, true); - - output += "Longhand: " + longhand + "\nShorthand: " + shorthand + "\n"; - - // Detect reserved addresses - if (shorthand === "::") { - // Unspecified address - output += "\nUnspecified address corresponding to 0.0.0.0/32 in IPv4."; - output += "\nUnspecified address range: ::/128"; - } else if (shorthand === "::1") { - // Loopback address - output += "\nLoopback address to the local host corresponding to 127.0.0.1/8 in IPv4."; - output += "\nLoopback addresses range: ::1/128"; - } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && - ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0xffff) { - // IPv4-mapped IPv6 address - output += "\nIPv4-mapped IPv6 address detected. IPv6 clients will be handled natively by default, and IPv4 clients appear as IPv6 clients at their IPv4-mapped IPv6 address."; - output += "\nMapped IPv4 address: " + IP._ipv4ToStr((ipv6[6] << 16) + ipv6[7]); - output += "\nIPv4-mapped IPv6 addresses range: ::ffff:0:0/96"; - } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && - ipv6[3] === 0 && ipv6[4] === 0xffff && ipv6[5] === 0) { - // IPv4-translated address - output += "\nIPv4-translated address detected. Used by Stateless IP/ICMP Translation (SIIT). See RFCs 6145 and 6052 for more details."; - output += "\nTranslated IPv4 address: " + IP._ipv4ToStr((ipv6[6] << 16) + ipv6[7]); - output += "\nIPv4-translated addresses range: ::ffff:0:0:0/96"; - } else if (ipv6[0] === 0x100) { - // Discard prefix per RFC 6666 - output += "\nDiscard prefix detected. This is used when forwarding traffic to a sinkhole router to mitigate the effects of a denial-of-service attack. See RFC 6666 for more details."; - output += "\nDiscard range: 100::/64"; - } else if (ipv6[0] === 0x64 && ipv6[1] === 0xff9b && ipv6[2] === 0 && - ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0) { - // IPv4/IPv6 translation per RFC 6052 - output += "\n'Well-Known' prefix for IPv4/IPv6 translation detected. See RFC 6052 for more details."; - output += "\nTranslated IPv4 address: " + IP._ipv4ToStr((ipv6[6] << 16) + ipv6[7]); - output += "\n'Well-Known' prefix range: 64:ff9b::/96"; - } else if (ipv6[0] === 0x2001 && ipv6[1] === 0) { - // Teredo tunneling - output += "\nTeredo tunneling IPv6 address detected\n"; - let serverIpv4 = (ipv6[2] << 16) + ipv6[3], - udpPort = (~ipv6[5]) & 0xffff, - clientIpv4 = ~((ipv6[6] << 16) + ipv6[7]), - flagCone = (ipv6[4] >>> 15) & 1, - flagR = (ipv6[4] >>> 14) & 1, - flagRandom1 = (ipv6[4] >>> 10) & 15, - flagUg = (ipv6[4] >>> 8) & 3, - flagRandom2 = ipv6[4] & 255; - - output += "\nServer IPv4 address: " + IP._ipv4ToStr(serverIpv4) + - "\nClient IPv4 address: " + IP._ipv4ToStr(clientIpv4) + - "\nClient UDP port: " + udpPort + - "\nFlags:" + - "\n\tCone: " + flagCone; - - if (flagCone) { - output += " (Client is behind a cone NAT)"; - } else { - output += " (Client is not behind a cone NAT)"; - } - - output += "\n\tR: " + flagR; - - if (flagR) { - output += " Error: This flag should be set to 0. See RFC 5991 and RFC 4380."; - } - - output += "\n\tRandom1: " + Utils.bin(flagRandom1, 4) + - "\n\tUG: " + Utils.bin(flagUg, 2); - - if (flagUg) { - output += " Error: This flag should be set to 00. See RFC 4380."; - } - - output += "\n\tRandom2: " + Utils.bin(flagRandom2, 8); - - if (!flagR && !flagUg && flagRandom1 && flagRandom2) { - output += "\n\nThis is a valid Teredo address which complies with RFC 4380 and RFC 5991."; - } else if (!flagR && !flagUg) { - output += "\n\nThis is a valid Teredo address which complies with RFC 4380, however it does not comply with RFC 5991 (Teredo Security Updates) as there are no randomised bits in the flag field."; - } else { - output += "\n\nThis is an invalid Teredo address."; - } - output += "\n\nTeredo prefix range: 2001::/32"; - } else if (ipv6[0] === 0x2001 && ipv6[1] === 0x2 && ipv6[2] === 0) { - // Benchmarking - output += "\nAssigned to the Benchmarking Methodology Working Group (BMWG) for benchmarking IPv6. Corresponds to 198.18.0.0/15 for benchmarking IPv4. See RFC 5180 for more details."; - output += "\nBMWG range: 2001:2::/48"; - } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x10 && ipv6[1] <= 0x1f) { - // ORCHIDv1 - output += "\nDeprecated, previously ORCHIDv1 (Overlay Routable Cryptographic Hash Identifiers).\nORCHIDv1 range: 2001:10::/28\nORCHIDv2 now uses 2001:20::/28."; - } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x20 && ipv6[1] <= 0x2f) { - // ORCHIDv2 - output += "\nORCHIDv2 (Overlay Routable Cryptographic Hash Identifiers).\nThese are non-routed IPv6 addresses used for Cryptographic Hash Identifiers."; - output += "\nORCHIDv2 range: 2001:20::/28"; - } else if (ipv6[0] === 0x2001 && ipv6[1] === 0xdb8) { - // Documentation - output += "\nThis is a documentation IPv6 address. This range should be used whenever an example IPv6 address is given or to model networking scenarios. Corresponds to 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 in IPv4."; - output += "\nDocumentation range: 2001:db8::/32"; - } else if (ipv6[0] === 0x2002) { - // 6to4 - output += "\n6to4 transition IPv6 address detected. See RFC 3056 for more details." + - "\n6to4 prefix range: 2002::/16"; - - let v4Addr = IP._ipv4ToStr((ipv6[1] << 16) + ipv6[2]), - slaId = ipv6[3], - interfaceIdStr = ipv6[4].toString(16) + ipv6[5].toString(16) + ipv6[6].toString(16) + ipv6[7].toString(16), - interfaceId = new BigInteger(interfaceIdStr, 16); - - output += "\n\nEncapsulated IPv4 address: " + v4Addr + - "\nSLA ID: " + slaId + - "\nInterface ID (base 16): " + interfaceIdStr + - "\nInterface ID (base 10): " + interfaceId.toString(); - } else if (ipv6[0] >= 0xfc00 && ipv6[0] <= 0xfdff) { - // Unique local address - output += "\nThis is a unique local address comparable to the IPv4 private addresses 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. See RFC 4193 for more details."; - output += "\nUnique local addresses range: fc00::/7"; - } else if (ipv6[0] >= 0xfe80 && ipv6[0] <= 0xfebf) { - // Link-local address - output += "\nThis is a link-local address comparable to the auto-configuration addresses 169.254.0.0/16 in IPv4."; - output += "\nLink-local addresses range: fe80::/10"; - } else if (ipv6[0] >= 0xff00) { - // Multicast - output += "\nThis is a reserved multicast address."; - output += "\nMulticast addresses range: ff00::/8"; - } - - - // Detect possible EUI-64 addresses - if ((ipv6[5] & 0xff === 0xff) && (ipv6[6] >>> 8 === 0xfe)) { - output += "\n\nThis IPv6 address contains a modified EUI-64 address, identified by the presence of FF:FE in the 12th and 13th octets."; - - let intIdent = Utils.hex(ipv6[4] >>> 8) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + - Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[5] & 0xff) + ":" + - Utils.hex(ipv6[6] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + - Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff), - mac = Utils.hex((ipv6[4] >>> 8) ^ 2) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + - Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + - Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff); - output += "\nInterface identifier: " + intIdent + - "\nMAC address: " + mac; - } - } else { - return "Invalid IPv6 address"; - } - return output; - }, - - - /** - * @constant - * @default - */ - IP_FORMAT_LIST: ["Dotted Decimal", "Decimal", "Hex"], - - /** - * Change IP format operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runChangeIpFormat: function(input, args) { - let inFormat = args[0], - outFormat = args[1], - lines = input.split("\n"), - output = "", - j = 0; - - - for (let i = 0; i < lines.length; i++) { - if (lines[i] === "") continue; - let baIp = []; - let octets; - let decimal; - - if (inFormat === outFormat) { - output += lines[i] + "\n"; - continue; - } - - // Convert to byte array IP from input format - switch (inFormat) { - case "Dotted Decimal": - octets = lines[i].split("."); - for (j = 0; j < octets.length; j++) { - baIp.push(parseInt(octets[j], 10)); - } - break; - case "Decimal": - decimal = lines[i].toString(); - baIp.push(decimal >> 24 & 255); - baIp.push(decimal >> 16 & 255); - baIp.push(decimal >> 8 & 255); - baIp.push(decimal & 255); - break; - case "Hex": - baIp = Utils.fromHex(lines[i]); - break; - default: - throw "Unsupported input IP format"; - } - - let ddIp; - let decIp; - let hexIp; - - // Convert byte array IP to output format - switch (outFormat) { - case "Dotted Decimal": - ddIp = ""; - for (j = 0; j < baIp.length; j++) { - ddIp += baIp[j] + "."; - } - output += ddIp.slice(0, ddIp.length-1) + "\n"; - break; - case "Decimal": - decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0; - output += decIp.toString() + "\n"; - break; - case "Hex": - hexIp = ""; - for (j = 0; j < baIp.length; j++) { - hexIp += Utils.hex(baIp[j]); - } - output += hexIp + "\n"; - break; - default: - throw "Unsupported output IP format"; - } - } - - return output.slice(0, output.length-1); - }, - - - /** - * @constant - * @default - */ - DELIM_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon"], - /** - * @constant - * @default - */ - GROUP_CIDR: 24, - /** - * @constant - * @default - */ - GROUP_ONLY_SUBNET: false, - - /** - * Group IP addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runGroupIps: function(input, args) { - let delim = Utils.charRep[args[0]], - cidr = args[1], - onlySubnets = args[2], - ipv4Mask = cidr < 32 ? ~(0xFFFFFFFF >>> cidr) : 0xFFFFFFFF, - ipv6Mask = IP._genIpv6Mask(cidr), - ips = input.split(delim), - ipv4Networks = {}, - ipv6Networks = {}, - match = null, - output = "", - ip = null, - network = null, - networkStr = "", - i; - - if (cidr < 0 || cidr > 127) { - return "CIDR must be less than 32 for IPv4 or 128 for IPv6"; - } - - // Parse all IPs and add to network dictionary - for (i = 0; i < ips.length; i++) { - if ((match = IP.IPV4_REGEX.exec(ips[i]))) { - ip = IP._strToIpv4(match[1]) >>> 0; - network = ip & ipv4Mask; - - if (ipv4Networks.hasOwnProperty(network)) { - ipv4Networks[network].push(ip); - } else { - ipv4Networks[network] = [ip]; - } - } else if ((match = IP.IPV6_REGEX.exec(ips[i]))) { - ip = IP._strToIpv6(match[1]); - network = []; - networkStr = ""; - - for (let j = 0; j < 8; j++) { - network.push(ip[j] & ipv6Mask[j]); - } - - networkStr = IP._ipv6ToStr(network, true); - - if (ipv6Networks.hasOwnProperty(networkStr)) { - ipv6Networks[networkStr].push(ip); - } else { - ipv6Networks[networkStr] = [ip]; - } - } - } - - // Sort IPv4 network dictionaries and print - for (network in ipv4Networks) { - ipv4Networks[network] = ipv4Networks[network].sort(); - - output += IP._ipv4ToStr(network) + "/" + cidr + "\n"; - - if (!onlySubnets) { - for (i = 0; i < ipv4Networks[network].length; i++) { - output += " " + IP._ipv4ToStr(ipv4Networks[network][i]) + "\n"; - } - output += "\n"; - } - } - - // Sort IPv6 network dictionaries and print - for (networkStr in ipv6Networks) { - //ipv6Networks[networkStr] = ipv6Networks[networkStr].sort(); TODO - - output += networkStr + "/" + cidr + "\n"; - - if (!onlySubnets) { - for (i = 0; i < ipv6Networks[networkStr].length; i++) { - output += " " + IP._ipv6ToStr(ipv6Networks[networkStr][i], true) + "\n"; - } - output += "\n"; - } - } - - return output; - }, - - - /** - * @constant - * @default - */ - IP_HEADER_FORMAT: ["Hex", "Raw"], - - /** - * Parse IPv4 header operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {html} - */ - runParseIPv4Header: function(input, args) { - let format = args[0], - output; - - if (format === "Hex") { - input = Utils.fromHex(input); - } else if (format === "Raw") { - input = Utils.strToByteArray(input); - } else { - return "Unrecognised input format."; - } - - let version = (input[0] >>> 4) & 0x0f, - ihl = input[0] & 0x0f, - dscp = (input[1] >>> 2) & 0x3f, - ecn = input[1] & 0x03, - length = input[2] << 8 | input[3], - identification = input[4] << 8 | input[5], - flags = (input[6] >>> 5) & 0x07, - fragOffset = (input[6] & 0x1f) << 8 | input[7], - ttl = input[8], - protocol = input[9], - checksum = input[10] << 8 | input[11], - srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15], - dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19], - checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20)), - options = []; - - // Version - if (version !== 4) { - version = version + " (Error: for IPv4 headers, this should always be set to 4)"; - } - - // IHL - if (ihl < 5) { - ihl = ihl + " (Error: this should always be at least 5)"; - } else if (ihl > 5) { - // sort out options... - const optionsLen = ihl * 4 - 20; - options = input.slice(20, optionsLen + 20); - } - - // Protocol - const protocolInfo = IP._protocolLookup[protocol] || {keyword: "", protocol: ""}; - - // Checksum - let correctChecksum = Checksum.runTCPIP(checksumHeader, []), - givenChecksum = Utils.hex(checksum), - checksumResult; - if (correctChecksum === givenChecksum) { - checksumResult = givenChecksum + " (correct)"; - } else { - checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")"; - } - - output = "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; - - if (ihl > 5) { - output += ""; - } - - return output + "
FieldValue
Version" + version + "
Internet Header Length (IHL)" + ihl + " (" + (ihl * 4) + " bytes)
Differentiated Services Code Point (DSCP)" + dscp + "
Explicit Congestion Notification (ECN)" + ecn + "
Total length" + length + " bytes" + - "\n IP header: " + (ihl * 4) + " bytes" + - "\n Data: " + (length - ihl * 4) + " bytes
Identification0x" + Utils.hex(identification) + " (" + identification + ")
Flags0x" + Utils.hex(flags, 2) + - "\n Reserved bit:" + (flags >> 2) + " (must be 0)" + - "\n Don't fragment:" + (flags >> 1 & 1) + - "\n More fragments:" + (flags & 1) + "
Fragment offset" + fragOffset + "
Time-To-Live" + ttl + "
Protocol" + protocol + ", " + protocolInfo.protocol + " (" + protocolInfo.keyword + ")
Header checksum" + checksumResult + "
Source IP address" + IP._ipv4ToStr(srcIP) + "
Destination IP address" + IP._ipv4ToStr(dstIP) + "
Options" + Utils.toHex(options) + "
"; - }, - - - /** - * @constant - * @default - * @private - */ - _LARGE_RANGE_ERROR: "The specified range contains more than 65,536 addresses. Running this query could crash your browser. If you want to run it, select the \"Allow large queries\" option. You are advised to turn off \"Auto Bake\" whilst editing large ranges.", - - /** - * Parses an IPv4 CIDR range (e.g. 192.168.0.0/24) and displays information about it. - * - * @private - * @param {RegExp} cidr - * @param {boolean} includeNetworkInfo - * @param {boolean} enumerateAddresses - * @param {boolean} allowLargeList - * @returns {string} - */ - _ipv4CidrRange: function(cidr, includeNetworkInfo, enumerateAddresses, allowLargeList) { - let output = "", - network = IP._strToIpv4(cidr[1]), - cidrRange = parseInt(cidr[2], 10); - - if (cidrRange < 0 || cidrRange > 31) { - return "IPv4 CIDR must be less than 32"; - } - - let mask = ~(0xFFFFFFFF >>> cidrRange), - ip1 = network & mask, - ip2 = ip1 | ~mask; - - if (includeNetworkInfo) { - output += "Network: " + IP._ipv4ToStr(network) + "\n"; - output += "CIDR: " + cidrRange + "\n"; - output += "Mask: " + IP._ipv4ToStr(mask) + "\n"; - output += "Range: " + IP._ipv4ToStr(ip1) + " - " + IP._ipv4ToStr(ip2) + "\n"; - output += "Total addresses in range: " + (((ip2 - ip1) >>> 0) + 1) + "\n\n"; - } - - if (enumerateAddresses) { - if (cidrRange >= 16 || allowLargeList) { - output += IP._generateIpv4Range(ip1, ip2).join("\n"); - } else { - output += IP._LARGE_RANGE_ERROR; - } - } - return output; - }, - - - /** - * Parses an IPv6 CIDR range (e.g. ff00::/48) and displays information about it. - * - * @private - * @param {RegExp} cidr - * @param {boolean} includeNetworkInfo - * @returns {string} - */ - _ipv6CidrRange: function(cidr, includeNetworkInfo) { - let output = "", - network = IP._strToIpv6(cidr[1]), - cidrRange = parseInt(cidr[cidr.length-1], 10); - - if (cidrRange < 0 || cidrRange > 127) { - return "IPv6 CIDR must be less than 128"; - } - - let mask = IP._genIpv6Mask(cidrRange), - ip1 = new Array(8), - ip2 = new Array(8), - totalDiff = "", - total = new Array(128); - - for (let i = 0; i < 8; i++) { - ip1[i] = network[i] & mask[i]; - ip2[i] = ip1[i] | (~mask[i] & 0x0000FFFF); - totalDiff = (ip2[i] - ip1[i]).toString(2); - - if (totalDiff !== "0") { - for (let n = 0; n < totalDiff.length; n++) { - total[i*16 + 16-(totalDiff.length-n)] = totalDiff[n]; - } - } - } - - if (includeNetworkInfo) { - output += "Network: " + IP._ipv6ToStr(network) + "\n"; - output += "Shorthand: " + IP._ipv6ToStr(network, true) + "\n"; - output += "CIDR: " + cidrRange + "\n"; - output += "Mask: " + IP._ipv6ToStr(mask) + "\n"; - output += "Range: " + IP._ipv6ToStr(ip1) + " - " + IP._ipv6ToStr(ip2) + "\n"; - output += "Total addresses in range: " + (parseInt(total.join(""), 2) + 1) + "\n\n"; - } - - return output; - }, - - - /** - * Generates an IPv6 subnet mask given a CIDR value. - * - * @private - * @param {number} cidr - * @returns {number[]} - */ - _genIpv6Mask: function(cidr) { - let mask = new Array(8), - shift; - - for (let i = 0; i < 8; i++) { - if (cidr > ((i+1)*16)) { - mask[i] = 0x0000FFFF; - } else { - shift = cidr-(i*16); - if (shift < 0) shift = 0; - mask[i] = ~((0x0000FFFF >>> shift) | 0xFFFF0000); - } - } - - return mask; - }, - - - /** - * Parses an IPv4 hyphenated range (e.g. 192.168.0.0 - 192.168.0.255) and displays information - * about it. - * - * @private - * @param {RegExp} range - * @param {boolean} includeNetworkInfo - * @param {boolean} enumerateAddresses - * @param {boolean} allowLargeList - * @returns {string} - */ - _ipv4HyphenatedRange: function(range, includeNetworkInfo, enumerateAddresses, allowLargeList) { - let output = "", - ip1 = IP._strToIpv4(range[1]), - ip2 = IP._strToIpv4(range[2]); - - // Calculate mask - let diff = ip1 ^ ip2, - cidr = 32, - mask = 0; - - while (diff !== 0) { - diff >>= 1; - cidr--; - mask = (mask << 1) | 1; - } - - mask = ~mask >>> 0; - let network = ip1 & mask, - subIp1 = network & mask, - subIp2 = subIp1 | ~mask; - - if (includeNetworkInfo) { - output += "Minimum subnet required to hold this range:\n"; - output += "\tNetwork: " + IP._ipv4ToStr(network) + "\n"; - output += "\tCIDR: " + cidr + "\n"; - output += "\tMask: " + IP._ipv4ToStr(mask) + "\n"; - output += "\tSubnet range: " + IP._ipv4ToStr(subIp1) + " - " + IP._ipv4ToStr(subIp2) + "\n"; - output += "\tTotal addresses in subnet: " + (((subIp2 - subIp1) >>> 0) + 1) + "\n\n"; - output += "Range: " + IP._ipv4ToStr(ip1) + " - " + IP._ipv4ToStr(ip2) + "\n"; - output += "Total addresses in range: " + (((ip2 - ip1) >>> 0) + 1) + "\n\n"; - } - - if (enumerateAddresses) { - if (((ip2 - ip1) >>> 0) <= 65536 || allowLargeList) { - output += IP._generateIpv4Range(ip1, ip2).join("\n"); - } else { - output += IP._LARGE_RANGE_ERROR; - } - } - return output; - }, - - - /** - * Parses an IPv6 hyphenated range (e.g. ff00:: - ffff::) and displays information about it. - * - * @private - * @param {RegExp} range - * @param {boolean} includeNetworkInfo - * @returns {string} - */ - _ipv6HyphenatedRange: function(range, includeNetworkInfo) { - let output = "", - ip1 = IP._strToIpv6(range[1]), - ip2 = IP._strToIpv6(range[14]); - - let t = "", - total = new Array(128).fill(), - i; - - for (i = 0; i < 8; i++) { - t = (ip2[i] - ip1[i]).toString(2); - if (t !== "0") { - for (let n = 0; n < t.length; n++) { - total[i*16 + 16-(t.length-n)] = t[n]; - } - } - } - - if (includeNetworkInfo) { - output += "Range: " + IP._ipv6ToStr(ip1) + " - " + IP._ipv6ToStr(ip2) + "\n"; - output += "Shorthand range: " + IP._ipv6ToStr(ip1, true) + " - " + IP._ipv6ToStr(ip2, true) + "\n"; - output += "Total addresses in range: " + (parseInt(total.join(""), 2) + 1) + "\n\n"; - } - - return output; - }, - - - /** - * Converts an IPv4 address from string format to numerical format. - * - * @private - * @param {string} ipStr - * @returns {number} - * - * @example - * // returns 168427520 - * IP._strToIpv4("10.10.0.0"); - */ - _strToIpv4: function (ipStr) { - let blocks = ipStr.split("."), - numBlocks = parseBlocks(blocks), - result = 0; - - result += numBlocks[0] << 24; - result += numBlocks[1] << 16; - result += numBlocks[2] << 8; - result += numBlocks[3]; - - return result; - - /** - * Converts a list of 4 numeric strings in the range 0-255 to a list of numbers. - */ - function parseBlocks(blocks) { - if (blocks.length !== 4) - throw "More than 4 blocks."; - - const numBlocks = []; - for (let i = 0; i < 4; i++) { - numBlocks[i] = parseInt(blocks[i], 10); - if (numBlocks[i] < 0 || numBlocks[i] > 255) - throw "Block out of range."; - } - return numBlocks; - } - }, - - - /** - * Converts an IPv4 address from numerical format to string format. - * - * @private - * @param {number} ipInt - * @returns {string} - * - * @example - * // returns "10.10.0.0" - * IP._ipv4ToStr(168427520); - */ - _ipv4ToStr: function(ipInt) { - let blockA = (ipInt >> 24) & 255, - blockB = (ipInt >> 16) & 255, - blockC = (ipInt >> 8) & 255, - blockD = ipInt & 255; - - return blockA + "." + blockB + "." + blockC + "." + blockD; - }, - - - /** - * Converts an IPv6 address from string format to numerical array format. - * - * @private - * @param {string} ipStr - * @returns {number[]} - * - * @example - * // returns [65280, 0, 0, 0, 0, 0, 4369, 8738] - * IP._strToIpv6("ff00::1111:2222"); - */ - _strToIpv6: function(ipStr) { - let blocks = ipStr.split(":"), - numBlocks = parseBlocks(blocks), - j = 0, - ipv6 = new Array(8); - - for (let i = 0; i < 8; i++) { - if (isNaN(numBlocks[j])) { - ipv6[i] = 0; - if (i === (8-numBlocks.slice(j).length)) j++; - } else { - ipv6[i] = numBlocks[j]; - j++; - } - } - return ipv6; - - /** - * Converts a list of 3-8 numeric hex strings in the range 0-65535 to a list of numbers. - */ - function parseBlocks(blocks) { - if (blocks.length < 3 || blocks.length > 8) - throw "Badly formatted IPv6 address."; - const numBlocks = []; - for (let i = 0; i < blocks.length; i++) { - numBlocks[i] = parseInt(blocks[i], 16); - if (numBlocks[i] < 0 || numBlocks[i] > 65535) - throw "Block out of range."; - } - return numBlocks; - } - }, - - - /** - * Converts an IPv6 address from numerical array format to string format. - * - * @private - * @param {number[]} ipv6 - * @param {boolean} compact - Whether or not to return the address in shorthand or not - * @returns {string} - * - * @example - * // returns "ff00::1111:2222" - * IP._ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], true); - * - * // returns "ff00:0000:0000:0000:0000:0000:1111:2222" - * IP._ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], false); - */ - _ipv6ToStr: function(ipv6, compact) { - let output = "", - i = 0; - - if (compact) { - let start = -1, - end = -1, - s = 0, - e = -1; - - for (i = 0; i < 8; i++) { - if (ipv6[i] === 0 && e === (i-1)) { - e = i; - } else if (ipv6[i] === 0) { - s = i; e = i; - } - if (e >= 0 && (e-s) > (end - start)) { - start = s; - end = e; - } - } - - for (i = 0; i < 8; i++) { - if (i !== start) { - output += Utils.hex(ipv6[i], 1) + ":"; - } else { - output += ":"; - i = end; - if (end === 7) output += ":"; - } - } - if (output[0] === ":") - output = ":" + output; - } else { - for (i = 0; i < 8; i++) { - output += Utils.hex(ipv6[i], 4) + ":"; - } - } - return output.slice(0, output.length-1); - }, - - - /** - * Generates a list of IPv4 addresses in string format between two given numerical values. - * - * @private - * @param {number} ip - * @param {number} endIp - * @returns {string[]} - * - * @example - * // returns ["0.0.0.1", "0.0.0.2", "0.0.0.3"] - * IP._generateIpv4Range(1, 3); - */ - _generateIpv4Range: function(ip, endIp) { - const range = []; - if (endIp >= ip) { - for (; ip <= endIp; ip++) { - range.push(IP._ipv4ToStr(ip)); - } - } else { - range[0] = "Second IP address smaller than first."; - } - return range; - }, - - - /** - * Lookup table for Internet Protocols. - * Taken from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml - * - * @private - * @constant - */ - _protocolLookup: { - 0: {keyword: "HOPOPT", protocol: "IPv6 Hop-by-Hop Option"}, - 1: {keyword: "ICMP", protocol: "Internet Control Message"}, - 2: {keyword: "IGMP", protocol: "Internet Group Management"}, - 3: {keyword: "GGP", protocol: "Gateway-to-Gateway"}, - 4: {keyword: "IPv4", protocol: "IPv4 encapsulation"}, - 5: {keyword: "ST", protocol: "Stream"}, - 6: {keyword: "TCP", protocol: "Transmission Control"}, - 7: {keyword: "CBT", protocol: "CBT"}, - 8: {keyword: "EGP", protocol: "Exterior Gateway Protocol"}, - 9: {keyword: "IGP", protocol: "any private interior gateway (used by Cisco for their IGRP)"}, - 10: {keyword: "BBN-RCC-MON", protocol: "BBN RCC Monitoring"}, - 11: {keyword: "NVP-II", protocol: "Network Voice Protocol"}, - 12: {keyword: "PUP", protocol: "PUP"}, - 13: {keyword: "ARGUS (deprecated)", protocol: "ARGUS"}, - 14: {keyword: "EMCON", protocol: "EMCON"}, - 15: {keyword: "XNET", protocol: "Cross Net Debugger"}, - 16: {keyword: "CHAOS", protocol: "Chaos"}, - 17: {keyword: "UDP", protocol: "User Datagram"}, - 18: {keyword: "MUX", protocol: "Multiplexing"}, - 19: {keyword: "DCN-MEAS", protocol: "DCN Measurement Subsystems"}, - 20: {keyword: "HMP", protocol: "Host Monitoring"}, - 21: {keyword: "PRM", protocol: "Packet Radio Measurement"}, - 22: {keyword: "XNS-IDP", protocol: "XEROX NS IDP"}, - 23: {keyword: "TRUNK-1", protocol: "Trunk-1"}, - 24: {keyword: "TRUNK-2", protocol: "Trunk-2"}, - 25: {keyword: "LEAF-1", protocol: "Leaf-1"}, - 26: {keyword: "LEAF-2", protocol: "Leaf-2"}, - 27: {keyword: "RDP", protocol: "Reliable Data Protocol"}, - 28: {keyword: "IRTP", protocol: "Internet Reliable Transaction"}, - 29: {keyword: "ISO-TP4", protocol: "ISO Transport Protocol Class 4"}, - 30: {keyword: "NETBLT", protocol: "Bulk Data Transfer Protocol"}, - 31: {keyword: "MFE-NSP", protocol: "MFE Network Services Protocol"}, - 32: {keyword: "MERIT-INP", protocol: "MERIT Internodal Protocol"}, - 33: {keyword: "DCCP", protocol: "Datagram Congestion Control Protocol"}, - 34: {keyword: "3PC", protocol: "Third Party Connect Protocol"}, - 35: {keyword: "IDPR", protocol: "Inter-Domain Policy Routing Protocol"}, - 36: {keyword: "XTP", protocol: "XTP"}, - 37: {keyword: "DDP", protocol: "Datagram Delivery Protocol"}, - 38: {keyword: "IDPR-CMTP", protocol: "IDPR Control Message Transport Proto"}, - 39: {keyword: "TP++", protocol: "TP++ Transport Protocol"}, - 40: {keyword: "IL", protocol: "IL Transport Protocol"}, - 41: {keyword: "IPv6", protocol: "IPv6 encapsulation"}, - 42: {keyword: "SDRP", protocol: "Source Demand Routing Protocol"}, - 43: {keyword: "IPv6-Route", protocol: "Routing Header for IPv6"}, - 44: {keyword: "IPv6-Frag", protocol: "Fragment Header for IPv6"}, - 45: {keyword: "IDRP", protocol: "Inter-Domain Routing Protocol"}, - 46: {keyword: "RSVP", protocol: "Reservation Protocol"}, - 47: {keyword: "GRE", protocol: "Generic Routing Encapsulation"}, - 48: {keyword: "DSR", protocol: "Dynamic Source Routing Protocol"}, - 49: {keyword: "BNA", protocol: "BNA"}, - 50: {keyword: "ESP", protocol: "Encap Security Payload"}, - 51: {keyword: "AH", protocol: "Authentication Header"}, - 52: {keyword: "I-NLSP", protocol: "Integrated Net Layer Security TUBA"}, - 53: {keyword: "SWIPE (deprecated)", protocol: "IP with Encryption"}, - 54: {keyword: "NARP", protocol: "NBMA Address Resolution Protocol"}, - 55: {keyword: "MOBILE", protocol: "IP Mobility"}, - 56: {keyword: "TLSP", protocol: "Transport Layer Security Protocol using Kryptonet key management"}, - 57: {keyword: "SKIP", protocol: "SKIP"}, - 58: {keyword: "IPv6-ICMP", protocol: "ICMP for IPv6"}, - 59: {keyword: "IPv6-NoNxt", protocol: "No Next Header for IPv6"}, - 60: {keyword: "IPv6-Opts", protocol: "Destination Options for IPv6"}, - 61: {keyword: "", protocol: "any host internal protocol"}, - 62: {keyword: "CFTP", protocol: "CFTP"}, - 63: {keyword: "", protocol: "any local network"}, - 64: {keyword: "SAT-EXPAK", protocol: "SATNET and Backroom EXPAK"}, - 65: {keyword: "KRYPTOLAN", protocol: "Kryptolan"}, - 66: {keyword: "RVD", protocol: "MIT Remote Virtual Disk Protocol"}, - 67: {keyword: "IPPC", protocol: "Internet Pluribus Packet Core"}, - 68: {keyword: "", protocol: "any distributed file system"}, - 69: {keyword: "SAT-MON", protocol: "SATNET Monitoring"}, - 70: {keyword: "VISA", protocol: "VISA Protocol"}, - 71: {keyword: "IPCV", protocol: "Internet Packet Core Utility"}, - 72: {keyword: "CPNX", protocol: "Computer Protocol Network Executive"}, - 73: {keyword: "CPHB", protocol: "Computer Protocol Heart Beat"}, - 74: {keyword: "WSN", protocol: "Wang Span Network"}, - 75: {keyword: "PVP", protocol: "Packet Video Protocol"}, - 76: {keyword: "BR-SAT-MON", protocol: "Backroom SATNET Monitoring"}, - 77: {keyword: "SUN-ND", protocol: "SUN ND PROTOCOL-Temporary"}, - 78: {keyword: "WB-MON", protocol: "WIDEBAND Monitoring"}, - 79: {keyword: "WB-EXPAK", protocol: "WIDEBAND EXPAK"}, - 80: {keyword: "ISO-IP", protocol: "ISO Internet Protocol"}, - 81: {keyword: "VMTP", protocol: "VMTP"}, - 82: {keyword: "SECURE-VMTP", protocol: "SECURE-VMTP"}, - 83: {keyword: "VINES", protocol: "VINES"}, - 84: {keyword: "TTP", protocol: "Transaction Transport Protocol"}, - 85: {keyword: "NSFNET-IGP", protocol: "NSFNET-IGP"}, - 86: {keyword: "DGP", protocol: "Dissimilar Gateway Protocol"}, - 87: {keyword: "TCF", protocol: "TCF"}, - 88: {keyword: "EIGRP", protocol: "EIGRP"}, - 89: {keyword: "OSPFIGP", protocol: "OSPFIGP"}, - 90: {keyword: "Sprite-RPC", protocol: "Sprite RPC Protocol"}, - 91: {keyword: "LARP", protocol: "Locus Address Resolution Protocol"}, - 92: {keyword: "MTP", protocol: "Multicast Transport Protocol"}, - 93: {keyword: "AX.25", protocol: "AX.25 Frames"}, - 94: {keyword: "IPIP", protocol: "IP-within-IP Encapsulation Protocol"}, - 95: {keyword: "MICP (deprecated)", protocol: "Mobile Internetworking Control Pro."}, - 96: {keyword: "SCC-SP", protocol: "Semaphore Communications Sec. Pro."}, - 97: {keyword: "ETHERIP", protocol: "Ethernet-within-IP Encapsulation"}, - 98: {keyword: "ENCAP", protocol: "Encapsulation Header"}, - 99: {keyword: "", protocol: "any private encryption scheme"}, - 100: {keyword: "GMTP", protocol: "GMTP"}, - 101: {keyword: "IFMP", protocol: "Ipsilon Flow Management Protocol"}, - 102: {keyword: "PNNI", protocol: "PNNI over IP"}, - 103: {keyword: "PIM", protocol: "Protocol Independent Multicast"}, - 104: {keyword: "ARIS", protocol: "ARIS"}, - 105: {keyword: "SCPS", protocol: "SCPS"}, - 106: {keyword: "QNX", protocol: "QNX"}, - 107: {keyword: "A/N", protocol: "Active Networks"}, - 108: {keyword: "IPComp", protocol: "IP Payload Compression Protocol"}, - 109: {keyword: "SNP", protocol: "Sitara Networks Protocol"}, - 110: {keyword: "Compaq-Peer", protocol: "Compaq Peer Protocol"}, - 111: {keyword: "IPX-in-IP", protocol: "IPX in IP"}, - 112: {keyword: "VRRP", protocol: "Virtual Router Redundancy Protocol"}, - 113: {keyword: "PGM", protocol: "PGM Reliable Transport Protocol"}, - 114: {keyword: "", protocol: "any 0-hop protocol"}, - 115: {keyword: "L2TP", protocol: "Layer Two Tunneling Protocol"}, - 116: {keyword: "DDX", protocol: "D-II Data Exchange (DDX)"}, - 117: {keyword: "IATP", protocol: "Interactive Agent Transfer Protocol"}, - 118: {keyword: "STP", protocol: "Schedule Transfer Protocol"}, - 119: {keyword: "SRP", protocol: "SpectraLink Radio Protocol"}, - 120: {keyword: "UTI", protocol: "UTI"}, - 121: {keyword: "SMP", protocol: "Simple Message Protocol"}, - 122: {keyword: "SM (deprecated)", protocol: "Simple Multicast Protocol"}, - 123: {keyword: "PTP", protocol: "Performance Transparency Protocol"}, - 124: {keyword: "ISIS over IPv4", protocol: ""}, - 125: {keyword: "FIRE", protocol: ""}, - 126: {keyword: "CRTP", protocol: "Combat Radio Transport Protocol"}, - 127: {keyword: "CRUDP", protocol: "Combat Radio User Datagram"}, - 128: {keyword: "SSCOPMCE", protocol: ""}, - 129: {keyword: "IPLT", protocol: ""}, - 130: {keyword: "SPS", protocol: "Secure Packet Shield"}, - 131: {keyword: "PIPE", protocol: "Private IP Encapsulation within IP"}, - 132: {keyword: "SCTP", protocol: "Stream Control Transmission Protocol"}, - 133: {keyword: "FC", protocol: "Fibre Channel"}, - 134: {keyword: "RSVP-E2E-IGNORE", protocol: ""}, - 135: {keyword: "Mobility Header", protocol: ""}, - 136: {keyword: "UDPLite", protocol: ""}, - 137: {keyword: "MPLS-in-IP", protocol: ""}, - 138: {keyword: "manet", protocol: "MANET Protocols"}, - 139: {keyword: "HIP", protocol: "Host Identity Protocol"}, - 140: {keyword: "Shim6", protocol: "Shim6 Protocol"}, - 141: {keyword: "WESP", protocol: "Wrapped Encapsulating Security Payload"}, - 142: {keyword: "ROHC", protocol: "Robust Header Compression"}, - 253: {keyword: "", protocol: "Use for experimentation and testing"}, - 254: {keyword: "", protocol: "Use for experimentation and testing"}, - 255: {keyword: "Reserved", protocol: ""} - }, - -}; - -export default IP; diff --git a/src/core/operations/Image.js b/src/core/operations/Image.js deleted file mode 100644 index afab21c1..00000000 --- a/src/core/operations/Image.js +++ /dev/null @@ -1,123 +0,0 @@ -import * as ExifParser from "exif-parser"; -import removeEXIF from "../lib/remove-exif.js"; -import Utils from "../Utils.js"; -import FileType from "./FileType.js"; - - -/** - * Image operations. - * - * @author tlwr [toby@toby.codes] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const Image = { - - /** - * Extract EXIF operation. - * - * Extracts EXIF data from a byteArray, representing a JPG or a TIFF image. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {string} - */ - runExtractEXIF(input, args) { - try { - const parser = ExifParser.create(input); - const result = parser.parse(); - - let lines = []; - for (let tagName in result.tags) { - let value = result.tags[tagName]; - lines.push(`${tagName}: ${value}`); - } - - const numTags = lines.length; - lines.unshift(`Found ${numTags} tags.\n`); - return lines.join("\n"); - } catch (err) { - throw "Could not extract EXIF data from image: " + err; - } - }, - - - /** - * Remove EXIF operation. - * - * Removes EXIF data from a byteArray, representing a JPG. - * - * @author David Moodie [davidmoodie12@gmail.com] - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRemoveEXIF(input, args) { - // Do nothing if input is empty - if (input.length === 0) return input; - - try { - return removeEXIF(input); - } catch (err) { - // Simply return input if no EXIF data is found - if (err === "Exif not found.") return input; - throw "Could not remove EXIF data from image: " + err; - } - }, - - - /** - * @constant - * @default - */ - INPUT_FORMAT: ["Raw", "Base64", "Hex"], - - /** - * Render Image operation. - * - * @author n1474335 [n1474335@gmail.com] - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runRenderImage(input, args) { - const inputFormat = args[0]; - let dataURI = "data:"; - - if (!input.length) return ""; - - // Convert input to raw bytes - switch (inputFormat) { - case "Hex": - input = Utils.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"); - break; - case "Raw": - default: - input = Utils.strToByteArray(input); - break; - } - - // Determine file type - const type = FileType.magicType(input); - if (type && type.mime.indexOf("image") === 0) { - dataURI += type.mime + ";"; - } else { - throw "Invalid file type"; - } - - // Add image data to URI - dataURI += "base64," + Utils.toBase64(input); - - return ""; - }, - -}; - -export default Image; diff --git a/src/core/operations/JPathExpression.mjs b/src/core/operations/JPathExpression.mjs new file mode 100644 index 00000000..21b4284f --- /dev/null +++ b/src/core/operations/JPathExpression.mjs @@ -0,0 +1,68 @@ +/** + * @author Matt C (matt@artemisbot.uk) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import jpath from "jsonpath"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * JPath expression operation + */ +class JPathExpression extends Operation { + + /** + * JPathExpression constructor + */ + constructor() { + super(); + + this.name = "JPath expression"; + this.module = "Code"; + this.description = "Extract information from a JSON object with a JPath query."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Query", + "type": "string", + "value": "" + }, + { + "name": "Result delimiter", + "type": "binaryShortString", + "value": "\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query, delimiter] = args; + let results, + obj; + + try { + obj = JSON.parse(input); + } catch (err) { + throw new OperationError(`Invalid input JSON: ${err.message}`); + } + + try { + results = jpath.query(obj, query); + } catch (err) { + throw new OperationError(`Invalid JPath expression: ${err.message}`); + } + + return results.map(result => JSON.stringify(result)).join(delimiter); + } + +} + +export default JPathExpression; diff --git a/src/core/operations/JS.js b/src/core/operations/JS.js deleted file mode 100755 index 58290aaa..00000000 --- a/src/core/operations/JS.js +++ /dev/null @@ -1,164 +0,0 @@ -import * as esprima from "esprima"; -import escodegen from "escodegen"; -import esmangle from "esmangle"; - - -/** - * JavaScript operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const JS = { - - /** - * @constant - * @default - */ - PARSE_LOC: false, - /** - * @constant - * @default - */ - PARSE_RANGE: false, - /** - * @constant - * @default - */ - PARSE_TOKENS: false, - /** - * @constant - * @default - */ - PARSE_COMMENT: false, - /** - * @constant - * @default - */ - PARSE_TOLERANT: false, - - /** - * JavaScript Parser operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParse: function (input, args) { - let parseLoc = args[0], - parseRange = args[1], - parseTokens = args[2], - parseComment = args[3], - parseTolerant = args[4], - result = {}, - options = { - loc: parseLoc, - range: parseRange, - tokens: parseTokens, - comment: parseComment, - tolerant: parseTolerant - }; - - result = esprima.parseScript(input, options); - return JSON.stringify(result, null, 2); - }, - - - /** - * @constant - * @default - */ - BEAUTIFY_INDENT: "\\t", - /** - * @constant - * @default - */ - BEAUTIFY_QUOTES: ["Auto", "Single", "Double"], - /** - * @constant - * @default - */ - BEAUTIFY_SEMICOLONS: true, - /** - * @constant - * @default - */ - BEAUTIFY_COMMENT: true, - - /** - * JavaScript Beautify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runBeautify: function(input, args) { - let beautifyIndent = args[0] || JS.BEAUTIFY_INDENT, - quotes = args[1].toLowerCase(), - beautifySemicolons = args[2], - beautifyComment = args[3], - result = "", - AST; - - try { - AST = esprima.parseScript(input, { - range: true, - tokens: true, - comment: true - }); - - const options = { - format: { - indent: { - style: beautifyIndent - }, - quotes: quotes, - semicolons: beautifySemicolons, - }, - comment: beautifyComment - }; - - if (options.comment) - AST = escodegen.attachComments(AST, AST.comments, AST.tokens); - - result = escodegen.generate(AST, options); - } catch (e) { - // Leave original error so the user can see the detail - throw "Unable to parse JavaScript.
" + e.message; - } - return result; - }, - - - /** - * JavaScript Minify operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMinify: function(input, args) { - let result = "", - AST = esprima.parseScript(input), - optimisedAST = esmangle.optimize(AST, null), - mangledAST = esmangle.mangle(optimisedAST); - - result = escodegen.generate(mangledAST, { - format: { - renumber: true, - hexadecimal: true, - escapeless: true, - compact: true, - semicolons: false, - parentheses: false - } - }); - return result; - }, - -}; - -export default JS; diff --git a/src/core/operations/JSONBeautify.mjs b/src/core/operations/JSONBeautify.mjs new file mode 100644 index 00000000..15fb7f58 --- /dev/null +++ b/src/core/operations/JSONBeautify.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * JSON Beautify operation + */ +class JSONBeautify extends Operation { + + /** + * JSONBeautify constructor + */ + constructor() { + super(); + + this.name = "JSON Beautify"; + this.module = "Code"; + this.description = "Indents and prettifies JavaScript Object Notation (JSON) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + if (!input) return ""; + return vkbeautify.json(input, indentStr); + } + +} + +export default JSONBeautify; diff --git a/src/core/operations/JSONMinify.mjs b/src/core/operations/JSONMinify.mjs new file mode 100644 index 00000000..ca594397 --- /dev/null +++ b/src/core/operations/JSONMinify.mjs @@ -0,0 +1,41 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * JSON Minify operation + */ +class JSONMinify extends Operation { + + /** + * JSONMinify constructor + */ + constructor() { + super(); + + this.name = "JSON Minify"; + this.module = "Code"; + this.description = "Compresses JavaScript Object Notation (JSON) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) return ""; + return vkbeautify.jsonmin(input); + } + +} + +export default JSONMinify; diff --git a/src/core/operations/JavaScriptBeautify.mjs b/src/core/operations/JavaScriptBeautify.mjs new file mode 100644 index 00000000..a4bf326b --- /dev/null +++ b/src/core/operations/JavaScriptBeautify.mjs @@ -0,0 +1,95 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import escodegen from "escodegen"; +import * as esprima from "esprima"; + +/** + * JavaScript Beautify operation + */ +class JavaScriptBeautify extends Operation { + + /** + * JavaScriptBeautify constructor + */ + constructor() { + super(); + + this.name = "JavaScript Beautify"; + this.module = "Code"; + this.description = "Parses and pretty prints valid JavaScript code. Also works with JavaScript Object Notation (JSON)."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + }, + { + "name": "Quotes", + "type": "option", + "value": ["Auto", "Single", "Double"] + }, + { + "name": "Semicolons before closing braces", + "type": "boolean", + "value": true + }, + { + "name": "Include comments", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const beautifyIndent = args[0] || "\\t", + quotes = args[1].toLowerCase(), + [,, beautifySemicolons, beautifyComment] = args; + let result = "", + AST; + + try { + AST = esprima.parseScript(input, { + range: true, + tokens: true, + comment: true + }); + + const options = { + format: { + indent: { + style: beautifyIndent + }, + quotes: quotes, + semicolons: beautifySemicolons, + }, + comment: beautifyComment + }; + + if (options.comment) + AST = escodegen.attachComments(AST, AST.comments, AST.tokens); + + result = escodegen.generate(AST, options); + } catch (e) { + // Leave original error so the user can see the detail + throw new OperationError("Unable to parse JavaScript.
" + e.message); + } + return result; + } + +} + +export default JavaScriptBeautify; diff --git a/src/core/operations/JavaScriptMinify.mjs b/src/core/operations/JavaScriptMinify.mjs new file mode 100644 index 00000000..9841d5bb --- /dev/null +++ b/src/core/operations/JavaScriptMinify.mjs @@ -0,0 +1,57 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import * as esprima from "esprima"; +import escodegen from "escodegen"; +import esmangle from "esmangle"; + +/** + * JavaScript Minify operation + */ +class JavaScriptMinify extends Operation { + + /** + * JavaScriptMinify constructor + */ + constructor() { + super(); + + this.name = "JavaScript Minify"; + this.module = "Code"; + this.description = "Compresses JavaScript code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let result = ""; + const AST = esprima.parseScript(input), + optimisedAST = esmangle.optimize(AST, null), + mangledAST = esmangle.mangle(optimisedAST); + + result = escodegen.generate(mangledAST, { + format: { + renumber: true, + hexadecimal: true, + escapeless: true, + compact: true, + semicolons: false, + parentheses: false + } + }); + return result; + } + +} + +export default JavaScriptMinify; diff --git a/src/core/operations/JavaScriptParser.mjs b/src/core/operations/JavaScriptParser.mjs new file mode 100644 index 00000000..047bc730 --- /dev/null +++ b/src/core/operations/JavaScriptParser.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import * as esprima from "esprima"; + +/** + * JavaScript Parser operation + */ +class JavaScriptParser extends Operation { + + /** + * JavaScriptParser constructor + */ + constructor() { + super(); + + this.name = "JavaScript Parser"; + this.module = "Code"; + this.description = "Returns an Abstract Syntax Tree for valid JavaScript code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Location info", + "type": "boolean", + "value": false + }, + { + "name": "Range info", + "type": "boolean", + "value": false + }, + { + "name": "Include tokens array", + "type": "boolean", + "value": false + }, + { + "name": "Include comments array", + "type": "boolean", + "value": false + }, + { + "name": "Report errors and try to continue", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [parseLoc, parseRange, parseTokens, parseComment, parseTolerant] = args, + options = { + loc: parseLoc, + range: parseRange, + tokens: parseTokens, + comment: parseComment, + tolerant: parseTolerant + }; + let result = {}; + + result = esprima.parseScript(input, options); + return JSON.stringify(result, null, 2); + } + +} + +export default JavaScriptParser; diff --git a/src/core/operations/Jump.mjs b/src/core/operations/Jump.mjs new file mode 100644 index 00000000..30fca5a0 --- /dev/null +++ b/src/core/operations/Jump.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { getLabelIndex } from "../lib/FlowControl"; + +/** + * Jump operation + */ +class Jump extends Operation { + + /** + * Jump constructor + */ + constructor() { + super(); + + this.name = "Jump"; + this.flowControl = true; + this.module = "Default"; + this.description = "Jump forwards or backwards to the specified Label"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Label name", + "type": "string", + "value": "" + }, + { + "name": "Maximum jumps (if jumping backwards)", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @param {number} state.numJumps - The number of jumps taken so far. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + const ings = state.opList[state.progress].ingValues; + const [label, maxJumps] = ings; + const jmpIndex = getLabelIndex(label, state); + + if (state.numJumps >= maxJumps || jmpIndex === -1) { + return state; + } + + state.progress = jmpIndex; + state.numJumps++; + return state; + } + +} + +export default Jump; diff --git a/src/core/operations/Keccak.mjs b/src/core/operations/Keccak.mjs new file mode 100644 index 00000000..cfe5dd8a --- /dev/null +++ b/src/core/operations/Keccak.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import JSSHA3 from "js-sha3"; +import OperationError from "../errors/OperationError"; + +/** + * Keccak operation + */ +class Keccak extends Operation { + + /** + * Keccak constructor + */ + constructor() { + super(); + + this.name = "Keccak"; + this.module = "Hashing"; + this.description = "The Keccak hash algorithm was designed by Guido Bertoni, Joan Daemen, Micha\xebl Peeters, and Gilles Van Assche, building upon RadioGat\xfan. It was selected as the winner of the SHA-3 design competition.

This version of the algorithm is Keccak[c=2d] and differs from the SHA-3 specification."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "option", + "value": ["512", "384", "256", "224"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = parseInt(args[0], 10); + let algo; + + switch (size) { + case 224: + algo = JSSHA3.keccak224; + break; + case 384: + algo = JSSHA3.keccak384; + break; + case 256: + algo = JSSHA3.keccak256; + break; + case 512: + algo = JSSHA3.keccak512; + break; + default: + throw new OperationError("Invalid size"); + } + + return algo(input); + } + +} + +export default Keccak; diff --git a/src/core/operations/Label.mjs b/src/core/operations/Label.mjs new file mode 100644 index 00000000..1444f3ac --- /dev/null +++ b/src/core/operations/Label.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Label operation. For use with Jump and Conditional Jump. + */ +class Label extends Operation { + + /** + * Label constructor + */ + constructor() { + super(); + + this.name = "Label"; + this.flowControl = true; + this.module = "Default"; + this.description = "Provides a location for conditional and fixed jumps to redirect execution to."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Name", + "type": "shortString", + "value": "" + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + return state; + } + +} + +export default Label; diff --git a/src/core/operations/MD2.mjs b/src/core/operations/MD2.mjs new file mode 100644 index 00000000..ab21a397 --- /dev/null +++ b/src/core/operations/MD2.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * MD2 operation + */ +class MD2 extends Operation { + + /** + * MD2 constructor + */ + constructor() { + super(); + + this.name = "MD2"; + this.module = "Hashing"; + this.description = "The MD2 (Message-Digest 2) algorithm is a cryptographic hash function developed by Ronald Rivest in 1989. The algorithm is optimized for 8-bit computers.

Although MD2 is no longer considered secure, even as of 2014, it remains in use in public key infrastructures as part of certificates generated with MD2 and RSA."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md2", input); + } + +} + +export default MD2; diff --git a/src/core/operations/MD4.mjs b/src/core/operations/MD4.mjs new file mode 100644 index 00000000..52712580 --- /dev/null +++ b/src/core/operations/MD4.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * MD4 operation + */ +class MD4 extends Operation { + + /** + * MD4 constructor + */ + constructor() { + super(); + + this.name = "MD4"; + this.module = "Hashing"; + this.description = "The MD4 (Message-Digest 4) algorithm is a cryptographic hash function developed by Ronald Rivest in 1990. The digest length is 128 bits. The algorithm has influenced later designs, such as the MD5, SHA-1 and RIPEMD algorithms.

The security of MD4 has been severely compromised."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md4", input); + } + +} + +export default MD4; diff --git a/src/core/operations/MD5.mjs b/src/core/operations/MD5.mjs new file mode 100644 index 00000000..692f0f96 --- /dev/null +++ b/src/core/operations/MD5.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * MD5 operation + */ +class MD5 extends Operation { + + /** + * MD5 constructor + */ + constructor() { + super(); + + this.name = "MD5"; + this.module = "Hashing"; + this.description = "MD5 (Message-Digest 5) is a widely used hash function. It has been used in a variety of security applications and is also commonly used to check the integrity of files.

However, MD5 is not collision resistant and it isn't suitable for applications like SSL/TLS certificates or digital signatures that rely on this property."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("md5", input); + } + +} + +export default MD5; diff --git a/src/core/operations/MD6.mjs b/src/core/operations/MD6.mjs new file mode 100644 index 00000000..2d12cc27 --- /dev/null +++ b/src/core/operations/MD6.mjs @@ -0,0 +1,64 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import NodeMD6 from "node-md6"; + +/** + * MD6 operation + */ +class MD6 extends Operation { + + /** + * MD6 constructor + */ + constructor() { + super(); + + this.name = "MD6"; + this.module = "Hashing"; + this.description = "The MD6 (Message-Digest 6) algorithm is a cryptographic hash function. It uses a Merkle tree-like structure to allow for immense parallel computation of hashes for very long inputs."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "number", + "value": 256 + }, + { + "name": "Levels", + "type": "number", + "value": 64 + }, + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [size, levels, key] = args; + + if (size < 0 || size > 512) + throw new OperationError("Size must be between 0 and 512"); + if (levels < 0) + throw new OperationError("Levels must be greater than 0"); + + return NodeMD6.getHashOfText(input, size, key, levels); + } + +} + +export default MD6; diff --git a/src/core/operations/MS.js b/src/core/operations/MS.js deleted file mode 100644 index a74dbe17..00000000 --- a/src/core/operations/MS.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * Microsoft operations. - * - * @author bmwhitn [brian.m.whitney@outlook.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const MS = { - - /** - * @constant - * @default - */ - D_DECODE: [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "\x57\x6E\x7B", - "\x4A\x4C\x41", - "\x0B\x0B\x0B", - "\x0C\x0C\x0C", - "\x4A\x4C\x41", - "\x0E\x0E\x0E", - "\x0F\x0F\x0F", - "\x10\x10\x10", - "\x11\x11\x11", - "\x12\x12\x12", - "\x13\x13\x13", - "\x14\x14\x14", - "\x15\x15\x15", - "\x16\x16\x16", - "\x17\x17\x17", - "\x18\x18\x18", - "\x19\x19\x19", - "\x1A\x1A\x1A", - "\x1B\x1B\x1B", - "\x1C\x1C\x1C", - "\x1D\x1D\x1D", - "\x1E\x1E\x1E", - "\x1F\x1F\x1F", - "\x2E\x2D\x32", - "\x47\x75\x30", - "\x7A\x52\x21", - "\x56\x60\x29", - "\x42\x71\x5B", - "\x6A\x5E\x38", - "\x2F\x49\x33", - "\x26\x5C\x3D", - "\x49\x62\x58", - "\x41\x7D\x3A", - "\x34\x29\x35", - "\x32\x36\x65", - "\x5B\x20\x39", - "\x76\x7C\x5C", - "\x72\x7A\x56", - "\x43\x7F\x73", - "\x38\x6B\x66", - "\x39\x63\x4E", - "\x70\x33\x45", - "\x45\x2B\x6B", - "\x68\x68\x62", - "\x71\x51\x59", - "\x4F\x66\x78", - "\x09\x76\x5E", - "\x62\x31\x7D", - "\x44\x64\x4A", - "\x23\x54\x6D", - "\x75\x43\x71", - "\x4A\x4C\x41", - "\x7E\x3A\x60", - "\x4A\x4C\x41", - "\x5E\x7E\x53", - "\x40\x4C\x40", - "\x77\x45\x42", - "\x4A\x2C\x27", - "\x61\x2A\x48", - "\x5D\x74\x72", - "\x22\x27\x75", - "\x4B\x37\x31", - "\x6F\x44\x37", - "\x4E\x79\x4D", - "\x3B\x59\x52", - "\x4C\x2F\x22", - "\x50\x6F\x54", - "\x67\x26\x6A", - "\x2A\x72\x47", - "\x7D\x6A\x64", - "\x74\x39\x2D", - "\x54\x7B\x20", - "\x2B\x3F\x7F", - "\x2D\x38\x2E", - "\x2C\x77\x4C", - "\x30\x67\x5D", - "\x6E\x53\x7E", - "\x6B\x47\x6C", - "\x66\x34\x6F", - "\x35\x78\x79", - "\x25\x5D\x74", - "\x21\x30\x43", - "\x64\x23\x26", - "\x4D\x5A\x76", - "\x52\x5B\x25", - "\x63\x6C\x24", - "\x3F\x48\x2B", - "\x7B\x55\x28", - "\x78\x70\x23", - "\x29\x69\x41", - "\x28\x2E\x34", - "\x73\x4C\x09", - "\x59\x21\x2A", - "\x33\x24\x44", - "\x7F\x4E\x3F", - "\x6D\x50\x77", - "\x55\x09\x3B", - "\x53\x56\x55", - "\x7C\x73\x69", - "\x3A\x35\x61", - "\x5F\x61\x63", - "\x65\x4B\x50", - "\x46\x58\x67", - "\x58\x3B\x51", - "\x31\x57\x49", - "\x69\x22\x4F", - "\x6C\x6D\x46", - "\x5A\x4D\x68", - "\x48\x25\x7C", - "\x27\x28\x36", - "\x5C\x46\x70", - "\x3D\x4A\x6E", - "\x24\x32\x7A", - "\x79\x41\x2F", - "\x37\x3D\x5F", - "\x60\x5F\x4B", - "\x51\x4F\x5A", - "\x20\x42\x2C", - "\x36\x65\x57" - ], - - /** - * @constant - * @default - */ - D_COMBINATION: [ - 0, 1, 2, 0, 1, 2, 1, 2, 2, 1, 2, 1, 0, 2, 1, 2, 0, 2, 1, 2, 0, 0, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, - 0, 0, 2, 1, 2, 1, 2, 0, 2, 0, 0, 1, 2, 0, 2, 1, 0, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 2, 1 - ], - - - /** - * Decodes Microsoft Encoded Script files that can be read and executed by cscript.exe/wscript.exe. - * This is a conversion of a Python script that was originally created by Didier Stevens - * (https://DidierStevens.com). - * - * @private - * @param {string} data - * @returns {string} - */ - _decode: function (data) { - let result = []; - let index = -1; - data = data.replace(/@&/g, String.fromCharCode(10)) - .replace(/@#/g, String.fromCharCode(13)) - .replace(/@\*/g, ">") - .replace(/@!/g, "<") - .replace(/@\$/g, "@"); - - for (let i = 0; i < data.length; i++) { - let byte = data.charCodeAt(i); - let char = data.charAt(i); - if (byte < 128) { - index++; - } - - if ((byte === 9 || byte > 31 && byte < 128) && - byte !== 60 && - byte !== 62 && - byte !== 64) { - char = MS.D_DECODE[byte].charAt(MS.D_COMBINATION[index % 64]); - } - result.push(char); - } - return result.join(""); - }, - - - /** - * Microsoft Script Decoder operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDecodeScript: function (input, args) { - let matcher = /#@~\^.{6}==(.+).{6}==\^#~@/; - let encodedData = matcher.exec(input); - if (encodedData){ - return MS._decode(encodedData[1]); - } else { - return ""; - } - } - -}; - -export default MS; diff --git a/src/core/operations/Magic.mjs b/src/core/operations/Magic.mjs new file mode 100644 index 00000000..7467c5dd --- /dev/null +++ b/src/core/operations/Magic.mjs @@ -0,0 +1,156 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import Dish from "../Dish"; +import MagicLib from "../lib/Magic"; + +/** + * Magic operation + */ +class Magic extends Operation { + + /** + * Magic constructor + */ + constructor() { + super(); + + this.name = "Magic"; + this.flowControl = true; + this.module = "Default"; + this.description = "The Magic operation attempts to detect various properties of the input data and suggests which operations could help to make more sense of it.

Options
Depth: If an operation appears to match the data, it will be run and the result will be analysed further. This argument controls the maximum number of levels of recursion.

Intensive mode: When this is turned on, various operations like XOR, bit rotates, and character encodings are brute-forced to attempt to detect valid data underneath. To improve performance, only the first 100 bytes of the data is brute-forced.

Extensive language support: At each stage, the relative byte frequencies of the data will be compared to average frequencies for a number of languages. The default set consists of ~40 of the most commonly used languages on the Internet. The extensive list consists of 284 languages and can result in many languages matching the data if their byte frequencies are similar.

A technical explanation of the Magic operation can be found here."; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = [ + { + "name": "Depth", + "type": "number", + "value": 3 + }, + { + "name": "Intensive mode", + "type": "boolean", + "value": false + }, + { + "name": "Extensive language support", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + async run(state) { + const ings = state.opList[state.progress].ingValues, + [depth, intensive, extLang] = ings, + dish = state.dish, + magic = new MagicLib(await dish.get(Dish.ARRAY_BUFFER)), + options = await magic.speculativeExecution(depth, extLang, intensive); + + // Record the current state for use when presenting + this.state = state; + + dish.set(options, Dish.JSON); + return state; + } + + /** + * Displays Magic results in HTML for web apps. + * + * @param {JSON} options + * @returns {html} + */ + present(options) { + const currentRecipeConfig = this.state.opList.map(op => op.config); + + let output = ` + + + + + `; + + /** + * Returns a CSS colour value based on an integer input. + * + * @param {number} val + * @returns {string} + */ + function chooseColour(val) { + if (val < 3) return "green"; + if (val < 5) return "goldenrod"; + return "red"; + } + + options.forEach(option => { + // Construct recipe URL + // Replace this Magic op with the generated recipe + const recipeConfig = currentRecipeConfig.slice(0, this.state.progress) + .concat(option.recipe) + .concat(currentRecipeConfig.slice(this.state.progress + 1)), + recipeURL = "recipe=" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(recipeConfig)); + + let language = "", + fileType = "", + matchingOps = "", + useful = ""; + const entropy = `Entropy: ${option.entropy.toFixed(2)}`, + validUTF8 = option.isUTF8 ? "Valid UTF8\n" : ""; + + if (option.languageScores[0].probability > 0) { + let likelyLangs = option.languageScores.filter(l => l.probability > 0); + if (likelyLangs.length < 1) likelyLangs = [option.languageScores[0]]; + language = "" + + "Possible languages:\n " + + likelyLangs.map(lang => { + return MagicLib.codeToLanguage(lang.lang); + }).join("\n ") + + "\n"; + } + + if (option.fileType) { + fileType = `File type: ${option.fileType.mime} (${option.fileType.ext})\n`; + } + + if (option.matchingOps.length) { + matchingOps = `Matching ops: ${[...new Set(option.matchingOps.map(op => op.op))].join(", ")}\n`; + } + + if (option.useful) { + useful = "Useful op detected\n"; + } + + output += ` + + + + `; + }); + + output += "
Recipe (click to load)Result snippetProperties
${Utils.generatePrettyRecipe(option.recipe, true)}${Utils.escapeHtml(Utils.printable(Utils.truncate(option.data, 99)))}${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy}
"; + + if (!options.length) { + output = "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?"; + } + + return output; + } + +} + +export default Magic; diff --git a/src/core/operations/Mean.mjs b/src/core/operations/Mean.mjs new file mode 100644 index 00000000..16342e7a --- /dev/null +++ b/src/core/operations/Mean.mjs @@ -0,0 +1,50 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { mean, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; +import BigNumber from "bignumber.js"; + +/** + * Mean operation + */ +class Mean extends Operation { + + /** + * Mean constructor + */ + constructor() { + super(); + + this.name = "Mean"; + this.module = "Default"; + this.description = "Computes the mean (average) of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 .5 becomes 4.75"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = mean(createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + } + +} + +export default Mean; diff --git a/src/core/operations/Median.mjs b/src/core/operations/Median.mjs new file mode 100644 index 00000000..b1936fa8 --- /dev/null +++ b/src/core/operations/Median.mjs @@ -0,0 +1,50 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { median, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + +/** + * Median operation + */ +class Median extends Operation { + + /** + * Median constructor + */ + constructor() { + super(); + + this.name = "Median"; + this.module = "Default"; + this.description = "Computes the median of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 1 .5 becomes 4.5"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = median(createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + } + +} + +export default Median; diff --git a/src/core/operations/Merge.mjs b/src/core/operations/Merge.mjs new file mode 100644 index 00000000..462660c4 --- /dev/null +++ b/src/core/operations/Merge.mjs @@ -0,0 +1,44 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Merge operation + */ +class Merge extends Operation { + + /** + * Merge constructor + */ + constructor() { + super(); + + this.name = "Merge"; + this.flowControl = true; + this.module = "Default"; + this.description = "Consolidate all branches back into a single trunk. The opposite of Fork."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + // No need to actually do anything here. The fork operation will + // merge when it sees this operation. + return state; + } + +} + +export default Merge; diff --git a/src/core/operations/MicrosoftScriptDecoder.mjs b/src/core/operations/MicrosoftScriptDecoder.mjs new file mode 100644 index 00000000..cc9d407f --- /dev/null +++ b/src/core/operations/MicrosoftScriptDecoder.mjs @@ -0,0 +1,217 @@ +/** + * @author bmwhitn [brian.m.whitney@outlook.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Microsoft Script Decoder operation + */ +class MicrosoftScriptDecoder extends Operation { + + /** + * MicrosoftScriptDecoder constructor + */ + constructor() { + super(); + + this.name = "Microsoft Script Decoder"; + this.module = "Default"; + this.description = "Decodes Microsoft Encoded Script files that have been encoded with Microsoft's custom encoding. These are often VBS (Visual Basic Script) files that are encoded and renamed with a '.vbe' extention or JS (JScript) files renamed with a '.jse' extention.

Sample

Encoded:
#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&@*eEI@#@&@#@&.jm.raY 214Wv:zms/obI0xEAAA==^#~@

Decoded:
var my_msg = "Testing <1><2><3>!";\n\nVScript.Echo(my_msg);"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const matcher = /#@~\^.{6}==(.+).{6}==\^#~@/; + const encodedData = matcher.exec(input); + if (encodedData){ + return MicrosoftScriptDecoder._decode(encodedData[1]); + } else { + return ""; + } + } + + /** + * Decodes Microsoft Encoded Script files that can be read and executed by cscript.exe/wscript.exe. + * This is a conversion of a Python script that was originally created by Didier Stevens + * (https://DidierStevens.com). + * + * @private + * @param {string} data + * @returns {string} + */ + static _decode(data) { + const result = []; + let index = -1; + data = data.replace(/@&/g, String.fromCharCode(10)) + .replace(/@#/g, String.fromCharCode(13)) + .replace(/@\*/g, ">") + .replace(/@!/g, "<") + .replace(/@\$/g, "@"); + + for (let i = 0; i < data.length; i++) { + const byte = data.charCodeAt(i); + let char = data.charAt(i); + if (byte < 128) { + index++; + } + + if ((byte === 9 || byte > 31 && byte < 128) && + byte !== 60 && + byte !== 62 && + byte !== 64) { + char = D_DECODE[byte].charAt(D_COMBINATION[index % 64]); + } + result.push(char); + } + return result.join(""); + } + +} + +const D_DECODE = [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "\x57\x6E\x7B", + "\x4A\x4C\x41", + "\x0B\x0B\x0B", + "\x0C\x0C\x0C", + "\x4A\x4C\x41", + "\x0E\x0E\x0E", + "\x0F\x0F\x0F", + "\x10\x10\x10", + "\x11\x11\x11", + "\x12\x12\x12", + "\x13\x13\x13", + "\x14\x14\x14", + "\x15\x15\x15", + "\x16\x16\x16", + "\x17\x17\x17", + "\x18\x18\x18", + "\x19\x19\x19", + "\x1A\x1A\x1A", + "\x1B\x1B\x1B", + "\x1C\x1C\x1C", + "\x1D\x1D\x1D", + "\x1E\x1E\x1E", + "\x1F\x1F\x1F", + "\x2E\x2D\x32", + "\x47\x75\x30", + "\x7A\x52\x21", + "\x56\x60\x29", + "\x42\x71\x5B", + "\x6A\x5E\x38", + "\x2F\x49\x33", + "\x26\x5C\x3D", + "\x49\x62\x58", + "\x41\x7D\x3A", + "\x34\x29\x35", + "\x32\x36\x65", + "\x5B\x20\x39", + "\x76\x7C\x5C", + "\x72\x7A\x56", + "\x43\x7F\x73", + "\x38\x6B\x66", + "\x39\x63\x4E", + "\x70\x33\x45", + "\x45\x2B\x6B", + "\x68\x68\x62", + "\x71\x51\x59", + "\x4F\x66\x78", + "\x09\x76\x5E", + "\x62\x31\x7D", + "\x44\x64\x4A", + "\x23\x54\x6D", + "\x75\x43\x71", + "\x4A\x4C\x41", + "\x7E\x3A\x60", + "\x4A\x4C\x41", + "\x5E\x7E\x53", + "\x40\x4C\x40", + "\x77\x45\x42", + "\x4A\x2C\x27", + "\x61\x2A\x48", + "\x5D\x74\x72", + "\x22\x27\x75", + "\x4B\x37\x31", + "\x6F\x44\x37", + "\x4E\x79\x4D", + "\x3B\x59\x52", + "\x4C\x2F\x22", + "\x50\x6F\x54", + "\x67\x26\x6A", + "\x2A\x72\x47", + "\x7D\x6A\x64", + "\x74\x39\x2D", + "\x54\x7B\x20", + "\x2B\x3F\x7F", + "\x2D\x38\x2E", + "\x2C\x77\x4C", + "\x30\x67\x5D", + "\x6E\x53\x7E", + "\x6B\x47\x6C", + "\x66\x34\x6F", + "\x35\x78\x79", + "\x25\x5D\x74", + "\x21\x30\x43", + "\x64\x23\x26", + "\x4D\x5A\x76", + "\x52\x5B\x25", + "\x63\x6C\x24", + "\x3F\x48\x2B", + "\x7B\x55\x28", + "\x78\x70\x23", + "\x29\x69\x41", + "\x28\x2E\x34", + "\x73\x4C\x09", + "\x59\x21\x2A", + "\x33\x24\x44", + "\x7F\x4E\x3F", + "\x6D\x50\x77", + "\x55\x09\x3B", + "\x53\x56\x55", + "\x7C\x73\x69", + "\x3A\x35\x61", + "\x5F\x61\x63", + "\x65\x4B\x50", + "\x46\x58\x67", + "\x58\x3B\x51", + "\x31\x57\x49", + "\x69\x22\x4F", + "\x6C\x6D\x46", + "\x5A\x4D\x68", + "\x48\x25\x7C", + "\x27\x28\x36", + "\x5C\x46\x70", + "\x3D\x4A\x6E", + "\x24\x32\x7A", + "\x79\x41\x2F", + "\x37\x3D\x5F", + "\x60\x5F\x4B", + "\x51\x4F\x5A", + "\x20\x42\x2C", + "\x36\x65\x57" +]; + +const D_COMBINATION = [ + 0, 1, 2, 0, 1, 2, 1, 2, 2, 1, 2, 1, 0, 2, 1, 2, 0, 2, 1, 2, 0, 0, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, + 0, 0, 2, 1, 2, 1, 2, 0, 2, 0, 0, 1, 2, 0, 2, 1, 0, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 2, 1 +]; + +export default MicrosoftScriptDecoder; diff --git a/src/core/operations/MorseCode.js b/src/core/operations/MorseCode.js deleted file mode 100644 index ae5d091b..00000000 --- a/src/core/operations/MorseCode.js +++ /dev/null @@ -1,190 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Morse Code translation operations. - * - * @author tlwr [toby@toby.codes] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const MorseCode = { - - /** - * @constant - * @default - */ - FORMAT_OPTIONS: ["-/.", "_/.", "Dash/Dot", "DASH/DOT", "dash/dot"], - /** - * @constant - * @default - */ - LETTER_DELIM_OPTIONS: ["Space", "Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"], - /** - * @constant - * @default - */ - WORD_DELIM_OPTIONS: ["Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"], - /** - * @constant - * @default - */ - MORSE_TABLE: { - "A": "", - "B": "", - "C": "", - "D": "", - "E": "", - "F": "", - "G": "", - "H": "", - "I": "", - "J": "", - "K": "", - "L": "", - "M": "", - "N": "", - "O": "", - "P": "", - "Q": "", - "R": "", - "S": "", - "T": "", - "U": "", - "V": "", - "W": "", - "X": "", - "Y": "", - "Z": "", - "1": "", - "2": "", - "3": "", - "4": "", - "5": "", - "6": "", - "7": "", - "8": "", - "9": "", - "0": "", - ".": "", - ",": "", - ":": "", - ";": "", - "!": "", - "?": "", - "'": "", - "\"": "", - "/": "", - "-": "", - "+": "", - "(": "", - ")": "", - "@": "", - "=": "", - "&": "", - "_": "", - "$": "" - }, - - - /** - * To Morse Code operation. - * - * @param {number} input - * @param {Object[]} args - * @returns {string} - */ - runTo: function(input, args) { - const format = args[0].split("/"); - const dash = format[0]; - const dot = format[1]; - - 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) { - let words = line.split(/ +/); - words = Array.prototype.map.call(words, function(word) { - const letters = Array.prototype.map.call(word, function(character) { - const letter = character.toUpperCase(); - if (typeof MorseCode.MORSE_TABLE[letter] == "undefined") { - return ""; - } - - return MorseCode.MORSE_TABLE[letter]; - }); - - return letters.join(""); - }); - line = words.join(""); - return line; - }); - input = input.join("\n"); - - input = input.replace( - /|||/g, - function(match) { - switch (match) { - case "": return dash; - case "": return dot; - case "": return letterDelim; - case "": return wordDelim; - } - } - ); - - return input; - }, - - - /** - * From Morse Code operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFrom: (function() { - let reversedTable = null; - const reverseTable = function() { - reversedTable = {}; - - for (const letter in MorseCode.MORSE_TABLE) { - const signal = MorseCode.MORSE_TABLE[letter]; - reversedTable[signal] = letter; - } - }; - - return function(input, args) { - if (reversedTable === null) { - reverseTable(); - } - - 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, ""); - - let words = input.split(wordDelim); - words = Array.prototype.map.call(words, function(word) { - const signals = word.split(letterDelim); - - const letters = signals.map(function(signal) { - return reversedTable[signal]; - }); - - return letters.join(""); - }); - words = words.join(" "); - - return words; - }; - })(), - -}; - -export default MorseCode; diff --git a/src/core/operations/Multiply.mjs b/src/core/operations/Multiply.mjs new file mode 100644 index 00000000..3f78daa4 --- /dev/null +++ b/src/core/operations/Multiply.mjs @@ -0,0 +1,51 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { multi, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Multiply operation + */ +class Multiply extends Operation { + + /** + * Multiply constructor + */ + constructor() { + super(); + + this.name = "Multiply"; + this.module = "Default"; + this.description = "Multiplies a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 40"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = multi(createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + } + +} + +export default Multiply; diff --git a/src/core/operations/NOT.mjs b/src/core/operations/NOT.mjs new file mode 100644 index 00000000..e0352dd0 --- /dev/null +++ b/src/core/operations/NOT.mjs @@ -0,0 +1,66 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { bitOp, not } from "../lib/BitwiseOp"; + +/** + * NOT operation + */ +class NOT extends Operation { + + /** + * NOT constructor + */ + constructor() { + super(); + + this.name = "NOT"; + this.module = "Default"; + this.description = "Returns the inverse of each byte."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + return bitOp(input, null, not); + } + + /** + * Highlight NOT + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight NOT in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default NOT; diff --git a/src/core/operations/NetBIOS.js b/src/core/operations/NetBIOS.js deleted file mode 100644 index 60866b65..00000000 --- a/src/core/operations/NetBIOS.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * NetBIOS operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const NetBIOS = { - - /** - * @constant - * @default - */ - OFFSET: 65, - - /** - * Encode NetBIOS Name operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runEncodeName: function(input, args) { - let output = [], - offset = args[0]; - - if (input.length <= 16) { - let len = input.length; - input.length = 16; - input.fill(32, len, 16); - for (let i = 0; i < input.length; i++) { - output.push((input[i] >> 4) + offset); - output.push((input[i] & 0xf) + offset); - } - } - - return output; - }, - - - /** - * Decode NetBIOS Name operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runDecodeName: function(input, args) { - let output = [], - offset = args[0]; - - if (input.length <= 32 && (input.length % 2) === 0) { - for (let i = 0; i < input.length; i += 2) { - output.push((((input[i] & 0xff) - offset) << 4) | - (((input[i + 1] & 0xff) - offset) & 0xf)); - } - for (let i = output.length - 1; i > 0; i--) { - if (output[i] === 32) output.splice(i, i); - else break; - } - } - - return output; - }, - -}; - -export default NetBIOS; diff --git a/src/core/operations/Numberwang.js b/src/core/operations/Numberwang.js deleted file mode 100755 index e323f74c..00000000 --- a/src/core/operations/Numberwang.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Numberwang operations. - * - * @author Unknown Male 282 - * @namespace - */ -const Numberwang = { - - /** - * Numberwang operation. Remain indoors. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - run: function(input, args) { - let output; - if (!input) { - output = "Let's play Wangernumb!"; - } else { - const match = input.match(/(f0rty-s1x|shinty-six|filth-hundred and neeb|-?√?\d+(\.\d+)?i?([a-z]?)%?)/i); - if (match) { - if (match[3]) output = match[0] + "! That's AlphaNumericWang!"; - else output = match[0] + "! That's Numberwang!"; - } else { - // That's a bad miss! - output = "Sorry, that's not Numberwang. Let's rotate the board!"; - } - } - - const rand = Math.floor(Math.random() * Numberwang._didYouKnow.length); - return output + "\n\nDid you know: " + Numberwang._didYouKnow[rand]; - }, - - - /** - * Taken from http://numberwang.wikia.com/wiki/Numberwang_Wikia - * - * @private - * @constant - */ - _didYouKnow: [ - "Numberwang, contrary to popular belief, is a fruit and not a vegetable.", - "Robert Webb once got WordWang while presenting an episode of Numberwang.", - "The 6705th digit of pi is Numberwang.", - "Numberwang was invented on a Sevenday.", - "Contrary to popular belief, Albert Einstein always got good grades in Numberwang at school. He once scored ^4$ on a test.", - "680 asteroids have been named after Numberwang.", - "Archimedes is most famous for proclaiming \"That's Numberwang!\" during an epiphany about water displacement he had while taking a bath.", - "Numberwang Day is celebrated in Japan on every day of the year apart from June 6.", - "Biologists recently discovered Numberwang within a strand of human DNA.", - "Numbernot is a special type of non-Numberwang number. It is divisible by 3 and the letter \"y\".", - "Julie once got 612.04 Numberwangs in a single episode of Emmerdale.", - "In India, it is traditional to shout out \"Numberwang!\" instead of checkmate during games of chess.", - "There is a rule on Countdown which states that if you get Numberwang in the numbers round, you automatically win. It has only ever been invoked twice.", - "\"Numberwang\" was the third-most common baby name for a brief period in 1722.", - "\"The Lion King\" was loosely based on Numberwang.", - "\"A Numberwang a day keeps the doctor away\" is how Donny Cosy, the oldest man in the world, explained how he was in such good health at the age of 136.", - "The \"number lock\" button on a keyboard is based on the popular round of the same name in \"Numberwang\".", - "Cambridge became the first university to offer a course in Numberwang in 1567.", - "Schrödinger's Numberwang is a number that has been confusing dentists for centuries.", - "\"Harry Potter and the Numberwang of Numberwang\" was rejected by publishers -41 times before it became a bestseller.", - "\"Numberwang\" is the longest-running British game show in history; it has aired 226 seasons, each containing 19 episodes, which makes a grand total of 132 episodes.", - "The triple Numberwang bonus was discovered by archaeologist Thomas Jefferson in Somerset.", - "Numberwang is illegal in parts of Czechoslovakia.", - "Numberwang was discovered in India in the 12th century.", - "Numberwang has the chemical formula Zn4SO2(HgEs)3.", - "The first pack of cards ever created featured two \"Numberwang\" cards instead of jokers.", - "Julius Caesar was killed by an overdose of Numberwang.", - "The most Numberwang musical note is G#.", - "In 1934, the forty-third Google Doodle promoted the upcoming television show \"Numberwang on Ice\".", - "A recent psychology study found that toddlers were 17% faster at identifying numbers which were Numberwang.", - "There are 700 ways to commit a foul in the television show \"Numberwang\". All 700 of these fouls were committed by Julie in one single episode in 1473.", - "Astronomers suspect God is Numberwang.", - "Numberwang is the official beverage of Canada.", - "In the pilot episode of \"The Price is Right\", if a contestant got the value of an item exactly right they were told \"That's Numberwang!\" and immediately won ₹5.7032.", - "The first person to get three Numberwangs in a row was Madonna.", - "\"Numberwang\" has the code U+46402 in Unicode.", - "The musical note \"Numberwang\" is between D# and E♮.", - "Numberwang was first played on the moon in 1834.", - ], - -}; - -export default Numberwang; diff --git a/src/core/operations/Numberwang.mjs b/src/core/operations/Numberwang.mjs new file mode 100644 index 00000000..202e4dc7 --- /dev/null +++ b/src/core/operations/Numberwang.mjs @@ -0,0 +1,100 @@ +/** + * @author Unknown Male 282 + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Numberwang operation. Remain indoors. + */ +class Numberwang extends Operation { + + /** + * Numberwang constructor + */ + constructor() { + super(); + + this.name = "Numberwang"; + this.module = "Default"; + this.description = "Based on the popular gameshow by Mitchell and Webb."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output; + if (!input) { + output = "Let's play Wangernumb!"; + } else { + const match = input.match(/(f0rty-s1x|shinty-six|filth-hundred and neeb|-?√?\d+(\.\d+)?i?([a-z]?)%?)/i); + if (match) { + if (match[3]) output = match[0] + "! That's AlphaNumericWang!"; + else output = match[0] + "! That's Numberwang!"; + } else { + // That's a bad miss! + output = "Sorry, that's not Numberwang. Let's rotate the board!"; + } + } + + const rand = Math.floor(Math.random() * didYouKnow.length); + return output + "\n\nDid you know: " + didYouKnow[rand]; + } + +} + +/** + * Taken from http://numberwang.wikia.com/wiki/Numberwang_Wikia + * + * @constant + */ +const didYouKnow = [ + "Numberwang, contrary to popular belief, is a fruit and not a vegetable.", + "Robert Webb once got WordWang while presenting an episode of Numberwang.", + "The 6705th digit of pi is Numberwang.", + "Numberwang was invented on a Sevenday.", + "Contrary to popular belief, Albert Einstein always got good grades in Numberwang at school. He once scored ^4$ on a test.", + "680 asteroids have been named after Numberwang.", + "Archimedes is most famous for proclaiming \"That's Numberwang!\" during an epiphany about water displacement he had while taking a bath.", + "Numberwang Day is celebrated in Japan on every day of the year apart from June 6.", + "Biologists recently discovered Numberwang within a strand of human DNA.", + "Numbernot is a special type of non-Numberwang number. It is divisible by 3 and the letter \"y\".", + "Julie once got 612.04 Numberwangs in a single episode of Emmerdale.", + "In India, it is traditional to shout out \"Numberwang!\" instead of checkmate during games of chess.", + "There is a rule on Countdown which states that if you get Numberwang in the numbers round, you automatically win. It has only ever been invoked twice.", + "\"Numberwang\" was the third-most common baby name for a brief period in 1722.", + "\"The Lion King\" was loosely based on Numberwang.", + "\"A Numberwang a day keeps the doctor away\" is how Donny Cosy, the oldest man in the world, explained how he was in such good health at the age of 136.", + "The \"number lock\" button on a keyboard is based on the popular round of the same name in \"Numberwang\".", + "Cambridge became the first university to offer a course in Numberwang in 1567.", + "Schrödinger's Numberwang is a number that has been confusing dentists for centuries.", + "\"Harry Potter and the Numberwang of Numberwang\" was rejected by publishers -41 times before it became a bestseller.", + "\"Numberwang\" is the longest-running British game show in history; it has aired 226 seasons, each containing 19 episodes, which makes a grand total of 132 episodes.", + "The triple Numberwang bonus was discovered by archaeologist Thomas Jefferson in Somerset.", + "Numberwang is illegal in parts of Czechoslovakia.", + "Numberwang was discovered in India in the 12th century.", + "Numberwang has the chemical formula Zn4SO2(HgEs)3.", + "The first pack of cards ever created featured two \"Numberwang\" cards instead of jokers.", + "Julius Caesar was killed by an overdose of Numberwang.", + "The most Numberwang musical note is G#.", + "In 1934, the forty-third Google Doodle promoted the upcoming television show \"Numberwang on Ice\".", + "A recent psychology study found that toddlers were 17% faster at identifying numbers which were Numberwang.", + "There are 700 ways to commit a foul in the television show \"Numberwang\". All 700 of these fouls were committed by Julie in one single episode in 1473.", + "Astronomers suspect God is Numberwang.", + "Numberwang is the official beverage of Canada.", + "In the pilot episode of \"The Price is Right\", if a contestant got the value of an item exactly right they were told \"That's Numberwang!\" and immediately won ₹5.7032.", + "The first person to get three Numberwangs in a row was Madonna.", + "\"Numberwang\" has the code U+46402 in Unicode.", + "The musical note \"Numberwang\" is between D# and E♮.", + "Numberwang was first played on the moon in 1834.", +]; + +export default Numberwang; diff --git a/src/core/operations/OR.mjs b/src/core/operations/OR.mjs new file mode 100644 index 00000000..33bb2f63 --- /dev/null +++ b/src/core/operations/OR.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import { bitOp, or } from "../lib/BitwiseOp"; + +/** + * OR operation + */ +class OR extends Operation { + + /** + * OR constructor + */ + constructor() { + super(); + + this.name = "OR"; + this.module = "Default"; + this.description = "OR the input with the given key.
e.g. fe023da5"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "Base64", "UTF8", "Latin1"] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); + + return bitOp(input, key, or); + } + + /** + * Highlight OR + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight OR in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default OR; diff --git a/src/core/operations/OS.js b/src/core/operations/OS.js deleted file mode 100755 index 93a780f1..00000000 --- a/src/core/operations/OS.js +++ /dev/null @@ -1,311 +0,0 @@ -/** - * Operating system operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const OS = { - - /** - * Parse UNIX file permissions operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseUnixPerms: function(input, args) { - let perms = { - d: false, // directory - sl: false, // symbolic link - np: false, // named pipe - s: false, // socket - cd: false, // character device - bd: false, // block device - dr: false, // door - sb: false, // sticky bit - su: false, // setuid - sg: false, // setgid - ru: false, // read user - wu: false, // write user - eu: false, // execute user - rg: false, // read group - wg: false, // write group - eg: false, // execute group - ro: false, // read other - wo: false, // write other - eo: false // execute other - }, - d = 0, - u = 0, - g = 0, - o = 0, - output = "", - octal = null, - textual = null; - - if (input.search(/\s*[0-7]{1,4}\s*/i) === 0) { - // Input is octal - octal = input.match(/\s*([0-7]{1,4})\s*/i)[1]; - - if (octal.length === 4) { - d = parseInt(octal[0], 8); - u = parseInt(octal[1], 8); - g = parseInt(octal[2], 8); - o = parseInt(octal[3], 8); - } else { - if (octal.length > 0) u = parseInt(octal[0], 8); - if (octal.length > 1) g = parseInt(octal[1], 8); - if (octal.length > 2) o = parseInt(octal[2], 8); - } - - perms.su = d >> 2 & 0x1; - perms.sg = d >> 1 & 0x1; - perms.sb = d & 0x1; - - perms.ru = u >> 2 & 0x1; - perms.wu = u >> 1 & 0x1; - perms.eu = u & 0x1; - - perms.rg = g >> 2 & 0x1; - perms.wg = g >> 1 & 0x1; - perms.eg = g & 0x1; - - perms.ro = o >> 2 & 0x1; - perms.wo = o >> 1 & 0x1; - perms.eo = o & 0x1; - } else if (input.search(/\s*[dlpcbDrwxsStT-]{1,10}\s*/) === 0) { - // Input is textual - textual = input.match(/\s*([dlpcbDrwxsStT-]{1,10})\s*/)[1]; - - switch (textual[0]) { - case "d": - perms.d = true; - break; - case "l": - perms.sl = true; - break; - case "p": - perms.np = true; - break; - case "s": - perms.s = true; - break; - case "c": - perms.cd = true; - break; - case "b": - perms.bd = true; - break; - case "D": - perms.dr = true; - break; - } - - if (textual.length > 1) perms.ru = textual[1] === "r"; - if (textual.length > 2) perms.wu = textual[2] === "w"; - if (textual.length > 3) { - switch (textual[3]) { - case "x": - perms.eu = true; - break; - case "s": - perms.eu = true; - perms.su = true; - break; - case "S": - perms.su = true; - break; - } - } - - if (textual.length > 4) perms.rg = textual[4] === "r"; - if (textual.length > 5) perms.wg = textual[5] === "w"; - if (textual.length > 6) { - switch (textual[6]) { - case "x": - perms.eg = true; - break; - case "s": - perms.eg = true; - perms.sg = true; - break; - case "S": - perms.sg = true; - break; - } - } - - if (textual.length > 7) perms.ro = textual[7] === "r"; - if (textual.length > 8) perms.wo = textual[8] === "w"; - if (textual.length > 9) { - switch (textual[9]) { - case "x": - perms.eo = true; - break; - case "t": - perms.eo = true; - perms.sb = true; - break; - case "T": - perms.sb = true; - break; - } - } - } else { - return "Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."; - } - - output += "Textual representation: " + OS._permsToStr(perms); - output += "\nOctal representation: " + OS._permsToOctal(perms); - - // File type - if (textual) { - output += "\nFile type: " + OS._ftFromPerms(perms); - } - - // setuid, setgid - if (perms.su) { - output += "\nThe setuid flag is set"; - } - if (perms.sg) { - output += "\nThe setgid flag is set"; - } - - // sticky bit - if (perms.sb) { - output += "\nThe sticky bit is set"; - } - - // Permission matrix - output += "\n\n +---------+-------+-------+-------+\n" + - " | | User | Group | Other |\n" + - " +---------+-------+-------+-------+\n" + - " | Read | " + (perms.ru ? "X" : " ") + " | " + (perms.rg ? "X" : " ") + " | " + (perms.ro ? "X" : " ") + " |\n" + - " +---------+-------+-------+-------+\n" + - " | Write | " + (perms.wu ? "X" : " ") + " | " + (perms.wg ? "X" : " ") + " | " + (perms.wo ? "X" : " ") + " |\n" + - " +---------+-------+-------+-------+\n" + - " | Execute | " + (perms.eu ? "X" : " ") + " | " + (perms.eg ? "X" : " ") + " | " + (perms.eo ? "X" : " ") + " |\n" + - " +---------+-------+-------+-------+\n"; - - return output; - }, - - - /** - * Given a permissions object dictionary, generates a textual permissions string. - * - * @private - * @param {Object} perms - * @returns {string} - */ - _permsToStr: function(perms) { - let str = "", - type = "-"; - - if (perms.d) type = "d"; - if (perms.sl) type = "l"; - if (perms.np) type = "p"; - if (perms.s) type = "s"; - if (perms.cd) type = "c"; - if (perms.bd) type = "b"; - if (perms.dr) type = "D"; - - str = type; - - str += perms.ru ? "r" : "-"; - str += perms.wu ? "w" : "-"; - if (perms.eu && perms.su) { - str += "s"; - } else if (perms.su) { - str += "S"; - } else if (perms.eu) { - str += "x"; - } else { - str += "-"; - } - - str += perms.rg ? "r" : "-"; - str += perms.wg ? "w" : "-"; - if (perms.eg && perms.sg) { - str += "s"; - } else if (perms.sg) { - str += "S"; - } else if (perms.eg) { - str += "x"; - } else { - str += "-"; - } - - str += perms.ro ? "r" : "-"; - str += perms.wo ? "w" : "-"; - if (perms.eo && perms.sb) { - str += "t"; - } else if (perms.sb) { - str += "T"; - } else if (perms.eo) { - str += "x"; - } else { - str += "-"; - } - - return str; - }, - - - /** - * Given a permissions object dictionary, generates an octal permissions string. - * - * @private - * @param {Object} perms - * @returns {string} - */ - _permsToOctal: function(perms) { - let d = 0, - u = 0, - g = 0, - o = 0; - - if (perms.su) d += 4; - if (perms.sg) d += 2; - if (perms.sb) d += 1; - - if (perms.ru) u += 4; - if (perms.wu) u += 2; - if (perms.eu) u += 1; - - if (perms.rg) g += 4; - if (perms.wg) g += 2; - if (perms.eg) g += 1; - - if (perms.ro) o += 4; - if (perms.wo) o += 2; - if (perms.eo) o += 1; - - return d.toString() + u.toString() + g.toString() + o.toString(); - }, - - - /** - * Given a permissions object dictionary, returns the file type. - * - * @private - * @param {Object} perms - * @returns {string} - */ - _ftFromPerms: function(perms) { - if (perms.d) return "Directory"; - if (perms.sl) return "Symbolic link"; - if (perms.np) return "Named pipe"; - if (perms.s) return "Socket"; - if (perms.cd) return "Character device"; - if (perms.bd) return "Block device"; - if (perms.dr) return "Door"; - return "Regular file"; - }, - -}; - -export default OS; diff --git a/src/core/operations/OTP.js b/src/core/operations/OTP.js deleted file mode 100755 index f7862310..00000000 --- a/src/core/operations/OTP.js +++ /dev/null @@ -1,56 +0,0 @@ -import otp from "otp"; -import Base64 from "./Base64.js"; - - -/** - * One-Time Password operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const OTP = { - - /** - * Generate TOTP operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runTOTP: function(input, args) { - const otpObj = otp({ - name: args[0], - keySize: args[1], - codeLength: args[2], - secret: Base64.runTo32(input, []), - epoch: args[3], - timeSlice: args[4] - }); - return `URI: ${otpObj.totpURL}\n\nPassword: ${otpObj.totp()}`; - }, - - - /** - * Generate HOTP operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {string} - */ - runHOTP: function(input, args) { - const otpObj = otp({ - name: args[0], - keySize: args[1], - codeLength: args[2], - secret: Base64.runTo32(input, []), - }); - const counter = args[3]; - return `URI: ${otpObj.hotpURL}\n\nPassword: ${otpObj.hotp(counter)}`; - }, - -}; - -export default OTP; diff --git a/src/core/operations/ObjectIdentifierToHex.mjs b/src/core/operations/ObjectIdentifierToHex.mjs new file mode 100644 index 00000000..9be7108c --- /dev/null +++ b/src/core/operations/ObjectIdentifierToHex.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Object Identifier to Hex operation + */ +class ObjectIdentifierToHex extends Operation { + + /** + * ObjectIdentifierToHex constructor + */ + constructor() { + super(); + + this.name = "Object Identifier to Hex"; + this.module = "PublicKey"; + this.description = "Converts an object identifier (OID) into a hexadecimal string."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.oidIntToHex(input); + } + +} + +export default ObjectIdentifierToHex; diff --git a/src/core/operations/OffsetChecker.mjs b/src/core/operations/OffsetChecker.mjs new file mode 100644 index 00000000..07b15d2f --- /dev/null +++ b/src/core/operations/OffsetChecker.mjs @@ -0,0 +1,107 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; + +/** + * Offset checker operation + */ +class OffsetChecker extends Operation { + + /** + * OffsetChecker constructor + */ + constructor() { + super(); + + this.name = "Offset checker"; + this.module = "Default"; + this.description = "Compares multiple inputs (separated by the specified delimiter) and highlights matching characters which appear at the same position in all samples."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Sample delimiter", + "type": "binaryString", + "value": "\\n\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const sampleDelim = args[0], + samples = input.split(sampleDelim), + outputs = new Array(samples.length); + let i = 0, + s = 0, + match = false, + inMatch = false, + chr; + + if (!samples || samples.length < 2) { + throw new OperationError("Not enough samples, perhaps you need to modify the sample delimiter or add more data?"); + } + + // Initialise output strings + outputs.fill("", 0, samples.length); + + // Loop through each character in the first sample + for (i = 0; i < samples[0].length; i++) { + chr = samples[0][i]; + match = false; + + // Loop through each sample to see if the chars are the same + for (s = 1; s < samples.length; s++) { + if (samples[s][i] !== chr) { + match = false; + break; + } + match = true; + } + + // Write output for each sample + for (s = 0; s < samples.length; s++) { + if (samples[s].length <= i) { + if (inMatch) outputs[s] += ""; + if (s === samples.length - 1) inMatch = false; + continue; + } + + if (match && !inMatch) { + outputs[s] += "" + Utils.escapeHtml(samples[s][i]); + if (samples[s].length === i + 1) outputs[s] += ""; + if (s === samples.length - 1) inMatch = true; + } else if (!match && inMatch) { + outputs[s] += "" + Utils.escapeHtml(samples[s][i]); + if (s === samples.length - 1) inMatch = false; + } else { + outputs[s] += Utils.escapeHtml(samples[s][i]); + if (inMatch && samples[s].length === i + 1) { + outputs[s] += ""; + if (samples[s].length - 1 !== i) inMatch = false; + } + } + + if (samples[0].length - 1 === i) { + if (inMatch) outputs[s] += ""; + outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1)); + } + } + } + + return outputs.join(sampleDelim); + } + +} + +export default OffsetChecker; diff --git a/src/core/operations/PEMToHex.mjs b/src/core/operations/PEMToHex.mjs new file mode 100644 index 00000000..36ac4d71 --- /dev/null +++ b/src/core/operations/PEMToHex.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * PEM to Hex operation + */ +class PEMToHex extends Operation { + + /** + * PEMToHex constructor + */ + constructor() { + super(); + + this.name = "PEM to Hex"; + this.module = "PublicKey"; + this.description = "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.indexOf("-----BEGIN") < 0) { + // Add header so that the KEYUTIL function works + input = "-----BEGIN CERTIFICATE-----" + input; + } + if (input.indexOf("-----END") < 0) { + // Add footer so that the KEYUTIL function works + input = input + "-----END CERTIFICATE-----"; + } + const cert = new r.X509(); + cert.readCertPEM(input); + return cert.hex; + } + +} + +export default PEMToHex; diff --git a/src/core/operations/PGP.js b/src/core/operations/PGP.js deleted file mode 100755 index b80ab4ae..00000000 --- a/src/core/operations/PGP.js +++ /dev/null @@ -1,364 +0,0 @@ -import * as kbpgp from "kbpgp"; -import {promisify} from "es6-promisify"; - - -/** - * PGP operations. - * - * @author tlwr [toby@toby.codes] - * @author Matt C [matt@artemisbot.uk] - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const PGP = { - - /** - * @constant - * @default - */ - KEY_TYPES: ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384"], - - - /** - * Get size of subkey - * - * @private - * @param {number} keySize - * @returns {number} - */ - _getSubkeySize(keySize) { - return { - 1024: 1024, - 2048: 1024, - 4096: 2048, - 256: 256, - 384: 256, - }[keySize]; - }, - - - /** - * Progress callback - * - * @private - */ - _ASP: new kbpgp.ASP({ - "progress_hook": info => { - let msg = ""; - - switch (info.what) { - case "guess": - msg = "Guessing a prime"; - break; - case "fermat": - msg = "Factoring prime using Fermat's factorization method"; - break; - case "mr": - msg = "Performing Miller-Rabin primality test"; - break; - case "passed_mr": - msg = "Passed Miller-Rabin primality test"; - break; - case "failed_mr": - msg = "Failed Miller-Rabin primality test"; - break; - case "found": - msg = "Prime found"; - break; - default: - msg = `Stage: ${info.what}`; - } - - if (ENVIRONMENT_IS_WORKER()) - self.sendStatusMessage(msg); - } - }), - - - /** - * Import private key and unlock if necessary - * - * @private - * @param {string} privateKey - * @param {string} [passphrase] - * @returns {Object} - */ - async _importPrivateKey(privateKey, passphrase) { - try { - const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ - armored: privateKey, - opts: { - "no_check_keys": true - } - }); - if (key.is_pgp_locked()) { - if (passphrase) { - await promisify(key.unlock_pgp.bind(key))({ - passphrase - }); - } else { - throw "Did not provide passphrase with locked private key."; - } - } - return key; - } catch (err) { - throw `Could not import private key: ${err}`; - } - }, - - - /** - * Import public key - * - * @private - * @param {string} publicKey - * @returns {Object} - */ - async _importPublicKey (publicKey) { - try { - const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ - armored: publicKey, - opts: { - "no_check_keys": true - } - }); - return key; - } catch (err) { - throw `Could not import public key: ${err}`; - } - }, - - - /** - * Generate PGP Key Pair operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runGenerateKeyPair(input, args) { - let [keyType, keySize] = args[0].split("-"), - password = args[1], - name = args[2], - email = args[3], - userIdentifier = ""; - - if (name) userIdentifier += name; - if (email) userIdentifier += ` <${email}>`; - - let flags = kbpgp.const.openpgp.certify_keys; - flags |= kbpgp.const.openpgp.sign_data; - flags |= kbpgp.const.openpgp.auth; - flags |= kbpgp.const.openpgp.encrypt_comm; - flags |= kbpgp.const.openpgp.encrypt_storage; - - let keyGenerationOptions = { - userid: userIdentifier, - ecc: keyType === "ecc", - primary: { - "nbits": keySize, - "flags": flags, - "expire_in": 0 - }, - subkeys: [{ - "nbits": PGP._getSubkeySize(keySize), - "flags": kbpgp.const.openpgp.sign_data, - "expire_in": 86400 * 365 * 8 - }, { - "nbits": PGP._getSubkeySize(keySize), - "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, - "expire_in": 86400 * 365 * 2 - }], - asp: PGP._ASP - }; - - return new Promise(async (resolve, reject) => { - try { - const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions); - await promisify(unsignedKey.sign.bind(unsignedKey))({}); - let signedKey = unsignedKey; - let privateKeyExportOptions = {}; - if (password) privateKeyExportOptions.passphrase = password; - const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions); - const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({}); - resolve(privateKey + "\n" + publicKey.trim()); - } catch (err) { - reject(`Error whilst generating key pair: ${err}`); - } - }); - }, - - - /** - * PGP Encrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - async runEncrypt(input, args) { - let plaintextMessage = input, - plainPubKey = args[0], - key, - encryptedMessage; - - if (!plainPubKey) return "Enter the public key of the recipient."; - - try { - key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ - armored: plainPubKey, - }); - } catch (err) { - throw `Could not import public key: ${err}`; - } - - try { - encryptedMessage = await promisify(kbpgp.box)({ - "msg": plaintextMessage, - "encrypt_for": key, - "asp": PGP._ASP - }); - } catch (err) { - throw `Couldn't encrypt message with provided public key: ${err}`; - } - - return encryptedMessage.toString(); - }, - - - /** - * PGP Decrypt operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - async runDecrypt(input, args) { - let encryptedMessage = input, - privateKey = args[0], - passphrase = args[1], - keyring = new kbpgp.keyring.KeyRing(), - plaintextMessage; - - if (!privateKey) return "Enter the private key of the recipient."; - - const key = await PGP._importPrivateKey(privateKey, passphrase); - keyring.add_key_manager(key); - - try { - plaintextMessage = await promisify(kbpgp.unbox)({ - armored: encryptedMessage, - keyfetch: keyring, - asp: PGP._ASP - }); - } catch (err) { - throw `Couldn't decrypt message with provided private key: ${err}`; - } - - return plaintextMessage.toString(); - }, - - - /** - * PGP Sign Message operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - async runSign(input, args) { - let message = input, - privateKey = args[0], - passphrase = args[1], - publicKey = args[2], - signedMessage; - - if (!privateKey) return "Enter the private key of the signer."; - if (!publicKey) return "Enter the public key of the recipient."; - const privKey = await PGP._importPrivateKey(privateKey, passphrase); - const pubKey = await PGP._importPublicKey(publicKey); - - try { - signedMessage = await promisify(kbpgp.box)({ - "msg": message, - "encrypt_for": pubKey, - "sign_with": privKey, - "asp": PGP._ASP - }); - } catch (err) { - throw `Couldn't sign message: ${err}`; - } - - return signedMessage; - }, - - - /** - * PGP Verify Message operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - async runVerify(input, args) { - let signedMessage = input, - publicKey = args[0], - privateKey = args[1], - passphrase = args[2], - keyring = new kbpgp.keyring.KeyRing(), - unboxedLiterals; - - if (!publicKey) return "Enter the public key of the signer."; - if (!privateKey) return "Enter the private key of the recipient."; - const privKey = await PGP._importPrivateKey(privateKey, passphrase); - const pubKey = await PGP._importPublicKey(publicKey); - keyring.add_key_manager(privKey); - keyring.add_key_manager(pubKey); - - try { - unboxedLiterals = await promisify(kbpgp.unbox)({ - armored: signedMessage, - keyfetch: keyring, - asp: PGP._ASP - }); - const ds = unboxedLiterals[0].get_data_signer(); - if (ds) { - const km = ds.get_key_manager(); - if (km) { - const signer = km.get_userids_mark_primary()[0].components; - let text = "Signed by "; - if (signer.email || signer.username || signer.comment) { - if (signer.username) { - text += `${signer.username} `; - } - if (signer.comment) { - text += `${signer.comment} `; - } - if (signer.email) { - text += `<${signer.email}>`; - } - text += "\n"; - } - text += [ - `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, - `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`, - "----------------------------------\n" - ].join("\n"); - text += unboxedLiterals.toString(); - return text.trim(); - } else { - return "Could not identify a key manager."; - } - } else { - return "The data does not appear to be signed."; - } - } catch (err) { - return `Couldn't verify message: ${err}`; - } - }, -}; - -export default PGP; diff --git a/src/core/operations/PGPDecrypt.mjs b/src/core/operations/PGPDecrypt.mjs new file mode 100644 index 00000000..6e0b3d3d --- /dev/null +++ b/src/core/operations/PGPDecrypt.mjs @@ -0,0 +1,86 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Decrypt operation + */ +class PGPDecrypt extends Operation { + + /** + * PGPDecrypt constructor + */ + constructor() { + super(); + + this.name = "PGP Decrypt"; + this.module = "PGP"; + this.description = [ + "Input: the ASCII-armoured PGP message you want to decrypt.", + "

", + "Arguments: the ASCII-armoured PGP private key of the recipient, ", + "(and the private key password if necessary).", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function uses the Keybase implementation of PGP.", + ].join("\n"); + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Private key of recipient", + "type": "text", + "value": "" + }, + { + "name": "Private key passphrase", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid private key + */ + async run(input, args) { + const encryptedMessage = input, + [privateKey, passphrase] = args, + keyring = new kbpgp.keyring.KeyRing(); + let plaintextMessage; + + if (!privateKey) throw new OperationError("Enter the private key of the recipient."); + + const key = await importPrivateKey(privateKey, passphrase); + keyring.add_key_manager(key); + + try { + plaintextMessage = await promisify(kbpgp.unbox)({ + armored: encryptedMessage, + keyfetch: keyring, + asp: ASP + }); + } catch (err) { + throw new OperationError(`Couldn't decrypt message with provided private key: ${err}`); + } + + return plaintextMessage.toString(); + } + +} + +export default PGPDecrypt; diff --git a/src/core/operations/PGPDecryptAndVerify.mjs b/src/core/operations/PGPDecryptAndVerify.mjs new file mode 100644 index 00000000..0f6463f2 --- /dev/null +++ b/src/core/operations/PGPDecryptAndVerify.mjs @@ -0,0 +1,122 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Decrypt and Verify operation + */ +class PGPDecryptAndVerify extends Operation { + + /** + * PGPDecryptAndVerify constructor + */ + constructor() { + super(); + + this.name = "PGP Decrypt and Verify"; + this.module = "PGP"; + this.description = [ + "Input: the ASCII-armoured encrypted PGP message you want to verify.", + "

", + "Arguments: the ASCII-armoured PGP public key of the signer, ", + "the ASCII-armoured private key of the recipient (and the private key password if necessary).", + "

", + "This operation uses PGP to decrypt and verify an encrypted digital signature.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function uses the Keybase implementation of PGP.", + ].join("\n"); + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Public key of signer", + "type": "text", + "value": "" + }, + { + "name": "Private key of recipient", + "type": "text", + "value": "" + }, + { + "name": "Private key password", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const signedMessage = input, + [publicKey, privateKey, passphrase] = args, + keyring = new kbpgp.keyring.KeyRing(); + let unboxedLiterals; + + if (!publicKey) throw new OperationError("Enter the public key of the signer."); + if (!privateKey) throw new OperationError("Enter the private key of the recipient."); + const privKey = await importPrivateKey(privateKey, passphrase); + const pubKey = await importPublicKey(publicKey); + keyring.add_key_manager(privKey); + keyring.add_key_manager(pubKey); + + try { + unboxedLiterals = await promisify(kbpgp.unbox)({ + armored: signedMessage, + keyfetch: keyring, + asp: ASP + }); + const ds = unboxedLiterals[0].get_data_signer(); + if (ds) { + const km = ds.get_key_manager(); + if (km) { + const signer = km.get_userids_mark_primary()[0].components; + let text = "Signed by "; + if (signer.email || signer.username || signer.comment) { + if (signer.username) { + text += `${signer.username} `; + } + if (signer.comment) { + text += `${signer.comment} `; + } + if (signer.email) { + text += `<${signer.email}>`; + } + text += "\n"; + } + text += [ + `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, + `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`, + "----------------------------------\n" + ].join("\n"); + text += unboxedLiterals.toString(); + return text.trim(); + } else { + throw new OperationError("Could not identify a key manager."); + } + } else { + throw new OperationError("The data does not appear to be signed."); + } + } catch (err) { + throw new OperationError(`Couldn't verify message: ${err}`); + } + } + +} + +export default PGPDecryptAndVerify; diff --git a/src/core/operations/PGPEncrypt.mjs b/src/core/operations/PGPEncrypt.mjs new file mode 100644 index 00000000..345d313f --- /dev/null +++ b/src/core/operations/PGPEncrypt.mjs @@ -0,0 +1,78 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPublicKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Encrypt operation + */ +class PGPEncrypt extends Operation { + + /** + * PGPEncrypt constructor + */ + constructor() { + super(); + + this.name = "PGP Encrypt"; + this.module = "PGP"; + this.description = [ + "Input: the message you want to encrypt.", + "

", + "Arguments: the ASCII-armoured PGP public key of the recipient.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function uses the Keybase implementation of PGP.", + ].join("\n"); + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Public key of recipient", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if failed private key import or failed encryption + */ + async run(input, args) { + const plaintextMessage = input, + plainPubKey = args[0]; + let encryptedMessage; + + if (!plainPubKey) throw new OperationError("Enter the public key of the recipient."); + + const key = await importPublicKey(plainPubKey); + + try { + encryptedMessage = await promisify(kbpgp.box)({ + "msg": plaintextMessage, + "encrypt_for": key, + "asp": ASP + }); + } catch (err) { + throw new OperationError(`Couldn't encrypt message with provided public key: ${err}`); + } + + return encryptedMessage.toString(); + } + +} + +export default PGPEncrypt; diff --git a/src/core/operations/PGPEncryptAndSign.mjs b/src/core/operations/PGPEncryptAndSign.mjs new file mode 100644 index 00000000..0daa27b5 --- /dev/null +++ b/src/core/operations/PGPEncryptAndSign.mjs @@ -0,0 +1,93 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import * as es6promisify from "es6-promisify"; +const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify; + +/** + * PGP Encrypt and Sign operation + */ +class PGPEncryptAndSign extends Operation { + + /** + * PGPEncryptAndSign constructor + */ + constructor() { + super(); + + this.name = "PGP Encrypt and Sign"; + this.module = "PGP"; + this.description = [ + "Input: the cleartext you want to sign.", + "

", + "Arguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)", + "and the ASCII-armoured PGP public key of the recipient.", + "

", + "This operation uses PGP to produce an encrypted digital signature.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function uses the Keybase implementation of PGP.", + ].join("\n"); + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Private key of signer", + "type": "text", + "value": "" + }, + { + "name": "Private key passphrase", + "type": "string", + "value": "" + }, + { + "name": "Public key of recipient", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if failure to sign message + */ + async run(input, args) { + const message = input, + [privateKey, passphrase, publicKey] = args; + let signedMessage; + + if (!privateKey) throw new OperationError("Enter the private key of the signer."); + if (!publicKey) throw new OperationError("Enter the public key of the recipient."); + const privKey = await importPrivateKey(privateKey, passphrase); + const pubKey = await importPublicKey(publicKey); + + try { + signedMessage = await promisify(kbpgp.box)({ + "msg": message, + "encrypt_for": pubKey, + "sign_with": privKey, + "asp": ASP + }); + } catch (err) { + throw new OperationError(`Couldn't sign message: ${err}`); + } + + return signedMessage; + } + +} + +export default PGPEncryptAndSign; diff --git a/src/core/operations/PHP.js b/src/core/operations/PHPDeserialize.mjs similarity index 64% rename from src/core/operations/PHP.js rename to src/core/operations/PHPDeserialize.mjs index e4bb0b5b..a54c04d2 100644 --- a/src/core/operations/PHP.js +++ b/src/core/operations/PHPDeserialize.mjs @@ -1,32 +1,43 @@ /** - * PHP operations. - * * @author Jarmo van Lenthe [github.com/jarmovanlenthe] * @copyright Jarmo van Lenthe * @license Apache-2.0 - * - * @namespace */ -const PHP = { + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * PHP Deserialize operation + */ +class PHPDeserialize extends Operation { /** - * @constant - * @default + * PHPDeserialize constructor */ - OUTPUT_VALID_JSON: true, + constructor() { + super(); + + this.name = "PHP Deserialize"; + this.module = "Default"; + this.description = "Deserializes PHP serialized data, outputting keyed arrays as JSON.

This function does not support object tags.

Example:
a:2:{s:1:"a";i:10;i:0;a:1:{s:2:"ab";b:1;}}
becomes
{"a": 10,0: {"ab": true}}

Output valid JSON: JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Output valid JSON", + "type": "boolean", + "value": true + } + ]; + } /** - * PHP Deserialize operation. - * - * This Javascript implementation is based on the Python implementation by - * Armin Ronacher (2016), who released it under the 3-Clause BSD license. - * See: https://github.com/mitsuhiko/phpserialize/ - * * @param {string} input * @param {Object[]} args * @returns {string} */ - runDeserialize: function (input, args) { + run(input, args) { /** * Recursive method for deserializing. * @returns {*} @@ -40,9 +51,9 @@ const PHP = { function read(length) { let result = ""; for (let idx = 0; idx < length; idx++) { - let char = inputPart.shift(); + const char = inputPart.shift(); if (char === undefined) { - throw "End of input reached before end of script"; + throw new OperationError("End of input reached before end of script"); } result += char; } @@ -57,7 +68,7 @@ const PHP = { function readUntil(until) { let result = ""; for (;;) { - let char = read(1); + const char = read(1); if (char === until) { break; } else { @@ -74,9 +85,9 @@ const PHP = { * @returns {string} */ function expect(expect) { - let result = read(expect.length); + const result = read(expect.length); if (result !== expect) { - throw "Unexpected input found"; + throw new OperationError("Unexpected input found"); } return result; } @@ -86,18 +97,18 @@ const PHP = { * @returns {Array} */ function handleArray() { - let items = parseInt(readUntil(":"), 10) * 2; + const items = parseInt(readUntil(":"), 10) * 2; expect("{"); - let result = []; + const result = []; let isKey = true; let lastItem = null; for (let idx = 0; idx < items; idx++) { - let item = handleInput(); + const item = handleInput(); if (isKey) { lastItem = item; isKey = false; } else { - let numberCheck = lastItem.match(/[0-9]+/); + const numberCheck = lastItem.match(/[0-9]+/); if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) { result.push("\"" + lastItem + "\": " + item); } else { @@ -111,7 +122,7 @@ const PHP = { } - let kind = read(1).toLowerCase(); + const kind = read(1).toLowerCase(); switch (kind) { case "n": @@ -122,7 +133,7 @@ const PHP = { case "d": case "b": { expect(":"); - let data = readUntil(";"); + const data = readUntil(";"); if (kind === "b") { return (parseInt(data, 10) !== 0); } @@ -135,9 +146,9 @@ const PHP = { case "s": { expect(":"); - let length = readUntil(":"); + const length = readUntil(":"); expect("\""); - let value = read(length); + const value = read(length); expect("\";"); if (args[0]) { return "\"" + value.replace(/"/g, "\\\"") + "\""; @@ -147,14 +158,14 @@ const PHP = { } default: - throw "Unknown type: " + kind; + throw new OperationError("Unknown type: " + kind); } } - let inputPart = input.split(""); + const inputPart = input.split(""); return handleInput(); } -}; +} -export default PHP; +export default PHPDeserialize; diff --git a/src/core/operations/PadLines.mjs b/src/core/operations/PadLines.mjs new file mode 100644 index 00000000..e9e2b45a --- /dev/null +++ b/src/core/operations/PadLines.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Pad lines operation + */ +class PadLines extends Operation { + + /** + * PadLines constructor + */ + constructor() { + super(); + + this.name = "Pad lines"; + this.module = "Default"; + this.description = "Add the specified number of the specified character to the beginning or end of each line"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Position", + "type": "option", + "value": ["Start", "End"] + }, + { + "name": "Length", + "type": "number", + "value": 5 + }, + { + "name": "Character", + "type": "binaryShortString", + "value": " " + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [position, len, chr] = args, + lines = input.split("\n"); + let output = "", + i = 0; + + if (position === "Start") { + for (i = 0; i < lines.length; i++) { + output += lines[i].padStart(lines[i].length+len, chr) + "\n"; + } + } else if (position === "End") { + for (i = 0; i < lines.length; i++) { + output += lines[i].padEnd(lines[i].length+len, chr) + "\n"; + } + } + + return output.slice(0, output.length-1); + } + +} + +export default PadLines; diff --git a/src/core/operations/ParseASN1HexString.mjs b/src/core/operations/ParseASN1HexString.mjs new file mode 100644 index 00000000..9f192156 --- /dev/null +++ b/src/core/operations/ParseASN1HexString.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Parse ASN.1 hex string operation + */ +class ParseASN1HexString extends Operation { + + /** + * ParseASN1HexString constructor + */ + constructor() { + super(); + + this.name = "Parse ASN.1 hex string"; + this.module = "PublicKey"; + this.description = "Abstract Syntax Notation One (ASN.1) is a standard and notation that describes rules and structures for representing, encoding, transmitting, and decoding data in telecommunications and computer networking.

This operation parses arbitrary ASN.1 data and presents the resulting tree."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Starting index", + "type": "number", + "value": 0 + }, + { + "name": "Truncate octet strings longer than", + "type": "number", + "value": 32 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [index, truncateLen] = args; + return r.ASN1HEX.dump(input.replace(/\s/g, ""), { + "ommitLongOctet": truncateLen + }, index); + } + +} + +export default ParseASN1HexString; diff --git a/src/core/operations/ParseColourCode.mjs b/src/core/operations/ParseColourCode.mjs new file mode 100644 index 00000000..ca6ee80d --- /dev/null +++ b/src/core/operations/ParseColourCode.mjs @@ -0,0 +1,195 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Parse colour code operation + */ +class ParseColourCode extends Operation { + + /** + * ParseColourCode constructor + */ + constructor() { + super(); + + this.name = "Parse colour code"; + this.module = "Default"; + this.description = "Converts a colour code in a standard format to other standard formats and displays the colour itself.

Example inputs
  • #d9edf7
  • rgba(217,237,247,1)
  • hsla(200,65%,91%,1)
  • cmyk(0.12, 0.04, 0.00, 0.03)
"; + this.inputType = "string"; + this.outputType = "html"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + let m = null, + r = 0, g = 0, b = 0, a = 1; + + // Read in the input + if ((m = input.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i))) { + // Hex - #d9edf7 + r = parseInt(m[1], 16); + g = parseInt(m[2], 16); + b = parseInt(m[3], 16); + } else if ((m = input.match(/rgba?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)(?:,\s?(\d(?:\.\d+)?))?\)/i))) { + // RGB or RGBA - rgb(217,237,247) or rgba(217,237,247,1) + r = parseFloat(m[1]); + g = parseFloat(m[2]); + b = parseFloat(m[3]); + a = m[4] ? parseFloat(m[4]) : 1; + } else if ((m = input.match(/hsla?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)%,\s?(\d{1,3}(?:\.\d+)?)%(?:,\s?(\d(?:\.\d+)?))?\)/i))) { + // HSL or HSLA - hsl(200, 65%, 91%) or hsla(200, 65%, 91%, 1) + const h_ = parseFloat(m[1]) / 360, + s_ = parseFloat(m[2]) / 100, + l_ = parseFloat(m[3]) / 100, + rgb_ = ParseColourCode._hslToRgb(h_, s_, l_); + + r = rgb_[0]; + g = rgb_[1]; + b = rgb_[2]; + a = m[4] ? parseFloat(m[4]) : 1; + } else if ((m = input.match(/cmyk\((\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?)\)/i))) { + // CMYK - cmyk(0.12, 0.04, 0.00, 0.03) + const c_ = parseFloat(m[1]), + m_ = parseFloat(m[2]), + y_ = parseFloat(m[3]), + k_ = parseFloat(m[4]); + + r = Math.round(255 * (1 - c_) * (1 - k_)); + g = Math.round(255 * (1 - m_) * (1 - k_)); + b = Math.round(255 * (1 - y_) * (1 - k_)); + } + + const hsl_ = ParseColourCode._rgbToHsl(r, g, b), + h = Math.round(hsl_[0] * 360), + s = Math.round(hsl_[1] * 100), + l = Math.round(hsl_[2] * 100); + let k = 1 - Math.max(r/255, g/255, b/255), + c = (1 - r/255 - k) / (1 - k), + y = (1 - b/255 - k) / (1 - k); + + m = (1 - g/255 - k) / (1 - k); + + c = isNaN(c) ? "0" : c.toFixed(2); + m = isNaN(m) ? "0" : m.toFixed(2); + y = isNaN(y) ? "0" : y.toFixed(2); + k = k.toFixed(2); + + const hex = "#" + + Math.round(r).toString(16).padStart(2, "0") + + Math.round(g).toString(16).padStart(2, "0") + + Math.round(b).toString(16).padStart(2, "0"), + rgb = "rgb(" + r + ", " + g + ", " + b + ")", + rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")", + hsl = "hsl(" + h + ", " + s + "%, " + l + "%)", + hsla = "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")", + cmyk = "cmyk(" + c + ", " + m + ", " + y + ", " + k + ")"; + + // Generate output + return `
+Hex: ${hex} +RGB: ${rgb} +RGBA: ${rgba} +HSL: ${hsl} +HSLA: ${hsla} +CMYK: ${cmyk} +`; + } + + /** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @author Mohsen (http://stackoverflow.com/a/9493060) + * + * @param {number} h - The hue + * @param {number} s - The saturation + * @param {number} l - The lightness + * @return {Array} The RGB representation + */ + static _hslToRgb(h, s, l) { + let r, g, b; + + if (s === 0){ + r = g = b = l; // achromatic + } else { + const hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1/6) return p + (q - p) * 6 * t; + if (t < 1/2) return q; + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + }; + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } + + /** + * Converts an RGB color value to HSL. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace. + * Assumes r, g, and b are contained in the set [0, 255] and + * returns h, s, and l in the set [0, 1]. + * + * @author Mohsen (http://stackoverflow.com/a/9493060) + * + * @param {number} r - The red color value + * @param {number} g - The green color value + * @param {number} b - The blue color value + * @return {Array} The HSL representation + */ + static _rgbToHsl(r, g, b) { + r /= 255; g /= 255; b /= 255; + const max = Math.max(r, g, b), + min = Math.min(r, g, b), + l = (max + min) / 2; + let h, s; + + if (max === min) { + h = s = 0; // achromatic + } else { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + return [h, s, l]; + } +} + +export default ParseColourCode; diff --git a/src/core/operations/ParseDateTime.mjs b/src/core/operations/ParseDateTime.mjs new file mode 100644 index 00000000..8b39616d --- /dev/null +++ b/src/core/operations/ParseDateTime.mjs @@ -0,0 +1,82 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime"; + +/** + * Parse DateTime operation + */ +class ParseDateTime extends Operation { + + /** + * ParseDateTime constructor + */ + constructor() { + super(); + + this.name = "Parse DateTime"; + this.module = "Default"; + this.description = "Parses a DateTime string in your specified format and displays it in whichever timezone you choose with the following information:
  • Date
  • Time
  • Period (AM/PM)
  • Timezone
  • UTC offset
  • Daylight Saving Time
  • Leap year
  • Days in this month
  • Day of year
  • Week number
  • Quarter
Run with no input to see format string examples if required."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Built in formats", + "type": "populateOption", + "value": DATETIME_FORMATS, + "target": 1 + }, + { + "name": "Input format string", + "type": "binaryString", + "value": "DD/MM/YYYY HH:mm:ss" + }, + { + "name": "Input timezone", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const inputFormat = args[1], + inputTimezone = args[2]; + let date, + output = ""; + + try { + date = moment.tz(input, inputFormat, inputTimezone); + if (!date || date.format() === "Invalid date") throw Error; + } catch (err) { + return `Invalid format.\n\n${FORMAT_EXAMPLES}`; + } + + output += "Date: " + date.format("dddd Do MMMM YYYY") + + "\nTime: " + date.format("HH:mm:ss") + + "\nPeriod: " + date.format("A") + + "\nTimezone: " + date.format("z") + + "\nUTC offset: " + date.format("ZZ") + + "\n\nDaylight Saving Time: " + date.isDST() + + "\nLeap year: " + date.isLeapYear() + + "\nDays in this month: " + date.daysInMonth() + + "\n\nDay of year: " + date.dayOfYear() + + "\nWeek number: " + date.weekYear() + + "\nQuarter: " + date.quarter(); + + return output; + } + +} + +export default ParseDateTime; diff --git a/src/core/operations/ParseIPRange.mjs b/src/core/operations/ParseIPRange.mjs new file mode 100644 index 00000000..0063f320 --- /dev/null +++ b/src/core/operations/ParseIPRange.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import {ipv4CidrRange, ipv4HyphenatedRange, ipv6CidrRange, ipv6HyphenatedRange} from "../lib/IP"; + +/** + * Parse IP range operation + */ +class ParseIPRange extends Operation { + + /** + * ParseIPRange constructor + */ + constructor() { + super(); + + this.name = "Parse IP range"; + this.module = "JSBN"; + this.description = "Given a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0), this operation provides network information and enumerates all IP addresses in the range.

IPv6 is supported but will not be enumerated."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Include network info", + "type": "boolean", + "value": true + }, + { + "name": "Enumerate IP addresses", + "type": "boolean", + "value": true + }, + { + "name": "Allow large queries", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [ + includeNetworkInfo, + enumerateAddresses, + allowLargeList + ] = args; + + // Check what type of input we are looking at + const ipv4CidrRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\/(\d\d?)\s*$/, + ipv4RangeRegex = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*-\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/, + ipv6CidrRegex = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\/(\d\d?\d?)\s*$/i, + ipv6RangeRegex = /^\s*(((?=.*::)(?!.*::[^-]+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*-\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\17)::|:\b|(?![\dA-F])))|(?!\16\17)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i; + let match; + + if ((match = ipv4CidrRegex.exec(input))) { + return ipv4CidrRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv4RangeRegex.exec(input))) { + return ipv4HyphenatedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList); + } else if ((match = ipv6CidrRegex.exec(input))) { + return ipv6CidrRange(match, includeNetworkInfo); + } else if ((match = ipv6RangeRegex.exec(input))) { + return ipv6HyphenatedRange(match, includeNetworkInfo); + } else { + throw new OperationError("Invalid input.\n\nEnter either a CIDR range (e.g. 10.0.0.0/24) or a hyphenated range (e.g. 10.0.0.0 - 10.0.1.0). IPv6 also supported."); + } + } + +} + + +export default ParseIPRange; diff --git a/src/core/operations/ParseIPv4Header.mjs b/src/core/operations/ParseIPv4Header.mjs new file mode 100644 index 00000000..cf5b31aa --- /dev/null +++ b/src/core/operations/ParseIPv4Header.mjs @@ -0,0 +1,129 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {fromHex, toHex} from "../lib/Hex"; +import {ipv4ToStr, protocolLookup} from "../lib/IP"; +import TCPIPChecksum from "./TCPIPChecksum"; + +/** + * Parse IPv4 header operation + */ +class ParseIPv4Header extends Operation { + + /** + * ParseIPv4Header constructor + */ + constructor() { + super(); + + this.name = "Parse IPv4 header"; + this.module = "JSBN"; + this.description = "Given an IPv4 header, this operations parses and displays each field in an easily readable format."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const format = args[0]; + let output; + + if (format === "Hex") { + input = fromHex(input); + } else if (format === "Raw") { + input = Utils.strToByteArray(input); + } else { + throw new OperationError("Unrecognised input format."); + } + + let ihl = input[0] & 0x0f; + const dscp = (input[1] >>> 2) & 0x3f, + ecn = input[1] & 0x03, + length = input[2] << 8 | input[3], + identification = input[4] << 8 | input[5], + flags = (input[6] >>> 5) & 0x07, + fragOffset = (input[6] & 0x1f) << 8 | input[7], + ttl = input[8], + protocol = input[9], + checksum = input[10] << 8 | input[11], + srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15], + dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19], + checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20)); + let version = (input[0] >>> 4) & 0x0f, + options = []; + + + // Version + if (version !== 4) { + version = version + " (Error: for IPv4 headers, this should always be set to 4)"; + } + + // IHL + if (ihl < 5) { + ihl = ihl + " (Error: this should always be at least 5)"; + } else if (ihl > 5) { + // sort out options... + const optionsLen = ihl * 4 - 20; + options = input.slice(20, optionsLen + 20); + } + + // Protocol + const protocolInfo = protocolLookup[protocol] || {keyword: "", protocol: ""}; + + // Checksum + const correctChecksum = (new TCPIPChecksum).run(checksumHeader), + givenChecksum = Utils.hex(checksum); + let checksumResult; + if (correctChecksum === givenChecksum) { + checksumResult = givenChecksum + " (correct)"; + } else { + checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")"; + } + + output = ` + + + + + + + + + + + + +`; + + if (ihl > 5) { + output += ``; + } + + return output + "
FieldValue
Version${version}
Internet Header Length (IHL)${ihl} (${ihl * 4} bytes)
Differentiated Services Code Point (DSCP)${dscp}
Explicit Congestion Notification (ECN)${ecn}
Total length${length} bytes + IP header: ${ihl * 4} bytes + Data: ${length - ihl * 4} bytes
Identification0x${Utils.hex(identification)} (${identification})
Flags0x${Utils.hex(flags, 2)} + Reserved bit:${flags >> 2} (must be 0) + Don't fragment:${flags >> 1 & 1} + More fragments:${flags & 1}
Fragment offset${fragOffset}
Time-To-Live${ttl}
Protocol${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})
Header checksum${checksumResult}
Source IP address${ipv4ToStr(srcIP)}
Destination IP address${ipv4ToStr(dstIP)}
Options${toHex(options)}
"; + } + +} + +export default ParseIPv4Header; diff --git a/src/core/operations/ParseIPv6Address.mjs b/src/core/operations/ParseIPv6Address.mjs new file mode 100644 index 00000000..26515fed --- /dev/null +++ b/src/core/operations/ParseIPv6Address.mjs @@ -0,0 +1,192 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {strToIpv6, ipv6ToStr, ipv4ToStr, IPV6_REGEX} from "../lib/IP"; +import BigInteger from "jsbn"; + +/** + * Parse IPv6 address operation + */ +class ParseIPv6Address extends Operation { + + /** + * ParseIPv6Address constructor + */ + constructor() { + super(); + + this.name = "Parse IPv6 address"; + this.module = "JSBN"; + this.description = "Displays the longhand and shorthand versions of a valid IPv6 address.

Recognises all reserved ranges and parses encapsulated or tunnelled addresses including Teredo and 6to4."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let match, + output = ""; + + if ((match = IPV6_REGEX.exec(input))) { + const ipv6 = strToIpv6(match[1]), + longhand = ipv6ToStr(ipv6), + shorthand = ipv6ToStr(ipv6, true); + + output += "Longhand: " + longhand + "\nShorthand: " + shorthand + "\n"; + + // Detect reserved addresses + if (shorthand === "::") { + // Unspecified address + output += "\nUnspecified address corresponding to 0.0.0.0/32 in IPv4."; + output += "\nUnspecified address range: ::/128"; + } else if (shorthand === "::1") { + // Loopback address + output += "\nLoopback address to the local host corresponding to 127.0.0.1/8 in IPv4."; + output += "\nLoopback addresses range: ::1/128"; + } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0xffff) { + // IPv4-mapped IPv6 address + output += "\nIPv4-mapped IPv6 address detected. IPv6 clients will be handled natively by default, and IPv4 clients appear as IPv6 clients at their IPv4-mapped IPv6 address."; + output += "\nMapped IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\nIPv4-mapped IPv6 addresses range: ::ffff:0:0/96"; + } else if (ipv6[0] === 0 && ipv6[1] === 0 && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0xffff && ipv6[5] === 0) { + // IPv4-translated address + output += "\nIPv4-translated address detected. Used by Stateless IP/ICMP Translation (SIIT). See RFCs 6145 and 6052 for more details."; + output += "\nTranslated IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\nIPv4-translated addresses range: ::ffff:0:0:0/96"; + } else if (ipv6[0] === 0x100) { + // Discard prefix per RFC 6666 + output += "\nDiscard prefix detected. This is used when forwarding traffic to a sinkhole router to mitigate the effects of a denial-of-service attack. See RFC 6666 for more details."; + output += "\nDiscard range: 100::/64"; + } else if (ipv6[0] === 0x64 && ipv6[1] === 0xff9b && ipv6[2] === 0 && + ipv6[3] === 0 && ipv6[4] === 0 && ipv6[5] === 0) { + // IPv4/IPv6 translation per RFC 6052 + output += "\n'Well-Known' prefix for IPv4/IPv6 translation detected. See RFC 6052 for more details."; + output += "\nTranslated IPv4 address: " + ipv4ToStr((ipv6[6] << 16) + ipv6[7]); + output += "\n'Well-Known' prefix range: 64:ff9b::/96"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0) { + // Teredo tunneling + output += "\nTeredo tunneling IPv6 address detected\n"; + const serverIpv4 = (ipv6[2] << 16) + ipv6[3], + udpPort = (~ipv6[5]) & 0xffff, + clientIpv4 = ~((ipv6[6] << 16) + ipv6[7]), + flagCone = (ipv6[4] >>> 15) & 1, + flagR = (ipv6[4] >>> 14) & 1, + flagRandom1 = (ipv6[4] >>> 10) & 15, + flagUg = (ipv6[4] >>> 8) & 3, + flagRandom2 = ipv6[4] & 255; + + output += "\nServer IPv4 address: " + ipv4ToStr(serverIpv4) + + "\nClient IPv4 address: " + ipv4ToStr(clientIpv4) + + "\nClient UDP port: " + udpPort + + "\nFlags:" + + "\n\tCone: " + flagCone; + + if (flagCone) { + output += " (Client is behind a cone NAT)"; + } else { + output += " (Client is not behind a cone NAT)"; + } + + output += "\n\tR: " + flagR; + + if (flagR) { + output += " Error: This flag should be set to 0. See RFC 5991 and RFC 4380."; + } + + output += "\n\tRandom1: " + Utils.bin(flagRandom1, 4) + + "\n\tUG: " + Utils.bin(flagUg, 2); + + if (flagUg) { + output += " Error: This flag should be set to 00. See RFC 4380."; + } + + output += "\n\tRandom2: " + Utils.bin(flagRandom2, 8); + + if (!flagR && !flagUg && flagRandom1 && flagRandom2) { + output += "\n\nThis is a valid Teredo address which complies with RFC 4380 and RFC 5991."; + } else if (!flagR && !flagUg) { + output += "\n\nThis is a valid Teredo address which complies with RFC 4380, however it does not comply with RFC 5991 (Teredo Security Updates) as there are no randomised bits in the flag field."; + } else { + output += "\n\nThis is an invalid Teredo address."; + } + output += "\n\nTeredo prefix range: 2001::/32"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0x2 && ipv6[2] === 0) { + // Benchmarking + output += "\nAssigned to the Benchmarking Methodology Working Group (BMWG) for benchmarking IPv6. Corresponds to 198.18.0.0/15 for benchmarking IPv4. See RFC 5180 for more details."; + output += "\nBMWG range: 2001:2::/48"; + } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x10 && ipv6[1] <= 0x1f) { + // ORCHIDv1 + output += "\nDeprecated, previously ORCHIDv1 (Overlay Routable Cryptographic Hash Identifiers).\nORCHIDv1 range: 2001:10::/28\nORCHIDv2 now uses 2001:20::/28."; + } else if (ipv6[0] === 0x2001 && ipv6[1] >= 0x20 && ipv6[1] <= 0x2f) { + // ORCHIDv2 + output += "\nORCHIDv2 (Overlay Routable Cryptographic Hash Identifiers).\nThese are non-routed IPv6 addresses used for Cryptographic Hash Identifiers."; + output += "\nORCHIDv2 range: 2001:20::/28"; + } else if (ipv6[0] === 0x2001 && ipv6[1] === 0xdb8) { + // Documentation + output += "\nThis is a documentation IPv6 address. This range should be used whenever an example IPv6 address is given or to model networking scenarios. Corresponds to 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 in IPv4."; + output += "\nDocumentation range: 2001:db8::/32"; + } else if (ipv6[0] === 0x2002) { + // 6to4 + output += "\n6to4 transition IPv6 address detected. See RFC 3056 for more details." + + "\n6to4 prefix range: 2002::/16"; + + const v4Addr = ipv4ToStr((ipv6[1] << 16) + ipv6[2]), + slaId = ipv6[3], + interfaceIdStr = ipv6[4].toString(16) + ipv6[5].toString(16) + ipv6[6].toString(16) + ipv6[7].toString(16), + interfaceId = new BigInteger(interfaceIdStr, 16); + + output += "\n\nEncapsulated IPv4 address: " + v4Addr + + "\nSLA ID: " + slaId + + "\nInterface ID (base 16): " + interfaceIdStr + + "\nInterface ID (base 10): " + interfaceId.toString(); + } else if (ipv6[0] >= 0xfc00 && ipv6[0] <= 0xfdff) { + // Unique local address + output += "\nThis is a unique local address comparable to the IPv4 private addresses 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. See RFC 4193 for more details."; + output += "\nUnique local addresses range: fc00::/7"; + } else if (ipv6[0] >= 0xfe80 && ipv6[0] <= 0xfebf) { + // Link-local address + output += "\nThis is a link-local address comparable to the auto-configuration addresses 169.254.0.0/16 in IPv4."; + output += "\nLink-local addresses range: fe80::/10"; + } else if (ipv6[0] >= 0xff00) { + // Multicast + output += "\nThis is a reserved multicast address."; + output += "\nMulticast addresses range: ff00::/8"; + } + + + // Detect possible EUI-64 addresses + if ((ipv6[5] & 0xff === 0xff) && (ipv6[6] >>> 8 === 0xfe)) { + output += "\n\nThis IPv6 address contains a modified EUI-64 address, identified by the presence of FF:FE in the 12th and 13th octets."; + + const intIdent = Utils.hex(ipv6[4] >>> 8) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + + Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[5] & 0xff) + ":" + + Utils.hex(ipv6[6] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + + Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff), + mac = Utils.hex((ipv6[4] >>> 8) ^ 2) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" + + Utils.hex(ipv6[5] >>> 8) + ":" + Utils.hex(ipv6[6] & 0xff) + ":" + + Utils.hex(ipv6[7] >>> 8) + ":" + Utils.hex(ipv6[7] & 0xff); + output += "\nInterface identifier: " + intIdent + + "\nMAC address: " + mac; + } + } else { + throw new OperationError("Invalid IPv6 address"); + } + return output; + } + +} + +export default ParseIPv6Address; diff --git a/src/core/operations/ParseUNIXFilePermissions.mjs b/src/core/operations/ParseUNIXFilePermissions.mjs new file mode 100644 index 00000000..a86ed9ce --- /dev/null +++ b/src/core/operations/ParseUNIXFilePermissions.mjs @@ -0,0 +1,324 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Parse UNIX file permissions operation + */ +class ParseUNIXFilePermissions extends Operation { + + /** + * ParseUNIXFilePermissions constructor + */ + constructor() { + super(); + + this.name = "Parse UNIX file permissions"; + this.module = "Default"; + this.description = "Given a UNIX/Linux file permission string in octal or textual format, this operation explains which permissions are granted to which user groups.

Input should be in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const perms = { + d: false, // directory + sl: false, // symbolic link + np: false, // named pipe + s: false, // socket + cd: false, // character device + bd: false, // block device + dr: false, // door + sb: false, // sticky bit + su: false, // setuid + sg: false, // setgid + ru: false, // read user + wu: false, // write user + eu: false, // execute user + rg: false, // read group + wg: false, // write group + eg: false, // execute group + ro: false, // read other + wo: false, // write other + eo: false // execute other + }; + let d = 0, + u = 0, + g = 0, + o = 0, + output = "", + octal = null, + textual = null; + + if (input.search(/\s*[0-7]{1,4}\s*/i) === 0) { + // Input is octal + octal = input.match(/\s*([0-7]{1,4})\s*/i)[1]; + + if (octal.length === 4) { + d = parseInt(octal[0], 8); + u = parseInt(octal[1], 8); + g = parseInt(octal[2], 8); + o = parseInt(octal[3], 8); + } else { + if (octal.length > 0) u = parseInt(octal[0], 8); + if (octal.length > 1) g = parseInt(octal[1], 8); + if (octal.length > 2) o = parseInt(octal[2], 8); + } + + perms.su = d >> 2 & 0x1; + perms.sg = d >> 1 & 0x1; + perms.sb = d & 0x1; + + perms.ru = u >> 2 & 0x1; + perms.wu = u >> 1 & 0x1; + perms.eu = u & 0x1; + + perms.rg = g >> 2 & 0x1; + perms.wg = g >> 1 & 0x1; + perms.eg = g & 0x1; + + perms.ro = o >> 2 & 0x1; + perms.wo = o >> 1 & 0x1; + perms.eo = o & 0x1; + } else if (input.search(/\s*[dlpcbDrwxsStT-]{1,10}\s*/) === 0) { + // Input is textual + textual = input.match(/\s*([dlpcbDrwxsStT-]{1,10})\s*/)[1]; + + switch (textual[0]) { + case "d": + perms.d = true; + break; + case "l": + perms.sl = true; + break; + case "p": + perms.np = true; + break; + case "s": + perms.s = true; + break; + case "c": + perms.cd = true; + break; + case "b": + perms.bd = true; + break; + case "D": + perms.dr = true; + break; + } + + if (textual.length > 1) perms.ru = textual[1] === "r"; + if (textual.length > 2) perms.wu = textual[2] === "w"; + if (textual.length > 3) { + switch (textual[3]) { + case "x": + perms.eu = true; + break; + case "s": + perms.eu = true; + perms.su = true; + break; + case "S": + perms.su = true; + break; + } + } + + if (textual.length > 4) perms.rg = textual[4] === "r"; + if (textual.length > 5) perms.wg = textual[5] === "w"; + if (textual.length > 6) { + switch (textual[6]) { + case "x": + perms.eg = true; + break; + case "s": + perms.eg = true; + perms.sg = true; + break; + case "S": + perms.sg = true; + break; + } + } + + if (textual.length > 7) perms.ro = textual[7] === "r"; + if (textual.length > 8) perms.wo = textual[8] === "w"; + if (textual.length > 9) { + switch (textual[9]) { + case "x": + perms.eo = true; + break; + case "t": + perms.eo = true; + perms.sb = true; + break; + case "T": + perms.sb = true; + break; + } + } + } else { + throw new OperationError("Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."); + } + + output += "Textual representation: " + permsToStr(perms); + output += "\nOctal representation: " + permsToOctal(perms); + + // File type + if (textual) { + output += "\nFile type: " + ftFromPerms(perms); + } + + // setuid, setgid + if (perms.su) { + output += "\nThe setuid flag is set"; + } + if (perms.sg) { + output += "\nThe setgid flag is set"; + } + + // sticky bit + if (perms.sb) { + output += "\nThe sticky bit is set"; + } + + // Permission matrix + output += ` + + +---------+-------+-------+-------+ + | | User | Group | Other | + +---------+-------+-------+-------+ + | Read | ${perms.ru ? "X" : " "} | ${perms.rg ? "X" : " "} | ${perms.ro ? "X" : " "} | + +---------+-------+-------+-------+ + | Write | ${perms.wu ? "X" : " "} | ${perms.wg ? "X" : " "} | ${perms.wo ? "X" : " "} | + +---------+-------+-------+-------+ + | Execute | ${perms.eu ? "X" : " "} | ${perms.eg ? "X" : " "} | ${perms.eo ? "X" : " "} | + +---------+-------+-------+-------+`; + + return output; + } + +} + + +/** + * Given a permissions object dictionary, generates a textual permissions string. + * + * @param {Object} perms + * @returns {string} + */ +function permsToStr(perms) { + let str = "", + type = "-"; + + if (perms.d) type = "d"; + if (perms.sl) type = "l"; + if (perms.np) type = "p"; + if (perms.s) type = "s"; + if (perms.cd) type = "c"; + if (perms.bd) type = "b"; + if (perms.dr) type = "D"; + + str = type; + + str += perms.ru ? "r" : "-"; + str += perms.wu ? "w" : "-"; + if (perms.eu && perms.su) { + str += "s"; + } else if (perms.su) { + str += "S"; + } else if (perms.eu) { + str += "x"; + } else { + str += "-"; + } + + str += perms.rg ? "r" : "-"; + str += perms.wg ? "w" : "-"; + if (perms.eg && perms.sg) { + str += "s"; + } else if (perms.sg) { + str += "S"; + } else if (perms.eg) { + str += "x"; + } else { + str += "-"; + } + + str += perms.ro ? "r" : "-"; + str += perms.wo ? "w" : "-"; + if (perms.eo && perms.sb) { + str += "t"; + } else if (perms.sb) { + str += "T"; + } else if (perms.eo) { + str += "x"; + } else { + str += "-"; + } + + return str; +} + +/** + * Given a permissions object dictionary, generates an octal permissions string. + * + * @param {Object} perms + * @returns {string} + */ +function permsToOctal(perms) { + let d = 0, + u = 0, + g = 0, + o = 0; + + if (perms.su) d += 4; + if (perms.sg) d += 2; + if (perms.sb) d += 1; + + if (perms.ru) u += 4; + if (perms.wu) u += 2; + if (perms.eu) u += 1; + + if (perms.rg) g += 4; + if (perms.wg) g += 2; + if (perms.eg) g += 1; + + if (perms.ro) o += 4; + if (perms.wo) o += 2; + if (perms.eo) o += 1; + + return d.toString() + u.toString() + g.toString() + o.toString(); +} + + +/** + * Given a permissions object dictionary, returns the file type. + * + * @param {Object} perms + * @returns {string} + */ +function ftFromPerms(perms) { + if (perms.d) return "Directory"; + if (perms.sl) return "Symbolic link"; + if (perms.np) return "Named pipe"; + if (perms.s) return "Socket"; + if (perms.cd) return "Character device"; + if (perms.bd) return "Block device"; + if (perms.dr) return "Door"; + return "Regular file"; +} + +export default ParseUNIXFilePermissions; diff --git a/src/core/operations/ParseURI.mjs b/src/core/operations/ParseURI.mjs new file mode 100644 index 00000000..a272ef53 --- /dev/null +++ b/src/core/operations/ParseURI.mjs @@ -0,0 +1,69 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import url from "url"; + +/** + * Parse URI operation + */ +class ParseURI extends Operation { + + /** + * ParseURI constructor + */ + constructor() { + super(); + + this.name = "Parse URI"; + this.module = "URL"; + this.description = "Pretty prints complicated Uniform Resource Identifier (URI) strings for ease of reading. Particularly useful for Uniform Resource Locators (URLs) with a lot of arguments."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const uri = url.parse(input, true); + + let output = ""; + + if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n"; + if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n"; + if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n"; + if (uri.port) output += "Port:\t\t" + uri.port + "\n"; + if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n"; + if (uri.query) { + const keys = Object.keys(uri.query); + let padding = 0; + + keys.forEach(k => { + padding = (k.length > padding) ? k.length : padding; + }); + + output += "Arguments:\n"; + for (const key in uri.query) { + output += "\t" + key.padEnd(padding, " "); + if (uri.query[key].length) { + output += " = " + uri.query[key] + "\n"; + } else { + output += "\n"; + } + } + } + if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n"; + + return output; + } + +} + +export default ParseURI; diff --git a/src/core/operations/ParseUserAgent.mjs b/src/core/operations/ParseUserAgent.mjs new file mode 100644 index 00000000..c1dda3cf --- /dev/null +++ b/src/core/operations/ParseUserAgent.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import UAParser from "ua-parser-js"; + +/** + * Parse User Agent operation + */ +class ParseUserAgent extends Operation { + + /** + * ParseUserAgent constructor + */ + constructor() { + super(); + + this.name = "Parse User Agent"; + this.module = "UserAgent"; + this.description = "Attempts to identify and categorise information contained in a user-agent string."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const ua = UAParser(input); + return `Browser + Name: ${ua.browser.name || "unknown"} + Version: ${ua.browser.version || "unknown"} +Device + Model: ${ua.device.model || "unknown"} + Type: ${ua.device.type || "unknown"} + Vendor: ${ua.device.vendor || "unknown"} +Engine + Name: ${ua.engine.name || "unknown"} + Version: ${ua.engine.version || "unknown"} +OS + Name: ${ua.os.name || "unknown"} + Version: ${ua.os.version || "unknown"} +CPU + Architecture: ${ua.cpu.architecture || "unknown"}`; + } + +} + +export default ParseUserAgent; diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs new file mode 100644 index 00000000..831738f3 --- /dev/null +++ b/src/core/operations/ParseX509Certificate.mjs @@ -0,0 +1,216 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import { fromBase64 } from "../lib/Base64"; +import { toHex } from "../lib/Hex"; +import { formatByteStr, formatDnStr } from "../lib/PublicKey"; +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Parse X.509 certificate operation + */ +class ParseX509Certificate extends Operation { + + /** + * ParseX509Certificate constructor + */ + constructor() { + super(); + + this.name = "Parse X.509 certificate"; + this.module = "PublicKey"; + this.description = "X.509 is an ITU-T standard for a public key infrastructure (PKI) and Privilege Management Infrastructure (PMI). It is commonly involved with SSL/TLS security.

This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.

Tags: X509, server hello, handshake"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["PEM", "DER Hex", "Base64", "Raw"] + } + ]; + this.patterns = [ + { + "match": "^-+BEGIN CERTIFICATE-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE-+\\r?\\n?$", + "flags": "i", + "args": [ + "PEM" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input.length) { + return "No input"; + } + + const cert = new r.X509(), + inputFormat = args[0]; + + switch (inputFormat) { + case "DER Hex": + input = input.replace(/\s/g, ""); + cert.readCertHex(input); + break; + case "PEM": + cert.readCertPEM(input); + break; + case "Base64": + cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), "")); + break; + case "Raw": + cert.readCertHex(toHex(Utils.strToByteArray(input), "")); + break; + default: + throw "Undefined input format"; + } + + const sn = cert.getSerialNumberHex(), + issuer = cert.getIssuerString(), + subject = cert.getSubjectString(), + pk = cert.getPublicKey(), + pkFields = [], + sig = cert.getSignatureValueHex(); + + let pkStr = "", + sigStr = "", + extensions = ""; + + // Public Key fields + pkFields.push({ + key: "Algorithm", + value: pk.type + }); + + if (pk.type === "EC") { // ECDSA + pkFields.push({ + key: "Curve Name", + value: pk.curveName + }); + pkFields.push({ + key: "Length", + value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + " bits" + }); + pkFields.push({ + key: "pub", + value: formatByteStr(pk.pubKeyHex, 16, 18) + }); + } else if (pk.type === "DSA") { // DSA + pkFields.push({ + key: "pub", + value: formatByteStr(pk.y.toString(16), 16, 18) + }); + pkFields.push({ + key: "P", + value: formatByteStr(pk.p.toString(16), 16, 18) + }); + pkFields.push({ + key: "Q", + value: formatByteStr(pk.q.toString(16), 16, 18) + }); + pkFields.push({ + key: "G", + value: formatByteStr(pk.g.toString(16), 16, 18) + }); + } else if (pk.e) { // RSA + pkFields.push({ + key: "Length", + value: pk.n.bitLength() + " bits" + }); + pkFields.push({ + key: "Modulus", + value: formatByteStr(pk.n.toString(16), 16, 18) + }); + pkFields.push({ + key: "Exponent", + value: pk.e + " (0x" + pk.e.toString(16) + ")" + }); + } else { + pkFields.push({ + key: "Error", + value: "Unknown Public Key type" + }); + } + + // Format Public Key fields + for (let i = 0; i < pkFields.length; i++) { + pkStr += ` ${pkFields[i].key}:${(pkFields[i].value + "\n").padStart( + 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, + " " + )}`; + } + + // Signature fields + let breakoutSig = false; + try { + breakoutSig = r.ASN1HEX.dump(sig).indexOf("SEQUENCE") === 0; + } catch (err) { + // Error processing signature, output without further breakout + } + + if (breakoutSig) { // DSA or ECDSA + sigStr = ` r: ${formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18)} + s: ${formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18)}`; + } else { // RSA or unknown + sigStr = ` Signature: ${formatByteStr(sig, 16, 18)}`; + } + + // Extensions + try { + extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; + } catch (err) {} + + const issuerStr = formatDnStr(issuer, 2), + nbDate = formatDate(cert.getNotBefore()), + naDate = formatDate(cert.getNotAfter()), + subjectStr = formatDnStr(subject, 2); + + return `Version: ${cert.version} (0x${Utils.hex(cert.version - 1)}) +Serial number: ${new r.BigInteger(sn, 16).toString()} (0x${sn}) +Algorithm ID: ${cert.getSignatureAlgorithmField()} +Validity + Not Before: ${nbDate} (dd-mm-yy hh:mm:ss) (${cert.getNotBefore()}) + Not After: ${naDate} (dd-mm-yy hh:mm:ss) (${cert.getNotAfter()}) +Issuer +${issuerStr} +Subject +${subjectStr} +Public Key +${pkStr.slice(0, -1)} +Certificate Signature + Algorithm: ${cert.getSignatureAlgorithmName()} +${sigStr} + +Extensions +${extensions}`; + } + +} + +/** + * Formats dates. + * + * @param {string} dateStr + * @returns {string} + */ +function formatDate (dateStr) { + return dateStr[4] + dateStr[5] + "/" + + dateStr[2] + dateStr[3] + "/" + + dateStr[0] + dateStr[1] + " " + + dateStr[6] + dateStr[7] + ":" + + dateStr[8] + dateStr[9] + ":" + + dateStr[10] + dateStr[11]; +} + +export default ParseX509Certificate; diff --git a/src/core/operations/PowerSet.mjs b/src/core/operations/PowerSet.mjs new file mode 100644 index 00000000..f3d32392 --- /dev/null +++ b/src/core/operations/PowerSet.mjs @@ -0,0 +1,92 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Power Set operation + */ +class PowerSet extends Operation { + + /** + * Power set constructor + */ + constructor() { + super(); + + this.name = "Power Set"; + this.module = "Default"; + this.description = "Calculates all the subsets of a set."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Generate the power set + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + [this.itemDelimiter] = args; + // Split and filter empty strings + const inputArray = input.split(this.itemDelimiter).filter(a => a); + + if (inputArray.length) { + return this.runPowerSet(inputArray); + } + + return ""; + } + + /** + * Return the power set of the inputted set. + * + * @param {Object[]} a + * @returns {Object[]} + */ + runPowerSet(a) { + // empty array items getting picked up + a = a.filter(i => i.length); + if (!a.length) { + return []; + } + + /** + * Decimal to binary function + * @param {*} dec + */ + const toBinary = (dec) => (dec >>> 0).toString(2); + const result = new Set(); + // Get the decimal number to make a binary as long as the input + const maxBinaryValue = parseInt(Number(a.map(i => "1").reduce((p, c) => p + c)), 2); + // Make an array of each binary number from 0 to maximum + const binaries = [...Array(maxBinaryValue + 1).keys()] + .map(toBinary) + .map(i => i.padStart(toBinary(maxBinaryValue).length, "0")); + + // XOR the input with each binary to get each unique permutation + binaries.forEach((binary) => { + const split = binary.split(""); + result.add(a.filter((item, index) => split[index] === "1")); + }); + + // map for formatting & put in length order. + return [...result] + .map(r => r.join(this.itemDelimiter)).sort((a, b) => a.length - b.length) + .map(i => `${i}\n`).join(""); + } +} + +export default PowerSet; diff --git a/src/core/operations/PseudoRandomNumberGenerator.mjs b/src/core/operations/PseudoRandomNumberGenerator.mjs new file mode 100644 index 00000000..0b5e45be --- /dev/null +++ b/src/core/operations/PseudoRandomNumberGenerator.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 forge from "node-forge/dist/forge.min.js"; +import BigNumber from "bignumber.js"; + +/** + * Pseudo-Random Number Generator operation + */ +class PseudoRandomNumberGenerator extends Operation { + + /** + * PseudoRandomNumberGenerator constructor + */ + constructor() { + super(); + + this.name = "Pseudo-Random Number Generator"; + this.module = "Ciphers"; + this.description = "A cryptographically-secure pseudo-random number generator (PRNG).

This operation uses the browser's built-in crypto.getRandomValues() method if available. If this cannot be found, it falls back to a Fortuna-based PRNG algorithm."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Number of bytes", + "type": "number", + "value": 32 + }, + { + "name": "Output as", + "type": "option", + "value": ["Hex", "Integer", "Byte array", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [numBytes, outputAs] = args; + + let bytes; + + if (ENVIRONMENT_IS_WORKER() && self.crypto) { + bytes = self.crypto.getRandomValues(new Uint8Array(numBytes)); + bytes = Utils.arrayBufferToStr(bytes.buffer); + } else { + bytes = forge.random.getBytesSync(numBytes); + } + + let value = new BigNumber(0), + i; + + switch (outputAs) { + case "Hex": + return forge.util.bytesToHex(bytes); + case "Integer": + for (i = bytes.length - 1; i >= 0; i--) { + value = value.times(256).plus(bytes.charCodeAt(i)); + } + return value.toFixed(); + case "Byte array": + return JSON.stringify(Utils.strToCharcode(bytes)); + case "Raw": + default: + return bytes; + } + } + +} + +export default PseudoRandomNumberGenerator; diff --git a/src/core/operations/PublicKey.js b/src/core/operations/PublicKey.js deleted file mode 100755 index 66b177a5..00000000 --- a/src/core/operations/PublicKey.js +++ /dev/null @@ -1,342 +0,0 @@ -import Utils from "../Utils.js"; -import * as r from "jsrsasign"; - - -/** - * Public Key operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const PublicKey = { - - /** - * @constant - * @default - */ - X509_INPUT_FORMAT: ["PEM", "DER Hex", "Base64", "Raw"], - - /** - * Parse X.509 certificate operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseX509: function (input, args) { - if (!input.length) { - return "No input"; - } - - let cert = new r.X509(), - inputFormat = args[0]; - - switch (inputFormat) { - case "DER Hex": - input = input.replace(/\s/g, ""); - cert.readCertHex(input); - break; - case "PEM": - cert.readCertPEM(input); - break; - case "Base64": - cert.readCertHex(Utils.toHex(Utils.fromBase64(input, null, "byteArray"), "")); - break; - case "Raw": - cert.readCertHex(Utils.toHex(Utils.strToByteArray(input), "")); - break; - default: - throw "Undefined input format"; - } - - let sn = cert.getSerialNumberHex(), - issuer = cert.getIssuerString(), - subject = cert.getSubjectString(), - pk = cert.getPublicKey(), - pkFields = [], - pkStr = "", - sig = cert.getSignatureValueHex(), - sigStr = "", - extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; - - // Public Key fields - pkFields.push({ - key: "Algorithm", - value: pk.type - }); - - if (pk.type === "EC") { // ECDSA - pkFields.push({ - key: "Curve Name", - value: pk.curveName - }); - pkFields.push({ - key: "Length", - value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + " bits" - }); - pkFields.push({ - key: "pub", - value: PublicKey._formatByteStr(pk.pubKeyHex, 16, 18) - }); - } else if (pk.type === "DSA") { // DSA - pkFields.push({ - key: "pub", - value: PublicKey._formatByteStr(pk.y.toString(16), 16, 18) - }); - pkFields.push({ - key: "P", - value: PublicKey._formatByteStr(pk.p.toString(16), 16, 18) - }); - pkFields.push({ - key: "Q", - value: PublicKey._formatByteStr(pk.q.toString(16), 16, 18) - }); - pkFields.push({ - key: "G", - value: PublicKey._formatByteStr(pk.g.toString(16), 16, 18) - }); - } else if (pk.e) { // RSA - pkFields.push({ - key: "Length", - value: pk.n.bitLength() + " bits" - }); - pkFields.push({ - key: "Modulus", - value: PublicKey._formatByteStr(pk.n.toString(16), 16, 18) - }); - pkFields.push({ - key: "Exponent", - value: pk.e + " (0x" + pk.e.toString(16) + ")" - }); - } else { - pkFields.push({ - key: "Error", - value: "Unknown Public Key type" - }); - } - - // Format Public Key fields - for (let i = 0; i < pkFields.length; i++) { - pkStr += " " + pkFields[i].key + ":" + - (pkFields[i].value + "\n").padStart( - 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, - " " - ); - } - - // Signature fields - let breakoutSig = false; - try { - breakoutSig = r.ASN1HEX.dump(sig).indexOf("SEQUENCE") === 0; - } catch (err) { - // Error processing signature, output without further breakout - } - - if (breakoutSig) { // DSA or ECDSA - sigStr = " r: " + PublicKey._formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18) + "\n" + - " s: " + PublicKey._formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18); - } else { // RSA or unknown - sigStr = " Signature: " + PublicKey._formatByteStr(sig, 16, 18); - } - - - let issuerStr = PublicKey._formatDnStr(issuer, 2), - nbDate = PublicKey._formatDate(cert.getNotBefore()), - naDate = PublicKey._formatDate(cert.getNotAfter()), - subjectStr = PublicKey._formatDnStr(subject, 2); - - return `Version: ${cert.version} (0x${Utils.hex(cert.version - 1)}) -Serial number: ${new r.BigInteger(sn, 16).toString()} (0x${sn}) -Algorithm ID: ${cert.getSignatureAlgorithmField()} -Validity - Not Before: ${nbDate} (dd-mm-yy hh:mm:ss) (${cert.getNotBefore()}) - Not After: ${naDate} (dd-mm-yy hh:mm:ss) (${cert.getNotAfter()}) -Issuer -${issuerStr} -Subject -${subjectStr} -Public Key -${pkStr.slice(0, -1)} -Certificate Signature - Algorithm: ${cert.getSignatureAlgorithmName()} -${sigStr} - -Extensions -${extensions}`; - }, - - - /** - * PEM to Hex operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runPemToHex: function(input, args) { - if (input.indexOf("-----BEGIN") < 0) { - // Add header so that the KEYUTIL function works - input = "-----BEGIN CERTIFICATE-----" + input; - } - if (input.indexOf("-----END") < 0) { - // Add footer so that the KEYUTIL function works - input = input + "-----END CERTIFICATE-----"; - } - let cert = new r.X509(); - cert.readCertPEM(input); - return cert.hex; - }, - - - /** - * @constant - * @default - */ - PEM_HEADER_STRING: "CERTIFICATE", - - /** - * Hex to PEM operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHexToPem: function(input, args) { - return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\s/g, ""), args[0]); - }, - - - /** - * Hex to Object Identifier operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHexToObjectIdentifier: function(input, args) { - return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\s/g, "")); - }, - - - /** - * Object Identifier to Hex operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runObjectIdentifierToHex: function(input, args) { - return r.KJUR.asn1.ASN1Util.oidIntToHex(input); - }, - - - /** - * @constant - * @default - */ - ASN1_TRUNCATE_LENGTH: 32, - - /** - * Parse ASN.1 hex string operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParseAsn1HexString: function(input, args) { - let truncateLen = args[1], - index = args[0]; - return r.ASN1HEX.dump(input.replace(/\s/g, ""), { - "ommitLongOctet": truncateLen - }, index); - }, - - - /** - * Formats Distinguished Name (DN) strings. - * - * @private - * @param {string} dnStr - * @param {number} indent - * @returns {string} - */ - _formatDnStr: function(dnStr, indent) { - let output = "", - fields = dnStr.substr(1).replace(/([^\\])\//g, "$1$1/").split(/[^\\]\//), - maxKeyLen = 0, - key, - value, - i, - str; - - for (i = 0; i < fields.length; i++) { - if (!fields[i].length) continue; - - key = fields[i].split("=")[0]; - - maxKeyLen = key.length > maxKeyLen ? key.length : maxKeyLen; - } - - for (i = 0; i < fields.length; i++) { - if (!fields[i].length) continue; - - key = fields[i].split("=")[0]; - value = fields[i].split("=")[1]; - str = key.padEnd(maxKeyLen, " ") + " = " + value + "\n"; - - output += str.padStart(indent + str.length, " "); - } - - return output.slice(0, -1); - }, - - - /** - * Formats byte strings by adding line breaks and delimiters. - * - * @private - * @param {string} byteStr - * @param {number} length - Line width - * @param {number} indent - * @returns {string} - */ - _formatByteStr: function(byteStr, length, indent) { - byteStr = Utils.toHex(Utils.fromHex(byteStr), ":"); - length = length * 3; - let output = ""; - - for (let i = 0; i < byteStr.length; i += length) { - const str = byteStr.slice(i, i + length) + "\n"; - if (i === 0) { - output += str; - } else { - output += str.padStart(indent + str.length, " "); - } - } - - return output.slice(0, output.length-1); - }, - - - /** - * Formats dates. - * - * @private - * @param {string} dateStr - * @returns {string} - */ - _formatDate: function(dateStr) { - return dateStr[4] + dateStr[5] + "/" + - dateStr[2] + dateStr[3] + "/" + - dateStr[0] + dateStr[1] + " " + - dateStr[6] + dateStr[7] + ":" + - dateStr[8] + dateStr[9] + ":" + - dateStr[10] + dateStr[11]; - }, - -}; - -export default PublicKey; diff --git a/src/core/operations/Punycode.js b/src/core/operations/Punycode.js deleted file mode 100755 index d3f94e69..00000000 --- a/src/core/operations/Punycode.js +++ /dev/null @@ -1,58 +0,0 @@ -import punycode from "punycode"; - - -/** - * Punycode operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Punycode = { - - /** - * @constant - * @default - */ - IDN: false, - - /** - * To Punycode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToAscii: function(input, args) { - const idn = args[0]; - - if (idn) { - return punycode.toASCII(input); - } else { - return punycode.encode(input); - } - }, - - - /** - * From Punycode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runToUnicode: function(input, args) { - const idn = args[0]; - - if (idn) { - return punycode.toUnicode(input); - } else { - return punycode.decode(input); - } - }, - -}; - -export default Punycode; diff --git a/src/core/operations/RC2Decrypt.mjs b/src/core/operations/RC2Decrypt.mjs new file mode 100644 index 00000000..a8a8a75f --- /dev/null +++ b/src/core/operations/RC2Decrypt.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * RC2 Decrypt operation + */ +class RC2Decrypt extends Operation { + + /** + * RC2Decrypt constructor + */ + constructor() { + super(); + + this.name = "RC2 Decrypt"; + this.module = "Ciphers"; + this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.

Key: RC2 uses a variable size key.

IV: To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.

Padding: In both CBC and ECB mode, PKCS#7 padding will be used."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + [,, inputType, outputType] = args, + decipher = forge.rc2.createDecryptionCipher(key); + + input = Utils.convertToByteString(input, inputType); + + decipher.start(iv || null); + decipher.update(forge.util.createBuffer(input)); + decipher.finish(); + + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } + +} + +export default RC2Decrypt; diff --git a/src/core/operations/RC2Encrypt.mjs b/src/core/operations/RC2Encrypt.mjs new file mode 100644 index 00000000..bf6642e6 --- /dev/null +++ b/src/core/operations/RC2Encrypt.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import forge from "node-forge/dist/forge.min.js"; + + +/** + * RC2 Encrypt operation + */ +class RC2Encrypt extends Operation { + + /** + * RC2Encrypt constructor + */ + constructor() { + super(); + + this.name = "RC2 Encrypt"; + this.module = "Ciphers"; + this.description = "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.

Key: RC2 uses a variable size key.

You can generate a password-based key using one of the KDF operations.

IV: To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.

Padding: In both CBC and ECB mode, PKCS#7 padding will be used."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + [,, inputType, outputType] = args, + cipher = forge.rc2.createEncryptionCipher(key); + + input = Utils.convertToByteString(input, inputType); + + cipher.start(iv || null); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); + } + +} + +export default RC2Encrypt; diff --git a/src/core/operations/RC4.mjs b/src/core/operations/RC4.mjs new file mode 100644 index 00000000..28968a0a --- /dev/null +++ b/src/core/operations/RC4.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import CryptoJS from "crypto-js"; +import { format } from "../lib/Ciphers"; + +/** + * RC4 operation + */ +class RC4 extends Operation { + + /** + * RC4 constructor + */ + constructor() { + super(); + + this.name = "RC4"; + this.module = "Ciphers"; + this.description = "RC4 (also known as ARC4) is a widely-used stream cipher designed by Ron Rivest. It is used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Passphrase", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"] + }, + { + "name": "Input format", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const message = format[args[1]].parse(input), + passphrase = format[args[0].option].parse(args[0].string), + encrypted = CryptoJS.RC4.encrypt(message, passphrase); + + return encrypted.ciphertext.toString(format[args[2]]); + } + + /** + * Highlight RC4 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight RC4 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default RC4; diff --git a/src/core/operations/RC4Drop.mjs b/src/core/operations/RC4Drop.mjs new file mode 100644 index 00000000..05f8aca9 --- /dev/null +++ b/src/core/operations/RC4Drop.mjs @@ -0,0 +1,94 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { format } from "../lib/Ciphers"; +import CryptoJS from "crypto-js"; + +/** + * RC4 Drop operation + */ +class RC4Drop extends Operation { + + /** + * RC4Drop constructor + */ + constructor() { + super(); + + this.name = "RC4 Drop"; + this.module = "Ciphers"; + this.description = "It was discovered that the first few bytes of the RC4 keystream are strongly non-random and leak information about the key. We can defend against this attack by discarding the initial portion of the keystream. This modified algorithm is traditionally called RC4-drop."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Passphrase", + "type": "toggleString", + "value": "", + "toggleValues": ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"] + }, + { + "name": "Input format", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"] + }, + { + "name": "Number of bytes to drop", + "type": "number", + "value": 768 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const message = format[args[1]].parse(input), + passphrase = format[args[0].option].parse(args[0].string), + drop = args[3], + encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop }); + + return encrypted.ciphertext.toString(format[args[2]]); + } + + /** + * Highlight RC4 Drop + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight RC4 Drop in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default RC4Drop; diff --git a/src/core/operations/RIPEMD.mjs b/src/core/operations/RIPEMD.mjs new file mode 100644 index 00000000..07cfe4b4 --- /dev/null +++ b/src/core/operations/RIPEMD.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * RIPEMD operation + */ +class RIPEMD extends Operation { + + /** + * RIPEMD constructor + */ + constructor() { + super(); + + this.name = "RIPEMD"; + this.module = "Hashing"; + this.description = "RIPEMD (RACE Integrity Primitives Evaluation Message Digest) is a family of cryptographic hash functions developed in Leuven, Belgium, by Hans Dobbertin, Antoon Bosselaers and Bart Preneel at the COSIC research group at the Katholieke Universiteit Leuven, and first published in 1996.

RIPEMD was based upon the design principles used in MD4, and is similar in performance to the more popular SHA-1.

"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "option", + "value": ["320", "256", "160", "128"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = args[0]; + return runHash("ripemd" + size, input); + } + +} + +export default RIPEMD; diff --git a/src/core/operations/ROT13.mjs b/src/core/operations/ROT13.mjs new file mode 100644 index 00000000..d3e4e75a --- /dev/null +++ b/src/core/operations/ROT13.mjs @@ -0,0 +1,103 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + + +/** + * ROT13 operation. + */ +class ROT13 extends Operation { + + /** + * ROT13 constructor + */ + constructor() { + super(); + + this.name = "ROT13"; + this.module = "Default"; + this.description = "A simple caesar substitution cipher which rotates alphabet characters by the specified amount (default 13)."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Rotate lower case chars", + type: "boolean", + value: true + }, + { + name: "Rotate upper case chars", + type: "boolean", + value: true + }, + { + name: "Amount", + type: "number", + value: 13 + }, + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = input, + rot13Lowercase = args[0], + rot13Upperacse = args[1]; + let amount = args[2], + chr; + + if (amount) { + if (amount < 0) { + amount = 26 - (Math.abs(amount) % 26); + } + + for (let i = 0; i < input.length; i++) { + chr = input[i]; + if (rot13Upperacse && chr >= 65 && chr <= 90) { // Upper case + chr = (chr - 65 + amount) % 26; + output[i] = chr + 65; + } else if (rot13Lowercase && chr >= 97 && chr <= 122) { // Lower case + chr = (chr - 97 + amount) % 26; + output[i] = chr + 97; + } + } + } + return output; + } + + /** + * Highlight ROT13 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT13 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default ROT13; diff --git a/src/core/operations/ROT47.mjs b/src/core/operations/ROT47.mjs new file mode 100644 index 00000000..e0722670 --- /dev/null +++ b/src/core/operations/ROT47.mjs @@ -0,0 +1,88 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + + +/** + * ROT47 operation. + */ +class ROT47 extends Operation { + + /** + * ROT47 constructor + */ + constructor() { + super(); + + this.name = "ROT47"; + this.module = "Default"; + this.description = "A slightly more complex variation of a caesar cipher, which includes ASCII characters from 33 '!' to 126 '~'. Default rotation: 47."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Amount", + type: "number", + value: 47 + }, + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = input; + let amount = args[0], + chr; + + if (amount) { + if (amount < 0) { + amount = 94 - (Math.abs(amount) % 94); + } + + for (let i = 0; i < input.length; i++) { + chr = input[i]; + if (chr >= 33 && chr <= 126) { + chr = (chr - 33 + amount) % 94; + output[i] = chr + 33; + } + } + } + return output; + } + + /** + * Highlight ROT47 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT47 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default ROT47; diff --git a/src/core/operations/RawDeflate.mjs b/src/core/operations/RawDeflate.mjs new file mode 100644 index 00000000..55a922b0 --- /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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const deflate = new Zlib.RawDeflate(new Uint8Array(input), { + compressionType: RAW_COMPRESSION_TYPE_LOOKUP[args[0]] + }); + return new Uint8Array(deflate.compress()).buffer; + } + +} + +export default RawDeflate; diff --git a/src/core/operations/RawInflate.mjs b/src/core/operations/RawInflate.mjs new file mode 100644 index 00000000..f1a5341b --- /dev/null +++ b/src/core/operations/RawInflate.mjs @@ -0,0 +1,103 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {INFLATE_BUFFER_TYPE} from "../lib/Zlib"; +import rawinflate from "zlibjs/bin/rawinflate.min"; +import OperationError from "../errors/OperationError"; + +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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + 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 {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inflate = new Zlib.RawInflate(new Uint8Array(input), { + index: args[0], + bufferSize: args[1], + bufferType: RAW_BUFFER_TYPE_LOOKUP[args[2]], + resize: args[3], + verify: args[4] + }), + result = new Uint8Array(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 new OperationError("Error: Unable to inflate data"); + } + } + // This seems to be the easiest way... + return result.buffer; + } + +} + +export default RawInflate; diff --git a/src/core/operations/Regex.js b/src/core/operations/Regex.js deleted file mode 100644 index a4f544ef..00000000 --- a/src/core/operations/Regex.js +++ /dev/null @@ -1,278 +0,0 @@ -import XRegExp from "xregexp"; -import Utils from "../Utils.js"; - - -/** - * Regex operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2018 - * @license Apache-2.0 - * - * @namespace - */ -const Regex = { - - /** - * @constant - * @default - */ - REGEX_PRE_POPULATE: [ - { - name: "User defined", - value: "" - }, - { - name: "IPv4 address", - value: "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?" - }, - { - name: "IPv6 address", - value: "((?=.*::)(?!.*::.+::)(::)?([\\dA-Fa-f]{1,4}:(:|\\b)|){5}|([\\dA-Fa-f]{1,4}:){6})((([\\dA-Fa-f]{1,4}((?!\\3)::|:\\b|(?![\\dA-Fa-f])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})" - }, - { - name: "Email address", - value: "\\b(\\w[-.\\w]*)@([-\\w]+(?:\\.[-\\w]+)*)\\.([A-Za-z]{2,4})\\b" - }, - { - name: "URL", - value: "([A-Za-z]+://)([-\\w]+(?:\\.\\w[-\\w]*)+)(:\\d+)?(/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*)?" - }, - { - name: "Domain", - value: "\\b((?=[a-z0-9-]{1,63}\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}\\b" - }, - { - name: "Windows file path", - value: "([A-Za-z]):\\\\((?:[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)~]{0,61}\\\\?)*[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61})(\\.[A-Za-z\\d]{1,6})?" - }, - { - name: "UNIX file path", - value: "(?:/[A-Za-z\\d.][A-Za-z\\d\\-.]{0,61})+" - }, - { - name: "MAC address", - value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}" - }, - { - name: "Date (yyyy-mm-dd)", - value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" - }, - { - name: "Date (dd/mm/yyyy)", - value: "(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\d\\d)" - }, - { - name: "Date (mm/dd/yyyy)", - value: "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\d\\d)" - }, - { - name: "Strings", - value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}" - }, - ], - /** - * @constant - * @default - */ - OUTPUT_FORMAT: ["Highlight matches", "List matches", "List capture groups", "List matches with capture groups"], - /** - * @constant - * @default - */ - DISPLAY_TOTAL: false, - - /** - * Regular expression operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runRegex: function(input, args) { - const userRegex = args[1], - i = args[2], - m = args[3], - s = args[4], - u = args[5], - a = args[6], - displayTotal = args[7], - outputFormat = args[8]; - let modifiers = "g"; - - if (i) modifiers += "i"; - if (m) modifiers += "m"; - if (s) modifiers += "s"; - if (u) modifiers += "u"; - if (a) modifiers += "A"; - - if (userRegex && userRegex !== "^" && userRegex !== "$") { - try { - const regex = new XRegExp(userRegex, modifiers); - - switch (outputFormat) { - case "Highlight matches": - return Regex._regexHighlight(input, regex, displayTotal); - case "List matches": - return Utils.escapeHtml(Regex._regexList(input, regex, displayTotal, true, false)); - case "List capture groups": - return Utils.escapeHtml(Regex._regexList(input, regex, displayTotal, false, true)); - case "List matches with capture groups": - return Utils.escapeHtml(Regex._regexList(input, regex, displayTotal, true, true)); - default: - return "Error: Invalid output format"; - } - } catch (err) { - return "Invalid regex. Details: " + err.message; - } - } else { - return Utils.escapeHtml(input); - } - }, - - - /** - * @constant - * @default - */ - SEARCH_TYPE: ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"], - /** - * @constant - * @default - */ - FIND_REPLACE_GLOBAL: true, - /** - * @constant - * @default - */ - FIND_REPLACE_CASE: false, - /** - * @constant - * @default - */ - FIND_REPLACE_MULTILINE: true, - - /** - * Find / Replace operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFindReplace: function(input, args) { - let find = args[0].string, - type = args[0].option, - replace = args[1], - g = args[2], - i = args[3], - m = args[4], - modifiers = ""; - - if (g) modifiers += "g"; - if (i) modifiers += "i"; - if (m) modifiers += "m"; - - if (type === "Regex") { - find = new RegExp(find, modifiers); - return input.replace(find, replace); - } - - if (type.indexOf("Extended") === 0) { - find = Utils.parseEscapedChars(find); - } - - find = new RegExp(Utils.escapeRegex(find), modifiers); - - return input.replace(find, replace); - }, - - - /** - * Adds HTML highlights to matches within a string. - * - * @private - * @param {string} input - * @param {RegExp} regex - * @param {boolean} displayTotal - * @returns {string} - */ - _regexHighlight: function(input, regex, displayTotal) { - let output = "", - m, - hl = 1, - i = 0, - total = 0; - - while ((m = regex.exec(input))) { - // Moves pointer when an empty string is matched (prevents infinite loop) - if (m.index === regex.lastIndex) { - regex.lastIndex++; - } - - // Add up to match - output += Utils.escapeHtml(input.slice(i, m.index)); - - // Add match with highlighting - output += "" + Utils.escapeHtml(m[0]) + ""; - - // Switch highlight - hl = hl === 1 ? 2 : 1; - - i = regex.lastIndex; - total++; - } - - // Add all after final match - output += Utils.escapeHtml(input.slice(i, input.length)); - - if (displayTotal) - output = "Total found: " + total + "\n\n" + output; - - return output; - }, - - - /** - * Creates a string listing the matches within a string. - * - * @private - * @param {string} input - * @param {RegExp} regex - * @param {boolean} displayTotal - * @param {boolean} matches - Display full match - * @param {boolean} captureGroups - Display each of the capture groups separately - * @returns {string} - */ - _regexList: function(input, regex, displayTotal, matches, captureGroups) { - let output = "", - total = 0, - match; - - while ((match = regex.exec(input))) { - // Moves pointer when an empty string is matched (prevents infinite loop) - if (match.index === regex.lastIndex) { - regex.lastIndex++; - } - - total++; - if (matches) { - output += match[0] + "\n"; - } - if (captureGroups) { - for (let i = 1; i < match.length; i++) { - if (matches) { - output += " Group " + i + ": "; - } - output += match[i] + "\n"; - } - } - } - - if (displayTotal) - output = "Total found: " + total + "\n\n" + output; - - return output.slice(0, -1); - }, -}; - -export default Regex; diff --git a/src/core/operations/Register.mjs b/src/core/operations/Register.mjs new file mode 100644 index 00000000..b3a5397f --- /dev/null +++ b/src/core/operations/Register.mjs @@ -0,0 +1,111 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Dish from "../Dish"; + +/** + * Register operation + */ +class Register extends Operation { + + /** + * Register constructor + */ + constructor() { + super(); + + this.name = "Register"; + this.flowControl = true; + this.module = "Default"; + this.description = "Extract data from the input and store it in registers which can then be passed into subsequent operations as arguments. Regular expression capture groups are used to select the data to extract.

To use registers in arguments, refer to them using the notation $Rn where n is the register number, starting at 0.

For example:
Input: Test
Extractor: (.*)
Argument: $R0 becomes Test

Registers can be escaped in arguments using a backslash. e.g. \\$R0 would become $R0 rather than Test."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Extractor", + "type": "binaryString", + "value": "([\\s\\S]*)" + }, + { + "name": "Case insensitive", + "type": "boolean", + "value": true + }, + { + "name": "Multiline matching", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + async run(state) { + const ings = state.opList[state.progress].ingValues; + const [extractorStr, i, m] = ings; + + let modifiers = ""; + if (i) modifiers += "i"; + if (m) modifiers += "m"; + + const extractor = new RegExp(extractorStr, modifiers), + input = await state.dish.get(Dish.STRING), + registers = input.match(extractor); + + if (!registers) return state; + + if (ENVIRONMENT_IS_WORKER()) { + self.setRegisters(state.forkOffset + state.progress, state.numRegisters, registers.slice(1)); + } + + /** + * Replaces references to registers (e.g. $R0) with the contents of those registers. + * + * @param {string} str + * @returns {string} + */ + function replaceRegister(str) { + // Replace references to registers ($Rn) with contents of registers + return str.replace(/(\\*)\$R(\d{1,2})/g, (match, slashes, regNum) => { + const index = parseInt(regNum, 10) + 1; + if (index <= state.numRegisters || index >= state.numRegisters + registers.length) + return match; + if (slashes.length % 2 !== 0) return match.slice(1); // Remove escape + return slashes + registers[index - state.numRegisters]; + }); + } + + // 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].disabled) continue; + + let args = state.opList[i].ingValues; + args = args.map(arg => { + if (typeof arg !== "string" && typeof arg !== "object") return arg; + + if (typeof arg === "object" && arg.hasOwnProperty("string")) { + arg.string = replaceRegister(arg.string); + return arg; + } + return replaceRegister(arg); + }); + state.opList[i].ingValues = args; + } + + state.numRegisters += registers.length - 1; + return state; + } + +} + +export default Register; diff --git a/src/core/operations/RegularExpression.mjs b/src/core/operations/RegularExpression.mjs new file mode 100644 index 00000000..4f8b9813 --- /dev/null +++ b/src/core/operations/RegularExpression.mjs @@ -0,0 +1,262 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import XRegExp from "xregexp"; +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; + +/** + * Regular expression operation + */ +class RegularExpression extends Operation { + + /** + * RegularExpression constructor + */ + constructor() { + super(); + + this.name = "Regular expression"; + this.module = "Regex"; + this.description = "Define your own regular expression (regex) to search the input data with, optionally choosing from a list of pre-defined patterns.

Supports extended regex syntax including the 'dot matches all' flag, named capture groups, full unicode coverage (including \\p{} categories and scripts as well as astral codes) and recursive matching."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Built in regexes", + "type": "populateOption", + "value": [ + { + name: "User defined", + value: "" + }, + { + name: "IPv4 address", + value: "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?" + }, + { + name: "IPv6 address", + value: "((?=.*::)(?!.*::.+::)(::)?([\\dA-Fa-f]{1,4}:(:|\\b)|){5}|([\\dA-Fa-f]{1,4}:){6})((([\\dA-Fa-f]{1,4}((?!\\3)::|:\\b|(?![\\dA-Fa-f])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})" + }, + { + name: "Email address", + value: "\\b(\\w[-.\\w]*)@([-\\w]+(?:\\.[-\\w]+)*)\\.([A-Za-z]{2,4})\\b" + }, + { + name: "URL", + value: "([A-Za-z]+://)([-\\w]+(?:\\.\\w[-\\w]*)+)(:\\d+)?(/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*)?" + }, + { + name: "Domain", + value: "\\b((?=[a-z0-9-]{1,63}\\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,63}\\b" + }, + { + name: "Windows file path", + value: "([A-Za-z]):\\\\((?:[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)~]{0,61}\\\\?)*[A-Za-z\\d][A-Za-z\\d\\- \\x27_\\(\\)]{0,61})(\\.[A-Za-z\\d]{1,6})?" + }, + { + name: "UNIX file path", + value: "(?:/[A-Za-z\\d.][A-Za-z\\d\\-.]{0,61})+" + }, + { + name: "MAC address", + value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}" + }, + { + name: "Date (yyyy-mm-dd)", + value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" + }, + { + name: "Date (dd/mm/yyyy)", + value: "(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.]((?:19|20)\\d\\d)" + }, + { + name: "Date (mm/dd/yyyy)", + value: "(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.]((?:19|20)\\d\\d)" + }, + { + name: "Strings", + value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}" + }, + ], + "target": 1 + }, + { + "name": "Regex", + "type": "text", + "value": "" + }, + { + "name": "Case insensitive", + "type": "boolean", + "value": true + }, + { + "name": "^ and $ match at newlines", + "type": "boolean", + "value": true + }, + { + "name": "Dot matches all", + "type": "boolean", + "value": false + }, + { + "name": "Unicode support", + "type": "boolean", + "value": false + }, + { + "name": "Astral support", + "type": "boolean", + "value": false + }, + { + "name": "Display total", + "type": "boolean", + "value": false + }, + { + "name": "Output format", + "type": "option", + "value": ["Highlight matches", "List matches", "List capture groups", "List matches with capture groups"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [, + userRegex, + i, m, s, u, a, + displayTotal, + outputFormat + ] = args; + let modifiers = "g"; + + if (i) modifiers += "i"; + if (m) modifiers += "m"; + if (s) modifiers += "s"; + if (u) modifiers += "u"; + if (a) modifiers += "A"; + + if (userRegex && userRegex !== "^" && userRegex !== "$") { + try { + const regex = new XRegExp(userRegex, modifiers); + + switch (outputFormat) { + case "Highlight matches": + return regexHighlight(input, regex, displayTotal); + case "List matches": + return Utils.escapeHtml(regexList(input, regex, displayTotal, true, false)); + case "List capture groups": + return Utils.escapeHtml(regexList(input, regex, displayTotal, false, true)); + case "List matches with capture groups": + return Utils.escapeHtml(regexList(input, regex, displayTotal, true, true)); + default: + return "Error: Invalid output format"; + } + } catch (err) { + throw new OperationError("Invalid regex. Details: " + err.message); + } + } else { + return Utils.escapeHtml(input); + } + } + +} + +/** + * Creates a string listing the matches within a string. + * + * @param {string} input + * @param {RegExp} regex + * @param {boolean} displayTotal + * @param {boolean} matches - Display full match + * @param {boolean} captureGroups - Display each of the capture groups separately + * @returns {string} + */ +function regexList (input, regex, displayTotal, matches, captureGroups) { + let output = "", + total = 0, + match; + + while ((match = regex.exec(input))) { + // Moves pointer when an empty string is matched (prevents infinite loop) + if (match.index === regex.lastIndex) { + regex.lastIndex++; + } + + total++; + if (matches) { + output += match[0] + "\n"; + } + if (captureGroups) { + for (let i = 1; i < match.length; i++) { + if (matches) { + output += " Group " + i + ": "; + } + output += match[i] + "\n"; + } + } + } + + if (displayTotal) + output = "Total found: " + total + "\n\n" + output; + + return output.slice(0, -1); +} + +/** + * Adds HTML highlights to matches within a string. + * + * @private + * @param {string} input + * @param {RegExp} regex + * @param {boolean} displayTotal + * @returns {string} + */ +function regexHighlight (input, regex, displayTotal) { + let output = "", + m, + hl = 1, + i = 0, + total = 0; + + while ((m = regex.exec(input))) { + // Moves pointer when an empty string is matched (prevents infinite loop) + if (m.index === regex.lastIndex) { + regex.lastIndex++; + } + + // Add up to match + output += Utils.escapeHtml(input.slice(i, m.index)); + + // Add match with highlighting + output += "" + Utils.escapeHtml(m[0]) + ""; + + // Switch highlight + hl = hl === 1 ? 2 : 1; + + i = regex.lastIndex; + total++; + } + + // Add all after final match + output += Utils.escapeHtml(input.slice(i, input.length)); + + if (displayTotal) + output = "Total found: " + total + "\n\n" + output; + + return output; +} + +export default RegularExpression; diff --git a/src/core/operations/RemoveEXIF.mjs b/src/core/operations/RemoveEXIF.mjs new file mode 100644 index 00000000..f2c479b3 --- /dev/null +++ b/src/core/operations/RemoveEXIF.mjs @@ -0,0 +1,54 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import { removeEXIF } from "../vendor/remove-exif"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Remove EXIF operation + */ +class RemoveEXIF extends Operation { + + /** + * RemoveEXIF constructor + */ + constructor() { + super(); + + this.name = "Remove EXIF"; + this.module = "Image"; + this.description = [ + "Removes EXIF data from a JPEG image.", + "

", + "EXIF data embedded in photos usually contains information about the image file itself as well as the device used to create it.", + ].join("\n"); + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + // Do nothing if input is empty + if (input.length === 0) return input; + + try { + return removeEXIF(input); + } catch (err) { + // Simply return input if no EXIF data is found + if (err === "Exif not found.") return input; + throw new OperationError(`Could not remove EXIF data from image: ${err}`); + } + } + +} + +export default RemoveEXIF; diff --git a/src/core/operations/RemoveLineNumbers.mjs b/src/core/operations/RemoveLineNumbers.mjs new file mode 100644 index 00000000..d7c615f7 --- /dev/null +++ b/src/core/operations/RemoveLineNumbers.mjs @@ -0,0 +1,39 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Remove line numbers operation + */ +class RemoveLineNumbers extends Operation { + + /** + * RemoveLineNumbers constructor + */ + constructor() { + super(); + + this.name = "Remove line numbers"; + this.module = "Default"; + this.description = "Removes line numbers from the output if they can be trivially detected."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.replace(/^[ \t]{0,5}\d+[\s:|\-,.)\]]/gm, ""); + } + +} + +export default RemoveLineNumbers; diff --git a/src/core/operations/RemoveNullBytes.mjs b/src/core/operations/RemoveNullBytes.mjs new file mode 100644 index 00000000..dcbf9251 --- /dev/null +++ b/src/core/operations/RemoveNullBytes.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Remove null bytes operation + */ +class RemoveNullBytes extends Operation { + + /** + * RemoveNullBytes constructor + */ + constructor() { + super(); + + this.name = "Remove null bytes"; + this.module = "Default"; + this.description = "Removes all null bytes (0x00) from the input."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const output = []; + for (let i = 0; i < input.length; i++) { + if (input[i] !== 0) output.push(input[i]); + } + return output; + } + +} + +export default RemoveNullBytes; diff --git a/src/core/operations/RemoveWhitespace.mjs b/src/core/operations/RemoveWhitespace.mjs new file mode 100644 index 00000000..a6564809 --- /dev/null +++ b/src/core/operations/RemoveWhitespace.mjs @@ -0,0 +1,86 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Remove whitespace operation + */ +class RemoveWhitespace extends Operation { + + /** + * RemoveWhitespace constructor + */ + constructor() { + super(); + + this.name = "Remove whitespace"; + this.module = "Default"; + this.description = "Optionally removes all spaces, carriage returns, line feeds, tabs and form feeds from the input data.

This operation also supports the removal of full stops which are sometimes used to represent non-printable bytes in ASCII output."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Spaces", + "type": "boolean", + "value": true + }, + { + "name": "Carriage returns (\\r)", + "type": "boolean", + "value": true + }, + { + "name": "Line feeds (\\n)", + "type": "boolean", + "value": true + }, + { + "name": "Tabs", + "type": "boolean", + "value": true + }, + { + "name": "Form feeds (\\f)", + "type": "boolean", + "value": true + }, + { + "name": "Full stops", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [ + removeSpaces, + removeCariageReturns, + removeLineFeeds, + removeTabs, + removeFormFeeds, + removeFullStops + ] = args; + let data = input; + + if (removeSpaces) data = data.replace(/ /g, ""); + if (removeCariageReturns) data = data.replace(/\r/g, ""); + if (removeLineFeeds) data = data.replace(/\n/g, ""); + if (removeTabs) data = data.replace(/\t/g, ""); + if (removeFormFeeds) data = data.replace(/\f/g, ""); + if (removeFullStops) data = data.replace(/\./g, ""); + return data; + } + +} + +export default RemoveWhitespace; diff --git a/src/core/operations/RenderImage.mjs b/src/core/operations/RenderImage.mjs new file mode 100644 index 00000000..8a8fb4a9 --- /dev/null +++ b/src/core/operations/RenderImage.mjs @@ -0,0 +1,90 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import { fromBase64, toBase64 } from "../lib/Base64"; +import { fromHex } from "../lib/Hex"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import Magic from "../lib/Magic"; + +/** + * Render Image operation + */ +class RenderImage extends Operation { + + /** + * RenderImage constructor + */ + constructor() { + super(); + + this.name = "Render Image"; + this.module = "Image"; + this.description = "Displays the input as an image. Supports the following formats:

  • jpg/jpeg
  • png
  • gif
  • webp
  • bmp
  • ico
"; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["Raw", "Base64", "Hex"] + } + ]; + this.patterns = [ + { + "match": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)", + "flags": "", + "args": ["Raw"], + "useful": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const inputFormat = args[0]; + let dataURI = "data:"; + + if (!input.length) return ""; + + // Convert input to raw bytes + switch (inputFormat) { + case "Hex": + input = fromHex(input); + break; + case "Base64": + // Don't trust the Base64 entered by the user. + // Unwrap it first, then re-encode later. + input = fromBase64(input, undefined, "byteArray"); + break; + case "Raw": + default: + input = Utils.strToByteArray(input); + break; + } + + // Determine file type + const type = Magic.magicFileType(input); + if (type && type.mime.indexOf("image") === 0) { + dataURI += type.mime + ";"; + } else { + throw new OperationError("Invalid file type"); + } + + // Add image data to URI + dataURI += "base64," + toBase64(input); + + return ""; + } + +} + +export default RenderImage; diff --git a/src/core/operations/Return.mjs b/src/core/operations/Return.mjs new file mode 100644 index 00000000..cc83bff8 --- /dev/null +++ b/src/core/operations/Return.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Return operation + */ +class Return extends Operation { + + /** + * Return constructor + */ + constructor() { + super(); + + this.name = "Return"; + this.flowControl = true; + this.module = "Default"; + this.description = "End execution of operations at this point in the recipe."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {Object} state - The current state of the recipe. + * @param {number} state.progress - The current position in the recipe. + * @param {Dish} state.dish - The Dish being operated on. + * @param {Operation[]} state.opList - The list of operations in the recipe. + * @returns {Object} The updated state of the recipe. + */ + run(state) { + state.progress = state.opList.length; + return state; + } + +} + +export default Return; diff --git a/src/core/operations/Reverse.mjs b/src/core/operations/Reverse.mjs new file mode 100644 index 00000000..c88bb275 --- /dev/null +++ b/src/core/operations/Reverse.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Reverse operation + */ +class Reverse extends Operation { + + /** + * Reverse constructor + */ + constructor() { + super(); + + this.name = "Reverse"; + this.module = "Default"; + this.description = "Reverses the input string."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "By", + "type": "option", + "value": ["Character", "Line"] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let i; + if (args[0] === "Line") { + const lines = []; + let line = [], + result = []; + for (i = 0; i < input.length; i++) { + if (input[i] === 0x0a) { + lines.push(line); + line = []; + } else { + line.push(input[i]); + } + } + lines.push(line); + lines.reverse(); + for (i = 0; i < lines.length; i++) { + result = result.concat(lines[i]); + result.push(0x0a); + } + return result.slice(0, input.length); + } else { + return input.reverse(); + } + } + +} + +export default Reverse; diff --git a/src/core/operations/Rotate.js b/src/core/operations/Rotate.js deleted file mode 100755 index 9125e575..00000000 --- a/src/core/operations/Rotate.js +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Bit rotation operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - * - * @todo Support for UTF16 - */ -const Rotate = { - - /** - * @constant - * @default - */ - ROTATE_AMOUNT: 1, - /** - * @constant - * @default - */ - ROTATE_CARRY: false, - - /** - * Runs rotation operations across the input data. - * - * @private - * @param {byteArray} data - * @param {number} amount - * @param {function} algo - The rotation operation to carry out - * @returns {byteArray} - */ - _rot: function(data, amount, algo) { - const result = []; - for (let i = 0; i < data.length; i++) { - let b = data[i]; - for (let j = 0; j < amount; j++) { - b = algo(b); - } - result.push(b); - } - return result; - }, - - - /** - * Rotate right operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRotr: function(input, args) { - if (args[1]) { - return Rotate._rotrCarry(input, args[0]); - } else { - return Rotate._rot(input, args[0], Rotate._rotr); - } - }, - - - /** - * Rotate left operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRotl: function(input, args) { - if (args[1]) { - return Rotate._rotlCarry(input, args[0]); - } else { - return Rotate._rot(input, args[0], Rotate._rotl); - } - }, - - - /** - * @constant - * @default - */ - ROT13_AMOUNT: 13, - /** - * @constant - * @default - */ - ROT13_LOWERCASE: true, - /** - * @constant - * @default - */ - ROT13_UPPERCASE: true, - - /** - * ROT13 operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRot13: function(input, args) { - let amount = args[2], - output = input, - chr, - rot13Lowercase = args[0], - rot13Upperacse = args[1]; - - if (amount) { - if (amount < 0) { - amount = 26 - (Math.abs(amount) % 26); - } - - for (let i = 0; i < input.length; i++) { - chr = input[i]; - if (rot13Upperacse && chr >= 65 && chr <= 90) { // Upper case - chr = (chr - 65 + amount) % 26; - output[i] = chr + 65; - } else if (rot13Lowercase && chr >= 97 && chr <= 122) { // Lower case - chr = (chr - 97 + amount) % 26; - output[i] = chr + 97; - } - } - } - return output; - }, - - - /** - * @constant - * @default - */ - ROT47_AMOUNT: 47, - - /** - * ROT47 operation. - * - * @author Matt C [matt@artemisbot.uk] - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRot47: function(input, args) { - let amount = args[0], - output = input, - chr; - - if (amount) { - if (amount < 0) { - amount = 94 - (Math.abs(amount) % 94); - } - - for (let i = 0; i < input.length; i++) { - chr = input[i]; - if (chr >= 33 && chr <= 126) { - chr = (chr - 33 + amount) % 94; - output[i] = chr + 33; - } - } - } - return output; - }, - - - /** - * Rotate right bitwise op. - * - * @private - * @param {byte} b - * @returns {byte} - */ - _rotr: function(b) { - const bit = (b & 1) << 7; - return (b >> 1) | bit; - }, - - - /** - * Rotate left bitwise op. - * - * @private - * @param {byte} b - * @returns {byte} - */ - _rotl: function(b) { - const bit = (b >> 7) & 1; - return ((b << 1) | bit) & 0xFF; - }, - - - /** - * Rotates a byte array to the right by a specific amount as a whole, so that bits are wrapped - * from the end of the array to the beginning. - * - * @private - * @param {byteArray} data - * @param {number} amount - * @returns {byteArray} - */ - _rotrCarry: function(data, amount) { - let carryBits = 0, - newByte, - result = []; - - amount = amount % 8; - for (let i = 0; i < data.length; i++) { - const oldByte = data[i] >>> 0; - newByte = (oldByte >> amount) | carryBits; - carryBits = (oldByte & (Math.pow(2, amount)-1)) << (8-amount); - result.push(newByte); - } - result[0] |= carryBits; - return result; - }, - - - /** - * Rotates a byte array to the left by a specific amount as a whole, so that bits are wrapped - * from the beginning of the array to the end. - * - * @private - * @param {byteArray} data - * @param {number} amount - * @returns {byteArray} - */ - _rotlCarry: function(data, amount) { - let carryBits = 0, - newByte, - result = []; - - amount = amount % 8; - for (let i = data.length-1; i >= 0; i--) { - const oldByte = data[i]; - newByte = ((oldByte << amount) | carryBits) & 0xFF; - carryBits = (oldByte >> (8-amount)) & (Math.pow(2, amount)-1); - result[i] = (newByte); - } - result[data.length-1] = result[data.length-1] | carryBits; - return result; - }, - -}; - -export default Rotate; diff --git a/src/core/operations/RotateLeft.mjs b/src/core/operations/RotateLeft.mjs new file mode 100644 index 00000000..d21e0def --- /dev/null +++ b/src/core/operations/RotateLeft.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {rot, rotl, rotlCarry} from "../lib/Rotate"; + + +/** + * Rotate left operation. + */ +class RotateLeft extends Operation { + + /** + * RotateLeft constructor + */ + constructor() { + super(); + + this.name = "Rotate left"; + this.module = "Default"; + this.description = "Rotates each byte to the left by the number of bits specified, optionally carrying the excess bits over to the next byte. Currently only supports 8-bit values."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Amount", + type: "number", + value: 1 + }, + { + name: "Carry through", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (args[1]) { + return rotlCarry(input, args[0]); + } else { + return rot(input, args[0], rotl); + } + } + + /** + * Highlight rotate left + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight rotate left in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default RotateLeft; diff --git a/src/core/operations/RotateRight.mjs b/src/core/operations/RotateRight.mjs new file mode 100644 index 00000000..f4a35cb6 --- /dev/null +++ b/src/core/operations/RotateRight.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {rot, rotr, rotrCarry} from "../lib/Rotate"; + + +/** + * Rotate right operation. + */ +class RotateRight extends Operation { + + /** + * RotateRight constructor + */ + constructor() { + super(); + + this.name = "Rotate right"; + this.module = "Default"; + this.description = "Rotates each byte to the right by the number of bits specified, optionally carrying the excess bits over to the next byte. Currently only supports 8-bit values."; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Amount", + type: "number", + value: 1 + }, + { + name: "Carry through", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (args[1]) { + return rotrCarry(input, args[0]); + } else { + return rot(input, args[0], rotr); + } + } + + /** + * Highlight rotate right + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight rotate right in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default RotateRight; diff --git a/src/core/operations/SHA0.mjs b/src/core/operations/SHA0.mjs new file mode 100644 index 00000000..292c63ce --- /dev/null +++ b/src/core/operations/SHA0.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * SHA0 operation + */ +class SHA0 extends Operation { + + /** + * SHA0 constructor + */ + constructor() { + super(); + + this.name = "SHA0"; + this.module = "Hashing"; + this.description = "SHA-0 is a retronym applied to the original version of the 160-bit hash function published in 1993 under the name 'SHA'. It was withdrawn shortly after publication due to an undisclosed 'significant flaw' and replaced by the slightly revised version SHA-1."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("sha0", input); + } + +} + +export default SHA0; diff --git a/src/core/operations/SHA1.mjs b/src/core/operations/SHA1.mjs new file mode 100644 index 00000000..904c55f0 --- /dev/null +++ b/src/core/operations/SHA1.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * SHA1 operation + */ +class SHA1 extends Operation { + + /** + * SHA1 constructor + */ + constructor() { + super(); + + this.name = "SHA1"; + this.module = "Hashing"; + this.description = "The SHA (Secure Hash Algorithm) hash functions were designed by the NSA. SHA-1 is the most established of the existing SHA hash functions and it is used in a variety of security applications and protocols.

However, SHA-1's collision resistance has been weakening as new attacks are discovered or improved."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("sha1", input); + } + +} + +export default SHA1; diff --git a/src/core/operations/SHA2.mjs b/src/core/operations/SHA2.mjs new file mode 100644 index 00000000..c5480727 --- /dev/null +++ b/src/core/operations/SHA2.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * SHA2 operation + */ +class SHA2 extends Operation { + + /** + * SHA2 constructor + */ + constructor() { + super(); + + this.name = "SHA2"; + this.module = "Hashing"; + this.description = "The SHA-2 (Secure Hash Algorithm 2) hash functions were designed by the NSA. SHA-2 includes significant changes from its predecessor, SHA-1. The SHA-2 family consists of hash functions with digests (hash values) that are 224, 256, 384 or 512 bits: SHA224, SHA256, SHA384, SHA512.

  • SHA-512 operates on 64-bit words.
  • SHA-256 operates on 32-bit words.
  • SHA-384 is largely identical to SHA-512 but is truncated to 384 bytes.
  • SHA-224 is largely identical to SHA-256 but is truncated to 224 bytes.
  • SHA-512/224 and SHA-512/256 are truncated versions of SHA-512, but the initial values are generated using the method described in Federal Information Processing Standards (FIPS) PUB 180-4.
"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "option", + "value": ["512", "256", "384", "224", "512/256", "512/224"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = args[0]; + return runHash("sha" + size, input); + } + +} + +export default SHA2; diff --git a/src/core/operations/SHA3.mjs b/src/core/operations/SHA3.mjs new file mode 100644 index 00000000..9e6b2828 --- /dev/null +++ b/src/core/operations/SHA3.mjs @@ -0,0 +1,67 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import JSSHA3 from "js-sha3"; +import OperationError from "../errors/OperationError"; + +/** + * SHA3 operation + */ +class SHA3 extends Operation { + + /** + * SHA3 constructor + */ + constructor() { + super(); + + this.name = "SHA3"; + this.module = "Hashing"; + this.description = "The SHA-3 (Secure Hash Algorithm 3) hash functions were released by NIST on August 5, 2015. Although part of the same series of standards, SHA-3 is internally quite different from the MD5-like structure of SHA-1 and SHA-2.

SHA-3 is a subset of the broader cryptographic primitive family Keccak designed by Guido Bertoni, Joan Daemen, Micha\xebl Peeters, and Gilles Van Assche, building upon RadioGat\xfan."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Size", + "type": "option", + "value": ["512", "384", "256", "224"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const size = parseInt(args[0], 10); + let algo; + + switch (size) { + case 224: + algo = JSSHA3.sha3_224; + break; + case 384: + algo = JSSHA3.sha3_384; + break; + case 256: + algo = JSSHA3.sha3_256; + break; + case 512: + algo = JSSHA3.sha3_512; + break; + default: + throw new OperationError("Invalid size"); + } + + return algo(input); + } + +} + +export default SHA3; diff --git a/src/core/operations/SQLBeautify.mjs b/src/core/operations/SQLBeautify.mjs new file mode 100644 index 00000000..1862972a --- /dev/null +++ b/src/core/operations/SQLBeautify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * SQL Beautify operation + */ +class SQLBeautify extends Operation { + + /** + * SQLBeautify constructor + */ + constructor() { + super(); + + this.name = "SQL Beautify"; + this.module = "Code"; + this.description = "Indents and prettifies Structured Query Language (SQL) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + return vkbeautify.sql(input, indentStr); + } + +} + +export default SQLBeautify; diff --git a/src/core/operations/SQLMinify.mjs b/src/core/operations/SQLMinify.mjs new file mode 100644 index 00000000..d81e29ad --- /dev/null +++ b/src/core/operations/SQLMinify.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * SQL Minify operation + */ +class SQLMinify extends Operation { + + /** + * SQLMinify constructor + */ + constructor() { + super(); + + this.name = "SQL Minify"; + this.module = "Code"; + this.description = "Compresses Structured Query Language (SQL) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return vkbeautify.sqlmin(input); + } + +} + +export default SQLMinify; diff --git a/src/core/operations/SSDEEP.mjs b/src/core/operations/SSDEEP.mjs new file mode 100644 index 00000000..02d23eba --- /dev/null +++ b/src/core/operations/SSDEEP.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import ssdeepjs from "ssdeep.js"; + +/** + * SSDEEP operation + */ +class SSDEEP extends Operation { + + /** + * SSDEEP constructor + */ + constructor() { + super(); + + this.name = "SSDEEP"; + this.module = "Hashing"; + this.description = "SSDEEP is a program for computing context triggered piecewise hashes (CTPH). Also called fuzzy hashes, CTPH can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.

SSDEEP hashes are now widely used for simple identification purposes (e.g. the 'Basic Properties' section in VirusTotal). Although 'better' fuzzy hashes are available, SSDEEP is still one of the primary choices because of its speed and being a de facto standard.

This operation is fundamentally the same as the CTPH operation, however their outputs differ in format."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return ssdeepjs.digest(input); + } + +} + +export default SSDEEP; diff --git a/src/core/operations/SUB.mjs b/src/core/operations/SUB.mjs new file mode 100644 index 00000000..79ce95d0 --- /dev/null +++ b/src/core/operations/SUB.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import { bitOp, sub } from "../lib/BitwiseOp"; + +/** + * SUB operation + */ +class SUB extends Operation { + + /** + * SUB constructor + */ + constructor() { + super(); + + this.name = "SUB"; + this.module = "Default"; + this.description = "SUB the input with the given key (e.g. fe023da5), MOD 255"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "Base64", "UTF8", "Latin1"] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); + + return bitOp(input, key, sub); + } + + /** + * Highlight SUB + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight SUB in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SUB; diff --git a/src/core/operations/ScanForEmbeddedFiles.mjs b/src/core/operations/ScanForEmbeddedFiles.mjs new file mode 100644 index 00000000..abbe7c09 --- /dev/null +++ b/src/core/operations/ScanForEmbeddedFiles.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"; +import Magic from "../lib/Magic"; + +/** + * Scan for Embedded Files operation + */ +class ScanForEmbeddedFiles extends Operation { + + /** + * ScanForEmbeddedFiles constructor + */ + constructor() { + super(); + + this.name = "Scan for Embedded Files"; + this.module = "Default"; + this.description = "Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.

WARNING: Files over about 100KB in size will take a VERY long time to process."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Ignore common byte sequences", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any suffiently long file is likely to contain these magic bytes coincidentally.\n", + type, + numFound = 0, + numCommonFound = 0; + const ignoreCommon = args[0], + commonExts = ["ico", "ttf", ""], + data = new Uint8Array(input); + + for (let i = 0; i < data.length; i++) { + type = Magic.magicFileType(data.slice(i)); + if (type) { + if (ignoreCommon && commonExts.indexOf(type.ext) > -1) { + numCommonFound++; + continue; + } + numFound++; + output += "\nOffset " + i + " (0x" + Utils.hex(i) + "):\n" + + " File extension: " + type.ext + "\n" + + " MIME type: " + type.mime + "\n"; + + if (type.desc && type.desc.length) { + output += " Description: " + type.desc + "\n"; + } + } + } + + if (numFound === 0) { + output += "\nNo embedded files were found."; + } + + if (numCommonFound > 0) { + output += "\n\n" + numCommonFound; + output += numCommonFound === 1 ? + " file type was detected that has a common byte sequence. This is likely to be a false positive." : + " file types were detected that have common byte sequences. These are likely to be false positives."; + output += " Run this operation with the 'Ignore common byte sequences' option unchecked to see details."; + } + + return output; + } + +} + +export default ScanForEmbeddedFiles; diff --git a/src/core/operations/Scrypt.mjs b/src/core/operations/Scrypt.mjs new file mode 100644 index 00000000..c7624e3c --- /dev/null +++ b/src/core/operations/Scrypt.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import scryptsy from "scryptsy"; + +/** + * Scrypt operation + */ +class Scrypt extends Operation { + + /** + * Scrypt constructor + */ + constructor() { + super(); + + this.name = "Scrypt"; + this.module = "Hashing"; + this.description = "scrypt is a password-based key derivation function (PBKDF) created by Colin Percival. The algorithm was specifically designed to make it costly to perform large-scale custom hardware attacks by requiring large amounts of memory. In 2016, the scrypt algorithm was published by IETF as RFC 7914.

Enter the password in the input to generate its hash."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Salt", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "Base64", "UTF8", "Latin1"] + }, + { + "name": "Iterations (N)", + "type": "number", + "value": 16384 + }, + { + "name": "Memory factor (r)", + "type": "number", + "value": 8 + }, + { + "name": "Parallelization factor (p)", + "type": "number", + "value": 1 + }, + { + "name": "Key length", + "type": "number", + "value": 64 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(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 = scryptsy( + 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) { + throw new OperationError("Error: " + err.toString()); + } + } + +} + +export default Scrypt; diff --git a/src/core/operations/SeqUtils.js b/src/core/operations/SeqUtils.js deleted file mode 100755 index fa900cf9..00000000 --- a/src/core/operations/SeqUtils.js +++ /dev/null @@ -1,257 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Sequence utility operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const SeqUtils = { - - /** - * @constant - * @default - */ - DELIMITER_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"], - /** - * @constant - * @default - */ - SORT_REVERSE: false, - /** - * @constant - * @default - */ - SORT_ORDER: ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric"], - - /** - * Sort operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSort: function (input, args) { - let delim = Utils.charRep[args[0]], - sortReverse = args[1], - order = args[2], - sorted = input.split(delim); - - if (order === "Alphabetical (case sensitive)") { - sorted = sorted.sort(); - } else if (order === "Alphabetical (case insensitive)") { - sorted = sorted.sort(SeqUtils._caseInsensitiveSort); - } else if (order === "IP address") { - sorted = sorted.sort(SeqUtils._ipSort); - } else if (order === "Numeric") { - sorted = sorted.sort(SeqUtils._numericSort); - } - - if (sortReverse) sorted.reverse(); - return sorted.join(delim); - }, - - - /** - * Unique operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUnique: function (input, args) { - const delim = Utils.charRep[args[0]]; - return input.split(delim).unique().join(delim); - }, - - - /** - * @constant - * @default - */ - SEARCH_TYPE: ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"], - - /** - * Count occurrences operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {number} - */ - runCount: function(input, args) { - let search = args[0].string, - type = args[0].option; - - if (type === "Regex" && search) { - try { - let regex = new RegExp(search, "gi"), - matches = input.match(regex); - return matches.length; - } catch (err) { - return 0; - } - } else if (search) { - if (type.indexOf("Extended") === 0) { - search = Utils.parseEscapedChars(search); - } - return input.count(search); - } else { - return 0; - } - }, - - - /** - * @constant - * @default - */ - REVERSE_BY: ["Character", "Line"], - - /** - * Reverse operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runReverse: function (input, args) { - let i; - if (args[0] === "Line") { - let lines = [], - line = [], - result = []; - for (i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { - lines.push(line); - line = []; - } else { - line.push(input[i]); - } - } - lines.push(line); - lines.reverse(); - for (i = 0; i < lines.length; i++) { - result = result.concat(lines[i]); - result.push(0x0a); - } - return result.slice(0, input.length); - } else { - return input.reverse(); - } - }, - - - /** - * Add line numbers operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runAddLineNumbers: function(input, args) { - let lines = input.split("\n"), - output = "", - width = lines.length.toString().length; - - for (let n = 0; n < lines.length; n++) { - output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n"; - } - return output.slice(0, output.length-1); - }, - - - /** - * Remove line numbers operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRemoveLineNumbers: function(input, args) { - return input.replace(/^[ \t]{0,5}\d+[\s:|\-,.)\]]/gm, ""); - }, - - - /** - * Expand alphabet range operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runExpandAlphRange: function(input, args) { - return Utils.expandAlphRange(input).join(args[0]); - }, - - - /** - * Comparison operation for sorting of strings ignoring case. - * - * @private - * @param {string} a - * @param {string} b - * @returns {number} - */ - _caseInsensitiveSort: function(a, b) { - return a.toLowerCase().localeCompare(b.toLowerCase()); - }, - - - /** - * Comparison operation for sorting of IPv4 addresses. - * - * @private - * @param {string} a - * @param {string} b - * @returns {number} - */ - _ipSort: function(a, b) { - let a_ = a.split("."), - b_ = b.split("."); - - a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1; - b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1; - - if (isNaN(a_) && !isNaN(b_)) return 1; - if (!isNaN(a_) && isNaN(b_)) return -1; - if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b); - - return a_ - b_; - }, - - - /** - * Comparison operation for sorting of numeric values. - * - * @author Chris van Marle - * @private - * @param {string} a - * @param {string} b - * @returns {number} - */ - _numericSort: function _numericSort(a, b) { - let a_ = a.split(/([^\d]+)/), - b_ = b.split(/([^\d]+)/); - - for (let i = 0; i < a_.length && i < b.length; ++i) { - if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers - if (!isNaN(a_[i]) && isNaN(b_[i])) return -1; - if (isNaN(a_[i]) && isNaN(b_[i])) { - let ret = a_[i].localeCompare(b_[i]); // Compare strings - if (ret !== 0) return ret; - } - if (!isNaN(a_[i]) && !isNaN(a_[i])) { // Compare numbers - if (a_[i] - b_[i] !== 0) return a_[i] - b_[i]; - } - } - - return a.localeCompare(b); - }, - -}; - -export default SeqUtils; diff --git a/src/core/operations/SetDifference.mjs b/src/core/operations/SetDifference.mjs new file mode 100644 index 00000000..0962fda2 --- /dev/null +++ b/src/core/operations/SetDifference.mjs @@ -0,0 +1,86 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set Difference operation + */ +class SetDifference extends Operation { + + /** + * Set Difference constructor + */ + constructor() { + super(); + + this.name = "Set Difference"; + this.module = "Default"; + this.description = "Calculates the difference of two sets."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the difference operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runSetDifference(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get elements in set a that are not in set b + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runSetDifference(a, b) { + return a + .filter((item) => { + return b.indexOf(item) === -1; + }) + .join(this.itemDelimiter); + } + +} + +export default SetDifference; diff --git a/src/core/operations/SetIntersection.mjs b/src/core/operations/SetIntersection.mjs new file mode 100644 index 00000000..4ede1f98 --- /dev/null +++ b/src/core/operations/SetIntersection.mjs @@ -0,0 +1,86 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set Intersection operation + */ +class SetIntersection extends Operation { + + /** + * Set Intersection constructor + */ + constructor() { + super(); + + this.name = "Set Intersection"; + this.module = "Default"; + this.description = "Calculates the intersection of two sets."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the intersection operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runIntersect(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get the intersection of the two sets. + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runIntersect(a, b) { + return a + .filter((item) => { + return b.indexOf(item) > -1; + }) + .join(this.itemDelimiter); + } + +} + +export default SetIntersection; diff --git a/src/core/operations/SetUnion.mjs b/src/core/operations/SetUnion.mjs new file mode 100644 index 00000000..6975ffb5 --- /dev/null +++ b/src/core/operations/SetUnion.mjs @@ -0,0 +1,96 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set Union operation + */ +class SetUnion extends Operation { + + /** + * Set Union constructor + */ + constructor() { + super(); + + this.name = "Set Union"; + this.module = "Default"; + this.description = "Calculates the union of two sets."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n\\n" + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the union operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runUnion(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get the union of the two sets. + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runUnion(a, b) { + const result = {}; + + /** + * Only add non-existing items + * @param {Object} hash + */ + const addUnique = (hash) => (item) => { + if (!hash[item]) { + hash[item] = true; + } + }; + + a.map(addUnique(result)); + b.map(addUnique(result)); + + return Object.keys(result).join(this.itemDelimiter); + } +} + +export default SetUnion; diff --git a/src/core/operations/Shake.mjs b/src/core/operations/Shake.mjs new file mode 100644 index 00000000..06914211 --- /dev/null +++ b/src/core/operations/Shake.mjs @@ -0,0 +1,70 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import JSSHA3 from "js-sha3"; + +/** + * Shake operation + */ +class Shake extends Operation { + + /** + * Shake constructor + */ + constructor() { + super(); + + this.name = "Shake"; + this.module = "Hashing"; + this.description = "Shake is an Extendable Output Function (XOF) of the SHA-3 hash algorithm, part of the Keccak family, allowing for variable output length/size."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Capacity", + "type": "option", + "value": ["256", "128"] + }, + { + "name": "Size", + "type": "number", + "value": 512 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const capacity = parseInt(args[0], 10), + size = args[1]; + let algo; + + if (size < 0) + throw new OperationError("Size must be greater than 0"); + + switch (capacity) { + case 128: + algo = JSSHA3.shake128; + break; + case 256: + algo = JSSHA3.shake256; + break; + default: + throw new OperationError("Invalid size"); + } + + return algo(input, size); + } + +} + +export default Shake; diff --git a/src/core/operations/Shellcode.js b/src/core/operations/Shellcode.js deleted file mode 100644 index 2fb6baeb..00000000 --- a/src/core/operations/Shellcode.js +++ /dev/null @@ -1,96 +0,0 @@ -import disassemble from "../lib/DisassembleX86-64.js"; - - -/** - * Shellcode operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @namespace - */ -const Shellcode = { - - /** - * @constant - * @default - */ - MODE: ["64", "32", "16"], - /** - * @constant - * @default - */ - COMPATIBILITY: [ - "Full x86 architecture", - "Knights Corner", - "Larrabee", - "Cyrix", - "Geode", - "Centaur", - "X86/486" - ], - - /** - * Disassemble x86 operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDisassemble: function(input, args) { - const mode = args[0], - compatibility = args[1], - codeSegment = args[2], - offset = args[3], - showInstructionHex = args[4], - showInstructionPos = args[5]; - - switch (mode) { - case "64": - disassemble.setBitMode(2); - break; - case "32": - disassemble.setBitMode(1); - break; - case "16": - disassemble.setBitMode(0); - break; - default: - throw "Invalid mode value"; - } - - switch (compatibility) { - case "Full x86 architecture": - disassemble.CompatibilityMode(0); - break; - case "Knights Corner": - disassemble.CompatibilityMode(1); - break; - case "Larrabee": - disassemble.CompatibilityMode(2); - break; - case "Cyrix": - disassemble.CompatibilityMode(3); - break; - case "Geode": - disassemble.CompatibilityMode(4); - break; - case "Centaur": - disassemble.CompatibilityMode(5); - break; - case "X86/486": - disassemble.CompatibilityMode(6); - break; - } - - disassemble.SetBasePosition(codeSegment + ":" + offset); - disassemble.setShowInstructionHex(showInstructionHex); - disassemble.setShowInstructionPos(showInstructionPos); - disassemble.LoadBinCode(input.replace(/\s/g, "")); - return disassemble.LDisassemble(); - }, - -}; - -export default Shellcode; diff --git a/src/core/operations/ShowBase64Offsets.mjs b/src/core/operations/ShowBase64Offsets.mjs new file mode 100644 index 00000000..43bcbbba --- /dev/null +++ b/src/core/operations/ShowBase64Offsets.mjs @@ -0,0 +1,163 @@ +/** + * @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"; +import OperationError from "../errors/OperationError"; + +/** + * 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) { + throw new OperationError("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/Sleep.mjs b/src/core/operations/Sleep.mjs new file mode 100644 index 00000000..4cd71bfe --- /dev/null +++ b/src/core/operations/Sleep.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Sleep operation + */ +class Sleep extends Operation { + + /** + * Sleep constructor + */ + constructor() { + super(); + + this.name = "Sleep"; + this.module = "Default"; + this.description = "Sleep causes the recipe to wait for a specified number of milliseconds before continuing execution."; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Time (ms)", + "type": "number", + "value": 1000 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + async run(input, args) { + const ms = args[0]; + await new Promise(r => setTimeout(r, ms)); + return input; + } + +} + +export default Sleep; diff --git a/src/core/operations/Snefru.mjs b/src/core/operations/Snefru.mjs new file mode 100644 index 00000000..7f1bbda7 --- /dev/null +++ b/src/core/operations/Snefru.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * Snefru operation + */ +class Snefru extends Operation { + + /** + * Snefru constructor + */ + constructor() { + super(); + + this.name = "Snefru"; + this.module = "Hashing"; + this.description = "Snefru is a cryptographic hash function invented by Ralph Merkle in 1990 while working at Xerox PARC. The function supports 128-bit and 256-bit output. It was named after the Egyptian Pharaoh Sneferu, continuing the tradition of the Khufu and Khafre block ciphers.

The original design of Snefru was shown to be insecure by Eli Biham and Adi Shamir who were able to use differential cryptanalysis to find hash collisions. The design was then modified by increasing the number of iterations of the main pass of the algorithm from two to eight."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Rounds", + "type": "option", + "value": ["8", "4", "2"] + }, + { + "name": "Size", + "type": "option", + "value": ["256", "128"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return runHash("snefru", input, { + rounds: args[0], + length: args[1] + }); + } + +} + +export default Snefru; diff --git a/src/core/operations/Sort.mjs b/src/core/operations/Sort.mjs new file mode 100644 index 00000000..400d2cca --- /dev/null +++ b/src/core/operations/Sort.mjs @@ -0,0 +1,136 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Sort operation + */ +class Sort extends Operation { + + /** + * Sort constructor + */ + constructor() { + super(); + + this.name = "Sort"; + this.module = "Default"; + this.description = "Alphabetically sorts strings separated by the specified delimiter.

The IP address option supports IPv4 only."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "Reverse", + "type": "boolean", + "value": false + }, + { + "name": "Order", + "type": "option", + "value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]), + sortReverse = args[1], + order = args[2]; + let sorted = input.split(delim); + + if (order === "Alphabetical (case sensitive)") { + sorted = sorted.sort(); + } else if (order === "Alphabetical (case insensitive)") { + sorted = sorted.sort(Sort._caseInsensitiveSort); + } else if (order === "IP address") { + sorted = sorted.sort(Sort._ipSort); + } else if (order === "Numeric") { + sorted = sorted.sort(Sort._numericSort); + } + + if (sortReverse) sorted.reverse(); + return sorted.join(delim); + } + + /** + * Comparison operation for sorting of strings ignoring case. + * + * @private + * @param {string} a + * @param {string} b + * @returns {number} + */ + static _caseInsensitiveSort(a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()); + } + + + /** + * Comparison operation for sorting of IPv4 addresses. + * + * @private + * @param {string} a + * @param {string} b + * @returns {number} + */ + static _ipSort(a, b) { + let a_ = a.split("."), + b_ = b.split("."); + + a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1; + b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1; + + if (isNaN(a_) && !isNaN(b_)) return 1; + if (!isNaN(a_) && isNaN(b_)) return -1; + if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b); + + return a_ - b_; + } + + /** + * Comparison operation for sorting of numeric values. + * + * @author Chris van Marle + * @private + * @param {string} a + * @param {string} b + * @returns {number} + */ + static _numericSort(a, b) { + const a_ = a.split(/([^\d]+)/), + b_ = b.split(/([^\d]+)/); + + for (let i = 0; i < a_.length && i < b.length; ++i) { + if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers + if (!isNaN(a_[i]) && isNaN(b_[i])) return -1; + if (isNaN(a_[i]) && isNaN(b_[i])) { + const ret = a_[i].localeCompare(b_[i]); // Compare strings + if (ret !== 0) return ret; + } + if (!isNaN(a_[i]) && !isNaN(a_[i])) { // Compare numbers + if (a_[i] - b_[i] !== 0) return a_[i] - b_[i]; + } + } + + return a.localeCompare(b); + } + +} + +export default Sort; diff --git a/src/core/operations/Split.mjs b/src/core/operations/Split.mjs new file mode 100644 index 00000000..88bf8aec --- /dev/null +++ b/src/core/operations/Split.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {SPLIT_DELIM_OPTIONS, JOIN_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Split operation + */ +class Split extends Operation { + + /** + * Split constructor + */ + constructor() { + super(); + + this.name = "Split"; + this.module = "Default"; + this.description = "Splits a string into sections around a given delimiter."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Split delimiter", + "type": "editableOption", + "value": SPLIT_DELIM_OPTIONS + }, + { + "name": "Join delimiter", + "type": "editableOption", + "value": JOIN_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const splitDelim = args[0], + joinDelim = args[1], + sections = input.split(splitDelim); + + return sections.join(joinDelim); + } + +} + +export default Split; diff --git a/src/core/operations/StandardDeviation.mjs b/src/core/operations/StandardDeviation.mjs new file mode 100644 index 00000000..d2da24f8 --- /dev/null +++ b/src/core/operations/StandardDeviation.mjs @@ -0,0 +1,52 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { stdDev, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Standard Deviation operation + */ +class StandardDeviation extends Operation { + + /** + * StandardDeviation constructor + */ + constructor() { + super(); + + this.name = "Standard Deviation"; + this.module = "Default"; + this.description = "Computes the standard deviation of a number list. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 4.089281382128433"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = stdDev(createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + + } + +} + +export default StandardDeviation; diff --git a/src/core/operations/StrUtils.js b/src/core/operations/StrUtils.js deleted file mode 100755 index 69781b1b..00000000 --- a/src/core/operations/StrUtils.js +++ /dev/null @@ -1,412 +0,0 @@ -import Utils from "../Utils.js"; -import jsesc from "jsesc"; - - -/** - * String utility operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const StrUtils = { - - /** - * @constant - * @default - */ - CASE_SCOPE: ["All", "Word", "Sentence", "Paragraph"], - - /** - * To Upper case operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUpper: function (input, args) { - const scope = args[0]; - - switch (scope) { - case "Word": - return input.replace(/(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "Sentence": - return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "Paragraph": - return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "All": - /* falls through */ - default: - return input.toUpperCase(); - } - }, - - - /** - * To Upper case operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runLower: function (input, args) { - return input.toLowerCase(); - }, - - - /** - * @constant - * @default - */ - SPLIT_DELIM_OPTIONS: [ - {name: "Comma", value: ","}, - {name: "Space", value: " "}, - {name: "Line feed", value: "\\n"}, - {name: "CRLF", value: "\\r\\n"}, - {name: "Semi-colon", value: ";"}, - {name: "Colon", value: ":"}, - {name: "Nothing (separate chars)", value: ""} - ], - /** - * @constant - * @default - */ - JOIN_DELIM_OPTIONS: [ - {name: "Line feed", value: "\\n"}, - {name: "CRLF", value: "\\r\\n"}, - {name: "Space", value: " "}, - {name: "Comma", value: ","}, - {name: "Semi-colon", value: ";"}, - {name: "Colon", value: ":"}, - {name: "Nothing (join chars)", value: ""} - ], - - /** - * Split operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runSplit: function(input, args) { - let splitDelim = args[0], - joinDelim = args[1], - sections = input.split(splitDelim); - - return sections.join(joinDelim); - }, - - - /** - * @constant - * @default - */ - DELIMITER_OPTIONS: ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"], - - /** - * Filter operation. - * - * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFilter: function(input, args) { - let delim = Utils.charRep[args[0]], - regex, - reverse = args[2]; - - try { - regex = new RegExp(args[1]); - } catch (err) { - return "Invalid regex. Details: " + err.message; - } - - const regexFilter = function(value) { - return reverse ^ regex.test(value); - }; - - return input.split(delim).filter(regexFilter).join(delim); - }, - - - /** - * @constant - * @default - */ - OFF_CHK_SAMPLE_DELIMITER: "\\n\\n", - - /** - * Offset checker operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runOffsetChecker: function(input, args) { - let sampleDelim = args[0], - samples = input.split(sampleDelim), - outputs = new Array(samples.length), - i = 0, - s = 0, - match = false, - inMatch = false, - chr; - - if (!samples || samples.length < 2) { - return "Not enough samples, perhaps you need to modify the sample delimiter or add more data?"; - } - - // Initialise output strings - outputs.fill("", 0, samples.length); - - // Loop through each character in the first sample - for (i = 0; i < samples[0].length; i++) { - chr = samples[0][i]; - match = false; - - // Loop through each sample to see if the chars are the same - for (s = 1; s < samples.length; s++) { - if (samples[s][i] !== chr) { - match = false; - break; - } - match = true; - } - - // Write output for each sample - for (s = 0; s < samples.length; s++) { - if (samples[s].length <= i) { - if (inMatch) outputs[s] += ""; - if (s === samples.length - 1) inMatch = false; - continue; - } - - if (match && !inMatch) { - outputs[s] += "" + Utils.escapeHtml(samples[s][i]); - if (samples[s].length === i + 1) outputs[s] += ""; - if (s === samples.length - 1) inMatch = true; - } else if (!match && inMatch) { - outputs[s] += "" + Utils.escapeHtml(samples[s][i]); - if (s === samples.length - 1) inMatch = false; - } else { - outputs[s] += Utils.escapeHtml(samples[s][i]); - if (inMatch && samples[s].length === i + 1) { - outputs[s] += ""; - if (samples[s].length - 1 !== i) inMatch = false; - } - } - - if (samples[0].length - 1 === i) { - if (inMatch) outputs[s] += ""; - outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1)); - } - } - } - - return outputs.join(sampleDelim); - }, - - - /** - * @constant - * @default - */ - 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.runEscape("Don't do that", []) - * > "Don\'t do that" - * StrUtils.runEscape(`Hello - * World`, []) - * > "Hello\nWorld" - */ - runEscape: function(input, args) { - 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, - }); - }, - - - /** - * Unescape 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", []) - * > "Don't do that" - * StrUtils.runUnescape("Hello\nWorld", []) - * > `Hello - * World` - */ - runUnescape: function(input, args) { - return Utils.parseEscapedChars(input); - }, - - - /** - * Head lines operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHead: function(input, args) { - let delimiter = args[0], - number = args[1]; - - delimiter = Utils.charRep[delimiter]; - const splitInput = input.split(delimiter); - - return splitInput - .filter((line, lineIndex) => { - lineIndex += 1; - - if (number < 0) { - return lineIndex <= splitInput.length + number; - } else { - return lineIndex <= number; - } - }) - .join(delimiter); - }, - - - /** - * Tail lines operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTail: function(input, args) { - let delimiter = args[0], - number = args[1]; - - delimiter = Utils.charRep[delimiter]; - const splitInput = input.split(delimiter); - - return splitInput - .filter((line, lineIndex) => { - lineIndex += 1; - - if (number < 0) { - return lineIndex > -number; - } else { - return lineIndex > splitInput.length - number; - } - }) - .join(delimiter); - }, - - - /** - * @constant - * @default - */ - HAMMING_DELIM: "\\n\\n", - /** - * @constant - * @default - */ - HAMMING_INPUT_TYPE: ["Raw string", "Hex"], - /** - * @constant - * @default - */ - HAMMING_UNIT: ["Byte", "Bit"], - - /** - * Hamming Distance operation. - * - * @author GCHQ Contributor [2] - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runHamming: function(input, args) { - const delim = args[0], - byByte = args[1] === "Byte", - inputType = args[2], - samples = input.split(delim); - - if (samples.length !== 2) { - return "Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter."; - } - - if (samples[0].length !== samples[1].length) { - return "Error: Both inputs must be of the same length."; - } - - if (inputType === "Hex") { - samples[0] = Utils.fromHex(samples[0]); - samples[1] = Utils.fromHex(samples[1]); - } else { - samples[0] = Utils.strToByteArray(samples[0]); - samples[1] = Utils.strToByteArray(samples[1]); - } - - let dist = 0; - - for (let i = 0; i < samples[0].length; i++) { - const lhs = samples[0][i], - rhs = samples[1][i]; - - if (byByte && lhs !== rhs) { - dist++; - } else if (!byByte) { - let xord = lhs ^ rhs; - - while (xord) { - dist++; - xord &= xord - 1; - } - } - } - - return dist.toString(); - }, -}; - -export default StrUtils; diff --git a/src/core/operations/Strings.mjs b/src/core/operations/Strings.mjs new file mode 100644 index 00000000..d3f110f9 --- /dev/null +++ b/src/core/operations/Strings.mjs @@ -0,0 +1,116 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import XRegExp from "xregexp"; +import { search } from "../lib/Extract"; + +/** + * Strings operation + */ +class Strings extends Operation { + + /** + * Strings constructor + */ + constructor() { + super(); + + this.name = "Strings"; + this.module = "Regex"; + this.description = "Extracts all strings from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"] + }, + { + "name": "Minimum length", + "type": "number", + "value": 4 + }, + { + "name": "Match", + "type": "option", + "value": [ + "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)", + "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)" + ] + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [encoding, minLen, matchType, displayTotal] = args, + alphanumeric = "A-Z\\d", + punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@", + printable = "\x20-\x7e", + uniAlphanumeric = "\\pL\\pN", + uniPunctuation = "\\pP\\pZ", + uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP"; + + let strings = ""; + + switch (matchType) { + case "Alphanumeric + punctuation (A)": + strings = `[${alphanumeric + punctuation}]`; + break; + case "All printable chars (A)": + case "Null-terminated strings (A)": + strings = `[${printable}]`; + break; + case "Alphanumeric + punctuation (U)": + strings = `[${uniAlphanumeric + uniPunctuation}]`; + break; + case "All printable chars (U)": + case "Null-terminated strings (U)": + strings = `[${uniPrintable}]`; + break; + } + + // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars + switch (encoding) { + case "All": + strings = `(\x00?${strings}\x00?)`; + break; + case "16-bit littleendian": + strings = `(${strings}\x00)`; + break; + case "16-bit bigendian": + strings = `(\x00${strings})`; + break; + case "Single byte": + default: + break; + } + + strings = `${strings}{${minLen},}`; + + if (matchType.includes("Null-terminated")) { + strings += "\x00"; + } + + const regex = new XRegExp(strings, "ig"); + + return search(input, regex, null, displayTotal); + } + +} + +export default Strings; diff --git a/src/core/operations/StripHTMLTags.mjs b/src/core/operations/StripHTMLTags.mjs new file mode 100644 index 00000000..f1b7b08e --- /dev/null +++ b/src/core/operations/StripHTMLTags.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Strip HTML tags operation + */ +class StripHTMLTags extends Operation { + + /** + * StripHTMLTags constructor + */ + constructor() { + super(); + + this.name = "Strip HTML tags"; + this.module = "Default"; + this.description = "Removes all HTML tags from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Remove indentation", + "type": "boolean", + "value": true + }, + { + "name": "Remove excess line breaks", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [removeIndentation, removeLineBreaks] = args; + + input = Utils.stripHtmlTags(input); + + if (removeIndentation) { + input = input.replace(/\n[ \f\t]+/g, "\n"); + } + + if (removeLineBreaks) { + input = input + .replace(/^\s*\n/, "") // first line + .replace(/(\n\s*){2,}/g, "\n"); // all others + } + + return input; + } + +} + +export default StripHTMLTags; diff --git a/src/core/operations/StripHTTPHeaders.mjs b/src/core/operations/StripHTTPHeaders.mjs new file mode 100644 index 00000000..a46e675c --- /dev/null +++ b/src/core/operations/StripHTTPHeaders.mjs @@ -0,0 +1,42 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * Strip HTTP headers operation + */ +class StripHTTPHeaders extends Operation { + + /** + * StripHTTPHeaders constructor + */ + constructor() { + super(); + + this.name = "Strip HTTP headers"; + this.module = "Default"; + this.description = "Removes HTTP headers from a request or response by looking for the first instance of a double newline."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let headerEnd = input.indexOf("\r\n\r\n"); + headerEnd = (headerEnd < 0) ? input.indexOf("\n\n") + 2 : headerEnd + 4; + + return (headerEnd < 2) ? input : input.slice(headerEnd, input.length); + } + +} + +export default StripHTTPHeaders; diff --git a/src/core/operations/Substitute.mjs b/src/core/operations/Substitute.mjs new file mode 100644 index 00000000..c2d114a2 --- /dev/null +++ b/src/core/operations/Substitute.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Substitute operation + */ +class Substitute extends Operation { + + /** + * Substitute constructor + */ + constructor() { + super(); + + this.name = "Substitute"; + this.module = "Ciphers"; + this.description = "A substitution cipher allowing you to specify bytes to replace with other byte values. This can be used to create Caesar ciphers but is more powerful as any byte value can be substituted, not just letters, and the substitution values need not be in order.

Enter the bytes you want to replace in the Plaintext field and the bytes to replace them with in the Ciphertext field.

Non-printable bytes can be specified using string escape notation. For example, a line feed character can be written as either \n or \x0a.

Byte ranges can be specified using a hyphen. For example, the sequence 0123456789 can be written as 0-9."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Plaintext", + "type": "binaryString", + "value": "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + }, + { + "name": "Ciphertext", + "type": "binaryString", + "value": "XYZABCDEFGHIJKLMNOPQRSTUVW" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const plaintext = Utils.expandAlphRange(args[0]).join(""), + ciphertext = Utils.expandAlphRange(args[1]).join(""); + let output = "", + index = -1; + + if (plaintext.length !== ciphertext.length) { + output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; + } + + for (let i = 0; i < input.length; i++) { + index = plaintext.indexOf(input[i]); + output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i]; + } + + return output; + } + +} + +export default Substitute; diff --git a/src/core/operations/Subtract.mjs b/src/core/operations/Subtract.mjs new file mode 100644 index 00000000..ac00156c --- /dev/null +++ b/src/core/operations/Subtract.mjs @@ -0,0 +1,51 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { sub, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Subtract operation + */ +class Subtract extends Operation { + + /** + * Subtract constructor + */ + constructor() { + super(); + + this.name = "Subtract"; + this.module = "Default"; + this.description = "Subtracts a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 1.5"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = sub(createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + } + +} + +export default Subtract; diff --git a/src/core/operations/Sum.mjs b/src/core/operations/Sum.mjs new file mode 100644 index 00000000..97214cda --- /dev/null +++ b/src/core/operations/Sum.mjs @@ -0,0 +1,51 @@ +/** + * @author bwhitn [brian.m.whitney@outlook.com] + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import Operation from "../Operation"; +import { sum, createNumArray } from "../lib/Arithmetic"; +import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim"; + + +/** + * Sum operation + */ +class Sum extends Operation { + + /** + * Sum constructor + */ + constructor() { + super(); + + this.name = "Sum"; + this.module = "Default"; + this.description = "Adds together a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 18.5"; + this.inputType = "string"; + this.outputType = "BigNumber"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": ARITHMETIC_DELIM_OPTIONS, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {BigNumber} + */ + run(input, args) { + const val = sum(createNumArray(input, args[0])); + return val instanceof BigNumber ? val : new BigNumber(NaN); + } + +} + +export default Sum; diff --git a/src/core/operations/SwapEndianness.mjs b/src/core/operations/SwapEndianness.mjs new file mode 100644 index 00000000..21cd4e9c --- /dev/null +++ b/src/core/operations/SwapEndianness.mjs @@ -0,0 +1,137 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {toHex, fromHex} from "../lib/Hex"; +import OperationError from "../errors/OperationError"; + +/** + * Swap endianness operation + */ +class SwapEndianness extends Operation { + + /** + * SwapEndianness constructor + */ + constructor() { + super(); + + this.name = "Swap endianness"; + this.module = "Default"; + this.description = "Switches the data from big-endian to little-endian or vice-versa. Data can be read in as hexadecimal or raw bytes. It will be returned in the same format as it is entered."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Data format", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Word length (bytes)", + "type": "number", + "value": 4 + }, + { + "name": "Pad incomplete words", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dataFormat, wordLength, padIncompleteWords] = args, + result = [], + words = []; + let i = 0, + j = 0, + data = []; + + if (wordLength <= 0) { + throw new OperationError("Word length must be greater than 0"); + } + + // Convert input to raw data based on specified data format + switch (dataFormat) { + case "Hex": + data = fromHex(input); + break; + case "Raw": + data = Utils.strToByteArray(input); + break; + default: + data = input; + } + + // Split up into words + for (i = 0; i < data.length; i += wordLength) { + const word = data.slice(i, i + wordLength); + + // Pad word if too short + if (padIncompleteWords && word.length < wordLength){ + for (j = word.length; j < wordLength; j++) { + word.push(0); + } + } + + words.push(word); + } + + // Swap endianness and flatten + for (i = 0; i < words.length; i++) { + j = words[i].length; + while (j--) { + result.push(words[i][j]); + } + } + + // Convert data back to specified data format + switch (dataFormat) { + case "Hex": + return toHex(result); + case "Raw": + return Utils.byteArrayToUtf8(result); + default: + return result; + } + } + + /** + * Highlight Swap endianness + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Swap endianness in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SwapEndianness; diff --git a/src/core/operations/SymmetricDifference.mjs b/src/core/operations/SymmetricDifference.mjs new file mode 100644 index 00000000..071a49d4 --- /dev/null +++ b/src/core/operations/SymmetricDifference.mjs @@ -0,0 +1,98 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Utils from "../Utils"; +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Set Symmetric Difference operation + */ +class SymmetricDifference extends Operation { + + /** + * Symmetric Difference constructor + */ + constructor() { + super(); + + this.name = "Symmetric Difference"; + this.module = "Default"; + this.description = "Calculates the symmetric difference of two sets."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: Utils.escapeHtml("\\n\\n") + }, + { + name: "Item delimiter", + type: "binaryString", + value: "," + }, + ]; + } + + /** + * Validate input length + * + * @param {Object[]} sets + * @throws {Error} if not two sets + */ + validateSampleNumbers(sets) { + if (!sets || (sets.length !== 2)) { + throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?"); + } + } + + /** + * Run the difference operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} + */ + run(input, args) { + [this.sampleDelim, this.itemDelimiter] = args; + const sets = input.split(this.sampleDelim); + + this.validateSampleNumbers(sets); + + return this.runSymmetricDifference(...sets.map(s => s.split(this.itemDelimiter))); + } + + /** + * Get elements in set a that are not in set b + * + * @param {Object[]} a + * @param {Object[]} b + * @returns {Object[]} + */ + runSetDifference(a, b) { + return a.filter((item) => { + return b.indexOf(item) === -1; + }); + } + + /** + * Get elements of each set that aren't in the other set. + * + * @param {Object[]} a + * @param {Object[]} b + * @return {Object[]} + */ + runSymmetricDifference(a, b) { + return this.runSetDifference(a, b) + .concat(this.runSetDifference(b, a)) + .join(this.itemDelimiter); + } + +} + +export default SymmetricDifference; diff --git a/src/core/operations/SyntaxHighlighter.mjs b/src/core/operations/SyntaxHighlighter.mjs new file mode 100644 index 00000000..bde6fbde --- /dev/null +++ b/src/core/operations/SyntaxHighlighter.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import hljs from "highlight.js"; + +/** + * Syntax highlighter operation + */ +class SyntaxHighlighter extends Operation { + + /** + * SyntaxHighlighter constructor + */ + constructor() { + super(); + + this.name = "Syntax highlighter"; + this.module = "Code"; + this.description = "Adds syntax highlighting to a range of source code languages. Note that this will not indent the code. Use one of the 'Beautify' operations for that."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Language", + "type": "option", + "value": ["auto detect"].concat(hljs.listLanguages()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const language = args[0]; + + if (language === "auto detect") { + return hljs.highlightAuto(input).value; + } + + return hljs.highlight(language, input, true).value; + } + + /** + * Highlight Syntax highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Syntax highlighter in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SyntaxHighlighter; diff --git a/src/core/operations/TCPIPChecksum.mjs b/src/core/operations/TCPIPChecksum.mjs new file mode 100644 index 00000000..6eac366d --- /dev/null +++ b/src/core/operations/TCPIPChecksum.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * TCP/IP Checksum operation + */ +class TCPIPChecksum extends Operation { + + /** + * TCPIPChecksum constructor + */ + constructor() { + super(); + + this.name = "TCP/IP Checksum"; + this.module = "Hashing"; + this.description = "Calculates the checksum for a TCP (Transport Control Protocol) or IP (Internet Protocol) header from an input of raw bytes."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let csum = 0; + + for (let i = 0; i < input.length; i++) { + if (i % 2 === 0) { + csum += (input[i] << 8); + } else { + csum += input[i]; + } + } + + csum = (csum >> 16) + (csum & 0xffff); + + return Utils.hex(0xffff - csum); + } + +} + +export default TCPIPChecksum; diff --git a/src/core/operations/Tail.mjs b/src/core/operations/Tail.mjs new file mode 100644 index 00000000..c9e0d726 --- /dev/null +++ b/src/core/operations/Tail.mjs @@ -0,0 +1,69 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Tail operation + */ +class Tail extends Operation { + + /** + * Tail constructor + */ + constructor() { + super(); + + this.name = "Tail"; + this.module = "Default"; + this.description = "Like the UNIX tail utility.
Gets the last n lines.
Optionally you can select all lines after line n by entering a negative value for n.
The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + }, + { + "name": "Number", + "type": "number", + "value": 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let delimiter = args[0]; + const number = args[1]; + + delimiter = Utils.charRep(delimiter); + const splitInput = input.split(delimiter); + + return splitInput + .filter((line, lineIndex) => { + lineIndex += 1; + + if (number < 0) { + return lineIndex > -number; + } else { + return lineIndex > splitInput.length - number; + } + }) + .join(delimiter); + + } + +} + +export default Tail; diff --git a/src/core/operations/TakeBytes.mjs b/src/core/operations/TakeBytes.mjs new file mode 100644 index 00000000..5806ee87 --- /dev/null +++ b/src/core/operations/TakeBytes.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Take bytes operation + */ +class TakeBytes extends Operation { + + /** + * TakeBytes constructor + */ + constructor() { + super(); + + this.name = "Take bytes"; + this.module = "Default"; + this.description = "Takes a slice of the specified number of bytes from the data."; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "Start", + "type": "number", + "value": 0 + }, + { + "name": "Length", + "type": "number", + "value": 5 + }, + { + "name": "Apply to each line", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + * + * @throws {OperationError} if invalid value + */ + run(input, args) { + const start = args[0], + length = args[1], + applyToEachLine = args[2]; + + if (start < 0 || length < 0) + throw new OperationError("Error: Invalid value"); + + if (!applyToEachLine) + return input.slice(start, start+length); + + // Split input into lines + const data = new Uint8Array(input); + const lines = []; + let line = [], + i; + + for (i = 0; i < data.length; i++) { + if (data[i] === 0x0a) { + lines.push(line); + line = []; + } else { + line.push(data[i]); + } + } + lines.push(line); + + let output = []; + for (i = 0; i < lines.length; i++) { + output = output.concat(lines[i].slice(start, start+length)); + output.push(0x0a); + } + return new Uint8Array(output.slice(0, output.length-1)).buffer; + } + +} + +export default TakeBytes; diff --git a/src/core/operations/Tar.mjs b/src/core/operations/Tar.mjs new file mode 100644 index 00000000..a2e8eb70 --- /dev/null +++ b/src/core/operations/Tar.mjs @@ -0,0 +1,139 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Tar operation + */ +class Tar extends Operation { + + /** + * Tar constructor + */ + constructor() { + super(); + + this.name = "Tar"; + this.module = "Compression"; + this.description = "Packs the input into a tarball.

No support for multiple files at this time."; + this.inputType = "byteArray"; + this.outputType = "File"; + this.args = [ + { + "name": "Filename", + "type": "string", + "value": "file.txt" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(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 new File([new Uint8Array(tarball.bytes)], args[0]); + } + +} + +export default Tar; diff --git a/src/core/operations/Tidy.js b/src/core/operations/Tidy.js deleted file mode 100755 index 175d888e..00000000 --- a/src/core/operations/Tidy.js +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Tidy operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Tidy = { - - /** - * @constant - * @default - */ - REMOVE_SPACES: true, - /** - * @constant - * @default - */ - REMOVE_CARIAGE_RETURNS: true, - /** - * @constant - * @default - */ - REMOVE_LINE_FEEDS: true, - /** - * @constant - * @default - */ - REMOVE_TABS: true, - /** - * @constant - * @default - */ - REMOVE_FORM_FEEDS: true, - /** - * @constant - * @default - */ - REMOVE_FULL_STOPS: false, - - /** - * Remove whitespace operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runRemoveWhitespace: function (input, args) { - let removeSpaces = args[0], - removeCariageReturns = args[1], - removeLineFeeds = args[2], - removeTabs = args[3], - removeFormFeeds = args[4], - removeFullStops = args[5], - data = input; - - if (removeSpaces) data = data.replace(/ /g, ""); - if (removeCariageReturns) data = data.replace(/\r/g, ""); - if (removeLineFeeds) data = data.replace(/\n/g, ""); - if (removeTabs) data = data.replace(/\t/g, ""); - if (removeFormFeeds) data = data.replace(/\f/g, ""); - if (removeFullStops) data = data.replace(/\./g, ""); - return data; - }, - - - /** - * Remove null bytes operation. - * - * @param {byteArray} input - * @param {Object[]} args - * @returns {byteArray} - */ - runRemoveNulls: function (input, args) { - const output = []; - for (let i = 0; i < input.length; i++) { - if (input[i] !== 0) output.push(input[i]); - } - return output; - }, - - - /** - * @constant - * @default - */ - APPLY_TO_EACH_LINE: false, - /** - * @constant - * @default - */ - DROP_START: 0, - /** - * @constant - * @default - */ - DROP_LENGTH: 5, - - /** - * Drop bytes operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {ArrayBuffer} - */ - runDropBytes: function(input, args) { - const start = args[0], - length = args[1], - applyToEachLine = args[2]; - - if (start < 0 || length < 0) - throw "Error: Invalid value"; - - if (!applyToEachLine) { - const left = input.slice(0, start), - right = input.slice(start + length, input.byteLength); - let result = new Uint8Array(left.byteLength + right.byteLength); - result.set(new Uint8Array(left), 0); - result.set(new Uint8Array(right), left.byteLength); - return result.buffer; - } - - // Split input into lines - const data = new Uint8Array(input); - let lines = [], - line = [], - i; - - for (i = 0; i < data.length; i++) { - if (data[i] === 0x0a) { - lines.push(line); - line = []; - } else { - line.push(data[i]); - } - } - lines.push(line); - - let output = []; - for (i = 0; i < lines.length; i++) { - output = output.concat(lines[i].slice(0, start).concat(lines[i].slice(start+length, lines[i].length))); - output.push(0x0a); - } - return new Uint8Array(output.slice(0, output.length-1)).buffer; - }, - - - /** - * @constant - * @default - */ - TAKE_START: 0, - /** - * @constant - * @default - */ - TAKE_LENGTH: 5, - - /** - * Take bytes operation. - * - * @param {ArrayBuffer} input - * @param {Object[]} args - * @returns {ArrayBuffer} - */ - runTakeBytes: function(input, args) { - const start = args[0], - length = args[1], - applyToEachLine = args[2]; - - if (start < 0 || length < 0) - throw "Error: Invalid value"; - - if (!applyToEachLine) - return input.slice(start, start+length); - - // Split input into lines - const data = new Uint8Array(input); - let lines = [], - line = [], - i; - - for (i = 0; i < data.length; i++) { - if (data[i] === 0x0a) { - lines.push(line); - line = []; - } else { - line.push(data[i]); - } - } - lines.push(line); - - let output = []; - for (i = 0; i < lines.length; i++) { - output = output.concat(lines[i].slice(start, start+length)); - output.push(0x0a); - } - return new Uint8Array(output.slice(0, output.length-1)).buffer; - }, - - - /** - * @constant - * @default - */ - PAD_POSITION: ["Start", "End"], - /** - * @constant - * @default - */ - PAD_LENGTH: 5, - /** - * @constant - * @default - */ - PAD_CHAR: " ", - - /** - * Pad lines operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runPad: function(input, args) { - let position = args[0], - len = args[1], - chr = args[2], - lines = input.split("\n"), - output = "", - i = 0; - - if (position === "Start") { - for (i = 0; i < lines.length; i++) { - output += lines[i].padStart(lines[i].length+len, chr) + "\n"; - } - } else if (position === "End") { - for (i = 0; i < lines.length; i++) { - output += lines[i].padEnd(lines[i].length+len, chr) + "\n"; - } - } - - return output.slice(0, output.length-1); - }, - -}; - -export default Tidy; diff --git a/src/core/operations/ToBCD.mjs b/src/core/operations/ToBCD.mjs new file mode 100644 index 00000000..43de14b6 --- /dev/null +++ b/src/core/operations/ToBCD.mjs @@ -0,0 +1,141 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD"; +import BigNumber from "bignumber.js"; + +/** + * To BCD operation + */ +class ToBCD extends Operation { + + /** + * ToBCD constructor + */ + constructor() { + super(); + + this.name = "To BCD"; + this.module = "Default"; + this.description = "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign"; + this.inputType = "BigNumber"; + this.outputType = "string"; + this.args = [ + { + "name": "Scheme", + "type": "option", + "value": ENCODING_SCHEME + }, + { + "name": "Packed", + "type": "boolean", + "value": true + }, + { + "name": "Signed", + "type": "boolean", + "value": false + }, + { + "name": "Output format", + "type": "option", + "value": FORMAT + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.isNaN()) + throw new OperationError("Invalid input"); + if (!input.integerValue(BigNumber.ROUND_DOWN).isEqualTo(input)) + throw new OperationError("Fractional values are not supported by BCD"); + + const encoding = ENCODING_LOOKUP[args[0]], + packed = args[1], + signed = args[2], + outputFormat = args[3]; + + // Split input number up into separate digits + const digits = input.toFixed().split(""); + + if (digits[0] === "-" || digits[0] === "+") { + digits.shift(); + } + + let nibbles = []; + + digits.forEach(d => { + const n = parseInt(d, 10); + nibbles.push(encoding[n]); + }); + + if (signed) { + if (packed && digits.length % 2 === 0) { + // If there are an even number of digits, we add a leading 0 so + // that the sign nibble doesn't sit in its own byte, leading to + // ambiguity around whether the number ends with a 0 or not. + nibbles.unshift(encoding[0]); + } + + nibbles.push(input > 0 ? 12 : 13); + // 12 ("C") for + (credit) + // 13 ("D") for - (debit) + } + + let bytes = []; + + if (packed) { + let encoded = 0, + little = false; + + nibbles.forEach(n => { + encoded ^= little ? n : (n << 4); + if (little) { + bytes.push(encoded); + encoded = 0; + } + little = !little; + }); + + if (little) bytes.push(encoded); + } else { + bytes = nibbles; + + // Add null high nibbles + nibbles = nibbles.map(n => { + return [0, n]; + }).reduce((a, b) => { + return a.concat(b); + }); + } + + // Output + switch (outputFormat) { + case "Nibbles": + return nibbles.map(n => { + return n.toString(2).padStart(4, "0"); + }).join(" "); + case "Bytes": + return bytes.map(b => { + return b.toString(2).padStart(8, "0"); + }).join(" "); + case "Raw": + default: + return Utils.byteArrayToChars(bytes); + } + } + +} + +export default ToBCD; diff --git a/src/core/operations/ToBase.mjs b/src/core/operations/ToBase.mjs new file mode 100644 index 00000000..e37431e2 --- /dev/null +++ b/src/core/operations/ToBase.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * To Base operation + */ +class ToBase extends Operation { + + /** + * ToBase constructor + */ + constructor() { + super(); + + this.name = "To Base"; + this.module = "Default"; + this.description = "Converts a decimal number to a given numerical base."; + this.inputType = "BigNumber"; + this.outputType = "string"; + this.args = [ + { + "name": "Radix", + "type": "number", + "value": 36 + } + ]; + } + + /** + * @param {BigNumber} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input) { + throw new OperationError("Error: Input must be a number"); + } + const radix = args[0]; + if (radix < 2 || radix > 36) { + throw new OperationError("Error: Radix argument must be between 2 and 36"); + } + return input.toString(radix); + } + +} + +export default ToBase; 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/ToBase58.mjs b/src/core/operations/ToBase58.mjs new file mode 100644 index 00000000..47e0096f --- /dev/null +++ b/src/core/operations/ToBase58.mjs @@ -0,0 +1,85 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import {ALPHABET_OPTIONS} from "../lib/Base58"; + +/** + * To Base58 operation + */ +class ToBase58 extends Operation { + + /** + * ToBase58 constructor + */ + constructor() { + super(); + + this.name = "To Base58"; + this.module = "Default"; + this.description = "Base58 (similar to Base64) is a notation for encoding arbitrary byte data. It differs from Base64 by removing easily misread characters (i.e. l, I, 0 and O) to improve human readability.

This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).

e.g. hello world becomes StV1DL6CwTryKyV

Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc)."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Alphabet", + "type": "editableOption", + "value": ALPHABET_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let alphabet = args[0] || ALPHABET_OPTIONS[0].value, + result = [0]; + + alphabet = Utils.expandAlphRange(alphabet).join(""); + + if (alphabet.length !== 58 || + [].unique.call(alphabet).length !== 58) { + throw new OperationError("Error: alphabet must be of length 58"); + } + + if (input.length === 0) return ""; + + input.forEach(function(b) { + let carry = (result[0] << 8) + b; + result[0] = carry % 58; + carry = (carry / 58) | 0; + + for (let i = 1; i < result.length; i++) { + carry += result[i] << 8; + result[i] = carry % 58; + carry = (carry / 58) | 0; + } + + while (carry > 0) { + result.push(carry % 58); + carry = (carry / 58) | 0; + } + }); + + result = result.map(function(b) { + return alphabet[b]; + }).reverse().join(""); + + while (result.length < input.length) { + result = alphabet[0] + result; + } + + return result; + } + +} + +export default ToBase58; 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/ToBinary.mjs b/src/core/operations/ToBinary.mjs new file mode 100644 index 00000000..f10f66a7 --- /dev/null +++ b/src/core/operations/ToBinary.mjs @@ -0,0 +1,91 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {BIN_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * To Binary operation + */ +class ToBinary extends Operation { + + /** + * ToBinary constructor + */ + constructor() { + super(); + + this.name = "To Binary"; + this.module = "Default"; + this.description = "Displays the input data as a binary string.

e.g. Hi becomes 01001000 01101001"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": BIN_DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"), + padding = 8; + let output = ""; + + for (let i = 0; i < input.length; i++) { + output += input[i].toString(2).padStart(padding, "0") + delim; + } + + if (delim.length) { + return output.slice(0, -delim.length); + } else { + return output; + } + } + + /** + * Highlight To Binary + * + * @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"); + pos[0].start = pos[0].start * (8 + delim.length); + pos[0].end = pos[0].end * (8 + delim.length) - delim.length; + return pos; + } + + /** + * Highlight To Binary in reverse + * + * @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"); + 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; + } + +} + +export default ToBinary; diff --git a/src/core/operations/ToCamelCase.mjs b/src/core/operations/ToCamelCase.mjs new file mode 100644 index 00000000..cc42949c --- /dev/null +++ b/src/core/operations/ToCamelCase.mjs @@ -0,0 +1,53 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import camelCase from "lodash/camelCase"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Camel case operation + */ +class ToCamelCase extends Operation { + + /** + * ToCamelCase constructor + */ + constructor() { + super(); + + this.name = "To Camel case"; + this.module = "Code"; + this.description = "Converts the input string to camel case.\n

\nCamel case is all lower case except letters after word boundaries which are uppercase.\n

\ne.g. thisIsCamelCase\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, camelCase); + } else { + return camelCase(input); + } + } + +} + +export default ToCamelCase; diff --git a/src/core/operations/ToCharcode.mjs b/src/core/operations/ToCharcode.mjs new file mode 100644 index 00000000..db043d79 --- /dev/null +++ b/src/core/operations/ToCharcode.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"; +import {DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; + +/** + * To Charcode operation + */ +class ToCharcode extends Operation { + + /** + * ToCharcode constructor + */ + constructor() { + super(); + + this.name = "To Charcode"; + this.module = "Default"; + this.description = "Converts text to its unicode character code equivalent.

e.g. Γειά σου becomes 0393 03b5 03b9 03ac 20 03c3 03bf 03c5"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + }, + { + "name": "Base", + "type": "number", + "value": 16 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if base argument out of range + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"), + base = args[1]; + let output = "", + padding = 2, + ordinal; + + if (base < 2 || base > 36) { + throw new OperationError("Error: Base argument must be between 2 and 36"); + } + + const charcode = Utils.strToCharcode(input); + for (let i = 0; i < charcode.length; i++) { + ordinal = charcode[i]; + + if (base === 16) { + if (ordinal < 256) padding = 2; + else if (ordinal < 65536) padding = 4; + else if (ordinal < 16777216) padding = 6; + else if (ordinal < 4294967296) padding = 8; + else padding = 2; + + if (padding > 2 && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + + output += Utils.hex(ordinal, padding) + delim; + } else { + if (ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + output += ordinal.toString(base) + delim; + } + } + + return output.slice(0, -delim.length); + } + +} + +export default ToCharcode; diff --git a/src/core/operations/ToDecimal.mjs b/src/core/operations/ToDecimal.mjs new file mode 100644 index 00000000..cad8dc18 --- /dev/null +++ b/src/core/operations/ToDecimal.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; + + +/** + * To Decimal operation + */ +class ToDecimal extends Operation { + + /** + * ToDecimal constructor + */ + constructor() { + super(); + + this.name = "To Decimal"; + this.module = "Default"; + this.description = "Converts the input data to an ordinal integer array.

e.g. Hello becomes 72 101 108 108 111"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]); + return input.join(delim); + } + +} + +export default ToDecimal; diff --git a/src/core/operations/ToHTMLEntity.mjs b/src/core/operations/ToHTMLEntity.mjs new file mode 100644 index 00000000..06b5ff25 --- /dev/null +++ b/src/core/operations/ToHTMLEntity.mjs @@ -0,0 +1,345 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * To HTML Entity operation + */ +class ToHTMLEntity extends Operation { + + /** + * ToHTMLEntity constructor + */ + constructor() { + super(); + + this.name = "To HTML Entity"; + this.module = "Default"; + this.description = "Converts characters to HTML entities

e.g. & becomes &amp;"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Convert all characters", + "type": "boolean", + "value": false + }, + { + "name": "Convert to", + "type": "option", + "value": ["Named entities where possible", "Numeric entities", "Hex entities"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const convertAll = args[0], + numeric = args[1] === "Numeric entities", + hexa = args[1] === "Hex entities"; + + const charcodes = Utils.strToCharcode(input); + let output = ""; + + for (let i = 0; i < charcodes.length; i++) { + if (convertAll && numeric) { + output += "&#" + charcodes[i] + ";"; + } else if (convertAll && hexa) { + output += "&#x" + Utils.hex(charcodes[i]) + ";"; + } else if (convertAll) { + output += byteToEntity[charcodes[i]] || "&#" + charcodes[i] + ";"; + } else if (numeric) { + if (charcodes[i] > 255 || byteToEntity.hasOwnProperty(charcodes[i])) { + output += "&#" + charcodes[i] + ";"; + } else { + output += Utils.chr(charcodes[i]); + } + } else if (hexa) { + if (charcodes[i] > 255 || byteToEntity.hasOwnProperty(charcodes[i])) { + output += "&#x" + Utils.hex(charcodes[i]) + ";"; + } else { + output += Utils.chr(charcodes[i]); + } + } else { + output += byteToEntity[charcodes[i]] || ( + charcodes[i] > 255 ? + "&#" + charcodes[i] + ";" : + Utils.chr(charcodes[i]) + ); + } + } + return output; + } + +} + +/** + * Lookup table to translate byte values to their HTML entity codes. + */ +const byteToEntity = { + 34: """, + 38: "&", + 39: "'", + 60: "<", + 62: ">", + 160: " ", + 161: "¡", + 162: "¢", + 163: "£", + 164: "¤", + 165: "¥", + 166: "¦", + 167: "§", + 168: "¨", + 169: "©", + 170: "ª", + 171: "«", + 172: "¬", + 173: "­", + 174: "®", + 175: "¯", + 176: "°", + 177: "±", + 178: "²", + 179: "³", + 180: "´", + 181: "µ", + 182: "¶", + 183: "·", + 184: "¸", + 185: "¹", + 186: "º", + 187: "»", + 188: "¼", + 189: "½", + 190: "¾", + 191: "¿", + 192: "À", + 193: "Á", + 194: "Â", + 195: "Ã", + 196: "Ä", + 197: "Å", + 198: "Æ", + 199: "Ç", + 200: "È", + 201: "É", + 202: "Ê", + 203: "Ë", + 204: "Ì", + 205: "Í", + 206: "Î", + 207: "Ï", + 208: "Ð", + 209: "Ñ", + 210: "Ò", + 211: "Ó", + 212: "Ô", + 213: "Õ", + 214: "Ö", + 215: "×", + 216: "Ø", + 217: "Ù", + 218: "Ú", + 219: "Û", + 220: "Ü", + 221: "Ý", + 222: "Þ", + 223: "ß", + 224: "à", + 225: "á", + 226: "â", + 227: "ã", + 228: "ä", + 229: "å", + 230: "æ", + 231: "ç", + 232: "è", + 233: "é", + 234: "ê", + 235: "ë", + 236: "ì", + 237: "í", + 238: "î", + 239: "ï", + 240: "ð", + 241: "ñ", + 242: "ò", + 243: "ó", + 244: "ô", + 245: "õ", + 246: "ö", + 247: "÷", + 248: "ø", + 249: "ù", + 250: "ú", + 251: "û", + 252: "ü", + 253: "ý", + 254: "þ", + 255: "ÿ", + 338: "Œ", + 339: "œ", + 352: "Š", + 353: "š", + 376: "Ÿ", + 402: "ƒ", + 710: "ˆ", + 732: "˜", + 913: "Α", + 914: "Β", + 915: "Γ", + 916: "Δ", + 917: "Ε", + 918: "Ζ", + 919: "Η", + 920: "Θ", + 921: "Ι", + 922: "Κ", + 923: "Λ", + 924: "Μ", + 925: "Ν", + 926: "Ξ", + 927: "Ο", + 928: "Π", + 929: "Ρ", + 931: "Σ", + 932: "Τ", + 933: "Υ", + 934: "Φ", + 935: "Χ", + 936: "Ψ", + 937: "Ω", + 945: "α", + 946: "β", + 947: "γ", + 948: "δ", + 949: "ε", + 950: "ζ", + 951: "η", + 952: "θ", + 953: "ι", + 954: "κ", + 955: "λ", + 956: "μ", + 957: "ν", + 958: "ξ", + 959: "ο", + 960: "π", + 961: "ρ", + 962: "ς", + 963: "σ", + 964: "τ", + 965: "υ", + 966: "φ", + 967: "χ", + 968: "ψ", + 969: "ω", + 977: "ϑ", + 978: "ϒ", + 982: "ϖ", + 8194: " ", + 8195: " ", + 8201: " ", + 8204: "‌", + 8205: "‍", + 8206: "‎", + 8207: "‏", + 8211: "–", + 8212: "—", + 8216: "‘", + 8217: "’", + 8218: "‚", + 8220: "“", + 8221: "”", + 8222: "„", + 8224: "†", + 8225: "‡", + 8226: "•", + 8230: "…", + 8240: "‰", + 8242: "′", + 8243: "″", + 8249: "‹", + 8250: "›", + 8254: "‾", + 8260: "⁄", + 8364: "€", + 8465: "ℑ", + 8472: "℘", + 8476: "ℜ", + 8482: "™", + 8501: "ℵ", + 8592: "←", + 8593: "↑", + 8594: "→", + 8595: "↓", + 8596: "↔", + 8629: "↵", + 8656: "⇐", + 8657: "⇑", + 8658: "⇒", + 8659: "⇓", + 8660: "⇔", + 8704: "∀", + 8706: "∂", + 8707: "∃", + 8709: "∅", + 8711: "∇", + 8712: "∈", + 8713: "∉", + 8715: "∋", + 8719: "∏", + 8721: "∑", + 8722: "−", + 8727: "∗", + 8730: "√", + 8733: "∝", + 8734: "∞", + 8736: "∠", + 8743: "∧", + 8744: "∨", + 8745: "∩", + 8746: "∪", + 8747: "∫", + 8756: "∴", + 8764: "∼", + 8773: "≅", + 8776: "≈", + 8800: "≠", + 8801: "≡", + 8804: "≤", + 8805: "≥", + 8834: "⊂", + 8835: "⊃", + 8836: "⊄", + 8838: "⊆", + 8839: "⊇", + 8853: "⊕", + 8855: "⊗", + 8869: "⊥", + 8901: "⋅", + 8942: "⋮", + 8968: "⌈", + 8969: "⌉", + 8970: "⌊", + 8971: "⌋", + 9001: "⟨", + 9002: "⟩", + 9674: "◊", + 9824: "♠", + 9827: "♣", + 9829: "♥", + 9830: "♦", +}; + +export default ToHTMLEntity; diff --git a/src/core/operations/ToHex.mjs b/src/core/operations/ToHex.mjs new file mode 100644 index 00000000..9b1ccade --- /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, TO_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: TO_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/ToHexContent.mjs b/src/core/operations/ToHexContent.mjs new file mode 100644 index 00000000..af9ae22c --- /dev/null +++ b/src/core/operations/ToHexContent.mjs @@ -0,0 +1,81 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {toHex} from "../lib/Hex"; + +/** + * To Hex Content operation + */ +class ToHexContent extends Operation { + + /** + * ToHexContent constructor + */ + constructor() { + super(); + + this.name = "To Hex Content"; + this.module = "Default"; + this.description = "Converts special characters in a string to hexadecimal.

e.g. foo=bar becomes foo|3d|bar."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Convert", + "type": "option", + "value": ["Only special chars", "Only special chars including spaces", "All chars"] + }, + { + "name": "Print spaces between bytes", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const convert = args[0]; + const spaces = args[1]; + if (convert === "All chars") { + let result = "|" + toHex(input) + "|"; + if (!spaces) result = result.replace(/ /g, ""); + return result; + } + + let output = "", + inHex = false, + b; + const convertSpaces = convert === "Only special chars including spaces"; + for (let i = 0; i < input.length; i++) { + b = input[i]; + if ((b === 32 && convertSpaces) || (b < 48 && b !== 32) || (b > 57 && b < 65) || (b > 90 && b < 97) || b > 122) { + if (!inHex) { + output += "|"; + inHex = true; + } else if (spaces) output += " "; + output += toHex([b]); + } else { + if (inHex) { + output += "|"; + inHex = false; + } + output += Utils.chr(input[i]); + } + } + if (inHex) output += "|"; + return output; + } + +} + +export default ToHexContent; diff --git a/src/core/operations/Hexdump.js b/src/core/operations/ToHexdump.mjs old mode 100755 new mode 100644 similarity index 67% rename from src/core/operations/Hexdump.js rename to src/core/operations/ToHexdump.mjs index a10a7060..89ebdc33 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/ToHexdump.mjs @@ -1,47 +1,58 @@ -import Utils from "../Utils.js"; - - /** - * Hexdump operations. - * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 - * - * @namespace */ -const Hexdump = { + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * To Hexdump operation + */ +class ToHexdump extends Operation { /** - * @constant - * @default + * ToHexdump constructor */ - WIDTH: 16, - /** - * @constant - * @default - */ - UPPER_CASE: false, - /** - * @constant - * @default - */ - INCLUDE_FINAL_LENGTH: false, + constructor() { + super(); + + this.name = "To Hexdump"; + this.module = "Default"; + this.description = "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside."; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Width", + "type": "number", + "value": 16 + }, + { + "name": "Upper case hex", + "type": "boolean", + "value": false + }, + { + "name": "Include final length", + "type": "boolean", + "value": false + } + ]; + } /** - * To Hexdump operation. - * * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ - runTo: function(input, args) { + run(input, args) { const data = new Uint8Array(input); - const length = args[0] || Hexdump.WIDTH; - const upperCase = args[1]; - const includeFinalLength = args[2]; + const [length, upperCase, includeFinalLength] = args; + const padding = 2; - let output = "", padding = 2; + let output = ""; for (let i = 0; i < data.length; i += length) { const buff = data.slice(i, i+length); let hexa = ""; @@ -66,40 +77,10 @@ const Hexdump = { } return output.slice(0, -1); - }, - + } /** - * From Hexdump operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFrom: function(input, args) { - let output = [], - 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, " ")); - for (let i = 0; i < line.length; i++) { - output.push(line[i]); - } - } - // Is this a CyberChef hexdump or is it from a different tool? - const width = input.indexOf("\n"); - const w = (width - 13) / 4; - // w should be the specified width of the hexdump and therefore a round number - if (Math.floor(w) !== w || input.indexOf("\r") !== -1 || output.indexOf(13) !== -1) { - if (ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); - } - return output; - }, - - - /** - * Highlight to hexdump + * Highlight To Hexdump * * @param {Object[]} pos * @param {number} pos[].start @@ -107,11 +88,11 @@ const Hexdump = { * @param {Object[]} args * @returns {Object[]} pos */ - highlightTo: function(pos, args) { + highlight(pos, args) { // Calculate overall selection - let w = args[0] || 16, - width = 14 + (w*4), - line = Math.floor(pos[0].start / w), + const w = args[0] || 16, + width = 14 + (w*4); + let line = Math.floor(pos[0].start / w), offset = pos[0].start % w, start = 0, end = 0; @@ -146,7 +127,8 @@ const Hexdump = { } // Set up multiple selections for ASCII - let len = pos.length, lineNum = 0; + const len = pos.length; + let lineNum = 0; start = 0; end = 0; for (let i = 1; i < len; i++) { @@ -156,11 +138,10 @@ const Hexdump = { pos.push({ start: start, end: end }); } return pos; - }, - + } /** - * Highlight from hexdump + * Highlight To Hexdump in reverse * * @param {Object[]} pos * @param {number} pos[].start @@ -168,7 +149,7 @@ const Hexdump = { * @param {Object[]} args * @returns {Object[]} pos */ - highlightFrom: function(pos, args) { + highlightReverse(pos, args) { const w = args[0] || 16; const width = 14 + (w*4); @@ -195,8 +176,8 @@ const Hexdump = { } return pos; - }, + } -}; +} -export default Hexdump; +export default ToHexdump; diff --git a/src/core/operations/ToKebabCase.mjs b/src/core/operations/ToKebabCase.mjs new file mode 100644 index 00000000..c293ede8 --- /dev/null +++ b/src/core/operations/ToKebabCase.mjs @@ -0,0 +1,53 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import kebabCase from "lodash/kebabCase"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Kebab case operation + */ +class ToKebabCase extends Operation { + + /** + * ToKebabCase constructor + */ + constructor() { + super(); + + this.name = "To Kebab case"; + this.module = "Code"; + this.description = "Converts the input string to kebab case.\n

\nKebab case is all lower case with dashes as word boundaries.\n

\ne.g. this-is-kebab-case\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, kebabCase); + } else { + return kebabCase(input); + } + } + +} + +export default ToKebabCase; diff --git a/src/core/operations/ToLowerCase.mjs b/src/core/operations/ToLowerCase.mjs new file mode 100644 index 00000000..f28380bc --- /dev/null +++ b/src/core/operations/ToLowerCase.mjs @@ -0,0 +1,65 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * To Lower case operation + */ +class ToLowerCase extends Operation { + + /** + * ToLowerCase constructor + */ + constructor() { + super(); + + this.name = "To Lower case"; + this.module = "Default"; + this.description = "Converts every character in the input to lower case."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input.toLowerCase(); + } + + /** + * Highlight To Lower case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To Lower case in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default ToLowerCase; diff --git a/src/core/operations/ToMorseCode.mjs b/src/core/operations/ToMorseCode.mjs new file mode 100644 index 00000000..3146a114 --- /dev/null +++ b/src/core/operations/ToMorseCode.mjs @@ -0,0 +1,153 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {LETTER_DELIM_OPTIONS, WORD_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * To Morse Code operation + */ +class ToMorseCode extends Operation { + + /** + * ToMorseCode constructor + */ + constructor() { + super(); + + this.name = "To Morse Code"; + this.module = "Default"; + this.description = "Translates alphanumeric characters into International Morse Code.

Ignores non-Morse characters.

e.g. SOS becomes ... --- ..."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Format options", + "type": "option", + "value": ["-/.", "_/.", "Dash/Dot", "DASH/DOT", "dash/dot"] + }, + { + "name": "Letter delimiter", + "type": "option", + "value": LETTER_DELIM_OPTIONS + }, + { + "name": "Word delimiter", + "type": "option", + "value": WORD_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const format = args[0].split("/"); + const dash = format[0]; + const dot = format[1]; + + 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) { + let words = line.split(/ +/); + words = Array.prototype.map.call(words, function(word) { + const letters = Array.prototype.map.call(word, function(character) { + const letter = character.toUpperCase(); + if (typeof MORSE_TABLE[letter] == "undefined") { + return ""; + } + + return MORSE_TABLE[letter]; + }); + + return letters.join(""); + }); + line = words.join(""); + return line; + }); + input = input.join("\n"); + + input = input.replace( + /|||/g, + function(match) { + switch (match) { + case "": return dash; + case "": return dot; + case "": return letterDelim; + case "": return wordDelim; + } + } + ); + + return input; + } + +} + +const MORSE_TABLE = { + "A": "", + "B": "", + "C": "", + "D": "", + "E": "", + "F": "", + "G": "", + "H": "", + "I": "", + "J": "", + "K": "", + "L": "", + "M": "", + "N": "", + "O": "", + "P": "", + "Q": "", + "R": "", + "S": "", + "T": "", + "U": "", + "V": "", + "W": "", + "X": "", + "Y": "", + "Z": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "6": "", + "7": "", + "8": "", + "9": "", + "0": "", + ".": "", + ",": "", + ":": "", + ";": "", + "!": "", + "?": "", + "'": "", + "\"": "", + "/": "", + "-": "", + "+": "", + "(": "", + ")": "", + "@": "", + "=": "", + "&": "", + "_": "", + "$": "" +}; + +export default ToMorseCode; diff --git a/src/core/operations/ToOctal.mjs b/src/core/operations/ToOctal.mjs new file mode 100644 index 00000000..7cfb4736 --- /dev/null +++ b/src/core/operations/ToOctal.mjs @@ -0,0 +1,49 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {DELIM_OPTIONS} from "../lib/Delim"; + + +/** + * To Octal operation + */ +class ToOctal extends Operation { + + /** + * ToOctal constructor + */ + constructor() { + super(); + + this.name = "To Octal"; + this.module = "Default"; + this.description = "Converts the input string to octal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes 316 223 316 265 316 271 316 254 40 317 203 316 277 317 205"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0] || "Space"); + return input.map(val => val.toString(8)).join(delim); + } + +} + +export default ToOctal; diff --git a/src/core/operations/ToPunycode.mjs b/src/core/operations/ToPunycode.mjs new file mode 100644 index 00000000..8951cb5f --- /dev/null +++ b/src/core/operations/ToPunycode.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import punycode from "punycode"; + +/** + * To Punycode operation + */ +class ToPunycode extends Operation { + + /** + * ToPunycode constructor + */ + constructor() { + super(); + + this.name = "To Punycode"; + this.module = "Encodings"; + this.description = "Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.

e.g. m\xfcnchen encodes to mnchen-3ya"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Internationalised domain name", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const idn = args[0]; + + if (idn) { + return punycode.toASCII(input); + } else { + return punycode.encode(input); + } + } + +} + +export default ToPunycode; diff --git a/src/core/operations/QuotedPrintable.js b/src/core/operations/ToQuotedPrintable.mjs old mode 100755 new mode 100644 similarity index 63% rename from src/core/operations/QuotedPrintable.js rename to src/core/operations/ToQuotedPrintable.mjs index a53ea0ca..aa1025d7 --- a/src/core/operations/QuotedPrintable.js +++ b/src/core/operations/ToQuotedPrintable.mjs @@ -1,46 +1,41 @@ -/** @license -======================================================================== - mimelib: http://github.com/andris9/mimelib - Copyright (c) 2011-2012 Andris Reinman - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - /** - * Quoted Printable operations. * Some parts taken from mimelib (http://github.com/andris9/mimelib) - * * @author Andris Reinman + * @license MIT + * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 - * - * @namespace */ -const QuotedPrintable = { + +import Operation from "../Operation"; + +/** + * To Quoted Printable operation + */ +class ToQuotedPrintable extends Operation { + + /** + * ToQuotedPrintable constructor + */ + constructor() { + super(); + + this.name = "To Quoted Printable"; + this.module = "Default"; + this.description = "Quoted-Printable, or QP encoding, is an encoding using printable ASCII characters (alphanumeric and the equals sign '=') to transmit 8-bit data over a 7-bit data path or, generally, over a medium which is not 8-bit clean. It is defined as a MIME content transfer encoding for use in e-mail.

QP works by using the equals sign '=' as an escape character. It also limits line length to 76, as some software has limits on line length."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = []; + } /** - * To Quoted Printable operation. - * * @param {byteArray} input * @param {Object[]} args * @returns {string} */ - runTo: function (input, args) { - let mimeEncodedStr = QuotedPrintable.mimeEncode(input); + run(input, args) { + let mimeEncodedStr = this.mimeEncode(input); // fix line breaks mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, function() { @@ -49,49 +44,30 @@ const QuotedPrintable = { return spaces.replace(/ /g, "=20").replace(/\t/g, "=09"); }); - return QuotedPrintable._addSoftLinebreaks(mimeEncodedStr, "qp"); - }, + return this._addSoftLinebreaks(mimeEncodedStr, "qp"); + } - /** - * From Quoted Printable operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {byteArray} - */ - runFrom: function (input, args) { - const str = input.replace(/=(?:\r?\n|$)/g, ""); - return QuotedPrintable.mimeDecode(str); - }, + /** @license + ======================================================================== + mimelib: http://github.com/andris9/mimelib + Copyright (c) 2011-2012 Andris Reinman + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - /** - * Decodes mime-encoded data. - * - * @param {string} str - * @returns {byteArray} - */ - mimeDecode: function(str) { - let encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length, - bufferLength = str.length - encodedBytesCount * 2, - chr, hex, - buffer = new Array(bufferLength), - bufferPos = 0; - - for (let i = 0, len = str.length; i < len; i++) { - chr = str.charAt(i); - if (chr === "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { - buffer[bufferPos++] = parseInt(hex, 16); - i += 2; - continue; - } - buffer[bufferPos++] = chr.charCodeAt(0); - } - - return buffer; - }, - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ /** * Encodes mime data. @@ -99,19 +75,19 @@ const QuotedPrintable = { * @param {byteArray} buffer * @returns {string} */ - mimeEncode: function(buffer) { - let ranges = [ - [0x09], - [0x0A], - [0x0D], - [0x20], - [0x21], - [0x23, 0x3C], - [0x3E], - [0x40, 0x5E], - [0x60, 0x7E] - ], - result = ""; + mimeEncode(buffer) { + const ranges = [ + [0x09], + [0x0A], + [0x0D], + [0x20], + [0x21], + [0x23, 0x3C], + [0x3E], + [0x40, 0x5E], + [0x60, 0x7E] + ]; + let result = ""; for (let i = 0, len = buffer.length; i < len; i++) { if (this._checkRanges(buffer[i], ranges)) { @@ -122,8 +98,7 @@ const QuotedPrintable = { } return result; - }, - + } /** * Checks if a given number falls within a given set of ranges. @@ -133,7 +108,7 @@ const QuotedPrintable = { * @param {byteArray[]} ranges * @returns {bolean} */ - _checkRanges: function(nr, ranges) { + _checkRanges(nr, ranges) { for (let i = ranges.length - 1; i >= 0; i--) { if (!ranges[i].length) continue; @@ -143,8 +118,7 @@ const QuotedPrintable = { return true; } return false; - }, - + } /** * Adds soft line breaks to a string. @@ -156,7 +130,7 @@ const QuotedPrintable = { * @param {string} encoding * @returns {string} */ - _addSoftLinebreaks: function(str, encoding) { + _addSoftLinebreaks(str, encoding) { const lineLengthMax = 76; encoding = (encoding || "base64").toString().toLowerCase().trim(); @@ -166,8 +140,7 @@ const QuotedPrintable = { } else { return this._addBase64SoftLinebreaks(str, lineLengthMax); } - }, - + } /** * Adds soft line breaks to a base64 string. @@ -177,11 +150,10 @@ const QuotedPrintable = { * @param {number} lineLengthMax * @returns {string} */ - _addBase64SoftLinebreaks: function(base64EncodedStr, lineLengthMax) { + _addBase64SoftLinebreaks(base64EncodedStr, lineLengthMax) { base64EncodedStr = (base64EncodedStr || "").toString().trim(); return base64EncodedStr.replace(new RegExp(".{" + lineLengthMax + "}", "g"), "$&\r\n").trim(); - }, - + } /** * Adds soft line breaks to a quoted printable string. @@ -191,11 +163,11 @@ const QuotedPrintable = { * @param {number} lineLengthMax * @returns {string} */ - _addQPSoftLinebreaks: function(mimeEncodedStr, lineLengthMax) { + _addQPSoftLinebreaks(mimeEncodedStr, lineLengthMax) { + const len = mimeEncodedStr.length, + lineMargin = Math.floor(lineLengthMax / 3); let pos = 0, - len = mimeEncodedStr.length, match, code, line, - lineMargin = Math.floor(lineLengthMax / 3), result = ""; // insert soft linebreaks where needed @@ -265,8 +237,8 @@ const QuotedPrintable = { } return result; - }, + } -}; +} -export default QuotedPrintable; +export default ToQuotedPrintable; diff --git a/src/core/operations/ToSnakeCase.mjs b/src/core/operations/ToSnakeCase.mjs new file mode 100644 index 00000000..10af102b --- /dev/null +++ b/src/core/operations/ToSnakeCase.mjs @@ -0,0 +1,52 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import snakeCase from "lodash/snakeCase"; +import Operation from "../Operation"; +import { replaceVariableNames } from "../lib/Code"; + +/** + * To Snake case operation + */ +class ToSnakeCase extends Operation { + + /** + * ToSnakeCase constructor + */ + constructor() { + super(); + + this.name = "To Snake case"; + this.module = "Code"; + this.description = "Converts the input string to snake case.\n

\nSnake case is all lower case with underscores as word boundaries.\n

\ne.g. this_is_snake_case\n

\n'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Attempt to be context aware", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const smart = args[0]; + + if (smart) { + return replaceVariableNames(input, snakeCase); + } else { + return snakeCase(input); + } + } +} + +export default ToSnakeCase; diff --git a/src/core/operations/ToTable.js b/src/core/operations/ToTable.mjs old mode 100755 new mode 100644 similarity index 73% rename from src/core/operations/ToTable.js rename to src/core/operations/ToTable.mjs index b18b56f1..5f6b9670 --- a/src/core/operations/ToTable.js +++ b/src/core/operations/ToTable.mjs @@ -1,34 +1,58 @@ /** - * ToTable operations. - * * @author Mark Jones [github.com/justanothermark] * @copyright Crown Copyright 2018 * @license Apache-2.0 - * - * @namespace */ -import Utils from "../Utils.js"; -const ToTable = { +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * To Table operation + */ +class ToTable extends Operation { /** - * @constant - * @default + * ToTable constructor */ - FORMATS: [ - "ASCII", - "HTML" - ], + constructor() { + super(); + this.name = "To Table"; + this.module = "Default"; + this.description = "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.

Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to \\t to support TSV (Tab Separated Values) or | for PSV (Pipe Separated Values).

You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Cell delimiters", + "type": "binaryShortString", + "value": "," + }, + { + "name": "Row delimiters", + "type": "binaryShortString", + "value": "\\n\\r" + }, + { + "name": "Make first row header", + "type": "boolean", + "value": false + }, + { + "name": "Format", + "type": "option", + "value": ["ASCII", "HTML"] + } + ]; + } /** - * To Table operation. - * * @param {string} input * @param {Object[]} args * @returns {html} */ - runToTable: function (input, args) { + run(input, args) { const [cellDelims, rowDelims, firstRowHeader, format] = args; // Process the input into a nested array of elements. @@ -57,7 +81,7 @@ const ToTable = { const crossBorder = "+"; let output = ""; - let longestCells = []; + const longestCells = []; // Find longestCells value per column to pad cells equally. tableData.forEach(function(row, index) { @@ -74,7 +98,7 @@ const ToTable = { // If the first row is a header, remove the row from the data and // add it to the output with another horizontal border. if (firstRowHeader) { - let row = tableData.shift(); + const row = tableData.shift(); output += outputRow(row, longestCells); output += outputHorizontalBorder(longestCells); } @@ -123,12 +147,12 @@ const ToTable = { */ function htmlOutput(tableData) { // Start the HTML output with suitable classes for styling. - let output = ""; + let output = "
"; // If the first row is a header then put it in with "; + const row = tableData.shift(); + output += ""; output += outputRow(row, "th"); output += ""; } @@ -159,6 +183,7 @@ const ToTable = { } } } -}; + +} export default ToTable; diff --git a/src/core/operations/ToUNIXTimestamp.mjs b/src/core/operations/ToUNIXTimestamp.mjs new file mode 100644 index 00000000..6983d617 --- /dev/null +++ b/src/core/operations/ToUNIXTimestamp.mjs @@ -0,0 +1,77 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import moment from "moment-timezone"; +import {UNITS} from "../lib/DateTime"; +import OperationError from "../errors/OperationError"; + +/** + * To UNIX Timestamp operation + */ +class ToUNIXTimestamp extends Operation { + + /** + * ToUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "To UNIX Timestamp"; + this.module = "Default"; + this.description = "Parses a datetime string in UTC and returns the corresponding UNIX timestamp.

e.g. Mon 1 January 2001 11:00:00 becomes 978346800

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch)."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Units", + "type": "option", + "value": UNITS + }, + { + "name": "Treat as UTC", + "type": "boolean", + "value": true + }, + { + "name": "Show parsed datetime", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if unit unrecognised + */ + run(input, args) { + const [units, treatAsUTC, showDateTime] = args, + d = treatAsUTC ? moment.utc(input) : moment(input); + + let result = ""; + + if (units === "Seconds (s)") { + result = d.unix(); + } else if (units === "Milliseconds (ms)") { + result = d.valueOf(); + } else if (units === "Microseconds (μs)") { + result = d.valueOf() * 1000; + } else if (units === "Nanoseconds (ns)") { + result = d.valueOf() * 1000000; + } else { + throw new OperationError("Unrecognised unit"); + } + + return showDateTime ? `${result} (${d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss")} UTC)` : result.toString(); + } + +} + +export default ToUNIXTimestamp; diff --git a/src/core/operations/ToUpperCase.mjs b/src/core/operations/ToUpperCase.mjs new file mode 100644 index 00000000..d56aacff --- /dev/null +++ b/src/core/operations/ToUpperCase.mjs @@ -0,0 +1,89 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * To Upper case operation + */ +class ToUpperCase extends Operation { + + /** + * ToUpperCase constructor + */ + constructor() { + super(); + + this.name = "To Upper case"; + this.module = "Default"; + this.description = "Converts the input string to upper case, optionally limiting scope to only the first character in each word, sentence or paragraph."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Scope", + "type": "option", + "value": ["All", "Word", "Sentence", "Paragraph"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const scope = args[0]; + + switch (scope) { + case "Word": + return input.replace(/(\b\w)/gi, function(m) { + return m.toUpperCase(); + }); + case "Sentence": + return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) { + return m.toUpperCase(); + }); + case "Paragraph": + return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) { + return m.toUpperCase(); + }); + case "All": /* falls through */ + default: + return input.toUpperCase(); + } + } + + /** + * Highlight To Upper case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight To Upper case in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default ToUpperCase; diff --git a/src/core/operations/TranslateDateTimeFormat.mjs b/src/core/operations/TranslateDateTimeFormat.mjs new file mode 100644 index 00000000..6a60f6f0 --- /dev/null +++ b/src/core/operations/TranslateDateTimeFormat.mjs @@ -0,0 +1,78 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime"; + +/** + * Translate DateTime Format operation + */ +class TranslateDateTimeFormat extends Operation { + + /** + * TranslateDateTimeFormat constructor + */ + constructor() { + super(); + + this.name = "Translate DateTime Format"; + this.module = "Default"; + this.description = "Parses a datetime string in one format and re-writes it in another.

Run with no input to see the relevant format string examples."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Built in formats", + "type": "populateOption", + "value": DATETIME_FORMATS, + "target": 1 + }, + { + "name": "Input format string", + "type": "binaryString", + "value": "DD/MM/YYYY HH:mm:ss" + }, + { + "name": "Input timezone", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + }, + { + "name": "Output format string", + "type": "binaryString", + "value": "dddd Do MMMM YYYY HH:mm:ss Z z" + }, + { + "name": "Output timezone", + "type": "option", + "value": ["UTC"].concat(moment.tz.names()) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const [inputFormat, inputTimezone, outputFormat, outputTimezone] = args; + let date; + + try { + date = moment.tz(input, inputFormat, inputTimezone); + if (!date || date.format() === "Invalid date") throw Error; + } catch (err) { + return `Invalid format.\n\n${FORMAT_EXAMPLES}`; + } + + return date.tz(outputTimezone).format(outputFormat); + } + +} + +export default TranslateDateTimeFormat; diff --git a/src/core/operations/TripleDESDecrypt.mjs b/src/core/operations/TripleDESDecrypt.mjs new file mode 100644 index 00000000..8f5a295d --- /dev/null +++ b/src/core/operations/TripleDESDecrypt.mjs @@ -0,0 +1,94 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * Triple DES Decrypt operation + */ +class TripleDESDecrypt extends Operation { + + /** + * TripleDESDecrypt constructor + */ + constructor() { + super(); + + this.name = "Triple DES Decrypt"; + this.module = "Ciphers"; + this.description = "Triple DES applies DES three times to each block to increase key size.

Key: Triple DES uses a key length of 24 bytes (192 bits).
DES uses a key length of 8 bytes (64 bits).

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + mode = args[2], + inputType = args[3], + outputType = args[4]; + + if (key.length !== 24) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +Triple DES uses a key length of 24 bytes (192 bits). +DES uses a key length of 8 bytes (64 bits).`); + } + + input = Utils.convertToByteString(input, inputType); + + const decipher = forge.cipher.createDecipher("3DES-" + mode, key); + decipher.start({iv: iv}); + decipher.update(forge.util.createBuffer(input)); + const result = decipher.finish(); + + if (result) { + return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); + } else { + throw new OperationError("Unable to decrypt input with these parameters."); + } + } + +} + +export default TripleDESDecrypt; diff --git a/src/core/operations/TripleDESEncrypt.mjs b/src/core/operations/TripleDESEncrypt.mjs new file mode 100644 index 00000000..2384ea3c --- /dev/null +++ b/src/core/operations/TripleDESEncrypt.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"; +import OperationError from "../errors/OperationError"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * Triple DES Encrypt operation + */ +class TripleDESEncrypt extends Operation { + + /** + * TripleDESEncrypt constructor + */ + constructor() { + super(); + + this.name = "Triple DES Encrypt"; + this.module = "Ciphers"; + this.description = "Triple DES applies DES three times to each block to increase key size.

Key: Triple DES uses a key length of 24 bytes (192 bits).
DES uses a key length of 8 bytes (64 bits).

You can generate a password-based key using one of the KDF operations.

IV: The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.

Padding: In CBC and ECB mode, PKCS#7 padding will be used."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Mode", + "type": "option", + "value": ["CBC", "CFB", "OFB", "CTR", "ECB"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + mode = args[2], + inputType = args[3], + outputType = args[4]; + + if (key.length !== 24) { + throw new OperationError(`Invalid key length: ${key.length} bytes + +Triple DES uses a key length of 24 bytes (192 bits). +DES uses a key length of 8 bytes (64 bits).`); + } + + input = Utils.convertToByteString(input, inputType); + + const cipher = forge.cipher.createCipher("3DES-" + mode, key); + cipher.start({iv: iv}); + cipher.update(forge.util.createBuffer(input)); + cipher.finish(); + + return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes(); + } + +} + +export default TripleDESEncrypt; diff --git a/src/core/operations/UNIXTimestampToWindowsFiletime.mjs b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs new file mode 100644 index 00000000..551b4273 --- /dev/null +++ b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError"; + +/** + * UNIX Timestamp to Windows Filetime operation + */ +class UNIXTimestampToWindowsFiletime extends Operation { + + /** + * UNIXTimestampToWindowsFiletime constructor + */ + constructor() { + super(); + + this.name = "UNIX Timestamp to Windows Filetime"; + this.module = "Default"; + this.description = "Converts a UNIX timestamp to a Windows Filetime value.

A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).

This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input units", + "type": "option", + "value": ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Decimal", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [units, format] = args; + + if (!input) return ""; + + input = new BigNumber(input); + + if (units === "Seconds (s)"){ + input = input.multipliedBy(new BigNumber("10000000")); + } else if (units === "Milliseconds (ms)") { + input = input.multipliedBy(new BigNumber("10000")); + } else if (units === "Microseconds (μs)") { + input = input.multiplyiedBy(new BigNumber("10")); + } else if (units === "Nanoseconds (ns)") { + input = input.dividedBy(new BigNumber("100")); + } else { + throw new OperationError("Unrecognised unit"); + } + + input = input.plus(new BigNumber("116444736000000000")); + + if (format === "Hex"){ + return input.toString(16); + } else { + return input.toFixed(); + } + } + +} + +export default UNIXTimestampToWindowsFiletime; diff --git a/src/core/operations/URL.js b/src/core/operations/URL.js deleted file mode 100755 index 2f30c952..00000000 --- a/src/core/operations/URL.js +++ /dev/null @@ -1,118 +0,0 @@ -/* globals unescape */ -import url from "url"; - - -/** - * URL operations. - * Namespace is appended with an underscore to prevent overwriting the global URL object. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const URL_ = { - - /** - * @constant - * @default - */ - ENCODE_ALL: false, - - /** - * URL Encode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTo: function(input, args) { - const encodeAll = args[0]; - return encodeAll ? URL_._encodeAllChars(input) : encodeURI(input); - }, - - - /** - * URL Decode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFrom: function(input, args) { - const data = input.replace(/\+/g, "%20"); - try { - return decodeURIComponent(data); - } catch (err) { - return unescape(data); - } - }, - - - /** - * Parse URI operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParse: function(input, args) { - const uri = url.parse(input, true); - - let output = ""; - - if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n"; - if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n"; - if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n"; - if (uri.port) output += "Port:\t\t" + uri.port + "\n"; - if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n"; - if (uri.query) { - let keys = Object.keys(uri.query), - padding = 0; - - keys.forEach(k => { - padding = (k.length > padding) ? k.length : padding; - }); - - output += "Arguments:\n"; - for (let key in uri.query) { - output += "\t" + key.padEnd(padding, " "); - if (uri.query[key].length) { - output += " = " + uri.query[key] + "\n"; - } else { - output += "\n"; - } - } - } - if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n"; - - return output; - }, - - - /** - * URL encodes additional special characters beyond the standard set. - * - * @private - * @param {string} str - * @returns {string} - */ - _encodeAllChars: function(str) { - //TODO Do this programatically - return encodeURIComponent(str) - .replace(/!/g, "%21") - .replace(/#/g, "%23") - .replace(/'/g, "%27") - .replace(/\(/g, "%28") - .replace(/\)/g, "%29") - .replace(/\*/g, "%2A") - .replace(/-/g, "%2D") - .replace(/\./g, "%2E") - .replace(/_/g, "%5F") - .replace(/~/g, "%7E"); - }, - -}; - -export default URL_; diff --git a/src/core/operations/URLDecode.mjs b/src/core/operations/URLDecode.mjs new file mode 100644 index 00000000..552dfe84 --- /dev/null +++ b/src/core/operations/URLDecode.mjs @@ -0,0 +1,51 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * URL Decode operation + */ +class URLDecode extends Operation { + + /** + * URLDecode constructor + */ + constructor() { + super(); + + this.name = "URL Decode"; + this.module = "URL"; + this.description = "Converts URI/URL percent-encoded characters back to their raw values.

e.g. %3d becomes ="; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + this.patterns = [ + { + match: ".*(?:%[\\da-f]{2}.*){4}", + flags: "i", + args: [] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = input.replace(/\+/g, "%20"); + try { + return decodeURIComponent(data); + } catch (err) { + return unescape(data); + } + } + +} + +export default URLDecode; diff --git a/src/core/operations/URLEncode.mjs b/src/core/operations/URLEncode.mjs new file mode 100644 index 00000000..23fd8ec9 --- /dev/null +++ b/src/core/operations/URLEncode.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * URL Encode operation + */ +class URLEncode extends Operation { + + /** + * URLEncode constructor + */ + constructor() { + super(); + + this.name = "URL Encode"; + this.module = "URL"; + this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.

e.g. = becomes %3d"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Encode all special chars", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const encodeAll = args[0]; + return encodeAll ? this.encodeAllChars(input) : encodeURI(input); + } + + /** + * Encode characters in URL outside of encodeURI() function spec + * + * @param {string} str + * @returns {string} + */ + encodeAllChars (str) { + // TODO Do this programatically + return encodeURIComponent(str) + .replace(/!/g, "%21") + .replace(/#/g, "%23") + .replace(/'/g, "%27") + .replace(/\(/g, "%28") + .replace(/\)/g, "%29") + .replace(/\*/g, "%2A") + .replace(/-/g, "%2D") + .replace(/\./g, "%2E") + .replace(/_/g, "%5F") + .replace(/~/g, "%7E"); + } + +} + + +export default URLEncode; diff --git a/src/core/operations/UUID.js b/src/core/operations/UUID.js deleted file mode 100755 index 7b485579..00000000 --- a/src/core/operations/UUID.js +++ /dev/null @@ -1,36 +0,0 @@ -import crypto from "crypto"; - - -/** - * UUID operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const UUID = { - - /** - * Generate UUID operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runGenerateV4: function(input, args) { - const buf = new Uint32Array(4).map(() => { - return crypto.randomBytes(4).readUInt32BE(0, true); - }); - let i = 0; - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - let r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, - v = c === "x" ? r : (r & 0x3 | 0x8); - i++; - return v.toString(16); - }); - } -}; - -export default UUID; diff --git a/src/core/operations/UnescapeString.mjs b/src/core/operations/UnescapeString.mjs new file mode 100644 index 00000000..0500eecd --- /dev/null +++ b/src/core/operations/UnescapeString.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Unescape string operation + */ +class UnescapeString extends Operation { + + /** + * UnescapeString constructor + */ + constructor() { + super(); + + this.name = "Unescape string"; + this.module = "Default"; + this.description = "Unescapes characters in a string that have been escaped. For example, Don\\'t stop me now becomes Don't stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
  • \\unnnn (Unicode character)
  • \\u{nnnnnn} (Unicode code point)
"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return Utils.parseEscapedChars(input); + } + +} + +export default UnescapeString; diff --git a/src/core/operations/UnescapeUnicodeCharacters.mjs b/src/core/operations/UnescapeUnicodeCharacters.mjs new file mode 100644 index 00000000..ab8af2a8 --- /dev/null +++ b/src/core/operations/UnescapeUnicodeCharacters.mjs @@ -0,0 +1,75 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Unescape Unicode Characters operation + */ +class UnescapeUnicodeCharacters extends Operation { + + /** + * UnescapeUnicodeCharacters constructor + */ + constructor() { + super(); + + this.name = "Unescape Unicode Characters"; + this.module = "Default"; + this.description = "Converts unicode-escaped character notation back into raw characters.

Supports the prefixes:
  • \\u
  • %u
  • U+
e.g. \\u03c3\\u03bf\\u03c5 becomes σου"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Prefix", + "type": "option", + "value": ["\\u", "%u", "U+"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const prefix = prefixToRegex[args[0]], + regex = new RegExp(prefix+"([a-f\\d]{4})", "ig"); + let output = "", + m, + i = 0; + + while ((m = regex.exec(input))) { + // Add up to match + output += input.slice(i, m.index); + i = m.index; + + // Add match + output += Utils.chr(parseInt(m[1], 16)); + + i = regex.lastIndex; + } + + // Add all after final match + output += input.slice(i, input.length); + + return output; + } + +} + +/** + * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex. + */ +const prefixToRegex = { + "\\u": "\\\\u", + "%u": "%u", + "U+": "U\\+" +}; + +export default UnescapeUnicodeCharacters; diff --git a/src/core/operations/Unicode.js b/src/core/operations/Unicode.js deleted file mode 100755 index 104ef07e..00000000 --- a/src/core/operations/Unicode.js +++ /dev/null @@ -1,101 +0,0 @@ -import Utils from "../Utils.js"; - - -/** - * Unicode operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Unicode = { - - /** - * @constant - * @default - */ - PREFIXES: ["\\u", "%u", "U+"], - - /** - * Unescape Unicode Characters operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUnescape: function(input, args) { - let prefix = Unicode._prefixToRegex[args[0]], - regex = new RegExp(prefix+"([a-f\\d]{4})", "ig"), - output = "", - m, - i = 0; - - while ((m = regex.exec(input))) { - // Add up to match - output += input.slice(i, m.index); - i = m.index; - - // Add match - output += Utils.chr(parseInt(m[1], 16)); - - i = regex.lastIndex; - } - - // Add all after final match - output += input.slice(i, input.length); - - return output; - }, - - - /** - * 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. - * - * @private - * @constant - */ - _prefixToRegex: { - "\\u": "\\\\u", - "%u": "%u", - "U+": "U\\+" - }, - -}; - -export default Unicode; diff --git a/src/core/operations/Unique.mjs b/src/core/operations/Unique.mjs new file mode 100644 index 00000000..6848968b --- /dev/null +++ b/src/core/operations/Unique.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; + +/** + * Unique operation + */ +class Unique extends Operation { + + /** + * Unique constructor + */ + constructor() { + super(); + + this.name = "Unique"; + this.module = "Default"; + this.description = "Removes duplicate strings from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Delimiter", + "type": "option", + "value": INPUT_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]); + return input.split(delim).unique().join(delim); + } + +} + +export default Unique; diff --git a/src/core/operations/Untar.mjs b/src/core/operations/Untar.mjs new file mode 100644 index 00000000..6bca05d0 --- /dev/null +++ b/src/core/operations/Untar.mjs @@ -0,0 +1,138 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Untar operation + */ +class Untar extends Operation { + + /** + * Untar constructor + */ + constructor() { + super(); + + this.name = "Untar"; + this.module = "Compression"; + this.description = "Unpacks a tarball and displays it per file."; + this.inputType = "byteArray"; + this.outputType = "List"; + this.presentType = "html"; + this.args = []; + this.patterns = [ + { + "match": "^.{257}\\x75\\x73\\x74\\x61\\x72", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {List} + */ + run(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; + }; + + const 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 + let endPosition = stream.position + file.size; + if (file.size % 512 !== 0) { + endPosition += 512 - (file.size % 512); + } + + file.bytes = stream.getBytes(file.size); + files.push(new File([new Uint8Array(file.bytes)], file.fileName)); + stream.position = endPosition; + } else if (file.type === "5") { + // Directory + files.push(new File([new Uint8Array(file.bytes)], file.fileName)); + } else { + // Symlink or empty bytes + } + } + + return files; + } + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } + +} + +export default Untar; diff --git a/src/core/operations/Unzip.mjs b/src/core/operations/Unzip.mjs new file mode 100644 index 00000000..8e6bb633 --- /dev/null +++ b/src/core/operations/Unzip.mjs @@ -0,0 +1,82 @@ +/** + * @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 = "ArrayBuffer"; + this.outputType = "List"; + this.presentType = "html"; + this.args = [ + { + name: "Password", + type: "binaryString", + value: "" + }, + { + name: "Verify result", + type: "boolean", + value: false + } + ]; + this.patterns = [ + { + match: "^\\x50\\x4b(?:\\x03|\\x05|\\x07)(?:\\x04|\\x06|\\x08)", + flags: "", + args: ["", false] + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {File[]} + */ + run(input, args) { + const options = { + password: Utils.strToByteArray(args[0]), + verify: args[1] + }, + unzip = new Zlib.Unzip(new Uint8Array(input), options), + filenames = unzip.getFilenames(); + + return filenames.map(fileName => { + const bytes = unzip.decompress(fileName); + return new File([bytes], fileName); + }); + } + + /** + * Displays the files in HTML for web apps. + * + * @param {File[]} files + * @returns {html} + */ + async present(files) { + return await Utils.displayFilesAsHTML(files); + } + +} + +export default Unzip; diff --git a/src/core/operations/VigenèreDecode.mjs b/src/core/operations/VigenèreDecode.mjs new file mode 100644 index 00000000..b1512a3e --- /dev/null +++ b/src/core/operations/VigenèreDecode.mjs @@ -0,0 +1,101 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +/** + * Vigenère Decode operation + */ +class VigenèreDecode extends Operation { + + /** + * VigenèreDecode constructor + */ + constructor() { + super(); + + this.name = "Vigenère Decode"; + this.module = "Ciphers"; + this.description = "The Vigenere cipher is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword. It is a simple form of polyalphabetic substitution."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = "abcdefghijklmnopqrstuvwxyz", + key = args[0].toLowerCase(); + let output = "", + fail = 0, + keyIndex, + msgIndex, + chr; + + if (!key) throw new OperationError("No key entered"); + if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("The key must consist only of letters"); + + for (let i = 0; i < input.length; i++) { + if (alphabet.indexOf(input[i]) >= 0) { + chr = key[(i - fail) % key.length]; + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i]); + // Subtract indexes from each other, add 26 just in case the value is negative, + // modulo to remove if neccessary + output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26]; + } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { + chr = key[(i - fail) % key.length].toLowerCase(); + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i].toLowerCase()); + output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase(); + } else { + output += input[i]; + fail++; + } + } + + return output; + } + + /** + * Highlight Vigenère Decode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Vigenère Decode in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default VigenèreDecode; diff --git a/src/core/operations/VigenèreEncode.mjs b/src/core/operations/VigenèreEncode.mjs new file mode 100644 index 00000000..9172ed06 --- /dev/null +++ b/src/core/operations/VigenèreEncode.mjs @@ -0,0 +1,106 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Vigenère Encode operation + */ +class VigenèreEncode extends Operation { + + /** + * VigenèreEncode constructor + */ + constructor() { + super(); + + this.name = "Vigenère Encode"; + this.module = "Ciphers"; + this.description = "The Vigenere cipher is a method of encrypting alphabetic text by using a series of different Caesar ciphers based on the letters of a keyword. It is a simple form of polyalphabetic substitution."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const alphabet = "abcdefghijklmnopqrstuvwxyz", + key = args[0].toLowerCase(); + let output = "", + fail = 0, + keyIndex, + msgIndex, + chr; + + if (!key) throw new OperationError("No key entered"); + if (!/^[a-zA-Z]+$/.test(key)) throw new OperationError("The key must consist only of letters"); + + for (let i = 0; i < input.length; i++) { + if (alphabet.indexOf(input[i]) >= 0) { + // Get the corresponding character of key for the current letter, accounting + // for chars not in alphabet + chr = key[(i - fail) % key.length]; + // Get the location in the vigenere square of the key char + keyIndex = alphabet.indexOf(chr); + // Get the location in the vigenere square of the message char + msgIndex = alphabet.indexOf(input[i]); + // Get the encoded letter by finding the sum of indexes modulo 26 and finding + // the letter corresponding to that + output += alphabet[(keyIndex + msgIndex) % 26]; + } else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) { + chr = key[(i - fail) % key.length].toLowerCase(); + keyIndex = alphabet.indexOf(chr); + msgIndex = alphabet.indexOf(input[i].toLowerCase()); + output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase(); + } else { + output += input[i]; + fail++; + } + } + + return output; + } + + /** + * Highlight Vigenère Encode + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Vigenère Encode in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default VigenèreEncode; diff --git a/src/core/operations/Whirlpool.mjs b/src/core/operations/Whirlpool.mjs new file mode 100644 index 00000000..1d32f244 --- /dev/null +++ b/src/core/operations/Whirlpool.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import {runHash} from "../lib/Hash"; + +/** + * Whirlpool operation + */ +class Whirlpool extends Operation { + + /** + * Whirlpool constructor + */ + constructor() { + super(); + + this.name = "Whirlpool"; + this.module = "Hashing"; + this.description = "Whirlpool is a cryptographic hash function designed by Vincent Rijmen (co-creator of AES) and Paulo S. L. M. Barreto, who first described it in 2000.

Several variants exist:
  • Whirlpool-0 is the original version released in 2000.
  • Whirlpool-T is the first revision, released in 2001, improving the generation of the s-box.
  • Wirlpool is the latest revision, released in 2003, fixing a flaw in the difusion matrix.
"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Variant", + "type": "option", + "value": ["Whirlpool", "Whirlpool-T", "Whirlpool-0"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const variant = args[0].toLowerCase(); + return runHash(variant, input); + } + +} + +export default Whirlpool; diff --git a/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs new file mode 100644 index 00000000..b3d66476 --- /dev/null +++ b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs @@ -0,0 +1,76 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import BigNumber from "bignumber.js"; +import OperationError from "../errors/OperationError"; + +/** + * Windows Filetime to UNIX Timestamp operation + */ +class WindowsFiletimeToUNIXTimestamp extends Operation { + + /** + * WindowsFiletimeToUNIXTimestamp constructor + */ + constructor() { + super(); + + this.name = "Windows Filetime to UNIX Timestamp"; + this.module = "Default"; + this.description = "Converts a Windows Filetime value to a UNIX timestamp.

A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).

This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Output units", + "type": "option", + "value": ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"] + }, + { + "name": "Input format", + "type": "option", + "value": ["Decimal", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [units, format] = args; + + if (!input) return ""; + + if (format === "Hex") { + input = new BigNumber(input, 16); + } else { + input = new BigNumber(input); + } + + input = input.minus(new BigNumber("116444736000000000")); + + if (units === "Seconds (s)"){ + input = input.dividedBy(new BigNumber("10000000")); + } else if (units === "Milliseconds (ms)") { + input = input.dividedBy(new BigNumber("10000")); + } else if (units === "Microseconds (μs)") { + input = input.dividedBy(new BigNumber("10")); + } else if (units === "Nanoseconds (ns)") { + input = input.multipliedBy(new BigNumber("100")); + } else { + throw new OperationError("Unrecognised unit"); + } + + return input.toFixed(); + } + +} + +export default WindowsFiletimeToUNIXTimestamp; diff --git a/src/core/operations/XKCD.js b/src/core/operations/XKCD.js deleted file mode 100755 index 4144d88f..00000000 --- a/src/core/operations/XKCD.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * XKCD operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2018 - * @license Apache-2.0 - * - * @namespace - */ -const XKCD = { - - /** - * XKCD Random Number operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {number} - */ - runRandomNumber: function(input, args) { - return 4; // chosen by fair dice roll. - // guaranteed to be random. - }, - -}; - -export default XKCD; diff --git a/src/core/operations/XKCDRandomNumber.mjs b/src/core/operations/XKCDRandomNumber.mjs new file mode 100644 index 00000000..a9481fbf --- /dev/null +++ b/src/core/operations/XKCDRandomNumber.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * XKCD Random Number operation + */ +class XKCDRandomNumber extends Operation { + + /** + * XKCDRandomNumber constructor + */ + constructor() { + super(); + + this.name = "XKCD Random Number"; + this.module = "Default"; + this.description = "RFC 1149.5 specifies 4 as the standard IEEE-vetted random number.

XKCD #221"; + this.inputType = "string"; + this.outputType = "number"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + return 4; // chosen by fair dice roll. + // guaranteed to be random. + } + +} + +export default XKCDRandomNumber; diff --git a/src/core/operations/XMLBeautify.mjs b/src/core/operations/XMLBeautify.mjs new file mode 100644 index 00000000..4c059411 --- /dev/null +++ b/src/core/operations/XMLBeautify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * XML Beautify operation + */ +class XMLBeautify extends Operation { + + /** + * XMLBeautify constructor + */ + constructor() { + super(); + + this.name = "XML Beautify"; + this.module = "Code"; + this.description = "Indents and prettifies eXtensible Markup Language (XML) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Indent string", + "type": "binaryShortString", + "value": "\\t" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const indentStr = args[0]; + return vkbeautify.xml(input, indentStr); + } + +} + +export default XMLBeautify; diff --git a/src/core/operations/XMLMinify.mjs b/src/core/operations/XMLMinify.mjs new file mode 100644 index 00000000..9c4fb2f6 --- /dev/null +++ b/src/core/operations/XMLMinify.mjs @@ -0,0 +1,47 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import vkbeautify from "vkbeautify"; +import Operation from "../Operation"; + +/** + * XML Minify operation + */ +class XMLMinify extends Operation { + + /** + * XMLMinify constructor + */ + constructor() { + super(); + + this.name = "XML Minify"; + this.module = "Code"; + this.description = "Compresses eXtensible Markup Language (XML) code."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Preserve comments", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const preserveComments = args[0]; + return vkbeautify.xmlmin(input, preserveComments); + } + +} + +export default XMLMinify; diff --git a/src/core/operations/XOR.mjs b/src/core/operations/XOR.mjs new file mode 100644 index 00000000..ae35eab7 --- /dev/null +++ b/src/core/operations/XOR.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import { bitOp, xor } from "../lib/BitwiseOp"; + +/** + * XOR operation + */ +class XOR extends Operation { + + /** + * XOR constructor + */ + constructor() { + super(); + + this.name = "XOR"; + this.module = "Default"; + this.description = "XOR the input with the given key.
e.g. fe023da5

Options
Null preserving: If the current byte is 0x00 or the same as the key, skip it.

Scheme:
  • Standard - key is unchanged after each round
  • Input differential - key is set to the value of the previous unprocessed byte
  • Output differential - key is set to the value of the previous processed byte
"; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "Base64", "UTF8", "Latin1"] + }, + { + "name": "Scheme", + "type": "option", + "value": ["Standard", "Input differential", "Output differential"] + }, + { + "name": "Null preserving", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string || "", args[0].option), + [, scheme, nullPreserving] = args; + + return bitOp(input, key, xor, nullPreserving, scheme); + } + + /** + * Highlight XOR + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight XOR in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default XOR; diff --git a/src/core/operations/XORBruteForce.mjs b/src/core/operations/XORBruteForce.mjs new file mode 100644 index 00000000..131d5a63 --- /dev/null +++ b/src/core/operations/XORBruteForce.mjs @@ -0,0 +1,140 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import Utils from "../Utils"; +import { bitOp, xor } from "../lib/BitwiseOp"; +import { toHex } from "../lib/Hex"; + +/** + * XOR Brute Force operation + */ +class XORBruteForce extends Operation { + + /** + * XORBruteForce constructor + */ + constructor() { + super(); + + this.name = "XOR Brute Force"; + this.module = "Default"; + this.description = "Enumerate all possible XOR solutions. Current maximum key length is 2 due to browser performance.

Optionally enter a string that you expect to find in the plaintext to filter results (crib)."; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Key length", + "type": "number", + "value": 1 + }, + { + "name": "Sample length", + "type": "number", + "value": 100 + }, + { + "name": "Sample offset", + "type": "number", + "value": 0 + }, + { + "name": "Scheme", + "type": "option", + "value": ["Standard", "Input differential", "Output differential"] + }, + { + "name": "Null preserving", + "type": "boolean", + "value": false + }, + { + "name": "Print key", + "type": "boolean", + "value": true + }, + { + "name": "Output as hex", + "type": "boolean", + "value": false + }, + { + "name": "Crib (known plaintext string)", + "type": "binaryString", + "value": "" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [ + keyLength, + sampleLength, + sampleOffset, + scheme, + nullPreserving, + printKey, + outputHex, + rawCrib + ] = args, + crib = rawCrib.toLowerCase(), + output = []; + let result, + resultUtf8, + record = ""; + + input = input.slice(sampleOffset, sampleOffset + sampleLength); + + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage("Calculating " + Math.pow(256, keyLength) + " values..."); + + /** + * Converts an integer to an array of bytes expressing that number. + * + * @param {number} int + * @param {number} len - Length of the resulting array + * @returns {array} + */ + const intToByteArray = (int, len) => { + const res = Array(len).fill(0); + for (let i = len - 1; i >= 0; i--) { + res[i] = int & 0xff; + int = int >>> 8; + } + return res; + }; + + for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) { + if (key % 10000 === 0 && ENVIRONMENT_IS_WORKER()) { + self.sendStatusMessage("Calculating " + l + " values... " + Math.floor(key / l * 100) + "%"); + } + + result = bitOp(input, intToByteArray(key, keyLength), xor, nullPreserving, scheme); + resultUtf8 = Utils.byteArrayToUtf8(result); + record = ""; + + if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue; + if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; + if (outputHex) { + record += toHex(result); + } else { + record += Utils.printable(resultUtf8, false); + } + + output.push(record); + } + + return output.join("\n"); + } + +} + +export default XORBruteForce; diff --git a/src/core/operations/XPathExpression.mjs b/src/core/operations/XPathExpression.mjs new file mode 100644 index 00000000..7b342004 --- /dev/null +++ b/src/core/operations/XPathExpression.mjs @@ -0,0 +1,73 @@ +/** + * @author Mikescher (https://github.com/Mikescher | https://mikescher.com) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import xmldom from "xmldom"; +import xpath from "xpath"; + +/** + * XPath expression operation + */ +class XPathExpression extends Operation { + + /** + * XPathExpression constructor + */ + constructor() { + super(); + + this.name = "XPath expression"; + this.module = "Code"; + this.description = "Extract information from an XML document with an XPath query"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "XPath", + "type": "string", + "value": "" + }, + { + "name": "Result delimiter", + "type": "binaryShortString", + "value": "\\n" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [query, delimiter] = args; + + let doc; + try { + doc = new xmldom.DOMParser().parseFromString(input, "application/xml"); + } catch (err) { + throw new OperationError("Invalid input XML."); + } + + let nodes; + try { + nodes = xpath.select(query, doc); + } catch (err) { + throw new OperationError(`Invalid XPath. Details:\n${err.message}.`); + } + + const nodeToString = function(node) { + return node.toString(); + }; + + return nodes.map(nodeToString).join(delimiter); + } + +} + +export default XPathExpression; diff --git a/src/core/operations/Zip.mjs b/src/core/operations/Zip.mjs new file mode 100644 index 00000000..aaa75dbb --- /dev/null +++ b/src/core/operations/Zip.mjs @@ -0,0 +1,102 @@ +/** + * @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 = "ArrayBuffer"; + this.outputType = "File"; + 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 {ArrayBuffer} input + * @param {Object[]} args + * @returns {File} + */ + run(input, args) { + const filename = args[0], + password = Utils.strToByteArray(args[2]), + options = { + filename: Utils.strToByteArray(filename), + 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(new Uint8Array(input), options); + return new File([zip.compress()], filename); + } + +} + +export default Zip; diff --git a/src/core/operations/ZlibDeflate.mjs b/src/core/operations/ZlibDeflate.mjs new file mode 100644 index 00000000..40f09dc5 --- /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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Compression type", + type: "option", + value: COMPRESSION_TYPE + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const deflate = new Zlib.Deflate(new Uint8Array(input), { + compressionType: ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]] + }); + return new Uint8Array(deflate.compress()).buffer; + } + +} + +export default ZlibDeflate; diff --git a/src/core/operations/ZlibInflate.mjs b/src/core/operations/ZlibInflate.mjs new file mode 100644 index 00000000..36f841d5 --- /dev/null +++ b/src/core/operations/ZlibInflate.mjs @@ -0,0 +1,88 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +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 = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + 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 + } + ]; + this.patterns = [ + { + match: "^\\x78(\\x01|\\x9c|\\xda|\\x5e)", + flags: "", + args: [0, 0, "Adaptive", false, false] + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inflate = new Zlib.Inflate(new Uint8Array(input), { + index: args[0], + bufferSize: args[1], + bufferType: ZLIB_BUFFER_TYPE_LOOKUP[args[2]], + resize: args[3], + verify: args[4] + }); + return new Uint8Array(inflate.decompress()).buffer; + } + +} + +export default ZlibInflate; diff --git a/src/core/vendor/Blowfish.mjs b/src/core/vendor/Blowfish.mjs new file mode 100644 index 00000000..f60fb90f --- /dev/null +++ b/src/core/vendor/Blowfish.mjs @@ -0,0 +1,652 @@ +/** + Blowfish.js from Dojo Toolkit 1.8.1 (https://github.com/dojo/dojox/tree/1.8/encoding) + Extracted by Sladex (xslade@gmail.com) + Shoehorned into working with mjs for CyberChef by Matt C (matt@artemisbot.uk) + + @license BSD + ======================================================================== + The "New" BSD License: + ********************** + + Copyright (c) 2005-2016, The Dojo Foundation + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Dojo Foundation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +let crypto = {}; + + + +/* dojo-release-1.8.1/dojox/encoding/crypto/_base.js.uncompressed.js */ + +crypto.cipherModes = { + // summary: + // Enumeration for various cipher modes. + ECB:0, CBC:1, PCBC:2, CFB:3, OFB:4, CTR:5 +}; +crypto.outputTypes = { + // summary: + // Enumeration for input and output encodings. + Base64:0, Hex:1, String:2, Raw:3 +}; + + + +/* dojo-release-1.8.1/dojox/encoding/base64.js.uncompressed.js */ + +var base64 = {}; +var p="="; +var tab="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +base64.encode=function(/* byte[] */ba){ + // summary: + // Encode an array of bytes as a base64-encoded string + var s=[], l=ba.length; + var rm=l%3; + var x=l-rm; + for (var i=0; i>>18)&0x3f)); + s.push(tab.charAt((t>>>12)&0x3f)); + s.push(tab.charAt((t>>>6)&0x3f)); + s.push(tab.charAt(t&0x3f)); + } + // deal with trailers, based on patch from Peter Wood. + switch(rm){ + case 2:{ + var t=ba[i++]<<16|ba[i++]<<8; + s.push(tab.charAt((t>>>18)&0x3f)); + s.push(tab.charAt((t>>>12)&0x3f)); + s.push(tab.charAt((t>>>6)&0x3f)); + s.push(p); + break; + } + case 1:{ + var t=ba[i++]<<16; + s.push(tab.charAt((t>>>18)&0x3f)); + s.push(tab.charAt((t>>>12)&0x3f)); + s.push(p); + s.push(p); + break; + } + } + return s.join(""); // string +}; + +base64.decode=function(/* string */str){ + // summary: + // Convert a base64-encoded string to an array of bytes + var s=str.split(""), out=[]; + var l=s.length; + while(s[--l]==p){ } // strip off trailing padding + for (var i=0; i>>16)&0xff); + out.push((t>>>8)&0xff); + out.push(t&0xff); + } + // strip off any null bytes + while(out[out.length-1]==0){ out.pop(); } + return out; // byte[] +}; + + + +/* dojo-release-1.8.1/dojo/_base/lang.js.uncompressed.js */ + +var lang = {}; +lang.isString = function(it){ + // summary: + // Return true if it is a String + // it: anything + // Item to test. + return (typeof it == "string" || it instanceof String); // Boolean +}; + + + +/* dojo-release-1.8.1/dojo/_base/array.js.uncompressed.js */ + +var arrayUtil = {}; +arrayUtil.map = function(arr, callback, thisObject, Ctr){ + // summary: + // applies callback to each element of arr and returns + // an Array with the results + // arr: Array|String + // the array to iterate on. If a string, operates on + // individual characters. + // callback: Function|String + // a function is invoked with three arguments, (item, index, + // array), and returns a value + // thisObject: Object? + // may be used to scope the call to callback + // returns: Array + // description: + // This function corresponds to the JavaScript 1.6 Array.map() method, with one difference: when + // run over sparse arrays, this implementation passes the "holes" in the sparse array to + // the callback function with a value of undefined. JavaScript 1.6's map skips the holes in the sparse array. + // For more details, see: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map + // example: + // | // returns [2, 3, 4, 5] + // | array.map([1, 2, 3, 4], function(item){ return item+1 }); + + // TODO: why do we have a non-standard signature here? do we need "Ctr"? + var i = 0, l = arr && arr.length || 0, out = new (Ctr || Array)(l); + if(l && typeof arr == "string") arr = arr.split(""); + if(typeof callback == "string") callback = cache[callback] || buildFn(callback); + if(thisObject){ + for(; i < l; ++i){ + out[i] = callback.call(thisObject, arr[i], i, arr); + } + }else{ + for(; i < l; ++i){ + out[i] = callback(arr[i], i, arr); + } + } + return out; // Array +}; + + + +/* dojo-release-1.8.1/dojox/encoding/crypto/Blowfish.js.uncompressed.js */ + +/* Blowfish + * Created based on the C# implementation by Marcus Hahn (http://www.hotpixel.net/) + * Unsigned math based on Paul Johnstone and Peter Wood patches. + * 2005-12-08 + */ +crypto.Blowfish = new function(){ + // summary: + // Object for doing Blowfish encryption/decryption. + var POW2=Math.pow(2,2); + var POW3=Math.pow(2,3); + var POW4=Math.pow(2,4); + var POW8=Math.pow(2,8); + var POW16=Math.pow(2,16); + var POW24=Math.pow(2,24); + var iv=null; // CBC mode initialization vector + var boxes={ + p:[ + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + ], + s0:[ + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + ], + s1:[ + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + ], + s2:[ + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + ], + s3:[ + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + ] + } +//////////////////////////////////////////////////////////////////////////// +// fixes based on patch submitted by Peter Wood (#5791) + function add(x,y){ + return (((x>>0x10)+(y>>0x10)+(((x&0xffff)+(y&0xffff))>>0x10))<<0x10)|(((x&0xffff)+(y&0xffff))&0xffff); + } + function xor(x,y){ + return (((x>>0x10)^(y>>0x10))<<0x10)|(((x&0xffff)^(y&0xffff))&0xffff); + } + + function $(v, box){ + var d=box.s3[v&0xff]; v>>=8; + var c=box.s2[v&0xff]; v>>=8; + var b=box.s1[v&0xff]; v>>=8; + var a=box.s0[v&0xff]; + + var r = (((a>>0x10)+(b>>0x10)+(((a&0xffff)+(b&0xffff))>>0x10))<<0x10)|(((a&0xffff)+(b&0xffff))&0xffff); + r = (((r>>0x10)^(c>>0x10))<<0x10)|(((r&0xffff)^(c&0xffff))&0xffff); + return (((r>>0x10)+(d>>0x10)+(((r&0xffff)+(d&0xffff))>>0x10))<<0x10)|(((r&0xffff)+(d&0xffff))&0xffff); + } +//////////////////////////////////////////////////////////////////////////// + function eb(o, box){ + // TODO: see if this can't be made more efficient + var l=o.left; + var r=o.right; + l=xor(l,box.p[0]); + r=xor(r,xor($(l,box),box.p[1])); + l=xor(l,xor($(r,box),box.p[2])); + r=xor(r,xor($(l,box),box.p[3])); + l=xor(l,xor($(r,box),box.p[4])); + r=xor(r,xor($(l,box),box.p[5])); + l=xor(l,xor($(r,box),box.p[6])); + r=xor(r,xor($(l,box),box.p[7])); + l=xor(l,xor($(r,box),box.p[8])); + r=xor(r,xor($(l,box),box.p[9])); + l=xor(l,xor($(r,box),box.p[10])); + r=xor(r,xor($(l,box),box.p[11])); + l=xor(l,xor($(r,box),box.p[12])); + r=xor(r,xor($(l,box),box.p[13])); + l=xor(l,xor($(r,box),box.p[14])); + r=xor(r,xor($(l,box),box.p[15])); + l=xor(l,xor($(r,box),box.p[16])); + o.right=l; + o.left=xor(r,box.p[17]); + } + + function db(o, box){ + var l=o.left; + var r=o.right; + l=xor(l,box.p[17]); + r=xor(r,xor($(l,box),box.p[16])); + l=xor(l,xor($(r,box),box.p[15])); + r=xor(r,xor($(l,box),box.p[14])); + l=xor(l,xor($(r,box),box.p[13])); + r=xor(r,xor($(l,box),box.p[12])); + l=xor(l,xor($(r,box),box.p[11])); + r=xor(r,xor($(l,box),box.p[10])); + l=xor(l,xor($(r,box),box.p[9])); + r=xor(r,xor($(l,box),box.p[8])); + l=xor(l,xor($(r,box),box.p[7])); + r=xor(r,xor($(l,box),box.p[6])); + l=xor(l,xor($(r,box),box.p[5])); + r=xor(r,xor($(l,box),box.p[4])); + l=xor(l,xor($(r,box),box.p[3])); + r=xor(r,xor($(l,box),box.p[2])); + l=xor(l,xor($(r,box),box.p[1])); + o.right=l; + o.left=xor(r,box.p[0]); + } + + // Note that we aren't caching contexts here; it might take a little longer + // but we should be more secure this way. + function init(key){ + var k=key; + if(lang.isString(k)){ + k = arrayUtil.map(k.split(""), function(item){ + return item.charCodeAt(0) & 0xff; + }); + } + + // init the boxes + var pos=0, data=0, res={ left:0, right:0 }, i, j, l; + var box = { + p: arrayUtil.map(boxes.p.slice(0), function(item){ + var l=k.length, j; + for(j=0; j<4; j++){ data=(data*POW8)|k[pos++ % l]; } + return (((item>>0x10)^(data>>0x10))<<0x10)|(((item&0xffff)^(data&0xffff))&0xffff); + }), + s0:boxes.s0.slice(0), + s1:boxes.s1.slice(0), + s2:boxes.s2.slice(0), + s3:boxes.s3.slice(0) + }; + + // encrypt p and the s boxes + for(i=0, l=box.p.length; i> 3, pos=0, o={}, isCBC=(mode==crypto.cipherModes.CBC); + var vector={left:iv.left||null, right:iv.right||null}; + for(var i=0; i>0x10)^(vector.left>>0x10))<<0x10)|(((o.left&0xffff)^(vector.left&0xffff))&0xffff); + o.right=(((o.right>>0x10)^(vector.right>>0x10))<<0x10)|(((o.right&0xffff)^(vector.right&0xffff))&0xffff); + } + + eb(o, bx); // encrypt the block + + if(isCBC){ + vector.left=o.left; + vector.right=o.right; + } + + cipher.push((o.left>>24)&0xff); + cipher.push((o.left>>16)&0xff); + cipher.push((o.left>>8)&0xff); + cipher.push(o.left&0xff); + cipher.push((o.right>>24)&0xff); + cipher.push((o.right>>16)&0xff); + cipher.push((o.right>>8)&0xff); + cipher.push(o.right&0xff); + pos+=8; + } + + switch(out){ + case crypto.outputTypes.Hex:{ + return arrayUtil.map(cipher, function(item){ + return (item<=0xf?'0':'')+item.toString(16); + }).join(""); // string + } + case crypto.outputTypes.String:{ + return cipher.join(""); // string + } + case crypto.outputTypes.Raw:{ + return cipher; // array + } + default:{ + return base64.encode(cipher); // string + } + } + }; + + this.decrypt = function(/* string */ciphertext, /* string */key, /* object? */ao){ + // summary: + // decrypts ciphertext using key; allows specification of how ciphertext is encoded via ao. + var ip=crypto.outputTypes.Base64; + var mode=crypto.cipherModes.ECB; + if (ao){ + if (ao.outputType) ip=ao.outputType; + if (ao.cipherMode) mode=ao.cipherMode; + } + var bx = init(key); + var pt=[]; + + var c=null; + switch(ip){ + case crypto.outputTypes.Hex:{ + c = []; + for(var i=0, l=ciphertext.length-1; i> 3, pos=0, o={}, isCBC=(mode==crypto.cipherModes.CBC); + var vector={left:iv.left||null, right:iv.right||null}; + for(var i=0; i>0x10)^(vector.left>>0x10))<<0x10)|(((o.left&0xffff)^(vector.left&0xffff))&0xffff); + o.right=(((o.right>>0x10)^(vector.right>>0x10))<<0x10)|(((o.right&0xffff)^(vector.right&0xffff))&0xffff); + vector.left=left; + vector.right=right; + } + + pt.push((o.left>>24)&0xff); + pt.push((o.left>>16)&0xff); + pt.push((o.left>>8)&0xff); + pt.push(o.left&0xff); + pt.push((o.right>>24)&0xff); + pt.push((o.right>>16)&0xff); + pt.push((o.right>>8)&0xff); + pt.push(o.right&0xff); + pos+=8; + } + + // check for padding, and remove. + if(pt[pt.length-1]==pt[pt.length-2]||pt[pt.length-1]==0x01){ + var n=pt[pt.length-1]; + pt.splice(pt.length-n, n); + } + + // convert to string + return arrayUtil.map(pt, function(item){ + return String.fromCharCode(item); + }).join(""); // string + }; + + this.setIV("0000000000000000", crypto.outputTypes.Hex); +}(); + +export const Blowfish = crypto.Blowfish; diff --git a/src/core/lib/DisassembleX86-64.js b/src/core/vendor/DisassembleX86-64.mjs similarity index 99% rename from src/core/lib/DisassembleX86-64.js rename to src/core/vendor/DisassembleX86-64.mjs index e320c6c2..f0d30511 100644 --- a/src/core/lib/DisassembleX86-64.js +++ b/src/core/vendor/DisassembleX86-64.mjs @@ -3316,7 +3316,7 @@ If input "type" is set 5 it will adjust the mnemonic array to decode Centaur ins If input "type" is set 6 it will adjust the mnemonic array to decode instruction for the X86/486 CPU which conflict with the vector unit instructions with UMOV. -------------------------------------------------------------------------------------------------------------------------*/ -function CompatibilityMode( type ) +export function CompatibilityMode( type ) { //Reset the changeable sections of the Mnemonics array, and operand encoding array. @@ -3515,7 +3515,7 @@ The function "GetPosition()" Gives back the current base address in it's proper If the hex input is invalid returns false. -------------------------------------------------------------------------------------------------------------------------*/ -function LoadBinCode( HexStr ) +export function LoadBinCode( HexStr ) { //Clear BinCode, and Reset Code Position in Bin Code array. @@ -3605,7 +3605,7 @@ segment, and offset address. Note that the Code Segment is used in 16 bit code. if set 36, or higher. Effects instruction location in memory when decoding a program. -------------------------------------------------------------------------------------------------------------------------*/ -function SetBasePosition( Address ) +export function SetBasePosition( Address ) { //Split the Segment:offset. @@ -5652,7 +5652,7 @@ function Reset() do an linear disassemble. -------------------------------------------------------------------------------------------------------------------------*/ -function LDisassemble() +export function LDisassemble() { var Instruction = ""; //Stores the Decoded instruction. var Out = ""; //The Disassemble output @@ -5709,13 +5709,13 @@ function LDisassemble() * The following code has been added to expose public methods for use in CyberChef */ -export default { - LoadBinCode: LoadBinCode, - LDisassemble: LDisassemble, - SetBasePosition: SetBasePosition, - CompatibilityMode: CompatibilityMode, - - setBitMode: val => { BitMode = val; }, - setShowInstructionHex: val => { ShowInstructionHex = val; }, - setShowInstructionPos: val => { ShowInstructionPos = val; }, +export function setBitMode (val) { + BitMode = val; }; +export function setShowInstructionHex (val) { + ShowInstructionHex = val; +}; +export function setShowInstructionPos (val) { + ShowInstructionPos = val; +}; + diff --git a/src/core/lib/bzip2.js b/src/core/vendor/bzip2.js similarity index 99% rename from src/core/lib/bzip2.js rename to src/core/vendor/bzip2.js index 03c8c97c..12dc3852 100755 --- a/src/core/lib/bzip2.js +++ b/src/core/vendor/bzip2.js @@ -261,3 +261,5 @@ bzip2.decompress = function(bits, size, len){ } return output; } + +module.exports = bzip2; 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.mjs similarity index 98% rename from src/core/lib/remove-exif.js rename to src/core/vendor/remove-exif.mjs index 5dbb5bb0..6c5c9e53 100644 --- a/src/core/lib/remove-exif.js +++ b/src/core/vendor/remove-exif.mjs @@ -18,10 +18,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import Utils from "../Utils.js"; +import Utils from "../Utils.mjs"; // Param jpeg should be a binaryArray -function removeEXIF(jpeg) { +export function removeEXIF(jpeg) { // Convert binaryArray to char string jpeg = Utils.byteArrayToChars(jpeg); if (jpeg.slice(0, 2) != "\xff\xd8") { @@ -149,5 +149,3 @@ function unpack(mark, str) { return unpacked; } - -export default removeEXIF; 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 deleted file mode 100755 index c1d1c018..00000000 --- a/src/web/App.js +++ /dev/null @@ -1,739 +0,0 @@ -import Utils from "../core/Utils.js"; -import Manager from "./Manager.js"; -import HTMLCategory from "./HTMLCategory.js"; -import HTMLOperation from "./HTMLOperation.js"; -import Split from "split.js"; - - -/** - * HTML view for CyberChef responsible for building the web page and dealing with all user - * interactions. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {CatConf[]} categories - The list of categories and operations to be populated. - * @param {Object.} operations - The list of operation configuration objects. - * @param {String[]} defaultFavourites - A list of default favourite operations. - * @param {Object} options - Default setting for app options. - */ -const App = function(categories, operations, defaultFavourites, defaultOptions) { - this.categories = categories; - this.operations = operations; - this.dfavourites = defaultFavourites; - this.doptions = defaultOptions; - this.options = Utils.extend({}, defaultOptions); - - this.manager = new Manager(this); - - this.baking = false; - this.autoBake_ = false; - this.autoBakePause = false; - this.progress = 0; - this.ingId = 0; -}; - - -/** - * This function sets up the stage and creates listeners for all events. - * - * @fires Manager#appstart - */ -App.prototype.setup = function() { - document.dispatchEvent(this.manager.appstart); - this.initialiseSplitter(); - this.loadLocalStorage(); - this.populateOperationsList(); - this.manager.setup(); - this.resetLayout(); - this.setCompileMessage(); - - log.debug("App loaded"); - this.appLoaded = true; - - this.loadURIParams(); - this.loaded(); -}; - - -/** - * Fires once all setup activities have completed. - * - * @fires Manager#apploaded - */ -App.prototype.loaded = function() { - // Check that both the app and the worker have loaded successfully, and that - // we haven't already loaded before attempting to remove the loading screen. - if (!this.workerLoaded || !this.appLoaded || - !document.getElementById("loader-wrapper")) return; - - // Trigger CSS animations to remove preloader - document.body.classList.add("loaded"); - - // Wait for animations to complete then remove the preloader and loaded style - // so that the animations for existing elements don't play again. - setTimeout(function() { - document.getElementById("loader-wrapper").remove(); - document.body.classList.remove("loaded"); - }, 1000); - - // Clear the loading message interval - clearInterval(window.loadingMsgsInt); - - // Remove the loading error handler - window.removeEventListener("error", window.loadingErrorHandler); - - document.dispatchEvent(this.manager.apploaded); -}; - - -/** - * An error handler for displaying the error to the user. - * - * @param {Error} err - * @param {boolean} [logToConsole=false] - */ -App.prototype.handleError = function(err, logToConsole) { - if (logToConsole) log.error(err); - const msg = err.displayStr || err.toString(); - this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors); -}; - - -/** - * Asks the ChefWorker to bake the current input using the current recipe. - * - * @param {boolean} [step] - Set to true if we should only execute one operation instead of the - * whole recipe. - */ -App.prototype.bake = function(step) { - if (this.baking) return; - - // Reset attemptHighlight flag - this.options.attemptHighlight = true; - - this.manager.worker.bake( - this.getInput(), // The user's input - this.getRecipeConfig(), // The configuration of the recipe - this.options, // Options set by the user - this.progress, // The current position in the recipe - step // Whether or not to take one step or execute the whole recipe - ); -}; - - -/** - * Runs Auto Bake if it is set. - */ -App.prototype.autoBake = function() { - // If autoBakePause is set, we are loading a full recipe (and potentially input), so there is no - // need to set the staleness indicator. Just exit and wait until auto bake is called after loading - // has completed. - if (this.autoBakePause) return false; - - if (this.autoBake_ && !this.baking) { - log.debug("Auto-baking"); - this.bake(); - } else { - this.manager.controls.showStaleIndicator(); - } -}; - - -/** - * Runs a silent bake, forcing the browser to load and cache all the relevant JavaScript code needed - * to do a real bake. - * - * 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 wake up the ChefWorker with an empty recipe. - */ -App.prototype.silentBake = function() { - let recipeConfig = []; - - if (this.autoBake_) { - // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled - // for a good reason. - recipeConfig = this.getRecipeConfig(); - } - - this.manager.worker.silentBake(recipeConfig); -}; - - -/** - * Gets the user's input data. - * - * @returns {string} - */ -App.prototype.getInput = function() { - return this.manager.input.get(); -}; - - -/** - * Sets the user's input data. - * - * @param {string} input - The string to set the input to - */ -App.prototype.setInput = function(input) { - this.manager.input.set(input); -}; - - -/** - * Populates the operations accordion list with the categories and operations specified in the - * view constructor. - * - * @fires Manager#oplistcreate - */ -App.prototype.populateOperationsList = function() { - // Move edit button away before we overwrite it - document.body.appendChild(document.getElementById("edit-favourites")); - - let html = ""; - let i; - - for (i = 0; i < this.categories.length; i++) { - let 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], - op = new HTMLOperation(opName, this.operations[opName], this, this.manager); - cat.addOperation(op); - } - - html += cat.toHtml(); - } - - document.getElementById("categories").innerHTML = html; - - const opLists = document.querySelectorAll("#categories .op-list"); - - for (i = 0; i < opLists.length; i++) { - opLists[i].dispatchEvent(this.manager.oplistcreate); - } - - // Add edit button to first category (Favourites) - document.querySelector("#categories a").appendChild(document.getElementById("edit-favourites")); -}; - - -/** - * Sets up the adjustable splitter to allow the user to resize areas of the page. - */ -App.prototype.initialiseSplitter = function() { - this.columnSplitter = Split(["#operations", "#recipe", "#IO"], { - sizes: [20, 30, 50], - minSize: [240, 325, 450], - gutterSize: 4, - onDrag: function() { - this.manager.controls.adjustWidth(); - this.manager.output.adjustWidth(); - }.bind(this) - }); - - this.ioSplitter = Split(["#input", "#output"], { - direction: "vertical", - gutterSize: 4, - }); - - this.resetLayout(); -}; - - -/** - * Loads the information previously saved to the HTML5 local storage object so that user options - * and favourites can be restored. - */ -App.prototype.loadLocalStorage = function() { - // Load options - let lOptions; - if (this.isLocalStorageAvailable() && localStorage.options !== undefined) { - lOptions = JSON.parse(localStorage.options); - } - this.manager.options.load(lOptions); - - // Load favourites - this.loadFavourites(); -}; - - -/** - * Loads the user's favourite operations from the HTML5 local storage object and populates the - * Favourites category with them. - * If the user currently has no saved favourites, the defaults from the view constructor are used. - */ -App.prototype.loadFavourites = function() { - let favourites; - - if (this.isLocalStorageAvailable()) { - favourites = localStorage.favourites && localStorage.favourites.length > 2 ? - JSON.parse(localStorage.favourites) : - this.dfavourites; - favourites = this.validFavourites(favourites); - this.saveFavourites(favourites); - } else { - favourites = this.dfavourites; - } - - const favCat = this.categories.filter(function(c) { - return c.name === "Favourites"; - })[0]; - - if (favCat) { - favCat.ops = favourites; - } else { - this.categories.unshift({ - name: "Favourites", - ops: favourites - }); - } -}; - - -/** - * Filters the list of favourite operations that the user had stored and removes any that are no - * longer available. The user is notified if this is the case. - - * @param {string[]} favourites - A list of the user's favourite operations - * @returns {string[]} A list of the valid favourites - */ -App.prototype.validFavourites = function(favourites) { - const validFavs = []; - for (let i = 0; i < favourites.length; i++) { - if (this.operations.hasOwnProperty(favourites[i])) { - validFavs.push(favourites[i]); - } else { - this.alert("The operation \"" + Utils.escapeHtml(favourites[i]) + - "\" is no longer available. It has been removed from your favourites.", "info"); - } - } - return validFavs; -}; - - -/** - * Saves a list of favourite operations to the HTML5 local storage object. - * - * @param {string[]} favourites - A list of the user's favourite operations - */ -App.prototype.saveFavourites = function(favourites) { - if (!this.isLocalStorageAvailable()) { - this.alert( - "Your security settings do not allow access to local storage so your favourites cannot be saved.", - "danger", - 5000 - ); - return false; - } - - localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites))); -}; - - -/** - * Resets favourite operations back to the default as specified in the view constructor and - * refreshes the operation list. - */ -App.prototype.resetFavourites = function() { - this.saveFavourites(this.dfavourites); - this.loadFavourites(); - this.populateOperationsList(); - this.manager.recipe.initialiseOperationDragNDrop(); -}; - - -/** - * Adds an operation to the user's favourites. - * - * @param {string} name - The name of the operation - */ -App.prototype.addFavourite = function(name) { - const favourites = JSON.parse(localStorage.favourites); - - if (favourites.indexOf(name) >= 0) { - this.alert("'" + name + "' is already in your favourites", "info", 2000); - return; - } - - favourites.push(name); - this.saveFavourites(favourites); - this.loadFavourites(); - this.populateOperationsList(); - this.manager.recipe.initialiseOperationDragNDrop(); -}; - - -/** - * Checks for input and recipe in the URI parameters and loads them if present. - */ -App.prototype.loadURIParams = function() { - // Load query string or hash from URI (depending on which is populated) - // We prefer getting the hash by splitting the href rather than referencing - // location.hash as some browsers (Firefox) automatically URL decode it, - // which cause issues. - const params = window.location.search || - window.location.href.split("#")[1] || - window.location.hash; - this.uriParams = Utils.parseURIParams(params); - this.autoBakePause = true; - - // Read in recipe from URI params - if (this.uriParams.recipe) { - try { - const recipeConfig = Utils.parseRecipeConfig(this.uriParams.recipe); - this.setRecipeConfig(recipeConfig); - } catch (err) {} - } else if (this.uriParams.op) { - // If there's no recipe, look for single operations - this.manager.recipe.clearRecipe(); - - // Search for nearest match and add it - const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false); - if (matchedOps.length) { - this.manager.recipe.addOperation(matchedOps[0].name); - } - - // Populate search with the string - const search = document.getElementById("search"); - - search.value = this.uriParams.op; - search.dispatchEvent(new Event("search")); - } - - // Read in input data from URI params - if (this.uriParams.input) { - try { - const inputData = Utils.fromBase64(this.uriParams.input); - this.setInput(inputData); - } catch (err) {} - } - - this.autoBakePause = false; - this.autoBake(); -}; - - -/** - * Returns the next ingredient ID and increments it for next time. - * - * @returns {number} - */ -App.prototype.nextIngId = function() { - return this.ingId++; -}; - - -/** - * Gets the current recipe configuration. - * - * @returns {Object[]} - */ -App.prototype.getRecipeConfig = function() { - return this.manager.recipe.getConfig(); -}; - - -/** - * Given a recipe configuration, sets the recipe to that configuration. - * - * @fires Manager#statechange - * @param {Object[]} recipeConfig - The recipe configuration - */ -App.prototype.setRecipeConfig = function(recipeConfig) { - document.getElementById("rec-list").innerHTML = null; - - // Pause auto-bake while loading but don't modify `this.autoBake_` - // otherwise `manualBake` cannot trigger. - this.autoBakePause = true; - - for (let i = 0; i < recipeConfig.length; i++) { - const item = this.manager.recipe.addOperation(recipeConfig[i].op); - - // Populate arguments - const args = item.querySelectorAll(".arg"); - for (let j = 0; j < args.length; j++) { - if (recipeConfig[i].args[j] === undefined) continue; - if (args[j].getAttribute("type") === "checkbox") { - // checkbox - args[j].checked = recipeConfig[i].args[j]; - } else if (args[j].classList.contains("toggle-string")) { - // toggleString - args[j].value = recipeConfig[i].args[j].string; - args[j].previousSibling.children[0].innerHTML = - Utils.escapeHtml(recipeConfig[i].args[j].option) + - " "; - } else { - // all others - args[j].value = recipeConfig[i].args[j]; - } - } - - // Set disabled and breakpoint - if (recipeConfig[i].disabled) { - item.querySelector(".disable-icon").click(); - } - if (recipeConfig[i].breakpoint) { - item.querySelector(".breakpoint").click(); - } - - this.progress = 0; - } - - // Unpause auto bake - this.autoBakePause = false; -}; - - -/** - * Resets the splitter positions to default. - */ -App.prototype.resetLayout = function() { - this.columnSplitter.setSizes([20, 30, 50]); - this.ioSplitter.setSizes([50, 50]); - - this.manager.controls.adjustWidth(); - this.manager.output.adjustWidth(); -}; - - -/** - * Sets the compile message. - */ -App.prototype.setCompileMessage = function() { - // Display time since last build and compile message - let now = new Date(), - timeSinceCompile = Utils.fuzzyTime(now.getTime() - window.compileTime); - - // Calculate previous version to compare to - let prev = PKG_VERSION.split(".").map(n => { - return parseInt(n, 10); - }); - if (prev[2] > 0) prev[2]--; - else if (prev[1] > 0) prev[1]--; - else prev[0]--; - - const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`; - - let compileInfo = `Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago`; - - if (window.compileMessage !== "") { - compileInfo += " - " + window.compileMessage; - } - - document.getElementById("notice").innerHTML = compileInfo; -}; - - -/** - * Determines whether the browser supports Local Storage and if it is accessible. - * - * @returns {boolean} - */ -App.prototype.isLocalStorageAvailable = function() { - try { - if (!localStorage) return false; - return true; - } catch (err) { - // Access to LocalStorage is denied - return false; - } -}; - - -/** - * Pops up a message to the user and writes it to the console log. - * - * @param {string} str - The message to display (HTML supported) - * @param {string} style - The colour of the popup - * "danger" = red - * "warning" = amber - * "info" = blue - * "success" = green - * @param {number} timeout - The number of milliseconds before the popup closes automatically - * 0 for never (until the user closes it) - * @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the - * console - * - * @example - * // Pops up a red box with the message "[current time] Error: Something has gone wrong!" - * // that will need to be dismissed by the user. - * this.alert("Error: Something has gone wrong!", "danger", 0); - * - * // Pops up a blue information box with the message "[current time] Happy Christmas!" - * // that will disappear after 5 seconds. - * this.alert("Happy Christmas!", "info", 5000); - */ -App.prototype.alert = function(str, style, timeout, silent) { - const time = new Date(); - - log.info("[" + time.toLocaleString() + "] " + str); - if (silent) return; - - style = style || "danger"; - timeout = timeout || 0; - - let alertEl = document.getElementById("alert"), - alertContent = document.getElementById("alert-content"); - - alertEl.classList.remove("alert-danger"); - alertEl.classList.remove("alert-warning"); - alertEl.classList.remove("alert-info"); - alertEl.classList.remove("alert-success"); - alertEl.classList.add("alert-" + style); - - // If the box hasn't been closed, append to it rather than replacing - if (alertEl.style.display === "block") { - alertContent.innerHTML += - "

[" + time.toLocaleTimeString() + "] " + str; - } else { - alertContent.innerHTML = - "[" + time.toLocaleTimeString() + "] " + str; - } - - // Stop the animation if it is in progress - $("#alert").stop(); - alertEl.style.display = "block"; - alertEl.style.opacity = 1; - - if (timeout > 0) { - clearTimeout(this.alertTimeout); - this.alertTimeout = setTimeout(function(){ - $("#alert").slideUp(100); - }, timeout); - } -}; - - -/** - * Pops up a box asking the user a question and sending the answer to a specified callback function. - * - * @param {string} title - The title of the box - * @param {string} body - The question (HTML supported) - * @param {function} callback - A function accepting one boolean argument which handles the - * response e.g. function(answer) {...} - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Pops up a box asking if the user would like a cookie. Prints the answer to the console. - * this.confirm("Question", "Would you like a cookie?", function(answer) {console.log(answer);}); - */ -App.prototype.confirm = function(title, body, callback, scope) { - scope = scope || this; - document.getElementById("confirm-title").innerHTML = title; - document.getElementById("confirm-body").innerHTML = body; - document.getElementById("confirm-modal").style.display = "block"; - - this.confirmClosed = false; - $("#confirm-modal").modal() - .one("show.bs.modal", function(e) { - this.confirmClosed = false; - }.bind(this)) - .one("click", "#confirm-yes", function() { - this.confirmClosed = true; - callback.bind(scope)(true); - $("#confirm-modal").modal("hide"); - }.bind(this)) - .one("hide.bs.modal", function(e) { - if (!this.confirmClosed) - callback.bind(scope)(false); - this.confirmClosed = true; - }.bind(this)); -}; - - -/** - * Handler for the alert close button click event. - * Closes the alert box. - */ -App.prototype.alertCloseClick = function() { - document.getElementById("alert").style.display = "none"; -}; - - -/** - * Handler for CyerChef statechange events. - * Fires whenever the input or recipe changes in any way. - * - * @listens Manager#statechange - * @param {event} e - */ -App.prototype.stateChange = function(e) { - this.autoBake(); - - // Set title - const recipeConfig = this.getRecipeConfig(); - let title = "CyberChef"; - if (recipeConfig.length === 1) { - title = `${recipeConfig[0].op} - ${title}`; - } else if (recipeConfig.length > 1) { - // See how long the full recipe is - const ops = recipeConfig.map(op => op.op).join(", "); - if (ops.length < 45) { - title = `${ops} - ${title}`; - } else { - // If it's too long, just use the first one and say how many more there are - title = `${recipeConfig[0].op}, ${recipeConfig.length - 1} more - ${title}`; - } - } - document.title = title; - - // Update the current history state (not creating a new one) - if (this.options.updateUrl) { - this.lastStateUrl = this.manager.controls.generateStateUrl(true, true, recipeConfig); - window.history.replaceState({}, title, this.lastStateUrl); - } -}; - - -/** - * Handler for the history popstate event. - * Reloads parameters from the URL. - * - * @param {event} e - */ -App.prototype.popState = function(e) { - this.loadURIParams(); -}; - - -/** - * Function to call an external API from this view. - */ -App.prototype.callApi = function(url, type, data, dataType, contentType) { - type = type || "POST"; - data = data || {}; - dataType = dataType || undefined; - contentType = contentType || "application/json"; - - let response = null, - success = false; - - $.ajax({ - url: url, - async: false, - type: type, - data: data, - dataType: dataType, - contentType: contentType, - success: function(data) { - success = true; - response = data; - }, - error: function(data) { - success = false; - response = data; - }, - }); - - return { - success: success, - response: response - }; -}; - -export default App; diff --git a/src/web/App.mjs b/src/web/App.mjs new file mode 100755 index 00000000..a291aa8b --- /dev/null +++ b/src/web/App.mjs @@ -0,0 +1,676 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils"; +import {fromBase64} from "../core/lib/Base64"; +import Manager from "./Manager"; +import HTMLCategory from "./HTMLCategory"; +import HTMLOperation from "./HTMLOperation"; +import Split from "split.js"; + + +/** + * HTML view for CyberChef responsible for building the web page and dealing with all user + * interactions. + */ +class App { + + /** + * App constructor. + * + * @param {CatConf[]} categories - The list of categories and operations to be populated. + * @param {Object.} operations - The list of operation configuration objects. + * @param {String[]} defaultFavourites - A list of default favourite operations. + * @param {Object} options - Default setting for app options. + */ + constructor(categories, operations, defaultFavourites, defaultOptions) { + this.categories = categories; + this.operations = operations; + this.dfavourites = defaultFavourites; + this.doptions = defaultOptions; + this.options = Object.assign({}, defaultOptions); + + this.manager = new Manager(this); + + this.baking = false; + this.autoBake_ = false; + this.autoBakePause = false; + this.progress = 0; + this.ingId = 0; + } + + + /** + * This function sets up the stage and creates listeners for all events. + * + * @fires Manager#appstart + */ + setup() { + document.dispatchEvent(this.manager.appstart); + this.initialiseSplitter(); + this.loadLocalStorage(); + this.populateOperationsList(); + this.manager.setup(); + this.resetLayout(); + this.setCompileMessage(); + + log.debug("App loaded"); + this.appLoaded = true; + + this.loadURIParams(); + this.loaded(); + } + + + /** + * Fires once all setup activities have completed. + * + * @fires Manager#apploaded + */ + loaded() { + // Check that both the app and the worker have loaded successfully, and that + // we haven't already loaded before attempting to remove the loading screen. + if (!this.workerLoaded || !this.appLoaded || + !document.getElementById("loader-wrapper")) return; + + // Trigger CSS animations to remove preloader + document.body.classList.add("loaded"); + + // Wait for animations to complete then remove the preloader and loaded style + // so that the animations for existing elements don't play again. + setTimeout(function() { + document.getElementById("loader-wrapper").remove(); + document.body.classList.remove("loaded"); + }, 1000); + + // Clear the loading message interval + clearInterval(window.loadingMsgsInt); + + // Remove the loading error handler + window.removeEventListener("error", window.loadingErrorHandler); + + document.dispatchEvent(this.manager.apploaded); + } + + + /** + * An error handler for displaying the error to the user. + * + * @param {Error} err + * @param {boolean} [logToConsole=false] + */ + handleError(err, logToConsole) { + if (logToConsole) log.error(err); + const msg = err.displayStr || err.toString(); + this.alert(msg, this.options.errorTimeout, !this.options.showErrors); + } + + + /** + * Asks the ChefWorker to bake the current input using the current recipe. + * + * @param {boolean} [step] - Set to true if we should only execute one operation instead of the + * whole recipe. + */ + bake(step) { + if (this.baking) return; + + // Reset attemptHighlight flag + this.options.attemptHighlight = true; + + this.manager.worker.bake( + this.getInput(), // The user's input + this.getRecipeConfig(), // The configuration of the recipe + this.options, // Options set by the user + this.progress, // The current position in the recipe + step // Whether or not to take one step or execute the whole recipe + ); + } + + + /** + * Runs Auto Bake if it is set. + */ + autoBake() { + // If autoBakePause is set, we are loading a full recipe (and potentially input), so there is no + // need to set the staleness indicator. Just exit and wait until auto bake is called after loading + // has completed. + if (this.autoBakePause) return false; + + if (this.autoBake_ && !this.baking) { + log.debug("Auto-baking"); + this.bake(); + } else { + this.manager.controls.showStaleIndicator(); + } + } + + + /** + * Runs a silent bake, forcing the browser to load and cache all the relevant JavaScript code needed + * to do a real bake. + * + * 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 wake up the ChefWorker with an empty recipe. + */ + silentBake() { + let recipeConfig = []; + + if (this.autoBake_) { + // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled + // for a good reason. + recipeConfig = this.getRecipeConfig(); + } + + this.manager.worker.silentBake(recipeConfig); + } + + + /** + * Gets the user's input data. + * + * @returns {string} + */ + getInput() { + return this.manager.input.get(); + } + + + /** + * Sets the user's input data. + * + * @param {string} input - The string to set the input to + * @param {boolean} [silent=false] - Suppress statechange event + */ + setInput(input, silent=false) { + this.manager.input.set(input, silent); + } + + + /** + * Populates the operations accordion list with the categories and operations specified in the + * view constructor. + * + * @fires Manager#oplistcreate + */ + populateOperationsList() { + // Move edit button away before we overwrite it + document.body.appendChild(document.getElementById("edit-favourites")); + + let html = ""; + let i; + + for (i = 0; i < this.categories.length; i++) { + const catConf = this.categories[i], + selected = i === 0, + cat = new HTMLCategory(catConf.name, selected); + + for (let j = 0; j < catConf.ops.length; j++) { + const opName = catConf.ops[j]; + if (!this.operations.hasOwnProperty(opName)) { + log.warn(`${opName} could not be found.`); + continue; + } + + const op = new HTMLOperation(opName, this.operations[opName], this, this.manager); + cat.addOperation(op); + } + + html += cat.toHtml(); + } + + document.getElementById("categories").innerHTML = html; + + const opLists = document.querySelectorAll("#categories .op-list"); + + for (i = 0; i < opLists.length; i++) { + opLists[i].dispatchEvent(this.manager.oplistcreate); + } + + // Add edit button to first category (Favourites) + document.querySelector("#categories a").appendChild(document.getElementById("edit-favourites")); + } + + + /** + * Sets up the adjustable splitter to allow the user to resize areas of the page. + */ + initialiseSplitter() { + this.columnSplitter = Split(["#operations", "#recipe", "#IO"], { + sizes: [20, 30, 50], + minSize: [240, 370, 450], + gutterSize: 4, + onDrag: function() { + this.manager.recipe.adjustWidth(); + }.bind(this) + }); + + this.ioSplitter = Split(["#input", "#output"], { + direction: "vertical", + gutterSize: 4 + }); + + this.resetLayout(); + } + + + /** + * Loads the information previously saved to the HTML5 local storage object so that user options + * and favourites can be restored. + */ + loadLocalStorage() { + // Load options + let lOptions; + if (this.isLocalStorageAvailable() && localStorage.options !== undefined) { + lOptions = JSON.parse(localStorage.options); + } + this.manager.options.load(lOptions); + + // Load favourites + this.loadFavourites(); + } + + + /** + * Loads the user's favourite operations from the HTML5 local storage object and populates the + * Favourites category with them. + * If the user currently has no saved favourites, the defaults from the view constructor are used. + */ + loadFavourites() { + let favourites; + + if (this.isLocalStorageAvailable()) { + favourites = localStorage.favourites && localStorage.favourites.length > 2 ? + JSON.parse(localStorage.favourites) : + this.dfavourites; + favourites = this.validFavourites(favourites); + this.saveFavourites(favourites); + } else { + favourites = this.dfavourites; + } + + const favCat = this.categories.filter(function(c) { + return c.name === "Favourites"; + })[0]; + + if (favCat) { + favCat.ops = favourites; + } else { + this.categories.unshift({ + name: "Favourites", + ops: favourites + }); + } + } + + + /** + * Filters the list of favourite operations that the user had stored and removes any that are no + * longer available. The user is notified if this is the case. + + * @param {string[]} favourites - A list of the user's favourite operations + * @returns {string[]} A list of the valid favourites + */ + validFavourites(favourites) { + const validFavs = []; + for (let i = 0; i < favourites.length; i++) { + if (this.operations.hasOwnProperty(favourites[i])) { + validFavs.push(favourites[i]); + } else { + this.alert(`The operation "${Utils.escapeHtml(favourites[i])}" is no longer available. ` + + "It has been removed from your favourites."); + } + } + return validFavs; + } + + + /** + * Saves a list of favourite operations to the HTML5 local storage object. + * + * @param {string[]} favourites - A list of the user's favourite operations + */ + saveFavourites(favourites) { + if (!this.isLocalStorageAvailable()) { + this.alert( + "Your security settings do not allow access to local storage so your favourites cannot be saved.", + 5000 + ); + return false; + } + + localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites))); + } + + + /** + * Resets favourite operations back to the default as specified in the view constructor and + * refreshes the operation list. + */ + resetFavourites() { + this.saveFavourites(this.dfavourites); + this.loadFavourites(); + this.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + + /** + * Adds an operation to the user's favourites. + * + * @param {string} name - The name of the operation + */ + addFavourite(name) { + const favourites = JSON.parse(localStorage.favourites); + + if (favourites.indexOf(name) >= 0) { + this.alert(`'${name}' is already in your favourites`, 3000); + return; + } + + favourites.push(name); + this.saveFavourites(favourites); + this.loadFavourites(); + this.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + + /** + * Checks for input and recipe in the URI parameters and loads them if present. + */ + loadURIParams() { + // Load query string or hash from URI (depending on which is populated) + // We prefer getting the hash by splitting the href rather than referencing + // location.hash as some browsers (Firefox) automatically URL decode it, + // which cause issues. + const params = window.location.search || + window.location.href.split("#")[1] || + window.location.hash; + this.uriParams = Utils.parseURIParams(params); + this.autoBakePause = true; + + // Read in recipe from URI params + if (this.uriParams.recipe) { + try { + const recipeConfig = Utils.parseRecipeConfig(this.uriParams.recipe); + this.setRecipeConfig(recipeConfig); + } catch (err) {} + } else if (this.uriParams.op) { + // If there's no recipe, look for single operations + this.manager.recipe.clearRecipe(); + + // Search for nearest match and add it + const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false); + if (matchedOps.length) { + this.manager.recipe.addOperation(matchedOps[0].name); + } + + // Populate search with the string + const search = document.getElementById("search"); + + search.value = this.uriParams.op; + search.dispatchEvent(new Event("search")); + } + + // Read in input data from URI params + if (this.uriParams.input) { + try { + const inputData = fromBase64(this.uriParams.input); + this.setInput(inputData, true); + } catch (err) {} + } + + this.autoBakePause = false; + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Returns the next ingredient ID and increments it for next time. + * + * @returns {number} + */ + nextIngId() { + return this.ingId++; + } + + + /** + * Gets the current recipe configuration. + * + * @returns {Object[]} + */ + getRecipeConfig() { + return this.manager.recipe.getConfig(); + } + + + /** + * Given a recipe configuration, sets the recipe to that configuration. + * + * @fires Manager#statechange + * @param {Object[]} recipeConfig - The recipe configuration + */ + setRecipeConfig(recipeConfig) { + document.getElementById("rec-list").innerHTML = null; + + // Pause auto-bake while loading but don't modify `this.autoBake_` + // otherwise `manualBake` cannot trigger. + this.autoBakePause = true; + + for (let i = 0; i < recipeConfig.length; i++) { + const item = this.manager.recipe.addOperation(recipeConfig[i].op); + + // Populate arguments + const args = item.querySelectorAll(".arg"); + for (let j = 0; j < args.length; j++) { + if (recipeConfig[i].args[j] === undefined) continue; + if (args[j].getAttribute("type") === "checkbox") { + // checkbox + args[j].checked = recipeConfig[i].args[j]; + } else if (args[j].classList.contains("toggle-string")) { + // toggleString + args[j].value = recipeConfig[i].args[j].string; + args[j].parentNode.parentNode.querySelector("button").innerHTML = + Utils.escapeHtml(recipeConfig[i].args[j].option); + } else { + // all others + args[j].value = recipeConfig[i].args[j]; + } + } + + // Set disabled and breakpoint + if (recipeConfig[i].disabled) { + item.querySelector(".disable-icon").click(); + } + if (recipeConfig[i].breakpoint) { + item.querySelector(".breakpoint").click(); + } + + this.progress = 0; + } + + // Unpause auto bake + this.autoBakePause = false; + } + + + /** + * Resets the splitter positions to default. + */ + resetLayout() { + this.columnSplitter.setSizes([20, 30, 50]); + this.ioSplitter.setSizes([50, 50]); + this.manager.recipe.adjustWidth(); + } + + + /** + * Sets the compile message. + */ + setCompileMessage() { + // Display time since last build and compile message + const now = new Date(), + timeSinceCompile = Utils.fuzzyTime(now.getTime() - window.compileTime); + + // Calculate previous version to compare to + const prev = PKG_VERSION.split(".").map(n => { + return parseInt(n, 10); + }); + if (prev[2] > 0) prev[2]--; + else if (prev[1] > 0) prev[1]--; + else prev[0]--; + + //const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`; + + let compileInfo = `Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago`; + + if (window.compileMessage !== "") { + compileInfo += " - " + window.compileMessage; + } + + document.getElementById("notice").innerHTML = compileInfo; + } + + + /** + * Determines whether the browser supports Local Storage and if it is accessible. + * + * @returns {boolean} + */ + isLocalStorageAvailable() { + try { + if (!localStorage) return false; + return true; + } catch (err) { + // Access to LocalStorage is denied + return false; + } + } + + + /** + * Pops up a message to the user and writes it to the console log. + * + * @param {string} str - The message to display (HTML supported) + * @param {number} timeout - The number of milliseconds before the alert closes automatically + * 0 for never (until the user closes it) + * @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the + * console + * + * @example + * // Pops up a box with the message "Error: Something has gone wrong!" that will need to be + * // dismissed by the user. + * this.alert("Error: Something has gone wrong!", 0); + * + * // Pops up a box with the message "Happy Christmas!" that will disappear after 5 seconds. + * this.alert("Happy Christmas!", 5000); + */ + alert(str, timeout, silent) { + const time = new Date(); + + log.info("[" + time.toLocaleString() + "] " + str); + if (silent) return; + + timeout = timeout || 0; + + this.currentSnackbar = $.snackbar({ + content: str, + timeout: timeout, + htmlAllowed: true, + onClose: () => { + this.currentSnackbar.remove(); + } + }); + } + + + /** + * Pops up a box asking the user a question and sending the answer to a specified callback function. + * + * @param {string} title - The title of the box + * @param {string} body - The question (HTML supported) + * @param {function} callback - A function accepting one boolean argument which handles the + * response e.g. function(answer) {...} + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Pops up a box asking if the user would like a cookie. Prints the answer to the console. + * this.confirm("Question", "Would you like a cookie?", function(answer) {console.log(answer);}); + */ + confirm(title, body, callback, scope) { + scope = scope || this; + document.getElementById("confirm-title").innerHTML = title; + document.getElementById("confirm-body").innerHTML = body; + document.getElementById("confirm-modal").style.display = "block"; + + this.confirmClosed = false; + $("#confirm-modal").modal() + .one("show.bs.modal", function(e) { + this.confirmClosed = false; + }.bind(this)) + .one("click", "#confirm-yes", function() { + this.confirmClosed = true; + callback.bind(scope)(true); + $("#confirm-modal").modal("hide"); + }.bind(this)) + .one("hide.bs.modal", function(e) { + if (!this.confirmClosed) + callback.bind(scope)(false); + this.confirmClosed = true; + }.bind(this)); + } + + + /** + * Handler for CyerChef statechange events. + * Fires whenever the input or recipe changes in any way. + * + * @listens Manager#statechange + * @param {event} e + */ + stateChange(e) { + this.autoBake(); + + // Set title + const recipeConfig = this.getRecipeConfig(); + let title = "CyberChef"; + if (recipeConfig.length === 1) { + title = `${recipeConfig[0].op} - ${title}`; + } else if (recipeConfig.length > 1) { + // See how long the full recipe is + const ops = recipeConfig.map(op => op.op).join(", "); + if (ops.length < 45) { + title = `${ops} - ${title}`; + } else { + // If it's too long, just use the first one and say how many more there are + title = `${recipeConfig[0].op}, ${recipeConfig.length - 1} more - ${title}`; + } + } + document.title = title; + + // Update the current history state (not creating a new one) + if (this.options.updateUrl) { + this.lastStateUrl = this.manager.controls.generateStateUrl(true, true, recipeConfig); + window.history.replaceState({}, title, this.lastStateUrl); + } + } + + + /** + * Handler for the history popstate event. + * Reloads parameters from the URL. + * + * @param {event} e + */ + popState(e) { + this.loadURIParams(); + } + +} + +export default App; diff --git a/src/web/BackgroundWorkerWaiter.mjs b/src/web/BackgroundWorkerWaiter.mjs new file mode 100644 index 00000000..340b9e76 --- /dev/null +++ b/src/web/BackgroundWorkerWaiter.mjs @@ -0,0 +1,156 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker"; + +/** + * Waiter to handle conversations with a ChefWorker in the background. + */ +class BackgroundWorkerWaiter { + + /** + * BackgroundWorkerWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.callbacks = {}; + this.callbackID = 0; + this.completedCallback = -1; + this.timeout = null; + } + + + /** + * Sets up the ChefWorker and associated listeners. + */ + registerChefWorker() { + log.debug("Registering new background ChefWorker"); + this.chefWorker = new ChefWorker(); + this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this)); + + let docURL = document.location.href.split(/[#?]/)[0]; + const index = docURL.lastIndexOf("/"); + if (index > 0) { + docURL = docURL.substring(0, index); + } + this.chefWorker.postMessage({"action": "docURL", "data": docURL}); + } + + + /** + * Handler for messages sent back by the ChefWorker. + * + * @param {MessageEvent} e + */ + handleChefMessage(e) { + const r = e.data; + log.debug("Receiving '" + r.action + "' from ChefWorker in the background"); + + switch (r.action) { + case "bakeComplete": + case "bakeError": + if (typeof r.data.id !== "undefined") { + clearTimeout(this.timeout); + this.callbacks[r.data.id].bind(this)(r.data); + this.completedCallback = r.data.id; + } + break; + case "workerLoaded": + log.debug("Background ChefWorker loaded"); + break; + case "optionUpdate": + // Ignore these messages + break; + default: + log.error("Unrecognised message from background ChefWorker", e); + break; + } + } + + + /** + * Cancels the current bake by terminating the ChefWorker and creating a new one. + */ + cancelBake() { + if (this.chefWorker) + this.chefWorker.terminate(); + this.registerChefWorker(); + } + + + /** + * Asks the ChefWorker to bake the input using the specified recipe. + * + * @param {string} input + * @param {Object[]} recipeConfig + * @param {Object} options + * @param {number} progress + * @param {boolean} step + * @param {Function} callback + */ + bake(input, recipeConfig, options, progress, step, callback) { + const id = this.callbackID++; + this.callbacks[id] = callback; + + this.chefWorker.postMessage({ + action: "bake", + data: { + input: input, + recipeConfig: recipeConfig, + options: options, + progress: progress, + step: step, + id: id + } + }); + } + + + /** + * Asks the Magic operation what it can do with the input data. + * + * @param {string|ArrayBuffer} input + */ + magic(input) { + // If we're still working on the previous bake, cancel it before stating a new one. + if (this.completedCallback + 1 < this.callbackID) { + clearTimeout(this.timeout); + this.cancelBake(); + } + + this.bake(input, [ + { + "op": "Magic", + "args": [3, false, false] + } + ], {}, 0, false, this.magicComplete); + + // Cancel this bake if it takes too long. + this.timeout = setTimeout(this.cancelBake.bind(this), 3000); + } + + + /** + * Handler for completed Magic bakes. + * + * @param {Object} response + */ + magicComplete(response) { + log.debug("--- Background Magic Bake complete ---"); + if (!response || response.error) return; + + this.manager.output.backgroundMagicResult(response.dish.value); + } + +} + + +export default BackgroundWorkerWaiter; diff --git a/src/web/BindingsWaiter.js b/src/web/BindingsWaiter.js deleted file mode 100644 index 802590b8..00000000 --- a/src/web/BindingsWaiter.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.) - * - * @author Matt C [matt@artemisbot.uk] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const BindingsWaiter = function (app, manager) { - this.app = app; - this.manager = manager; -}; - - -/** - * Handler for all keydown events - * Checks whether valid keyboard shortcut has been instated - * - * @fires Manager#statechange - * @param {event} e - */ -BindingsWaiter.prototype.parseInput = function(e) { - const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey; - - if (e.ctrlKey && modKey) { - let elem; - switch (e.code) { - case "KeyF": // Focus search - e.preventDefault(); - document.getElementById("search").focus(); - break; - case "KeyI": // Focus input - e.preventDefault(); - document.getElementById("input-text").focus(); - break; - case "KeyO": // Focus output - e.preventDefault(); - document.getElementById("output-text").focus(); - break; - case "Period": // Focus next operation - e.preventDefault(); - try { - elem = document.activeElement.closest(".operation") || document.querySelector("#rec-list .operation"); - if (elem.parentNode.lastChild === elem) { - // If operation is last in recipe, loop around to the top operation's first argument - elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); - } else { - // Focus first argument of next operation - elem.nextSibling.querySelectorAll(".arg")[0].focus(); - } - } catch (e) { - // do nothing, just don't throw an error - } - break; - case "KeyB": // Set breakpoint - e.preventDefault(); - try { - elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0]; - if (elem.getAttribute("break") === "false") { - elem.setAttribute("break", "true"); // add break point if not already enabled - elem.classList.add("breakpoint-selected"); - } else { - elem.setAttribute("break", "false"); // remove break point if already enabled - elem.classList.remove("breakpoint-selected"); - } - window.dispatchEvent(this.manager.statechange); - } catch (e) { - // do nothing, just don't throw an error - } - break; - case "KeyD": // Disable operation - e.preventDefault(); - try { - elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0]; - if (elem.getAttribute("disabled") === "false") { - elem.setAttribute("disabled", "true"); // disable operation if enabled - elem.classList.add("disable-elem-selected"); - elem.parentNode.parentNode.classList.add("disabled"); - } else { - elem.setAttribute("disabled", "false"); // enable operation if disabled - elem.classList.remove("disable-elem-selected"); - elem.parentNode.parentNode.classList.remove("disabled"); - } - this.app.progress = 0; - window.dispatchEvent(this.manager.statechange); - } catch (e) { - // do nothing, just don't throw an error - } - break; - case "Space": // Bake - e.preventDefault(); - this.app.bake(); - break; - case "Quote": // Step through - e.preventDefault(); - this.app.bake(true); - break; - case "KeyC": // Clear recipe - e.preventDefault(); - this.manager.recipe.clearRecipe(); - break; - case "KeyS": // Save output to file - e.preventDefault(); - this.manager.output.saveClick(); - break; - case "KeyL": // Load recipe - e.preventDefault(); - this.manager.controls.loadClick(); - break; - case "KeyM": // Switch input and output - e.preventDefault(); - this.manager.output.switchClick(); - break; - default: - if (e.code.match(/Digit[0-9]/g)) { // Select nth operation - e.preventDefault(); - try { - // Select the first argument of the operation corresponding to the number pressed - document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); - } catch (e) { - // do nothing, just don't throw an error - } - } - break; - } - } -}; - - -/** - * Updates keybinding list when metaKey option is toggled - * - */ -BindingsWaiter.prototype.updateKeybList = function() { - let modWinLin = "Alt"; - let modMac = "Opt"; - if (this.app.options.useMetaKey) { - modWinLin = "Win"; - modMac = "Cmd"; - } - document.getElementById("keybList").innerHTML = ` -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - `; -}; - -export default BindingsWaiter; diff --git a/src/web/BindingsWaiter.mjs b/src/web/BindingsWaiter.mjs new file mode 100755 index 00000000..74262c61 --- /dev/null +++ b/src/web/BindingsWaiter.mjs @@ -0,0 +1,224 @@ +/** + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.) + */ +class BindingsWaiter { + + /** + * BindingsWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + } + + + /** + * Handler for all keydown events + * Checks whether valid keyboard shortcut has been instated + * + * @fires Manager#statechange + * @param {event} e + */ + parseInput(e) { + const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey; + + if (e.ctrlKey && modKey) { + let elem; + switch (e.code) { + case "KeyF": // Focus search + e.preventDefault(); + document.getElementById("search").focus(); + break; + case "KeyI": // Focus input + e.preventDefault(); + document.getElementById("input-text").focus(); + break; + case "KeyO": // Focus output + e.preventDefault(); + document.getElementById("output-text").focus(); + break; + case "Period": // Focus next operation + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation") || document.querySelector("#rec-list .operation"); + if (elem.parentNode.lastChild === elem) { + // If operation is last in recipe, loop around to the top operation's first argument + elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); + } else { + // Focus first argument of next operation + elem.nextSibling.querySelectorAll(".arg")[0].focus(); + } + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyB": // Set breakpoint + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0]; + if (elem.getAttribute("break") === "false") { + elem.setAttribute("break", "true"); // add break point if not already enabled + elem.classList.add("breakpoint-selected"); + } else { + elem.setAttribute("break", "false"); // remove break point if already enabled + elem.classList.remove("breakpoint-selected"); + } + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyD": // Disable operation + e.preventDefault(); + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0]; + if (elem.getAttribute("disabled") === "false") { + elem.setAttribute("disabled", "true"); // disable operation if enabled + elem.classList.add("disable-elem-selected"); + elem.parentNode.parentNode.classList.add("disabled"); + } else { + elem.setAttribute("disabled", "false"); // enable operation if disabled + elem.classList.remove("disable-elem-selected"); + elem.parentNode.parentNode.classList.remove("disabled"); + } + this.app.progress = 0; + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "Space": // Bake + e.preventDefault(); + this.app.bake(); + break; + case "Quote": // Step through + e.preventDefault(); + this.app.bake(true); + break; + case "KeyC": // Clear recipe + e.preventDefault(); + this.manager.recipe.clearRecipe(); + break; + case "KeyS": // Save output to file + e.preventDefault(); + this.manager.output.saveClick(); + break; + case "KeyL": // Load recipe + e.preventDefault(); + this.manager.controls.loadClick(); + break; + case "KeyM": // Switch input and output + e.preventDefault(); + this.manager.output.switchClick(); + break; + default: + if (e.code.match(/Digit[0-9]/g)) { // Select nth operation + e.preventDefault(); + try { + // Select the first argument of the operation corresponding to the number pressed + document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); + } catch (e) { + // do nothing, just don't throw an error + } + } + break; + } + } + } + + + /** + * Updates keybinding list when metaKey option is toggled + */ + updateKeybList() { + let modWinLin = "Alt"; + let modMac = "Opt"; + if (this.app.options.useMetaKey) { + modWinLin = "Win"; + modMac = "Cmd"; + } + document.getElementById("keybList").innerHTML = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; + } + +} + +export default BindingsWaiter; diff --git a/src/web/ControlsWaiter.js b/src/web/ControlsWaiter.js deleted file mode 100755 index a9b7490c..00000000 --- a/src/web/ControlsWaiter.js +++ /dev/null @@ -1,440 +0,0 @@ -import Utils from "../core/Utils.js"; - - -/** - * Waiter to handle events related to the CyberChef controls (i.e. Bake, Step, Save, Load etc.) - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const ControlsWaiter = function(app, manager) { - this.app = app; - this.manager = manager; -}; - - -/** - * Adjusts the display properties of the control buttons so that they fit within the current width - * without wrapping or overflowing. - */ -ControlsWaiter.prototype.adjustWidth = function() { - const controls = document.getElementById("controls"); - const step = document.getElementById("step"); - const clrBreaks = document.getElementById("clr-breaks"); - const saveImg = document.querySelector("#save img"); - const loadImg = document.querySelector("#load img"); - const stepImg = document.querySelector("#step img"); - const clrRecipImg = document.querySelector("#clr-recipe img"); - const clrBreaksImg = document.querySelector("#clr-breaks img"); - - if (controls.clientWidth < 470) { - step.childNodes[1].nodeValue = " Step"; - } else { - step.childNodes[1].nodeValue = " Step through"; - } - - if (controls.clientWidth < 400) { - saveImg.style.display = "none"; - loadImg.style.display = "none"; - stepImg.style.display = "none"; - clrRecipImg.style.display = "none"; - clrBreaksImg.style.display = "none"; - } else { - saveImg.style.display = "inline"; - loadImg.style.display = "inline"; - stepImg.style.display = "inline"; - clrRecipImg.style.display = "inline"; - clrBreaksImg.style.display = "inline"; - } - - if (controls.clientWidth < 330) { - clrBreaks.childNodes[1].nodeValue = " Clear breaks"; - } else { - clrBreaks.childNodes[1].nodeValue = " Clear breakpoints"; - } -}; - - -/** - * Checks or unchecks the Auto Bake checkbox based on the given value. - * - * @param {boolean} value - The new value for Auto Bake. - */ -ControlsWaiter.prototype.setAutoBake = function(value) { - const autoBakeCheckbox = document.getElementById("auto-bake"); - - if (autoBakeCheckbox.checked !== value) { - autoBakeCheckbox.click(); - } -}; - - -/** - * Handler to trigger baking. - */ -ControlsWaiter.prototype.bakeClick = function() { - if (document.getElementById("bake").textContent.indexOf("Bake") > 0) { - this.app.bake(); - } else { - this.manager.worker.cancelBake(); - } -}; - - -/** - * Handler for the 'Step through' command. Executes the next step of the recipe. - */ -ControlsWaiter.prototype.stepClick = function() { - this.app.bake(true); -}; - - -/** - * Handler for changes made to the Auto Bake checkbox. - */ -ControlsWaiter.prototype.autoBakeChange = function() { - const autoBakeLabel = document.getElementById("auto-bake-label"); - const autoBakeCheckbox = document.getElementById("auto-bake"); - - this.app.autoBake_ = autoBakeCheckbox.checked; - - if (autoBakeCheckbox.checked) { - autoBakeLabel.classList.add("btn-success"); - autoBakeLabel.classList.remove("btn-default"); - } else { - autoBakeLabel.classList.add("btn-default"); - autoBakeLabel.classList.remove("btn-success"); - } -}; - - -/** - * Handler for the 'Clear recipe' command. Removes all operations from the recipe. - */ -ControlsWaiter.prototype.clearRecipeClick = function() { - this.manager.recipe.clearRecipe(); -}; - - -/** - * Handler for the 'Clear breakpoints' command. Removes all breakpoints from operations in the - * recipe. - */ -ControlsWaiter.prototype.clearBreaksClick = function() { - const bps = document.querySelectorAll("#rec-list li.operation .breakpoint"); - - for (let i = 0; i < bps.length; i++) { - bps[i].setAttribute("break", "false"); - bps[i].classList.remove("breakpoint-selected"); - } -}; - - -/** - * Populates the save disalog box with a URL incorporating the recipe and input. - * - * @param {Object[]} [recipeConfig] - The recipe configuration object array. - */ -ControlsWaiter.prototype.initialiseSaveLink = function(recipeConfig) { - recipeConfig = recipeConfig || this.app.getRecipeConfig(); - - const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked; - const includeInput = document.getElementById("save-link-input-checkbox").checked; - const saveLinkEl = document.getElementById("save-link"); - const saveLink = this.generateStateUrl(includeRecipe, includeInput, recipeConfig); - - saveLinkEl.innerHTML = Utils.truncate(saveLink, 120); - saveLinkEl.setAttribute("href", saveLink); -}; - - -/** - * Generates a URL containing the current recipe and input state. - * - * @param {boolean} includeRecipe - Whether to include the recipe in the URL. - * @param {boolean} includeInput - Whether to include the input in the URL. - * @param {Object[]} [recipeConfig] - The recipe configuration object array. - * @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included - * @returns {string} - */ -ControlsWaiter.prototype.generateStateUrl = function(includeRecipe, includeInput, recipeConfig, baseURL) { - recipeConfig = recipeConfig || this.app.getRecipeConfig(); - - const link = baseURL || window.location.protocol + "//" + - 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 - - includeRecipe = includeRecipe && (recipeConfig.length > 0); - // Only inlcude input if it is less than 50KB (51200 * 4/3 as it is Base64 encoded) - includeInput = includeInput && (inputStr.length > 0) && (inputStr.length <= 68267); - - const params = [ - includeRecipe ? ["recipe", recipeStr] : undefined, - includeInput ? ["input", inputStr] : undefined, - ]; - - const hash = params - .filter(v => v) - .map(([key, value]) => `${key}=${Utils.encodeURIFragment(value)}`) - .join("&"); - - if (hash) { - return `${link}#${hash}`; - } - - return link; -}; - - -/** - * Handler for changes made to the save dialog text area. Re-initialises the save link. - */ -ControlsWaiter.prototype.saveTextChange = function(e) { - try { - const recipeConfig = Utils.parseRecipeConfig(e.target.value); - this.initialiseSaveLink(recipeConfig); - } catch (err) {} -}; - - -/** - * Handler for the 'Save' command. Pops up the save dialog box. - */ -ControlsWaiter.prototype.saveClick = function() { - const recipeConfig = this.app.getRecipeConfig(); - const recipeStr = JSON.stringify(recipeConfig); - - document.getElementById("save-text-chef").value = Utils.generatePrettyRecipe(recipeConfig, true); - document.getElementById("save-text-clean").value = JSON.stringify(recipeConfig, null, 2) - .replace(/{\n\s+"/g, "{ \"") - .replace(/\[\n\s{3,}/g, "[") - .replace(/\n\s{3,}]/g, "]") - .replace(/\s*\n\s*}/g, " }") - .replace(/\n\s{6,}/g, " "); - document.getElementById("save-text-compact").value = recipeStr; - - this.initialiseSaveLink(recipeConfig); - $("#save-modal").modal(); -}; - - -/** - * Handler for the save link recipe checkbox change event. - */ -ControlsWaiter.prototype.slrCheckChange = function() { - this.initialiseSaveLink(); -}; - - -/** - * Handler for the save link input checkbox change event. - */ -ControlsWaiter.prototype.sliCheckChange = function() { - this.initialiseSaveLink(); -}; - - -/** - * Handler for the 'Load' command. Pops up the load dialog box. - */ -ControlsWaiter.prototype.loadClick = function() { - this.populateLoadRecipesList(); - $("#load-modal").modal(); -}; - - -/** - * Saves the recipe specified in the save textarea to local storage. - */ -ControlsWaiter.prototype.saveButtonClick = function() { - if (!this.app.isLocalStorageAvailable()) { - this.app.alert( - "Your security settings do not allow access to local storage so your recipe cannot be saved.", - "danger", - 5000 - ); - return false; - } - - const recipeName = Utils.escapeHtml(document.getElementById("save-name").value); - const recipeStr = document.querySelector("#save-texts .tab-pane.active textarea").value; - - if (!recipeName) { - this.app.alert("Please enter a recipe name", "danger", 2000); - return; - } - - let savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : [], - recipeId = localStorage.recipeId || 0; - - savedRecipes.push({ - id: ++recipeId, - name: recipeName, - recipe: recipeStr - }); - - localStorage.savedRecipes = JSON.stringify(savedRecipes); - localStorage.recipeId = recipeId; - - this.app.alert("Recipe saved as \"" + recipeName + "\".", "success", 2000); -}; - - -/** - * Populates the list of saved recipes in the load dialog box from local storage. - */ -ControlsWaiter.prototype.populateLoadRecipesList = function() { - if (!this.app.isLocalStorageAvailable()) return false; - - const loadNameEl = document.getElementById("load-name"); - - // Remove current recipes from select - let i = loadNameEl.options.length; - while (i--) { - loadNameEl.remove(i); - } - - // Add recipes to select - const savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : []; - - for (i = 0; i < savedRecipes.length; i++) { - const opt = document.createElement("option"); - opt.value = savedRecipes[i].id; - // Unescape then re-escape in case localStorage has been corrupted - opt.innerHTML = Utils.escapeHtml(Utils.unescapeHtml(savedRecipes[i].name)); - - loadNameEl.appendChild(opt); - } - - // Populate textarea with first recipe - document.getElementById("load-text").value = savedRecipes.length ? savedRecipes[0].recipe : ""; -}; - - -/** - * Removes the currently selected recipe from local storage. - */ -ControlsWaiter.prototype.loadDeleteClick = function() { - if (!this.app.isLocalStorageAvailable()) return false; - - const id = parseInt(document.getElementById("load-name").value, 10); - const rawSavedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : []; - - const savedRecipes = rawSavedRecipes.filter(r => r.id !== id); - - localStorage.savedRecipes = JSON.stringify(savedRecipes); - this.populateLoadRecipesList(); -}; - - -/** - * Displays the selected recipe in the load text box. - */ -ControlsWaiter.prototype.loadNameChange = function(e) { - if (!this.app.isLocalStorageAvailable()) return false; - - const el = e.target; - const savedRecipes = localStorage.savedRecipes ? - JSON.parse(localStorage.savedRecipes) : []; - const id = parseInt(el.value, 10); - - const recipe = savedRecipes.find(r => r.id === id); - - document.getElementById("load-text").value = recipe.recipe; -}; - - -/** - * Loads the selected recipe and populates the Recipe with its operations. - */ -ControlsWaiter.prototype.loadButtonClick = function() { - try { - const recipeConfig = Utils.parseRecipeConfig(document.getElementById("load-text").value); - this.app.setRecipeConfig(recipeConfig); - this.app.autoBake(); - - $("#rec-list [data-toggle=popover]").popover(); - } catch (e) { - this.app.alert("Invalid recipe", "danger", 2000); - } -}; - - -/** - * Populates the bug report information box with useful technical info. - * - * @param {event} e - */ -ControlsWaiter.prototype.supportButtonClick = function(e) { - e.preventDefault(); - - const reportBugInfo = document.getElementById("report-bug-info"); - const saveLink = this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/"); - - if (reportBugInfo) { - reportBugInfo.innerHTML = `* Version: ${PKG_VERSION + (typeof INLINE === "undefined" ? "" : "s")} -* Compile time: ${COMPILE_TIME} -* User-Agent: -${navigator.userAgent} -* [Link to reproduce](${saveLink}) - -`; - } -}; - - -/** - * Shows the stale indicator to show that the input or recipe has changed - * since the last bake. - */ -ControlsWaiter.prototype.showStaleIndicator = function() { - const staleIndicator = document.getElementById("stale-indicator"); - - staleIndicator.style.visibility = "visible"; - staleIndicator.style.opacity = 1; -}; - - -/** - * Hides the stale indicator to show that the input or recipe has not changed - * since the last bake. - */ -ControlsWaiter.prototype.hideStaleIndicator = function() { - const staleIndicator = document.getElementById("stale-indicator"); - - staleIndicator.style.opacity = 0; - staleIndicator.style.visibility = "hidden"; -}; - - -/** - * Switches the Bake button between 'Bake' and 'Cancel' functions. - * - * @param {boolean} cancel - Whether to change to cancel or not - */ -ControlsWaiter.prototype.toggleBakeButtonFunction = function(cancel) { - const bakeButton = document.getElementById("bake"), - btnText = bakeButton.querySelector("span"); - - if (cancel) { - btnText.innerText = "Cancel"; - bakeButton.classList.remove("btn-success"); - bakeButton.classList.add("btn-danger"); - } else { - btnText.innerText = "Bake!"; - bakeButton.classList.remove("btn-danger"); - bakeButton.classList.add("btn-success"); - } -}; - -export default ControlsWaiter; diff --git a/src/web/ControlsWaiter.mjs b/src/web/ControlsWaiter.mjs new file mode 100755 index 00000000..a3f74e29 --- /dev/null +++ b/src/web/ControlsWaiter.mjs @@ -0,0 +1,394 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils"; +import {toBase64} from "../core/lib/Base64"; + + +/** + * Waiter to handle events related to the CyberChef controls (i.e. Bake, Step, Save, Load etc.) + */ +class ControlsWaiter { + + /** + * ControlsWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + } + + + /** + * Initialise Bootstrap componenets + */ + initComponents() { + $("body").bootstrapMaterialDesign(); + $("[data-toggle=tooltip]").tooltip({ + animation: false, + container: "body", + boundary: "viewport", + trigger: "hover" + }); + } + + + /** + * Checks or unchecks the Auto Bake checkbox based on the given value. + * + * @param {boolean} value - The new value for Auto Bake. + */ + setAutoBake(value) { + const autoBakeCheckbox = document.getElementById("auto-bake"); + + if (autoBakeCheckbox.checked !== value) { + autoBakeCheckbox.click(); + } + } + + + /** + * Handler to trigger baking. + */ + bakeClick() { + if (document.getElementById("bake").textContent.indexOf("Bake") > 0) { + this.app.bake(); + } else { + this.manager.worker.cancelBake(); + } + } + + + /** + * Handler for the 'Step through' command. Executes the next step of the recipe. + */ + stepClick() { + this.app.bake(true); + } + + + /** + * Handler for changes made to the Auto Bake checkbox. + */ + autoBakeChange() { + this.app.autoBake_ = document.getElementById("auto-bake").checked; + } + + + /** + * Handler for the 'Clear recipe' command. Removes all operations from the recipe. + */ + clearRecipeClick() { + this.manager.recipe.clearRecipe(); + } + + + /** + * Populates the save disalog box with a URL incorporating the recipe and input. + * + * @param {Object[]} [recipeConfig] - The recipe configuration object array. + */ + initialiseSaveLink(recipeConfig) { + recipeConfig = recipeConfig || this.app.getRecipeConfig(); + + const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked; + const includeInput = document.getElementById("save-link-input-checkbox").checked; + const saveLinkEl = document.getElementById("save-link"); + const saveLink = this.generateStateUrl(includeRecipe, includeInput, recipeConfig); + + saveLinkEl.innerHTML = Utils.truncate(saveLink, 120); + saveLinkEl.setAttribute("href", saveLink); + } + + + /** + * Generates a URL containing the current recipe and input state. + * + * @param {boolean} includeRecipe - Whether to include the recipe in the URL. + * @param {boolean} includeInput - Whether to include the input in the URL. + * @param {Object[]} [recipeConfig] - The recipe configuration object array. + * @param {string} [baseURL] - The CyberChef URL, set to the current URL if not included + * @returns {string} + */ + generateStateUrl(includeRecipe, includeInput, recipeConfig, baseURL) { + recipeConfig = recipeConfig || this.app.getRecipeConfig(); + + const link = baseURL || window.location.protocol + "//" + + window.location.host + + window.location.pathname; + const recipeStr = Utils.generatePrettyRecipe(recipeConfig); + 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) + includeInput = includeInput && (inputStr.length > 0) && (inputStr.length <= 68267); + + const params = [ + includeRecipe ? ["recipe", recipeStr] : undefined, + includeInput ? ["input", inputStr] : undefined, + ]; + + const hash = params + .filter(v => v) + .map(([key, value]) => `${key}=${Utils.encodeURIFragment(value)}`) + .join("&"); + + if (hash) { + return `${link}#${hash}`; + } + + return link; + } + + + /** + * Handler for changes made to the save dialog text area. Re-initialises the save link. + */ + saveTextChange(e) { + try { + const recipeConfig = Utils.parseRecipeConfig(e.target.value); + this.initialiseSaveLink(recipeConfig); + } catch (err) {} + } + + + /** + * Handler for the 'Save' command. Pops up the save dialog box. + */ + saveClick() { + const recipeConfig = this.app.getRecipeConfig(); + const recipeStr = JSON.stringify(recipeConfig); + + document.getElementById("save-text-chef").value = Utils.generatePrettyRecipe(recipeConfig, true); + document.getElementById("save-text-clean").value = JSON.stringify(recipeConfig, null, 2) + .replace(/{\n\s+"/g, "{ \"") + .replace(/\[\n\s{3,}/g, "[") + .replace(/\n\s{3,}]/g, "]") + .replace(/\s*\n\s*}/g, " }") + .replace(/\n\s{6,}/g, " "); + document.getElementById("save-text-compact").value = recipeStr; + + this.initialiseSaveLink(recipeConfig); + $("#save-modal").modal(); + } + + + /** + * Handler for the save link recipe checkbox change event. + */ + slrCheckChange() { + this.initialiseSaveLink(); + } + + + /** + * Handler for the save link input checkbox change event. + */ + sliCheckChange() { + this.initialiseSaveLink(); + } + + + /** + * Handler for the 'Load' command. Pops up the load dialog box. + */ + loadClick() { + this.populateLoadRecipesList(); + $("#load-modal").modal(); + } + + + /** + * Saves the recipe specified in the save textarea to local storage. + */ + saveButtonClick() { + if (!this.app.isLocalStorageAvailable()) { + this.app.alert( + "Your security settings do not allow access to local storage so your recipe cannot be saved.", + 5000 + ); + return false; + } + + const recipeName = Utils.escapeHtml(document.getElementById("save-name").value); + const recipeStr = document.querySelector("#save-texts .tab-pane.active textarea").value; + + if (!recipeName) { + this.app.alert("Please enter a recipe name", 3000); + return; + } + + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + let recipeId = localStorage.recipeId || 0; + + savedRecipes.push({ + id: ++recipeId, + name: recipeName, + recipe: recipeStr + }); + + localStorage.savedRecipes = JSON.stringify(savedRecipes); + localStorage.recipeId = recipeId; + + this.app.alert(`Recipe saved as "${recipeName}".`, 3000); + } + + + /** + * Populates the list of saved recipes in the load dialog box from local storage. + */ + populateLoadRecipesList() { + if (!this.app.isLocalStorageAvailable()) return false; + + const loadNameEl = document.getElementById("load-name"); + + // Remove current recipes from select + let i = loadNameEl.options.length; + while (i--) { + loadNameEl.remove(i); + } + + // Add recipes to select + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + + for (i = 0; i < savedRecipes.length; i++) { + const opt = document.createElement("option"); + opt.value = savedRecipes[i].id; + // Unescape then re-escape in case localStorage has been corrupted + opt.innerHTML = Utils.escapeHtml(Utils.unescapeHtml(savedRecipes[i].name)); + + loadNameEl.appendChild(opt); + } + + // Populate textarea with first recipe + const loadText = document.getElementById("load-text"); + const evt = new Event("change"); + loadText.value = savedRecipes.length ? savedRecipes[0].recipe : ""; + loadText.dispatchEvent(evt); + } + + + /** + * Removes the currently selected recipe from local storage. + */ + loadDeleteClick() { + if (!this.app.isLocalStorageAvailable()) return false; + + const id = parseInt(document.getElementById("load-name").value, 10); + const rawSavedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + + const savedRecipes = rawSavedRecipes.filter(r => r.id !== id); + + localStorage.savedRecipes = JSON.stringify(savedRecipes); + this.populateLoadRecipesList(); + } + + + /** + * Displays the selected recipe in the load text box. + */ + loadNameChange(e) { + if (!this.app.isLocalStorageAvailable()) return false; + + const el = e.target; + const savedRecipes = localStorage.savedRecipes ? + JSON.parse(localStorage.savedRecipes) : []; + const id = parseInt(el.value, 10); + + const recipe = savedRecipes.find(r => r.id === id); + + document.getElementById("load-text").value = recipe.recipe; + } + + + /** + * Loads the selected recipe and populates the Recipe with its operations. + */ + loadButtonClick() { + try { + const recipeConfig = Utils.parseRecipeConfig(document.getElementById("load-text").value); + this.app.setRecipeConfig(recipeConfig); + this.app.autoBake(); + + $("#rec-list [data-toggle=popover]").popover(); + } catch (e) { + this.app.alert("Invalid recipe", 2000); + } + } + + + /** + * Populates the bug report information box with useful technical info. + * + * @param {event} e + */ + supportButtonClick(e) { + e.preventDefault(); + + const reportBugInfo = document.getElementById("report-bug-info"); + const saveLink = this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/"); + + if (reportBugInfo) { + reportBugInfo.innerHTML = `* Version: ${PKG_VERSION + (typeof INLINE === "undefined" ? "" : "s")} +* Compile time: ${COMPILE_TIME} +* User-Agent: +${navigator.userAgent} +* [Link to reproduce](${saveLink}) + +`; + } + } + + + /** + * Shows the stale indicator to show that the input or recipe has changed + * since the last bake. + */ + showStaleIndicator() { + const staleIndicator = document.getElementById("stale-indicator"); + staleIndicator.classList.remove("hidden"); + } + + + /** + * Hides the stale indicator to show that the input or recipe has not changed + * since the last bake. + */ + hideStaleIndicator() { + const staleIndicator = document.getElementById("stale-indicator"); + staleIndicator.classList.add("hidden"); + } + + + /** + * Switches the Bake button between 'Bake' and 'Cancel' functions. + * + * @param {boolean} cancel - Whether to change to cancel or not + */ + toggleBakeButtonFunction(cancel) { + const bakeButton = document.getElementById("bake"), + btnText = bakeButton.querySelector("span"); + + if (cancel) { + btnText.innerText = "Cancel"; + bakeButton.classList.remove("btn-success"); + bakeButton.classList.add("btn-danger"); + } else { + btnText.innerText = "Bake!"; + bakeButton.classList.remove("btn-danger"); + bakeButton.classList.add("btn-success"); + } + } + +} + +export default ControlsWaiter; diff --git a/src/web/HTMLCategory.js b/src/web/HTMLCategory.js deleted file mode 100755 index e3a168ba..00000000 --- a/src/web/HTMLCategory.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Object to handle the creation of operation categories. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {string} name - The name of the category. - * @param {boolean} selected - Whether this category is pre-selected or not. - */ -const HTMLCategory = function(name, selected) { - this.name = name; - this.selected = selected; - this.opList = []; -}; - - -/** - * Adds an operation to this category. - * - * @param {HTMLOperation} operation - The operation to add. - */ -HTMLCategory.prototype.addOperation = function(operation) { - this.opList.push(operation); -}; - - -/** - * Renders the category and all operations within it in HTML. - * - * @returns {string} - */ -HTMLCategory.prototype.toHtml = function() { - const catName = "cat" + this.name.replace(/[\s/-:_]/g, ""); - let html = "
\ - \ - " + this.name + "\ - \ -
    "; - - for (let i = 0; i < this.opList.length; i++) { - html += this.opList[i].toStubHtml(); - } - - html += "
"; - return html; -}; - -export default HTMLCategory; diff --git a/src/web/HTMLCategory.mjs b/src/web/HTMLCategory.mjs new file mode 100755 index 00000000..95b8a4df --- /dev/null +++ b/src/web/HTMLCategory.mjs @@ -0,0 +1,59 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Object to handle the creation of operation categories. + */ +class HTMLCategory { + + /** + * HTMLCategory constructor. + * + * @param {string} name - The name of the category. + * @param {boolean} selected - Whether this category is pre-selected or not. + */ + constructor(name, selected) { + this.name = name; + this.selected = selected; + this.opList = []; + } + + + /** + * Adds an operation to this category. + * + * @param {HTMLOperation} operation - The operation to add. + */ + addOperation(operation) { + this.opList.push(operation); + } + + + /** + * Renders the category and all operations within it in HTML. + * + * @returns {string} + */ + toHtml() { + const catName = "cat" + this.name.replace(/[\s/-:_]/g, ""); + let html = `
+ + ${this.name} + +
+
    `; + + for (let i = 0; i < this.opList.length; i++) { + html += this.opList[i].toStubHtml(); + } + + html += "
"; + return html; + } + +} + +export default HTMLCategory; diff --git a/src/web/HTMLIngredient.js b/src/web/HTMLIngredient.js deleted file mode 100755 index 3f360f04..00000000 --- a/src/web/HTMLIngredient.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * Object to handle the creation of operation ingredients. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {Object} config - The configuration object for this ingredient. - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const HTMLIngredient = function(config, app, manager) { - this.app = app; - this.manager = manager; - - this.name = config.name; - this.type = config.type; - this.value = config.value; - this.disabled = config.disabled || false; - this.disableArgs = config.disableArgs || false; - this.placeholder = config.placeholder || false; - this.target = config.target; - this.toggleValues = config.toggleValues; - this.id = "ing-" + this.app.nextIngId(); -}; - - -/** - * Renders the ingredient in HTML. - * - * @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 ? "" : "
 
", - i, m; - - html += "
"; - - switch (this.type) { - case "string": - case "binaryString": - case "byteArray": - html += ""; - break; - case "shortString": - case "binaryShortString": - html += ""; - break; - case "toggleString": - html += "
\ -
"; - break; - case "number": - html += ""; - break; - case "boolean": - html += ""; - - if (this.disableArgs) { - this.manager.addDynamicListener("#" + this.id, "click", this.toggleDisableArgs, this); - } - break; - case "option": - html += ""; - break; - case "populateOption": - html += ""; - - this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this); - break; - case "editableOption": - html += "
"; - html += ""; - html += ""; - html += "
"; - - - this.manager.addDynamicListener("#sel-" + this.id, "change", this.editableOptionChange, this); - break; - case "text": - html += ""; - break; - default: - break; - } - html += "
"; - - return html; -}; - - -/** - * Handler for argument disable toggle. - * Toggles disabled state for all arguments in the disableArgs list for this ingredient. - * - * @param {event} e - */ -HTMLIngredient.prototype.toggleDisableArgs = function(e) { - const el = e.target; - const op = el.parentNode.parentNode; - const args = op.querySelectorAll(".arg-group"); - - for (let i = 0; i < this.disableArgs.length; i++) { - const els = args[this.disableArgs[i]].querySelectorAll("input, select, button"); - - for (let j = 0; j < els.length; j++) { - if (els[j].getAttribute("disabled")) { - els[j].removeAttribute("disabled"); - } else { - els[j].setAttribute("disabled", "disabled"); - } - } - } - - this.manager.recipe.ingChange(); -}; - - -/** - * Handler for populate option changes. - * Populates the relevant argument with the specified value. - * - * @param {event} e - */ -HTMLIngredient.prototype.populateOptionChange = function(e) { - const el = e.target; - const op = el.parentNode.parentNode; - const target = op.querySelectorAll(".arg-group")[this.target].querySelector("input, select, textarea"); - - target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value"); - - this.manager.recipe.ingChange(); -}; - - -/** - * Handler for editable option changes. - * Populates the input box with the selected value. - * - * @param {event} e - */ -HTMLIngredient.prototype.editableOptionChange = function(e) { - let select = e.target, - input = select.nextSibling; - - input.value = select.childNodes[select.selectedIndex].value; - - this.manager.recipe.ingChange(); -}; - -export default HTMLIngredient; diff --git a/src/web/HTMLIngredient.mjs b/src/web/HTMLIngredient.mjs new file mode 100755 index 00000000..f94f8ca6 --- /dev/null +++ b/src/web/HTMLIngredient.mjs @@ -0,0 +1,255 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Object to handle the creation of operation ingredients. + */ +class HTMLIngredient { + + /** + * HTMLIngredient constructor. + * + * @param {Object} config - The configuration object for this ingredient. + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(config, app, manager) { + this.app = app; + this.manager = manager; + + this.name = config.name; + this.type = config.type; + this.value = config.value; + this.disabled = config.disabled || false; + this.hint = config.hint || false; + this.target = config.target; + this.toggleValues = config.toggleValues; + this.id = "ing-" + this.app.nextIngId(); + } + + + /** + * Renders the ingredient in HTML. + * + * @returns {string} + */ + toHtml() { + let html = "", + i, m; + + switch (this.type) { + case "string": + case "binaryString": + case "byteArray": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + case "shortString": + case "binaryShortString": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + case "toggleString": + html += `
+
+ + + ${this.hint ? "" + this.hint + "" : ""} +
+
+ + +
+ +
`; + break; + case "number": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + case "boolean": + html += `
+
+ + ${this.hint ? "" + this.hint + "" : ""} +
+
`; + break; + case "option": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + case "populateOption": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + + this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this); + break; + case "editableOption": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
+ + +
+
`; + + this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this); + break; + case "text": + html += `
+ + + ${this.hint ? "" + this.hint + "" : ""} +
`; + break; + default: + break; + } + + return html; + } + + + /** + * Handler for populate option changes. + * Populates the relevant argument with the specified value. + * + * @param {event} e + */ + populateOptionChange(e) { + const el = e.target; + const op = el.parentNode.parentNode; + const target = op.querySelectorAll(".arg")[this.target]; + + target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value"); + const evt = new Event("change"); + target.dispatchEvent(evt); + + this.manager.recipe.ingChange(); + } + + + /** + * Handler for editable option clicks. + * Populates the input box with the selected value. + * + * @param {event} e + */ + editableOptionClick(e) { + e.preventDefault(); + e.stopPropagation(); + + const link = e.target, + input = link.parentNode.parentNode.parentNode.querySelector("input"); + + input.value = link.getAttribute("value"); + const evt = new Event("change"); + input.dispatchEvent(evt); + + this.manager.recipe.ingChange(); + } + +} + +export default HTMLIngredient; diff --git a/src/web/HTMLOperation.js b/src/web/HTMLOperation.js deleted file mode 100755 index e709066f..00000000 --- a/src/web/HTMLOperation.js +++ /dev/null @@ -1,128 +0,0 @@ -import HTMLIngredient from "./HTMLIngredient.js"; - - -/** - * Object to handle the creation of operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {string} name - The name of the operation. - * @param {Object} config - The configuration object for this operation. - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const HTMLOperation = function(name, config, app, manager) { - this.app = app; - this.manager = manager; - - this.name = name; - this.description = config.description; - this.manualBake = config.manualBake || false; - this.config = config; - this.ingList = []; - - for (let i = 0; i < config.args.length; i++) { - const ing = new HTMLIngredient(config.args[i], this.app, this.manager); - this.ingList.push(ing); - } -}; - - -/** - * @constant - */ -HTMLOperation.INFO_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAByElEQVR4XqVTzWoaYRQ9KZJmoVaS1J1QiYTIuOgqi9lEugguQhYhdGs3hTyAi0CWJTvJIks30ZBNsimUtlqkVLoQCuJsphRriyFjabWtEyf/Rv3iWcwwymTlgQuH851z5hu43wRGkEwmXwCIA4hiGAUAmUQikQbhEHwyGCWVSglVVUW73RYmyKnxjB56ncJ6NpsVxHGrI/ZLuniVb3DIqQmCHnrNkgcggNeSJPlisRgyJR2b737j/TcDsQUPwv6H5NR4BnroZcb6Z16N2PvyX6yna9Z8qp6JQ0Uf0ughmGHWBSAuyzJqrQ7eqKewY/dzE363C71e39LoWQq5wUwul4uzIBoIBHD01RgyrkZ8eDbvwUWnj623v2DHx4qB51IAzLIAXq8XP/7W0bUVVJtXWIk8wvlN364TA+/1IDMLwmWK/Hq3axmhaBdoGLeklm73ElaBYRgIzkyifHIOO4QQJKM3oJcZq6CgaVp0OTyHw9K/kQI4FiyHfdC0n2CWe5ApFosIPZ7C2tNpXpcDOehGyD/FIbd0euhlhllzFxRzC3fydbG4XRYbB9/tQ41n9m1U7l3lyp9LkfygiZeZCoecmtMqj/+Yxn7Od3v0j50qCO3zAAAAAElFTkSuQmCC"; -/** - * @constant - */ -HTMLOperation.REMOVE_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwklEQVR42qRTPU8CQRB9K2CCMRJ6NTQajOUaqfxIbLCRghhjQixosLAgFNBQ3l8wsabxLxBJbCyVUBiMCVQEQkOEKBbCnefM3p4eohWXzM3uvHlv52b2hG3bmOWZw4yPn1/XQkCQ9wFxcgZZ0QLKpifpN8Z1n1L13griBBjHhYK0nMT4b+wom53ClAAFQacZJ/m8rNfrSOZy0vxJjPP6IJ2WzWYTO6mUwiwtILiJJSHUKVSWkchkZK1WQzQaxU2pVGUglkjIbreLUCiEx0qlStlFCpfPiPstYDtVKJH9ZFI2Gw1FGA6H6LTbCAaDeGu1FJl6UuYjpwTGzucokZW1NfnS66kyfT4fXns9RaZmlgNcuhZQU+jowLzuOK/HgwEW3E5ZlhLXVWKk11P3wNYNWw+HZdA0sUgx1zjGmD05nckx0ilGjBJdUq3fr7K5e8bGf43RdL7fOPSQb4lI8SLbrUfkUIuY32VTI1bJn5BqDnh4Dodt9ryPUDzyD7aquWoKQohl2i9sAbubwPkTcHkP3FHsg+yT+7sN7G0AF3Xg6sHB3onbdgWWKBDQg/BcTuVt51dQA/JrnIcyIu6rmPV3/hJgACPc0BMEYTg+AAAAAElFTkSuQmCC"; - - -/** - * Renders the operation in HTML as a stub operation with no ingredients. - * - * @returns {string} - */ -HTMLOperation.prototype.toStubHtml = function(removeIcon) { - let html = "
  • "; - } - - if (this.description) { - html += ""; - } - - html += "
  • "; - - return html; -}; - - -/** - * Renders the operation in HTML as a full operation with ingredients. - * - * @returns {string} - */ -HTMLOperation.prototype.toFullHtml = function() { - let html = "
    " + this.name + "
    "; - - for (let i = 0; i < this.ingList.length; i++) { - html += this.ingList[i].toHtml(); - } - - html += "
    \ -
    \ -
    "; - - html += "
    \ -
     
    "; - - return html; -}; - - -/** - * Highlights the searched string in the name and description of the operation. - * - * @param {string} searchStr - * @param {number} namePos - The position of the search string in the operation name - * @param {number} descPos - The position of the search string in the operation description - */ -HTMLOperation.prototype.highlightSearchString = function(searchStr, namePos, descPos) { - if (namePos >= 0) { - this.name = this.name.slice(0, namePos) + "" + - this.name.slice(namePos, namePos + searchStr.length) + "" + - this.name.slice(namePos + searchStr.length); - } - - if (this.description && descPos >= 0) { - // Find HTML tag offsets - const re = /<[^>]+>/g; - let match; - while ((match = re.exec(this.description))) { - // If the search string occurs within an HTML tag, return without highlighting it. - if (descPos >= match.index && descPos <= (match.index + match[0].length)) - return; - } - - this.description = this.description.slice(0, descPos) + "" + - this.description.slice(descPos, descPos + searchStr.length) + "" + - this.description.slice(descPos + searchStr.length); - } -}; - -export default HTMLOperation; diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs new file mode 100755 index 00000000..1a6146e4 --- /dev/null +++ b/src/web/HTMLOperation.mjs @@ -0,0 +1,122 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import HTMLIngredient from "./HTMLIngredient"; + + +/** + * Object to handle the creation of operations. + */ +class HTMLOperation { + + /** + * HTMLOperation constructor. + * + * @param {string} name - The name of the operation. + * @param {Object} config - The configuration object for this operation. + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(name, config, app, manager) { + this.app = app; + this.manager = manager; + + this.name = name; + this.description = config.description; + this.manualBake = config.manualBake || false; + this.config = config; + this.ingList = []; + + for (let i = 0; i < config.args.length; i++) { + const ing = new HTMLIngredient(config.args[i], this.app, this.manager); + this.ingList.push(ing); + } + } + + + /** + * Renders the operation in HTML as a stub operation with no ingredients. + * + * @returns {string} + */ + toStubHtml(removeIcon) { + let html = "
  • ${this.name} +
    `; + + for (let i = 0; i < this.ingList.length; i++) { + html += this.ingList[i].toHtml(); + } + + html += `
    +
    + pause + not_interested +
    +
     
    `; + + return html; + } + + + /** + * Highlights the searched string in the name and description of the operation. + * + * @param {string} searchStr + * @param {number} namePos - The position of the search string in the operation name + * @param {number} descPos - The position of the search string in the operation description + */ + highlightSearchString(searchStr, namePos, descPos) { + if (namePos >= 0) { + this.name = this.name.slice(0, namePos) + "" + + this.name.slice(namePos, namePos + searchStr.length) + "" + + this.name.slice(namePos + searchStr.length); + } + + if (this.description && descPos >= 0) { + // Find HTML tag offsets + const re = /<[^>]+>/g; + let match; + while ((match = re.exec(this.description))) { + // If the search string occurs within an HTML tag, return without highlighting it. + if (descPos >= match.index && descPos <= (match.index + match[0].length)) + return; + } + + this.description = this.description.slice(0, descPos) + "" + + this.description.slice(descPos, descPos + searchStr.length) + "" + + this.description.slice(descPos + searchStr.length); + } + } + +} + +export default HTMLOperation; diff --git a/src/web/HighlighterWaiter.js b/src/web/HighlighterWaiter.js deleted file mode 100755 index 6e4ca599..00000000 --- a/src/web/HighlighterWaiter.js +++ /dev/null @@ -1,461 +0,0 @@ -/** - * Waiter to handle events related to highlighting in CyberChef. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const HighlighterWaiter = function(app, manager) { - this.app = app; - this.manager = manager; - - this.mouseButtonDown = false; - this.mouseTarget = null; -}; - - -/** - * HighlighterWaiter data type enum for the input. - * @readonly - * @enum - */ -HighlighterWaiter.INPUT = 0; -/** - * HighlighterWaiter data type enum for the output. - * @readonly - * @enum - */ -HighlighterWaiter.OUTPUT = 1; - - -/** - * Determines if the current text selection is running backwards or forwards. - * StackOverflow answer id: 12652116 - * - * @private - * @returns {boolean} - */ -HighlighterWaiter.prototype._isSelectionBackwards = function() { - let backwards = false, - sel = window.getSelection(); - - if (!sel.isCollapsed) { - const range = document.createRange(); - range.setStart(sel.anchorNode, sel.anchorOffset); - range.setEnd(sel.focusNode, sel.focusOffset); - backwards = range.collapsed; - range.detach(); - } - return backwards; -}; - - -/** - * Calculates the text offset of a position in an HTML element, ignoring HTML tags. - * - * @private - * @param {element} node - The parent HTML node. - * @param {number} offset - The offset since the last HTML element. - * @returns {number} - */ -HighlighterWaiter.prototype._getOutputHtmlOffset = function(node, offset) { - const sel = window.getSelection(); - const range = document.createRange(); - - range.selectNodeContents(document.getElementById("output-html")); - range.setEnd(node, offset); - sel.removeAllRanges(); - sel.addRange(range); - - return sel.toString().length; -}; - - -/** - * Gets the current selection offsets in the output HTML, ignoring HTML tags. - * - * @private - * @returns {Object} pos - * @returns {number} pos.start - * @returns {number} pos.end - */ -HighlighterWaiter.prototype._getOutputHtmlSelectionOffsets = function() { - const sel = window.getSelection(); - let range, - start = 0, - end = 0, - backwards = false; - - if (sel.rangeCount) { - range = sel.getRangeAt(sel.rangeCount - 1); - backwards = this._isSelectionBackwards(); - start = this._getOutputHtmlOffset(range.startContainer, range.startOffset); - end = this._getOutputHtmlOffset(range.endContainer, range.endOffset); - sel.removeAllRanges(); - sel.addRange(range); - - if (backwards) { - // If selecting backwards, reverse the start and end offsets for the selection to - // prevent deselecting as the drag continues. - sel.collapseToEnd(); - sel.extend(sel.anchorNode, range.startOffset); - } - } - - return { - start: start, - end: end - }; -}; - - -/** - * Handler for input scroll events. - * Scrolls the highlighter pane to match the input textarea position. - * - * @param {event} e - */ -HighlighterWaiter.prototype.inputScroll = function(e) { - const el = e.target; - document.getElementById("input-highlighter").scrollTop = el.scrollTop; - document.getElementById("input-highlighter").scrollLeft = el.scrollLeft; -}; - - -/** - * Handler for output scroll events. - * Scrolls the highlighter pane to match the output textarea position. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputScroll = function(e) { - const el = e.target; - document.getElementById("output-highlighter").scrollTop = el.scrollTop; - document.getElementById("output-highlighter").scrollLeft = el.scrollLeft; -}; - - -/** - * Handler for input mousedown events. - * Calculates the current selection info, and highlights the corresponding data in the output. - * - * @param {event} e - */ -HighlighterWaiter.prototype.inputMousedown = function(e) { - this.mouseButtonDown = true; - this.mouseTarget = HighlighterWaiter.INPUT; - this.removeHighlights(); - - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; - - if (start !== 0 || end !== 0) { - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); - this.highlightOutput([{start: start, end: end}]); - } -}; - - -/** - * Handler for output mousedown events. - * Calculates the current selection info, and highlights the corresponding data in the input. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputMousedown = function(e) { - this.mouseButtonDown = true; - this.mouseTarget = HighlighterWaiter.OUTPUT; - this.removeHighlights(); - - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; - - if (start !== 0 || end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); - this.highlightInput([{start: start, end: end}]); - } -}; - - -/** - * Handler for output HTML mousedown events. - * Calculates the current selection info. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputHtmlMousedown = function(e) { - this.mouseButtonDown = true; - this.mouseTarget = HighlighterWaiter.OUTPUT; - - const sel = this._getOutputHtmlSelectionOffsets(); - if (sel.start !== 0 || sel.end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); - } -}; - - -/** - * Handler for input mouseup events. - * - * @param {event} e - */ -HighlighterWaiter.prototype.inputMouseup = function(e) { - this.mouseButtonDown = false; -}; - - -/** - * Handler for output mouseup events. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputMouseup = function(e) { - this.mouseButtonDown = false; -}; - - -/** - * Handler for output HTML mouseup events. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputHtmlMouseup = function(e) { - this.mouseButtonDown = false; -}; - - -/** - * Handler for input mousemove events. - * Calculates the current selection info, and highlights the corresponding data in the output. - * - * @param {event} e - */ -HighlighterWaiter.prototype.inputMousemove = function(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== HighlighterWaiter.INPUT) - return; - - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; - - if (start !== 0 || end !== 0) { - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); - this.highlightOutput([{start: start, end: end}]); - } -}; - - -/** - * Handler for output mousemove events. - * Calculates the current selection info, and highlights the corresponding data in the input. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputMousemove = function(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== HighlighterWaiter.OUTPUT) - return; - - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; - - if (start !== 0 || end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); - this.highlightInput([{start: start, end: end}]); - } -}; - - -/** - * Handler for output HTML mousemove events. - * Calculates the current selection info. - * - * @param {event} e - */ -HighlighterWaiter.prototype.outputHtmlMousemove = function(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== HighlighterWaiter.OUTPUT) - return; - - const sel = this._getOutputHtmlSelectionOffsets(); - if (sel.start !== 0 || sel.end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); - } -}; - - -/** - * Given start and end offsets, writes the HTML for the selection info element with the correct - * padding. - * - * @param {number} start - The start offset. - * @param {number} end - The end offset. - * @returns {string} - */ -HighlighterWaiter.prototype.selectionInfo = function(start, end) { - const len = end.toString().length; - const width = len < 2 ? 2 : len; - const startStr = start.toString().padStart(width, " ").replace(/ /g, " "); - const endStr = end.toString().padStart(width, " ").replace(/ /g, " "); - const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, " "); - - return "start: " + startStr + "
    end: " + endStr + "
    length: " + lenStr; -}; - - -/** - * Removes highlighting and selection information. - */ -HighlighterWaiter.prototype.removeHighlights = function() { - document.getElementById("input-highlighter").innerHTML = ""; - document.getElementById("output-highlighter").innerHTML = ""; - document.getElementById("input-selection-info").innerHTML = ""; - document.getElementById("output-selection-info").innerHTML = ""; -}; - - -/** - * Highlights the given offsets in the output. - * We will only highlight if: - * - input hasn't changed since last bake - * - last bake was a full bake - * - all operations in the recipe support highlighting - * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - */ -HighlighterWaiter.prototype.highlightOutput = function(pos) { - if (!this.app.autoBake_ || this.app.baking) return false; - this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos); -}; - - -/** - * Highlights the given offsets in the input. - * We will only highlight if: - * - input hasn't changed since last bake - * - last bake was a full bake - * - all operations in the recipe support highlighting - * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - */ -HighlighterWaiter.prototype.highlightInput = function(pos) { - if (!this.app.autoBake_ || this.app.baking) return false; - this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos); -}; - - -/** - * Displays highlight offsets sent back from the Chef. - * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - * @param {string} direction - */ -HighlighterWaiter.prototype.displayHighlights = function(pos, direction) { - if (!pos) return; - - const io = direction === "forward" ? "output" : "input"; - - document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); - this.highlight( - document.getElementById(io + "-text"), - document.getElementById(io + "-highlighter"), - pos); -}; - - -/** - * Adds the relevant HTML to the specified highlight element such that highlighting appears - * underneath the correct offset. - * - * @param {element} textarea - The input or output textarea. - * @param {element} highlighter - The input or output highlighter element. - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - */ -HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) { - if (!this.app.options.showHighlighter) return false; - if (!this.app.options.attemptHighlight) return false; - - // Check if there is a carriage return in the output dish as this will not - // be displayed by the HTML textarea and will mess up highlighting offsets. - if (this.manager.output.containsCR()) return false; - - const startPlaceholder = "[startHighlight]"; - const startPlaceholderRegex = /\[startHighlight\]/g; - const endPlaceholder = "[endHighlight]"; - const endPlaceholderRegex = /\[endHighlight\]/g; - let text = textarea.value; - - // Put placeholders in position - // If there's only one value, select that - // If there are multiple, ignore the first one and select all others - if (pos.length === 1) { - if (pos[0].end < pos[0].start) return; - text = text.slice(0, pos[0].start) + - startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + - text.slice(pos[0].end, text.length); - } else { - // O(n^2) - Can anyone improve this without overwriting placeholders? - let result = "", - endPlaced = true; - - for (let i = 0; i < text.length; i++) { - for (let j = 1; j < pos.length; j++) { - if (pos[j].end < pos[j].start) continue; - if (pos[j].start === i) { - result += startPlaceholder; - endPlaced = false; - } - if (pos[j].end === i) { - result += endPlaceholder; - endPlaced = true; - } - } - result += text[i]; - } - if (!endPlaced) result += endPlaceholder; - text = result; - } - - const cssClass = "hl1"; - //if (colour) cssClass += "-"+colour; - - // Remove HTML tags - text = text - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/\n/g, " ") - // Convert placeholders to tags - .replace(startPlaceholderRegex, "") - .replace(endPlaceholderRegex, "") + " "; - - // Adjust width to allow for scrollbars - highlighter.style.width = textarea.clientWidth + "px"; - highlighter.innerHTML = text; - highlighter.scrollTop = textarea.scrollTop; - highlighter.scrollLeft = textarea.scrollLeft; -}; - -export default HighlighterWaiter; diff --git a/src/web/HighlighterWaiter.mjs b/src/web/HighlighterWaiter.mjs new file mode 100755 index 00000000..99ae10b1 --- /dev/null +++ b/src/web/HighlighterWaiter.mjs @@ -0,0 +1,468 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * HighlighterWaiter data type enum for the input. + * @enum + */ +const INPUT = 0; + +/** + * HighlighterWaiter data type enum for the output. + * @enum + */ +const OUTPUT = 1; + + +/** + * Waiter to handle events related to highlighting in CyberChef. + */ +class HighlighterWaiter { + + /** + * HighlighterWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.mouseButtonDown = false; + this.mouseTarget = null; + } + + + /** + * Determines if the current text selection is running backwards or forwards. + * StackOverflow answer id: 12652116 + * + * @private + * @returns {boolean} + */ + _isSelectionBackwards() { + let backwards = false; + const sel = window.getSelection(); + + if (!sel.isCollapsed) { + const range = document.createRange(); + range.setStart(sel.anchorNode, sel.anchorOffset); + range.setEnd(sel.focusNode, sel.focusOffset); + backwards = range.collapsed; + range.detach(); + } + return backwards; + } + + + /** + * Calculates the text offset of a position in an HTML element, ignoring HTML tags. + * + * @private + * @param {element} node - The parent HTML node. + * @param {number} offset - The offset since the last HTML element. + * @returns {number} + */ + _getOutputHtmlOffset(node, offset) { + const sel = window.getSelection(); + const range = document.createRange(); + + range.selectNodeContents(document.getElementById("output-html")); + range.setEnd(node, offset); + sel.removeAllRanges(); + sel.addRange(range); + + return sel.toString().length; + } + + + /** + * Gets the current selection offsets in the output HTML, ignoring HTML tags. + * + * @private + * @returns {Object} pos + * @returns {number} pos.start + * @returns {number} pos.end + */ + _getOutputHtmlSelectionOffsets() { + const sel = window.getSelection(); + let range, + start = 0, + end = 0, + backwards = false; + + if (sel.rangeCount) { + range = sel.getRangeAt(sel.rangeCount - 1); + backwards = this._isSelectionBackwards(); + start = this._getOutputHtmlOffset(range.startContainer, range.startOffset); + end = this._getOutputHtmlOffset(range.endContainer, range.endOffset); + sel.removeAllRanges(); + sel.addRange(range); + + if (backwards) { + // If selecting backwards, reverse the start and end offsets for the selection to + // prevent deselecting as the drag continues. + sel.collapseToEnd(); + sel.extend(sel.anchorNode, range.startOffset); + } + } + + return { + start: start, + end: end + }; + } + + + /** + * Handler for input scroll events. + * Scrolls the highlighter pane to match the input textarea position. + * + * @param {event} e + */ + inputScroll(e) { + const el = e.target; + document.getElementById("input-highlighter").scrollTop = el.scrollTop; + document.getElementById("input-highlighter").scrollLeft = el.scrollLeft; + } + + + /** + * Handler for output scroll events. + * Scrolls the highlighter pane to match the output textarea position. + * + * @param {event} e + */ + outputScroll(e) { + const el = e.target; + document.getElementById("output-highlighter").scrollTop = el.scrollTop; + document.getElementById("output-highlighter").scrollLeft = el.scrollLeft; + } + + + /** + * Handler for input mousedown events. + * Calculates the current selection info, and highlights the corresponding data in the output. + * + * @param {event} e + */ + inputMousedown(e) { + this.mouseButtonDown = true; + this.mouseTarget = INPUT; + this.removeHighlights(); + + const el = e.target; + const start = el.selectionStart; + const end = el.selectionEnd; + + if (start !== 0 || end !== 0) { + document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); + this.highlightOutput([{start: start, end: end}]); + } + } + + + /** + * Handler for output mousedown events. + * Calculates the current selection info, and highlights the corresponding data in the input. + * + * @param {event} e + */ + outputMousedown(e) { + this.mouseButtonDown = true; + this.mouseTarget = OUTPUT; + this.removeHighlights(); + + const el = e.target; + const start = el.selectionStart; + const end = el.selectionEnd; + + if (start !== 0 || end !== 0) { + document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); + this.highlightInput([{start: start, end: end}]); + } + } + + + /** + * Handler for output HTML mousedown events. + * Calculates the current selection info. + * + * @param {event} e + */ + outputHtmlMousedown(e) { + this.mouseButtonDown = true; + this.mouseTarget = OUTPUT; + + const sel = this._getOutputHtmlSelectionOffsets(); + if (sel.start !== 0 || sel.end !== 0) { + document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); + } + } + + + /** + * Handler for input mouseup events. + * + * @param {event} e + */ + inputMouseup(e) { + this.mouseButtonDown = false; + } + + + /** + * Handler for output mouseup events. + * + * @param {event} e + */ + outputMouseup(e) { + this.mouseButtonDown = false; + } + + + /** + * Handler for output HTML mouseup events. + * + * @param {event} e + */ + outputHtmlMouseup(e) { + this.mouseButtonDown = false; + } + + + /** + * Handler for input mousemove events. + * Calculates the current selection info, and highlights the corresponding data in the output. + * + * @param {event} e + */ + inputMousemove(e) { + // Check that the left mouse button is pressed + if (!this.mouseButtonDown || + e.which !== 1 || + this.mouseTarget !== INPUT) + return; + + const el = e.target; + const start = el.selectionStart; + const end = el.selectionEnd; + + if (start !== 0 || end !== 0) { + document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); + this.highlightOutput([{start: start, end: end}]); + } + } + + + /** + * Handler for output mousemove events. + * Calculates the current selection info, and highlights the corresponding data in the input. + * + * @param {event} e + */ + outputMousemove(e) { + // Check that the left mouse button is pressed + if (!this.mouseButtonDown || + e.which !== 1 || + this.mouseTarget !== OUTPUT) + return; + + const el = e.target; + const start = el.selectionStart; + const end = el.selectionEnd; + + if (start !== 0 || end !== 0) { + document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); + this.highlightInput([{start: start, end: end}]); + } + } + + + /** + * Handler for output HTML mousemove events. + * Calculates the current selection info. + * + * @param {event} e + */ + outputHtmlMousemove(e) { + // Check that the left mouse button is pressed + if (!this.mouseButtonDown || + e.which !== 1 || + this.mouseTarget !== OUTPUT) + return; + + const sel = this._getOutputHtmlSelectionOffsets(); + if (sel.start !== 0 || sel.end !== 0) { + document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); + } + } + + + /** + * Given start and end offsets, writes the HTML for the selection info element with the correct + * padding. + * + * @param {number} start - The start offset. + * @param {number} end - The end offset. + * @returns {string} + */ + selectionInfo(start, end) { + const len = end.toString().length; + const width = len < 2 ? 2 : len; + const startStr = start.toString().padStart(width, " ").replace(/ /g, " "); + const endStr = end.toString().padStart(width, " ").replace(/ /g, " "); + const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, " "); + + return "start: " + startStr + "
    end: " + endStr + "
    length: " + lenStr; + } + + + /** + * Removes highlighting and selection information. + */ + removeHighlights() { + document.getElementById("input-highlighter").innerHTML = ""; + document.getElementById("output-highlighter").innerHTML = ""; + document.getElementById("input-selection-info").innerHTML = ""; + document.getElementById("output-selection-info").innerHTML = ""; + } + + + /** + * Highlights the given offsets in the output. + * We will only highlight if: + * - input hasn't changed since last bake + * - last bake was a full bake + * - all operations in the recipe support highlighting + * + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ + highlightOutput(pos) { + if (!this.app.autoBake_ || this.app.baking) return false; + this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos); + } + + + /** + * Highlights the given offsets in the input. + * We will only highlight if: + * - input hasn't changed since last bake + * - last bake was a full bake + * - all operations in the recipe support highlighting + * + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ + highlightInput(pos) { + if (!this.app.autoBake_ || this.app.baking) return false; + this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos); + } + + + /** + * Displays highlight offsets sent back from the Chef. + * + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + * @param {string} direction + */ + displayHighlights(pos, direction) { + if (!pos) return; + + const io = direction === "forward" ? "output" : "input"; + + document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); + this.highlight( + document.getElementById(io + "-text"), + document.getElementById(io + "-highlighter"), + pos); + } + + + /** + * Adds the relevant HTML to the specified highlight element such that highlighting appears + * underneath the correct offset. + * + * @param {element} textarea - The input or output textarea. + * @param {element} highlighter - The input or output highlighter element. + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + */ + async highlight(textarea, highlighter, pos) { + if (!this.app.options.showHighlighter) return false; + if (!this.app.options.attemptHighlight) return false; + + // Check if there is a carriage return in the output dish as this will not + // be displayed by the HTML textarea and will mess up highlighting offsets. + if (await this.manager.output.containsCR()) return false; + + const startPlaceholder = "[startHighlight]"; + const startPlaceholderRegex = /\[startHighlight\]/g; + const endPlaceholder = "[endHighlight]"; + const endPlaceholderRegex = /\[endHighlight\]/g; + let text = textarea.value; + + // Put placeholders in position + // If there's only one value, select that + // If there are multiple, ignore the first one and select all others + if (pos.length === 1) { + if (pos[0].end < pos[0].start) return; + text = text.slice(0, pos[0].start) + + startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + + text.slice(pos[0].end, text.length); + } else { + // O(n^2) - Can anyone improve this without overwriting placeholders? + let result = "", + endPlaced = true; + + for (let i = 0; i < text.length; i++) { + for (let j = 1; j < pos.length; j++) { + if (pos[j].end < pos[j].start) continue; + if (pos[j].start === i) { + result += startPlaceholder; + endPlaced = false; + } + if (pos[j].end === i) { + result += endPlaceholder; + endPlaced = true; + } + } + result += text[i]; + } + if (!endPlaced) result += endPlaceholder; + text = result; + } + + const cssClass = "hl1"; + //if (colour) cssClass += "-"+colour; + + // Remove HTML tags + text = text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\n/g, " ") + // Convert placeholders to tags + .replace(startPlaceholderRegex, "") + .replace(endPlaceholderRegex, "") + " "; + + // Adjust width to allow for scrollbars + highlighter.style.width = textarea.clientWidth + "px"; + highlighter.innerHTML = text; + highlighter.scrollTop = textarea.scrollTop; + highlighter.scrollLeft = textarea.scrollLeft; + } + +} + +export default HighlighterWaiter; diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js deleted file mode 100755 index 0a04352e..00000000 --- a/src/web/InputWaiter.js +++ /dev/null @@ -1,321 +0,0 @@ -import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js"; -import Utils from "../core/Utils.js"; - - -/** - * Waiter to handle events related to the input. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const InputWaiter = function(app, manager) { - this.app = app; - this.manager = manager; - - // Define keys that don't change the input so we don't have to autobake when they are pressed - this.badKeys = [ - 16, //Shift - 17, //Ctrl - 18, //Alt - 19, //Pause - 20, //Caps - 27, //Esc - 33, 34, 35, 36, //PgUp, PgDn, End, Home - 37, 38, 39, 40, //Directional - 44, //PrntScrn - 91, 92, //Win - 93, //Context - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, //F1-12 - 144, //Num - 145, //Scroll - ]; - - this.loaderWorker = null; - this.fileBuffer = null; -}; - - -/** - * Gets the user's input from the input textarea. - * - * @returns {string} - */ -InputWaiter.prototype.get = function() { - return this.fileBuffer || document.getElementById("input-text").value; -}; - - -/** - * Sets the input in the input area. - * - * @param {string|File} input - * - * @fires Manager#statechange - */ -InputWaiter.prototype.set = function(input) { - const inputText = document.getElementById("input-text"); - if (input instanceof File) { - this.setFile(input); - inputText.value = ""; - this.setInputInfo(input.size, null); - } else { - inputText.value = input; - this.closeFile(); - window.dispatchEvent(this.manager.statechange); - const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ? - input.count("\n") + 1 : null; - this.setInputInfo(input.length, lines); - } -}; - - -/** - * Shows file details. - * - * @param {File} file - */ -InputWaiter.prototype.setFile = function(file) { - // Display file overlay in input area with details - const fileOverlay = document.getElementById("input-file"), - fileName = document.getElementById("input-file-name"), - fileSize = document.getElementById("input-file-size"), - fileType = document.getElementById("input-file-type"), - fileLoaded = document.getElementById("input-file-loaded"); - - this.fileBuffer = new ArrayBuffer(); - fileOverlay.style.display = "block"; - fileName.textContent = file.name; - fileSize.textContent = file.size.toLocaleString() + " bytes"; - fileType.textContent = file.type || "unknown"; - fileLoaded.textContent = "0%"; -}; - - -/** - * Displays information about the input. - * - * @param {number} length - The length of the current input string - * @param {number} lines - The number of the lines in the current input string - */ -InputWaiter.prototype.setInputInfo = function(length, lines) { - let width = length.toString().length; - width = width < 2 ? 2 : width; - - const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); - let msg = "length: " + lengthStr; - - if (typeof lines === "number") { - const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); - msg += "
    lines: " + linesStr; - } - - document.getElementById("input-info").innerHTML = msg; -}; - - -/** - * Handler for input change events. - * - * @param {event} e - * - * @fires Manager#statechange - */ -InputWaiter.prototype.inputChange = function(e) { - // Ignore this function if the input is a File - if (this.fileBuffer) return; - - // Remove highlighting from input and output panes as the offsets might be different now - this.manager.highlighter.removeHighlights(); - - // Reset recipe progress as any previous processing will be redundant now - this.app.progress = 0; - - // Update the input metadata info - const inputText = this.get(); - const lines = inputText.length < (this.app.options.ioDisplayThreshold * 1024) ? - inputText.count("\n") + 1 : null; - - this.setInputInfo(inputText.length, lines); - - if (e && this.badKeys.indexOf(e.keyCode) < 0) { - // Fire the statechange event as the input has been modified - window.dispatchEvent(this.manager.statechange); - } -}; - - -/** - * Handler for input paste events. - * Checks that the size of the input is below the display limit, otherwise treats it as a file/blob. - * - * @param {event} e - */ -InputWaiter.prototype.inputPaste = function(e) { - const pastedData = e.clipboardData.getData("Text"); - - if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024)) { - this.inputChange(e); - } else { - e.preventDefault(); - e.stopPropagation(); - - const file = new File([pastedData], "PastedData", { - type: "text/plain", - lastModified: Date.now() - }); - - this.loaderWorker = new LoaderWorker(); - this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); - this.loaderWorker.postMessage({"file": file}); - this.set(file); - return false; - } -}; - - -/** - * Handler for input dragover events. - * Gives the user a visual cue to show that items can be dropped here. - * - * @param {event} e - */ -InputWaiter.prototype.inputDragover = function(e) { - // This will be set if we're dragging an operation - if (e.dataTransfer.effectAllowed === "move") - return false; - - e.stopPropagation(); - e.preventDefault(); - e.target.closest("#input-text,#input-file").classList.add("dropping-file"); -}; - - -/** - * Handler for input dragleave events. - * Removes the visual cue. - * - * @param {event} e - */ -InputWaiter.prototype.inputDragleave = function(e) { - e.stopPropagation(); - e.preventDefault(); - document.getElementById("input-text").classList.remove("dropping-file"); - document.getElementById("input-file").classList.remove("dropping-file"); -}; - - -/** - * Handler for input drop events. - * Loads the dragged data into the input textarea. - * - * @param {event} e - */ -InputWaiter.prototype.inputDrop = function(e) { - // This will be set if we're dragging an operation - if (e.dataTransfer.effectAllowed === "move") - return false; - - e.stopPropagation(); - e.preventDefault(); - - const file = e.dataTransfer.files[0]; - const text = e.dataTransfer.getData("Text"); - - document.getElementById("input-text").classList.remove("dropping-file"); - document.getElementById("input-file").classList.remove("dropping-file"); - - if (text) { - this.closeFile(); - this.set(text); - return; - } - - if (file) { - this.closeFile(); - this.loaderWorker = new LoaderWorker(); - this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); - this.loaderWorker.postMessage({"file": file}); - this.set(file); - } -}; - - -/** - * Handler for messages sent back by the LoaderWorker. - * - * @param {MessageEvent} e - */ -InputWaiter.prototype.handleLoaderMessage = function(e) { - const r = e.data; - if (r.hasOwnProperty("progress")) { - const fileLoaded = document.getElementById("input-file-loaded"); - fileLoaded.textContent = r.progress + "%"; - } - - if (r.hasOwnProperty("error")) { - this.app.alert(r.error, "danger", 10000); - } - - if (r.hasOwnProperty("fileBuffer")) { - log.debug("Input file loaded"); - this.fileBuffer = r.fileBuffer; - this.displayFilePreview(); - window.dispatchEvent(this.manager.statechange); - } -}; - - -/** - * Shows a chunk of the file in the input behind the file overlay. - */ -InputWaiter.prototype.displayFilePreview = function() { - const inputText = document.getElementById("input-text"), - fileSlice = this.fileBuffer.slice(0, 4096); - - inputText.style.overflow = "hidden"; - inputText.classList.add("blur"); - inputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice)); - if (this.fileBuffer.byteLength > 4096) { - inputText.value += "[truncated]..."; - } -}; - - -/** - * Handler for file close events. - */ -InputWaiter.prototype.closeFile = function() { - if (this.loaderWorker) this.loaderWorker.terminate(); - this.fileBuffer = null; - document.getElementById("input-file").style.display = "none"; - const inputText = document.getElementById("input-text"); - inputText.style.overflow = "auto"; - inputText.classList.remove("blur"); -}; - - -/** - * Handler for clear IO events. - * Resets the input, output and info areas. - * - * @fires Manager#statechange - */ -InputWaiter.prototype.clearIoClick = function() { - this.closeFile(); - this.manager.output.closeFile(); - this.manager.highlighter.removeHighlights(); - document.getElementById("input-text").value = ""; - document.getElementById("output-text").value = ""; - document.getElementById("input-info").innerHTML = ""; - document.getElementById("output-info").innerHTML = ""; - document.getElementById("input-selection-info").innerHTML = ""; - document.getElementById("output-selection-info").innerHTML = ""; - window.dispatchEvent(this.manager.statechange); -}; - -export default InputWaiter; diff --git a/src/web/InputWaiter.mjs b/src/web/InputWaiter.mjs new file mode 100755 index 00000000..37f1134a --- /dev/null +++ b/src/web/InputWaiter.mjs @@ -0,0 +1,330 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker"; +import Utils from "../core/Utils"; + + +/** + * Waiter to handle events related to the input. + */ +class InputWaiter { + + /** + * InputWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + // Define keys that don't change the input so we don't have to autobake when they are pressed + this.badKeys = [ + 16, //Shift + 17, //Ctrl + 18, //Alt + 19, //Pause + 20, //Caps + 27, //Esc + 33, 34, 35, 36, //PgUp, PgDn, End, Home + 37, 38, 39, 40, //Directional + 44, //PrntScrn + 91, 92, //Win + 93, //Context + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, //F1-12 + 144, //Num + 145, //Scroll + ]; + + this.loaderWorker = null; + this.fileBuffer = null; + } + + + /** + * Gets the user's input from the input textarea. + * + * @returns {string} + */ + get() { + return this.fileBuffer || document.getElementById("input-text").value; + } + + + /** + * Sets the input in the input area. + * + * @param {string|File} input + * @param {boolean} [silent=false] - Suppress statechange event + * + * @fires Manager#statechange + */ + set(input, silent=false) { + const inputText = document.getElementById("input-text"); + if (input instanceof File) { + this.setFile(input); + inputText.value = ""; + this.setInputInfo(input.size, null); + } else { + inputText.value = input; + this.closeFile(); + if (!silent) window.dispatchEvent(this.manager.statechange); + const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ? + input.count("\n") + 1 : null; + this.setInputInfo(input.length, lines); + } + } + + + /** + * Shows file details. + * + * @param {File} file + */ + setFile(file) { + // Display file overlay in input area with details + const fileOverlay = document.getElementById("input-file"), + fileName = document.getElementById("input-file-name"), + fileSize = document.getElementById("input-file-size"), + fileType = document.getElementById("input-file-type"), + fileLoaded = document.getElementById("input-file-loaded"); + + this.fileBuffer = new ArrayBuffer(); + fileOverlay.style.display = "block"; + fileName.textContent = file.name; + fileSize.textContent = file.size.toLocaleString() + " bytes"; + fileType.textContent = file.type || "unknown"; + fileLoaded.textContent = "0%"; + } + + + /** + * Displays information about the input. + * + * @param {number} length - The length of the current input string + * @param {number} lines - The number of the lines in the current input string + */ + setInputInfo(length, lines) { + let width = length.toString().length; + width = width < 2 ? 2 : width; + + const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); + let msg = "length: " + lengthStr; + + if (typeof lines === "number") { + const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); + msg += "
    lines: " + linesStr; + } + + document.getElementById("input-info").innerHTML = msg; + } + + + /** + * Handler for input change events. + * + * @param {event} e + * + * @fires Manager#statechange + */ + inputChange(e) { + // Ignore this function if the input is a File + if (this.fileBuffer) return; + + // Remove highlighting from input and output panes as the offsets might be different now + this.manager.highlighter.removeHighlights(); + + // Reset recipe progress as any previous processing will be redundant now + this.app.progress = 0; + + // Update the input metadata info + const inputText = this.get(); + const lines = inputText.length < (this.app.options.ioDisplayThreshold * 1024) ? + inputText.count("\n") + 1 : null; + + this.setInputInfo(inputText.length, lines); + + if (e && this.badKeys.indexOf(e.keyCode) < 0) { + // Fire the statechange event as the input has been modified + window.dispatchEvent(this.manager.statechange); + } + } + + + /** + * Handler for input paste events. + * Checks that the size of the input is below the display limit, otherwise treats it as a file/blob. + * + * @param {event} e + */ + inputPaste(e) { + const pastedData = e.clipboardData.getData("Text"); + + if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024)) { + this.inputChange(e); + } else { + e.preventDefault(); + e.stopPropagation(); + + const file = new File([pastedData], "PastedData", { + type: "text/plain", + lastModified: Date.now() + }); + + this.loaderWorker = new LoaderWorker(); + this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); + this.loaderWorker.postMessage({"file": file}); + this.set(file); + return false; + } + } + + + /** + * Handler for input dragover events. + * Gives the user a visual cue to show that items can be dropped here. + * + * @param {event} e + */ + inputDragover(e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + e.target.closest("#input-text,#input-file").classList.add("dropping-file"); + } + + + /** + * Handler for input dragleave events. + * Removes the visual cue. + * + * @param {event} e + */ + inputDragleave(e) { + e.stopPropagation(); + e.preventDefault(); + document.getElementById("input-text").classList.remove("dropping-file"); + document.getElementById("input-file").classList.remove("dropping-file"); + } + + + /** + * Handler for input drop events. + * Loads the dragged data into the input textarea. + * + * @param {event} e + */ + inputDrop(e) { + // This will be set if we're dragging an operation + if (e.dataTransfer.effectAllowed === "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + + const file = e.dataTransfer.files[0]; + const text = e.dataTransfer.getData("Text"); + + document.getElementById("input-text").classList.remove("dropping-file"); + document.getElementById("input-file").classList.remove("dropping-file"); + + if (text) { + this.closeFile(); + this.set(text); + return; + } + + if (file) { + this.closeFile(); + this.loaderWorker = new LoaderWorker(); + this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); + this.loaderWorker.postMessage({"file": file}); + this.set(file); + } + } + + + /** + * Handler for messages sent back by the LoaderWorker. + * + * @param {MessageEvent} e + */ + handleLoaderMessage(e) { + const r = e.data; + if (r.hasOwnProperty("progress")) { + const fileLoaded = document.getElementById("input-file-loaded"); + fileLoaded.textContent = r.progress + "%"; + } + + if (r.hasOwnProperty("error")) { + this.app.alert(r.error, 10000); + } + + if (r.hasOwnProperty("fileBuffer")) { + log.debug("Input file loaded"); + this.fileBuffer = r.fileBuffer; + this.displayFilePreview(); + window.dispatchEvent(this.manager.statechange); + } + } + + + /** + * Shows a chunk of the file in the input behind the file overlay. + */ + displayFilePreview() { + const inputText = document.getElementById("input-text"), + fileSlice = this.fileBuffer.slice(0, 4096); + + inputText.style.overflow = "hidden"; + inputText.classList.add("blur"); + inputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice)); + if (this.fileBuffer.byteLength > 4096) { + inputText.value += "[truncated]..."; + } + } + + + /** + * Handler for file close events. + */ + closeFile() { + if (this.loaderWorker) this.loaderWorker.terminate(); + this.fileBuffer = null; + document.getElementById("input-file").style.display = "none"; + const inputText = document.getElementById("input-text"); + inputText.style.overflow = "auto"; + inputText.classList.remove("blur"); + } + + + /** + * Handler for clear IO events. + * Resets the input, output and info areas. + * + * @fires Manager#statechange + */ + clearIoClick() { + this.closeFile(); + this.manager.output.closeFile(); + this.manager.highlighter.removeHighlights(); + document.getElementById("input-text").value = ""; + document.getElementById("output-text").value = ""; + document.getElementById("input-info").innerHTML = ""; + document.getElementById("output-info").innerHTML = ""; + document.getElementById("input-selection-info").innerHTML = ""; + document.getElementById("output-selection-info").innerHTML = ""; + window.dispatchEvent(this.manager.statechange); + } + +} + +export default InputWaiter; diff --git a/src/web/LoaderWorker.js b/src/web/LoaderWorker.js old mode 100644 new mode 100755 index bda4e667..076e0e7d --- a/src/web/LoaderWorker.js +++ b/src/web/LoaderWorker.js @@ -25,7 +25,7 @@ self.addEventListener("message", function(e) { */ self.loadFile = function(file) { const reader = new FileReader(); - let data = new Uint8Array(file.size); + const data = new Uint8Array(file.size); let offset = 0; const CHUNK_SIZE = 10485760; // 10MiB diff --git a/src/web/Manager.js b/src/web/Manager.js deleted file mode 100755 index 878077c1..00000000 --- a/src/web/Manager.js +++ /dev/null @@ -1,300 +0,0 @@ -import WorkerWaiter from "./WorkerWaiter.js"; -import WindowWaiter from "./WindowWaiter.js"; -import ControlsWaiter from "./ControlsWaiter.js"; -import RecipeWaiter from "./RecipeWaiter.js"; -import OperationsWaiter from "./OperationsWaiter.js"; -import InputWaiter from "./InputWaiter.js"; -import OutputWaiter from "./OutputWaiter.js"; -import OptionsWaiter from "./OptionsWaiter.js"; -import HighlighterWaiter from "./HighlighterWaiter.js"; -import SeasonalWaiter from "./SeasonalWaiter.js"; -import BindingsWaiter from "./BindingsWaiter.js"; - - -/** - * This object controls the Waiters responsible for handling events from all areas of the app. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - */ -const Manager = function(app) { - this.app = app; - - // Define custom events - /** - * @event Manager#appstart - */ - this.appstart = new CustomEvent("appstart", {bubbles: true}); - /** - * @event Manager#apploaded - */ - this.apploaded = new CustomEvent("apploaded", {bubbles: true}); - /** - * @event Manager#operationadd - */ - this.operationadd = new CustomEvent("operationadd", {bubbles: true}); - /** - * @event Manager#operationremove - */ - this.operationremove = new CustomEvent("operationremove", {bubbles: true}); - /** - * @event Manager#oplistcreate - */ - this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true}); - /** - * @event Manager#statechange - */ - this.statechange = new CustomEvent("statechange", {bubbles: true}); - - // Define Waiter objects to handle various areas - this.worker = new WorkerWaiter(this.app, this); - this.window = new WindowWaiter(this.app); - this.controls = new ControlsWaiter(this.app, this); - this.recipe = new RecipeWaiter(this.app, this); - this.ops = new OperationsWaiter(this.app, this); - this.input = new InputWaiter(this.app, this); - this.output = new OutputWaiter(this.app, this); - this.options = new OptionsWaiter(this.app, this); - this.highlighter = new HighlighterWaiter(this.app, this); - this.seasonal = new SeasonalWaiter(this.app, this); - this.bindings = new BindingsWaiter(this.app, this); - - // Object to store dynamic handlers to fire on elements that may not exist yet - this.dynamicHandlers = {}; - - this.initialiseEventListeners(); -}; - - -/** - * Sets up the various components and listeners. - */ -Manager.prototype.setup = function() { - this.worker.registerChefWorker(); - this.recipe.initialiseOperationDragNDrop(); - this.controls.autoBakeChange(); - this.bindings.updateKeybList(); - this.seasonal.load(); -}; - - -/** - * Main function to handle the creation of the event listeners. - */ -Manager.prototype.initialiseEventListeners = function() { - // Global - window.addEventListener("resize", this.window.windowResize.bind(this.window)); - window.addEventListener("blur", this.window.windowBlur.bind(this.window)); - window.addEventListener("focus", this.window.windowFocus.bind(this.window)); - window.addEventListener("statechange", this.app.stateChange.bind(this.app)); - window.addEventListener("popstate", this.app.popState.bind(this.app)); - - // Controls - document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); - document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); - document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls)); - document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls)); - document.getElementById("clr-breaks").addEventListener("click", this.controls.clearBreaksClick.bind(this.controls)); - document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls)); - document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls)); - document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls)); - document.getElementById("save-link-input-checkbox").addEventListener("change", this.controls.sliCheckChange.bind(this.controls)); - document.getElementById("load").addEventListener("click", this.controls.loadClick.bind(this.controls)); - document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls)); - document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls)); - document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls)); - document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls)); - this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls); - - // Operations - this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops); - this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops); - document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops)); - document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops)); - document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops)); - this.addDynamicListener(".op-list .op-icon", "mouseover", this.ops.opIconMouseover, this.ops); - this.addDynamicListener(".op-list .op-icon", "mouseleave", this.ops.opIconMouseleave, this.ops); - this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops); - this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe); - - // Recipe - this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe); - this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe); - this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); - this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); - this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); - this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe); - this.addDynamicListener("#rec-list .input-group .dropdown-menu a", "click", this.recipe.dropdownToggleClick, this.recipe); - this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe)); - - // Input - this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input); - this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input); - document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); - document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input)); - this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); - this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input); - this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input); - document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter)); - document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter)); - document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter)); - this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter); - document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); - - // Output - document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); - document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output)); - document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output)); - document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); - document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); - document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter)); - document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter)); - document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter)); - document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter)); - document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter)); - this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); - this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); - this.addDynamicListener(".file-switch", "click", this.output.fileSwitch, this.output); - this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); - this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, this.output); - document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output)); - - // Options - document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); - document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options)); - $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options)); - $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options)); - $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings)); - this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options); - this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options); - this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); - document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); - document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options)); - - // Misc - window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); - document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app)); -}; - - -/** - * Adds an event listener to each element in the specified group. - * - * @param {string} selector - A selector string for the element group to add the event to, see - * this.getAll() - * @param {string} eventType - The event to listen for - * @param {function} callback - The function to execute when the event is triggered - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Calls the clickable function whenever any element with the .clickable class is clicked - * this.addListeners(".clickable", "click", this.clickable, this); - */ -Manager.prototype.addListeners = function(selector, eventType, callback, scope) { - scope = scope || this; - [].forEach.call(document.querySelectorAll(selector), function(el) { - el.addEventListener(eventType, callback.bind(scope)); - }); -}; - - -/** - * Adds multiple event listeners to the specified element. - * - * @param {string} selector - A selector string for the element to add the events to - * @param {string} eventTypes - A space-separated string of all the event types to listen for - * @param {function} callback - The function to execute when the events are triggered - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Calls the search function whenever the the keyup, paste or search events are triggered on the - * // search element - * this.addMultiEventListener("search", "keyup paste search", this.search, this); - */ -Manager.prototype.addMultiEventListener = function(selector, eventTypes, callback, scope) { - const evs = eventTypes.split(" "); - for (let i = 0; i < evs.length; i++) { - document.querySelector(selector).addEventListener(evs[i], callback.bind(scope)); - } -}; - - -/** - * Adds multiple event listeners to each element in the specified group. - * - * @param {string} selector - A selector string for the element group to add the events to - * @param {string} eventTypes - A space-separated string of all the event types to listen for - * @param {function} callback - The function to execute when the events are triggered - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Calls the save function whenever the the keyup or paste events are triggered on any element - * // with the .saveable class - * this.addMultiEventListener(".saveable", "keyup paste", this.save, this); - */ -Manager.prototype.addMultiEventListeners = function(selector, eventTypes, callback, scope) { - const evs = eventTypes.split(" "); - for (let i = 0; i < evs.length; i++) { - this.addListeners(selector, evs[i], callback, scope); - } -}; - - -/** - * Adds an event listener to the global document object which will listen on dynamic elements which - * may not exist in the DOM yet. - * - * @param {string} selector - A selector string for the element(s) to add the event to - * @param {string} eventType - The event(s) to listen for - * @param {function} callback - The function to execute when the event(s) is/are triggered - * @param {Object} [scope=this] - The object to bind to the callback function - * - * @example - * // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this - * // listener is created - * this.addDynamicListener("button", "click", alert, this); - */ -Manager.prototype.addDynamicListener = function(selector, eventType, callback, scope) { - const eventConfig = { - selector: selector, - callback: callback.bind(scope || this) - }; - - if (this.dynamicHandlers.hasOwnProperty(eventType)) { - // Listener already exists, add new handler to the appropriate list - this.dynamicHandlers[eventType].push(eventConfig); - } else { - this.dynamicHandlers[eventType] = [eventConfig]; - // Set up listener for this new type - document.addEventListener(eventType, this.dynamicListenerHandler.bind(this)); - } -}; - - -/** - * Handler for dynamic events. This function is called for any dynamic event and decides which - * callback(s) to execute based on the type and selector. - * - * @param {Event} e - The event to be handled - */ -Manager.prototype.dynamicListenerHandler = function(e) { - const { type, target } = e; - const handlers = this.dynamicHandlers[type]; - const matches = target.matches || - target.webkitMatchesSelector || - target.mozMatchesSelector || - target.msMatchesSelector || - target.oMatchesSelector; - - for (let i = 0; i < handlers.length; i++) { - if (matches && matches.call(target, handlers[i].selector)) { - handlers[i].callback(e); - } - } -}; - -export default Manager; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs new file mode 100755 index 00000000..d33616a4 --- /dev/null +++ b/src/web/Manager.mjs @@ -0,0 +1,308 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import WorkerWaiter from "./WorkerWaiter"; +import WindowWaiter from "./WindowWaiter"; +import ControlsWaiter from "./ControlsWaiter"; +import RecipeWaiter from "./RecipeWaiter"; +import OperationsWaiter from "./OperationsWaiter"; +import InputWaiter from "./InputWaiter"; +import OutputWaiter from "./OutputWaiter"; +import OptionsWaiter from "./OptionsWaiter"; +import HighlighterWaiter from "./HighlighterWaiter"; +import SeasonalWaiter from "./SeasonalWaiter"; +import BindingsWaiter from "./BindingsWaiter"; +import BackgroundWorkerWaiter from "./BackgroundWorkerWaiter"; + + +/** + * This object controls the Waiters responsible for handling events from all areas of the app. + */ +class Manager { + + /** + * Manager constructor. + * + * @param {App} app - The main view object for CyberChef. + */ + constructor(app) { + this.app = app; + + // Define custom events + /** + * @event Manager#appstart + */ + this.appstart = new CustomEvent("appstart", {bubbles: true}); + /** + * @event Manager#apploaded + */ + this.apploaded = new CustomEvent("apploaded", {bubbles: true}); + /** + * @event Manager#operationadd + */ + this.operationadd = new CustomEvent("operationadd", {bubbles: true}); + /** + * @event Manager#operationremove + */ + this.operationremove = new CustomEvent("operationremove", {bubbles: true}); + /** + * @event Manager#oplistcreate + */ + this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true}); + /** + * @event Manager#statechange + */ + this.statechange = new CustomEvent("statechange", {bubbles: true}); + + // Define Waiter objects to handle various areas + this.worker = new WorkerWaiter(this.app, this); + this.window = new WindowWaiter(this.app); + this.controls = new ControlsWaiter(this.app, this); + this.recipe = new RecipeWaiter(this.app, this); + this.ops = new OperationsWaiter(this.app, this); + this.input = new InputWaiter(this.app, this); + this.output = new OutputWaiter(this.app, this); + this.options = new OptionsWaiter(this.app, this); + this.highlighter = new HighlighterWaiter(this.app, this); + this.seasonal = new SeasonalWaiter(this.app, this); + this.bindings = new BindingsWaiter(this.app, this); + this.background = new BackgroundWorkerWaiter(this.app, this); + + // Object to store dynamic handlers to fire on elements that may not exist yet + this.dynamicHandlers = {}; + + this.initialiseEventListeners(); + } + + + /** + * Sets up the various components and listeners. + */ + setup() { + this.worker.registerChefWorker(); + this.recipe.initialiseOperationDragNDrop(); + this.controls.initComponents(); + this.controls.autoBakeChange(); + this.bindings.updateKeybList(); + this.background.registerChefWorker(); + this.seasonal.load(); + } + + + /** + * Main function to handle the creation of the event listeners. + */ + initialiseEventListeners() { + // Global + window.addEventListener("resize", this.window.windowResize.bind(this.window)); + window.addEventListener("blur", this.window.windowBlur.bind(this.window)); + window.addEventListener("focus", this.window.windowFocus.bind(this.window)); + window.addEventListener("statechange", this.app.stateChange.bind(this.app)); + window.addEventListener("popstate", this.app.popState.bind(this.app)); + + // Controls + document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); + document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); + document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls)); + document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls)); + document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls)); + document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls)); + document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls)); + document.getElementById("save-link-input-checkbox").addEventListener("change", this.controls.sliCheckChange.bind(this.controls)); + document.getElementById("load").addEventListener("click", this.controls.loadClick.bind(this.controls)); + document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls)); + document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls)); + document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls)); + document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls)); + this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls); + + // Operations + this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops); + this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops); + document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops)); + document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops)); + document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops)); + this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops); + this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe); + + // Recipe + this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe); + this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe); + this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); + this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); + this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); + this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe); + this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe); + this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe)); + + // Input + this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input); + this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input); + document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); + document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input)); + this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); + this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input); + this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input); + document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter)); + document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter)); + document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter)); + this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter); + document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); + + // Output + document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); + document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output)); + document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output)); + document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); + document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); + document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); + document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter)); + document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter)); + document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter)); + document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter)); + document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter)); + this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); + this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); + this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); + this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output); + document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output)); + + // Options + document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); + document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options)); + this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.switchChange, this.options); + this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.setWordWrap, this.options); + this.addDynamicListener(".option-item input[type=checkbox]#useMetaKey", "change", this.bindings.updateKeybList, this.bindings); + this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options); + this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options); + this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); + document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); + document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options)); + + // Misc + window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); + } + + + /** + * Adds an event listener to each element in the specified group. + * + * @param {string} selector - A selector string for the element group to add the event to, see + * this.getAll() + * @param {string} eventType - The event to listen for + * @param {function} callback - The function to execute when the event is triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the clickable function whenever any element with the .clickable class is clicked + * this.addListeners(".clickable", "click", this.clickable, this); + */ + addListeners(selector, eventType, callback, scope) { + scope = scope || this; + [].forEach.call(document.querySelectorAll(selector), function(el) { + el.addEventListener(eventType, callback.bind(scope)); + }); + } + + + /** + * Adds multiple event listeners to the specified element. + * + * @param {string} selector - A selector string for the element to add the events to + * @param {string} eventTypes - A space-separated string of all the event types to listen for + * @param {function} callback - The function to execute when the events are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the search function whenever the the keyup, paste or search events are triggered on the + * // search element + * this.addMultiEventListener("search", "keyup paste search", this.search, this); + */ + addMultiEventListener(selector, eventTypes, callback, scope) { + const evs = eventTypes.split(" "); + for (let i = 0; i < evs.length; i++) { + document.querySelector(selector).addEventListener(evs[i], callback.bind(scope)); + } + } + + + /** + * Adds multiple event listeners to each element in the specified group. + * + * @param {string} selector - A selector string for the element group to add the events to + * @param {string} eventTypes - A space-separated string of all the event types to listen for + * @param {function} callback - The function to execute when the events are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Calls the save function whenever the the keyup or paste events are triggered on any element + * // with the .saveable class + * this.addMultiEventListener(".saveable", "keyup paste", this.save, this); + */ + addMultiEventListeners(selector, eventTypes, callback, scope) { + const evs = eventTypes.split(" "); + for (let i = 0; i < evs.length; i++) { + this.addListeners(selector, evs[i], callback, scope); + } + } + + + /** + * Adds an event listener to the global document object which will listen on dynamic elements which + * may not exist in the DOM yet. + * + * @param {string} selector - A selector string for the element(s) to add the event to + * @param {string} eventType - The event(s) to listen for + * @param {function} callback - The function to execute when the event(s) is/are triggered + * @param {Object} [scope=this] - The object to bind to the callback function + * + * @example + * // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this + * // listener is created + * this.addDynamicListener("button", "click", alert, this); + */ + addDynamicListener(selector, eventType, callback, scope) { + const eventConfig = { + selector: selector, + callback: callback.bind(scope || this) + }; + + if (this.dynamicHandlers.hasOwnProperty(eventType)) { + // Listener already exists, add new handler to the appropriate list + this.dynamicHandlers[eventType].push(eventConfig); + } else { + this.dynamicHandlers[eventType] = [eventConfig]; + // Set up listener for this new type + document.addEventListener(eventType, this.dynamicListenerHandler.bind(this)); + } + } + + + /** + * Handler for dynamic events. This function is called for any dynamic event and decides which + * callback(s) to execute based on the type and selector. + * + * @param {Event} e - The event to be handled + */ + dynamicListenerHandler(e) { + const { type, target } = e; + const handlers = this.dynamicHandlers[type]; + const matches = target.matches || + target.webkitMatchesSelector || + target.mozMatchesSelector || + target.msMatchesSelector || + target.oMatchesSelector; + + for (let i = 0; i < handlers.length; i++) { + if (matches && matches.call(target, handlers[i].selector)) { + handlers[i].callback(e); + } + } + } + +} + +export default Manager; diff --git a/src/web/OperationsWaiter.js b/src/web/OperationsWaiter.js deleted file mode 100755 index 48bbe158..00000000 --- a/src/web/OperationsWaiter.js +++ /dev/null @@ -1,313 +0,0 @@ -import HTMLOperation from "./HTMLOperation.js"; -import Sortable from "sortablejs"; - - -/** - * Waiter to handle events related to the operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const OperationsWaiter = function(app, manager) { - this.app = app; - this.manager = manager; - - this.options = {}; - this.removeIntent = false; -}; - - -/** - * Handler for search events. - * Finds operations which match the given search term and displays them under the search box. - * - * @param {event} e - */ -OperationsWaiter.prototype.searchOperations = function(e) { - let ops, selected; - - if (e.type === "search") { // Search - e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); - if (ops.length) { - selected = this.getSelectedOp(ops); - if (selected > -1) { - this.manager.recipe.addOperation(ops[selected].innerHTML); - } - } - } - - if (e.keyCode === 13) { // Return - e.preventDefault(); - } else if (e.keyCode === 40) { // Down - e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); - if (ops.length) { - selected = this.getSelectedOp(ops); - if (selected > -1) { - ops[selected].classList.remove("selected-op"); - } - if (selected === ops.length-1) selected = -1; - ops[selected+1].classList.add("selected-op"); - } - } else if (e.keyCode === 38) { // Up - e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); - if (ops.length) { - selected = this.getSelectedOp(ops); - if (selected > -1) { - ops[selected].classList.remove("selected-op"); - } - if (selected === 0) selected = ops.length; - ops[selected-1].classList.add("selected-op"); - } - } else { - const searchResultsEl = document.getElementById("search-results"); - const el = e.target; - const str = el.value; - - while (searchResultsEl.firstChild) { - try { - $(searchResultsEl.firstChild).popover("destroy"); - } catch (err) {} - searchResultsEl.removeChild(searchResultsEl.firstChild); - } - - $("#categories .in").collapse("hide"); - if (str) { - const matchedOps = this.filterOperations(str, true); - const matchedOpsHtml = matchedOps - .map(v => v.toStubHtml()) - .join(""); - - searchResultsEl.innerHTML = matchedOpsHtml; - searchResultsEl.dispatchEvent(this.manager.oplistcreate); - } - } -}; - - -/** - * Filters operations based on the search string and returns the matching ones. - * - * @param {string} searchStr - * @param {boolean} highlight - Whether or not to highlight the matching string in the operation - * name and description - * @returns {string[]} - */ -OperationsWaiter.prototype.filterOperations = function(inStr, highlight) { - const matchedOps = []; - const matchedDescs = []; - - const searchStr = inStr.toLowerCase(); - - for (const opName in this.app.operations) { - const op = this.app.operations[opName]; - const namePos = opName.toLowerCase().indexOf(searchStr); - const descPos = op.description.toLowerCase().indexOf(searchStr); - - if (namePos >= 0 || descPos >= 0) { - const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); - if (highlight) { - operation.highlightSearchString(searchStr, namePos, descPos); - } - - if (namePos < 0) { - matchedOps.push(operation); - } else { - matchedDescs.push(operation); - } - } - } - - return matchedDescs.concat(matchedOps); -}; - - -/** - * Finds the operation which has been selected using keyboard shortcuts. This will have the class - * 'selected-op' set. Returns the index of the operation within the given list. - * - * @param {element[]} ops - * @returns {number} - */ -OperationsWaiter.prototype.getSelectedOp = function(ops) { - for (let i = 0; i < ops.length; i++) { - if (ops[i].classList.contains("selected-op")) { - return i; - } - } - return -1; -}; - - -/** - * Handler for oplistcreate events. - * - * @listens Manager#oplistcreate - * @param {event} e - */ -OperationsWaiter.prototype.opListCreate = function(e) { - this.manager.recipe.createSortableSeedList(e.target); - this.enableOpsListPopovers(e.target); -}; - - -/** - * Sets up popovers, allowing the popover itself to gain focus which enables scrolling - * and other interactions. - * - * @param {Element} el - The element to start selecting from - */ -OperationsWaiter.prototype.enableOpsListPopovers = function(el) { - $(el).find("[data-toggle=popover]").addBack("[data-toggle=popover]") - .popover({trigger: "manual"}) - .on("mouseenter", function(e) { - if (e.buttons > 0) return; // Mouse button held down - likely dragging an opertion - const _this = this; - $(this).popover("show"); - $(".popover").on("mouseleave", function () { - $(_this).popover("hide"); - }); - }).on("mouseleave", function () { - const _this = this; - setTimeout(function() { - // Determine if the popover associated with this element is being hovered over - if ($(_this).data("bs.popover") && - ($(_this).data("bs.popover").$tip && !$(_this).data("bs.popover").$tip.is(":hover"))) { - $(_this).popover("hide"); - } - }, 50); - }); -}; - - -/** - * Handler for operation doubleclick events. - * Adds the operation to the recipe and auto bakes. - * - * @param {event} e - */ -OperationsWaiter.prototype.operationDblclick = function(e) { - const li = e.target; - - this.manager.recipe.addOperation(li.textContent); -}; - - -/** - * Handler for edit favourites click events. - * Sets up the 'Edit favourites' pane and displays it. - * - * @param {event} e - */ -OperationsWaiter.prototype.editFavouritesClick = function(e) { - e.preventDefault(); - e.stopPropagation(); - - // Add favourites to modal - const favCat = this.app.categories.filter(function(c) { - return c.name === "Favourites"; - })[0]; - - let html = ""; - for (let i = 0; i < favCat.ops.length; i++) { - const opName = favCat.ops[i]; - const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); - html += operation.toStubHtml(true); - } - - const editFavouritesList = document.getElementById("edit-favourites-list"); - editFavouritesList.innerHTML = html; - this.removeIntent = false; - - const editableList = Sortable.create(editFavouritesList, { - filter: ".remove-icon", - onFilter: function (evt) { - const el = editableList.closest(evt.item); - if (el && el.parentNode) { - $(el).popover("destroy"); - el.parentNode.removeChild(el); - } - }, - onEnd: function(evt) { - if (this.removeIntent) { - $(evt.item).popover("destroy"); - evt.item.remove(); - } - }.bind(this), - }); - - Sortable.utils.on(editFavouritesList, "dragleave", function() { - this.removeIntent = true; - }.bind(this)); - - Sortable.utils.on(editFavouritesList, "dragover", function() { - this.removeIntent = false; - }.bind(this)); - - $("#edit-favourites-list [data-toggle=popover]").popover(); - $("#favourites-modal").modal(); -}; - - -/** - * Handler for save favourites click events. - * Saves the selected favourites and reloads them. - */ -OperationsWaiter.prototype.saveFavouritesClick = function() { - const favs = document.querySelectorAll("#edit-favourites-list li"); - const favouritesList = Array.from(favs, e => e.textContent); - - this.app.saveFavourites(favouritesList); - this.app.loadFavourites(); - this.app.populateOperationsList(); - this.manager.recipe.initialiseOperationDragNDrop(); -}; - - -/** - * Handler for reset favourites click events. - * Resets favourites to their defaults. - */ -OperationsWaiter.prototype.resetFavouritesClick = function() { - this.app.resetFavourites(); -}; - - -/** - * Handler for opIcon mouseover events. - * Hides any popovers already showing on the operation so that there aren't two at once. - * - * @param {event} e - */ -OperationsWaiter.prototype.opIconMouseover = function(e) { - const opEl = e.target.parentNode; - if (e.target.getAttribute("data-toggle") === "popover") { - $(opEl).popover("hide"); - } -}; - - -/** - * Handler for opIcon mouseleave events. - * If this icon created a popover and we're moving back to the operation element, display the - * operation popover again. - * - * @param {event} e - */ -OperationsWaiter.prototype.opIconMouseleave = function(e) { - const opEl = e.target.parentNode; - const toEl = e.toElement || e.relatedElement; - - if (e.target.getAttribute("data-toggle") === "popover" && toEl === opEl) { - $(opEl).popover("show"); - } -}; - -export default OperationsWaiter; diff --git a/src/web/OperationsWaiter.mjs b/src/web/OperationsWaiter.mjs new file mode 100755 index 00000000..e0286d42 --- /dev/null +++ b/src/web/OperationsWaiter.mjs @@ -0,0 +1,290 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import HTMLOperation from "./HTMLOperation"; +import Sortable from "sortablejs"; + + +/** + * Waiter to handle events related to the operations. + */ +class OperationsWaiter { + + /** + * OperationsWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.options = {}; + this.removeIntent = false; + } + + + /** + * Handler for search events. + * Finds operations which match the given search term and displays them under the search box. + * + * @param {event} e + */ + searchOperations(e) { + let ops, selected; + + if (e.type === "search") { // Search + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + this.manager.recipe.addOperation(ops[selected].innerHTML); + } + } + } + + if (e.keyCode === 13) { // Return + e.preventDefault(); + } else if (e.keyCode === 40) { // Down + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + ops[selected].classList.remove("selected-op"); + } + if (selected === ops.length-1) selected = -1; + ops[selected+1].classList.add("selected-op"); + } + } else if (e.keyCode === 38) { // Up + e.preventDefault(); + ops = document.querySelectorAll("#search-results li"); + if (ops.length) { + selected = this.getSelectedOp(ops); + if (selected > -1) { + ops[selected].classList.remove("selected-op"); + } + if (selected === 0) selected = ops.length; + ops[selected-1].classList.add("selected-op"); + } + } else { + const searchResultsEl = document.getElementById("search-results"); + const el = e.target; + const str = el.value; + + while (searchResultsEl.firstChild) { + try { + $(searchResultsEl.firstChild).popover("dispose"); + } catch (err) {} + searchResultsEl.removeChild(searchResultsEl.firstChild); + } + + $("#categories .show").collapse("hide"); + if (str) { + const matchedOps = this.filterOperations(str, true); + const matchedOpsHtml = matchedOps + .map(v => v.toStubHtml()) + .join(""); + + searchResultsEl.innerHTML = matchedOpsHtml; + searchResultsEl.dispatchEvent(this.manager.oplistcreate); + } + } + } + + + /** + * Filters operations based on the search string and returns the matching ones. + * + * @param {string} searchStr + * @param {boolean} highlight - Whether or not to highlight the matching string in the operation + * name and description + * @returns {string[]} + */ + filterOperations(inStr, highlight) { + const matchedOps = []; + const matchedDescs = []; + + const searchStr = inStr.toLowerCase(); + + for (const opName in this.app.operations) { + const op = this.app.operations[opName]; + const namePos = opName.toLowerCase().indexOf(searchStr); + const descPos = op.description.toLowerCase().indexOf(searchStr); + + if (namePos >= 0 || descPos >= 0) { + const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + if (highlight) { + operation.highlightSearchString(searchStr, namePos, descPos); + } + + if (namePos < 0) { + matchedOps.push(operation); + } else { + matchedDescs.push(operation); + } + } + } + + return matchedDescs.concat(matchedOps); + } + + + /** + * Finds the operation which has been selected using keyboard shortcuts. This will have the class + * 'selected-op' set. Returns the index of the operation within the given list. + * + * @param {element[]} ops + * @returns {number} + */ + getSelectedOp(ops) { + for (let i = 0; i < ops.length; i++) { + if (ops[i].classList.contains("selected-op")) { + return i; + } + } + return -1; + } + + + /** + * Handler for oplistcreate events. + * + * @listens Manager#oplistcreate + * @param {event} e + */ + opListCreate(e) { + this.manager.recipe.createSortableSeedList(e.target); + this.enableOpsListPopovers(e.target); + } + + + /** + * Sets up popovers, allowing the popover itself to gain focus which enables scrolling + * and other interactions. + * + * @param {Element} el - The element to start selecting from + */ + enableOpsListPopovers(el) { + $(el).find("[data-toggle=popover]").addBack("[data-toggle=popover]") + .popover({trigger: "manual"}) + .on("mouseenter", function(e) { + if (e.buttons > 0) return; // Mouse button held down - likely dragging an opertion + const _this = this; + $(this).popover("show"); + $(".popover").on("mouseleave", function () { + $(_this).popover("hide"); + }); + }).on("mouseleave", function () { + const _this = this; + setTimeout(function() { + // Determine if the popover associated with this element is being hovered over + if ($(_this).data("bs.popover") && + ($(_this).data("bs.popover").tip && !$($(_this).data("bs.popover").tip).is(":hover"))) { + $(_this).popover("hide"); + } + }, 50); + }); + } + + + /** + * Handler for operation doubleclick events. + * Adds the operation to the recipe and auto bakes. + * + * @param {event} e + */ + operationDblclick(e) { + const li = e.target; + + this.manager.recipe.addOperation(li.textContent); + } + + + /** + * Handler for edit favourites click events. + * Sets up the 'Edit favourites' pane and displays it. + * + * @param {event} e + */ + editFavouritesClick(e) { + e.preventDefault(); + e.stopPropagation(); + + // Add favourites to modal + const favCat = this.app.categories.filter(function(c) { + return c.name === "Favourites"; + })[0]; + + let html = ""; + for (let i = 0; i < favCat.ops.length; i++) { + const opName = favCat.ops[i]; + const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + html += operation.toStubHtml(true); + } + + const editFavouritesList = document.getElementById("edit-favourites-list"); + editFavouritesList.innerHTML = html; + this.removeIntent = false; + + const editableList = Sortable.create(editFavouritesList, { + filter: ".remove-icon", + onFilter: function (evt) { + const el = editableList.closest(evt.item); + if (el && el.parentNode) { + $(el).popover("dispose"); + el.parentNode.removeChild(el); + } + }, + onEnd: function(evt) { + if (this.removeIntent) { + $(evt.item).popover("dispose"); + evt.item.remove(); + } + }.bind(this), + }); + + Sortable.utils.on(editFavouritesList, "dragleave", function() { + this.removeIntent = true; + }.bind(this)); + + Sortable.utils.on(editFavouritesList, "dragover", function() { + this.removeIntent = false; + }.bind(this)); + + $("#edit-favourites-list [data-toggle=popover]").popover(); + $("#favourites-modal").modal(); + } + + + /** + * Handler for save favourites click events. + * Saves the selected favourites and reloads them. + */ + saveFavouritesClick() { + const favs = document.querySelectorAll("#edit-favourites-list li"); + const favouritesList = Array.from(favs, e => e.childNodes[0].textContent); + + this.app.saveFavourites(favouritesList); + this.app.loadFavourites(); + this.app.populateOperationsList(); + this.manager.recipe.initialiseOperationDragNDrop(); + } + + + /** + * Handler for reset favourites click events. + * Resets favourites to their defaults. + */ + resetFavouritesClick() { + this.app.resetFavourites(); + } + +} + +export default OperationsWaiter; diff --git a/src/web/OptionsWaiter.js b/src/web/OptionsWaiter.mjs similarity index 94% rename from src/web/OptionsWaiter.js rename to src/web/OptionsWaiter.mjs index a3832dc4..3f08b91b 100755 --- a/src/web/OptionsWaiter.js +++ b/src/web/OptionsWaiter.mjs @@ -20,11 +20,6 @@ const OptionsWaiter = function(app, manager) { * @param {Object} options */ OptionsWaiter.prototype.load = function(options) { - $(".option-item input:checkbox").bootstrapSwitch({ - size: "small", - animate: false, - }); - for (const option in options) { this.app.options[option] = options[option]; } @@ -33,7 +28,7 @@ OptionsWaiter.prototype.load = function(options) { const cboxes = document.querySelectorAll("#options-body input[type=checkbox]"); let i; for (i = 0; i < cboxes.length; i++) { - $(cboxes[i]).bootstrapSwitch("state", this.app.options[cboxes[i].getAttribute("option")]); + cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")]; } const nboxes = document.querySelectorAll("#options-body input[type=number]"); @@ -81,11 +76,11 @@ OptionsWaiter.prototype.resetOptionsClick = function() { * Modifies the option state and saves it to local storage. * * @param {event} e - * @param {boolean} state */ -OptionsWaiter.prototype.switchChange = function(e, state) { +OptionsWaiter.prototype.switchChange = function(e) { const el = e.target; const option = el.getAttribute("option"); + const state = el.checked; log.debug(`Setting ${option} to ${state}`); this.app.options[option] = state; diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js deleted file mode 100755 index 5543f07d..00000000 --- a/src/web/OutputWaiter.js +++ /dev/null @@ -1,416 +0,0 @@ -import Utils from "../core/Utils.js"; -import FileSaver from "file-saver"; - - -/** - * Waiter to handle events related to the output. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const OutputWaiter = function(app, manager) { - this.app = app; - this.manager = manager; - - this.dishBuffer = null; - this.dishStr = null; -}; - - -/** - * Gets the output string from the output textarea. - * - * @returns {string} - */ -OutputWaiter.prototype.get = function() { - return document.getElementById("output-text").value; -}; - - -/** - * Sets the output in the output textarea. - * - * @param {string|ArrayBuffer} data - The output string/HTML/ArrayBuffer - * @param {string} type - The data type of the output - * @param {number} duration - The length of time (ms) it took to generate the output - * @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer - */ -OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { - log.debug("Output type: " + type); - const outputText = document.getElementById("output-text"); - const outputHtml = document.getElementById("output-html"); - const outputFile = document.getElementById("output-file"); - const outputHighlighter = document.getElementById("output-highlighter"); - const inputHighlighter = document.getElementById("input-highlighter"); - let scriptElements, lines, length; - - if (!preserveBuffer) { - this.closeFile(); - document.getElementById("show-file-overlay").style.display = "none"; - } - - switch (type) { - case "html": - outputText.style.display = "none"; - outputHtml.style.display = "block"; - outputFile.style.display = "none"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; - - outputText.value = ""; - outputHtml.innerHTML = data; - this.dishStr = Utils.unescapeHtml(Utils.stripHtmlTags(data, true)); - length = data.length; - lines = this.dishStr.count("\n") + 1; - - // Execute script sections - scriptElements = outputHtml.querySelectorAll("script"); - for (let i = 0; i < scriptElements.length; i++) { - try { - eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval - } catch (err) { - log.error(err); - } - } - break; - case "ArrayBuffer": - outputText.style.display = "block"; - outputHtml.style.display = "none"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; - - outputText.value = ""; - outputHtml.innerHTML = ""; - this.dishStr = ""; - length = data.byteLength; - - this.setFile(data); - break; - case "string": - default: - outputText.style.display = "block"; - outputHtml.style.display = "none"; - outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; - - outputText.value = Utils.printable(data, true); - outputHtml.innerHTML = ""; - - lines = data.count("\n") + 1; - length = data.length; - this.dishStr = data; - break; - } - - this.manager.highlighter.removeHighlights(); - this.setOutputInfo(length, lines, duration); -}; - - -/** - * Shows file details. - * - * @param {ArrayBuffer} buf - */ -OutputWaiter.prototype.setFile = function(buf) { - this.dishBuffer = buf; - const file = new File([buf], "output.dat"); - - // Display file overlay in output area with details - const fileOverlay = document.getElementById("output-file"), - fileSize = document.getElementById("output-file-size"); - - fileOverlay.style.display = "block"; - fileSize.textContent = file.size.toLocaleString() + " bytes"; - - // Display preview slice in the background - const outputText = document.getElementById("output-text"), - fileSlice = this.dishBuffer.slice(0, 4096); - - outputText.classList.add("blur"); - outputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice)); -}; - - -/** - * Removes the output file and nulls its memory. - */ -OutputWaiter.prototype.closeFile = function() { - this.dishBuffer = null; - document.getElementById("output-file").style.display = "none"; - document.getElementById("output-text").classList.remove("blur"); -}; - - -/** - * Handler for file download events. - */ -OutputWaiter.prototype.downloadFile = function() { - this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat"); - const file = new File([this.dishBuffer], this.filename); - - if (this.filename) FileSaver.saveAs(file, this.filename, false); -}; - - -/** - * Handler for file slice display events. - */ -OutputWaiter.prototype.displayFileSlice = function() { - const startTime = new Date().getTime(), - showFileOverlay = document.getElementById("show-file-overlay"), - sliceFromEl = document.getElementById("output-file-slice-from"), - sliceToEl = document.getElementById("output-file-slice-to"), - sliceFrom = parseInt(sliceFromEl.value, 10), - sliceTo = parseInt(sliceToEl.value, 10), - str = Utils.arrayBufferToStr(this.dishBuffer.slice(sliceFrom, sliceTo)); - - document.getElementById("output-text").classList.remove("blur"); - showFileOverlay.style.display = "block"; - this.set(str, "string", new Date().getTime() - startTime, true); -}; - - -/** - * Handler for show file overlay events. - * - * @param {Event} e - */ -OutputWaiter.prototype.showFileOverlayClick = function(e) { - const outputFile = document.getElementById("output-file"), - showFileOverlay = e.target; - - document.getElementById("output-text").classList.add("blur"); - outputFile.style.display = "block"; - showFileOverlay.style.display = "none"; - this.setOutputInfo(this.dishBuffer.byteLength, null, 0); -}; - - -/** - * Displays information about the output. - * - * @param {number} length - The length of the current output string - * @param {number} lines - The number of the lines in the current output string - * @param {number} duration - The length of time (ms) it took to generate the output - */ -OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) { - let width = length.toString().length; - width = width < 4 ? 4 : width; - - const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); - const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " "); - - let msg = "time: " + timeStr + "
    length: " + lengthStr; - - if (typeof lines === "number") { - const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); - msg += "
    lines: " + linesStr; - } - - document.getElementById("output-info").innerHTML = msg; - document.getElementById("input-selection-info").innerHTML = ""; - document.getElementById("output-selection-info").innerHTML = ""; -}; - - -/** - * Adjusts the display properties of the output buttons so that they fit within the current width - * without wrapping or overflowing. - */ -OutputWaiter.prototype.adjustWidth = function() { - const output = document.getElementById("output"); - const saveToFile = document.getElementById("save-to-file"); - const copyOutput = document.getElementById("copy-output"); - const switchIO = document.getElementById("switch"); - const undoSwitch = document.getElementById("undo-switch"); - const maximiseOutput = document.getElementById("maximise-output"); - - if (output.clientWidth < 680) { - saveToFile.childNodes[1].nodeValue = ""; - copyOutput.childNodes[1].nodeValue = ""; - switchIO.childNodes[1].nodeValue = ""; - undoSwitch.childNodes[1].nodeValue = ""; - maximiseOutput.childNodes[1].nodeValue = ""; - } else { - saveToFile.childNodes[1].nodeValue = " Save to file"; - copyOutput.childNodes[1].nodeValue = " Copy output"; - switchIO.childNodes[1].nodeValue = " Move output to input"; - undoSwitch.childNodes[1].nodeValue = " Undo"; - maximiseOutput.childNodes[1].nodeValue = - maximiseOutput.getAttribute("title") === "Maximise" ? " Max" : " Restore"; - } -}; - - -/** - * Handler for save click events. - * Saves the current output to a file. - */ -OutputWaiter.prototype.saveClick = function() { - if (!this.dishBuffer) { - this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.dishStr)).buffer; - } - this.downloadFile(); -}; - - -/** - * Handler for copy click events. - * Copies the output to the clipboard. - */ -OutputWaiter.prototype.copyClick = function() { - // Create invisible textarea to populate with the raw dishStr (not the printable version that - // contains dots instead of the actual bytes) - const textarea = document.createElement("textarea"); - textarea.style.position = "fixed"; - textarea.style.top = 0; - textarea.style.left = 0; - textarea.style.width = 0; - textarea.style.height = 0; - textarea.style.border = "none"; - - textarea.value = this.dishStr; - document.body.appendChild(textarea); - - // Select and copy the contents of this textarea - let success = false; - try { - textarea.select(); - success = this.dishStr && document.execCommand("copy"); - } catch (err) { - success = false; - } - - if (success) { - this.app.alert("Copied raw output successfully.", "success", 2000); - } else { - this.app.alert("Sorry, the output could not be copied.", "danger", 2000); - } - - // Clean up - document.body.removeChild(textarea); -}; - - -/** - * Handler for switch click events. - * Moves the current output into the input textarea. - */ -OutputWaiter.prototype.switchClick = function() { - this.switchOrigData = this.manager.input.get(); - document.getElementById("undo-switch").disabled = false; - if (this.dishBuffer) { - this.manager.input.setFile(new File([this.dishBuffer], "output.dat")); - this.manager.input.handleLoaderMessage({ - data: { - progress: 100, - fileBuffer: this.dishBuffer - } - }); - } else { - this.app.setInput(this.dishStr); - } -}; - - -/** - * Handler for undo switch click events. - * Removes the output from the input and replaces the input that was removed. - */ -OutputWaiter.prototype.undoSwitchClick = function() { - this.app.setInput(this.switchOrigData); - document.getElementById("undo-switch").disabled = true; -}; - -/** - * Handler for file switch click events. - * Moves a file's data for items created via Utils.displayFilesAsHTML to the input. - */ -OutputWaiter.prototype.fileSwitch = function(e) { - e.preventDefault(); - this.switchOrigData = this.manager.input.get(); - this.app.setInput(e.target.getAttribute("fileValue")); - document.getElementById("undo-switch").disabled = false; -}; - - -/** - * Handler for maximise output click events. - * Resizes the output frame to be as large as possible, or restores it to its original size. - */ -OutputWaiter.prototype.maximiseOutputClick = function(e) { - const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode; - - if (el.getAttribute("title") === "Maximise") { - this.app.columnSplitter.collapse(0); - this.app.columnSplitter.collapse(1); - this.app.ioSplitter.collapse(0); - - el.setAttribute("title", "Restore"); - el.innerHTML = " Restore"; - this.adjustWidth(); - } else { - el.setAttribute("title", "Maximise"); - el.innerHTML = " Max"; - this.app.resetLayout(); - } -}; - - -/** - * Shows or hides the loading icon. - * - * @param {boolean} value - */ -OutputWaiter.prototype.toggleLoader = function(value) { - const outputLoader = document.getElementById("output-loader"), - outputElement = document.getElementById("output-text"); - - if (value) { - this.manager.controls.hideStaleIndicator(); - this.bakingStatusTimeout = setTimeout(function() { - outputElement.disabled = true; - outputLoader.style.visibility = "visible"; - outputLoader.style.opacity = 1; - this.manager.controls.toggleBakeButtonFunction(true); - }.bind(this), 200); - } else { - clearTimeout(this.bakingStatusTimeout); - outputElement.disabled = false; - outputLoader.style.opacity = 0; - outputLoader.style.visibility = "hidden"; - this.manager.controls.toggleBakeButtonFunction(false); - this.setStatusMsg(""); - } -}; - - -/** - * Sets the baking status message value. - * - * @param {string} msg - */ -OutputWaiter.prototype.setStatusMsg = function(msg) { - const el = document.querySelector("#output-loader .loading-msg"); - - el.textContent = msg; -}; - - -/** - * Returns true if the output contains carriage returns - * - * @returns {boolean} - */ -OutputWaiter.prototype.containsCR = function() { - return this.dishStr.indexOf("\r") >= 0; -}; - -export default OutputWaiter; diff --git a/src/web/OutputWaiter.mjs b/src/web/OutputWaiter.mjs new file mode 100755 index 00000000..7203a16f --- /dev/null +++ b/src/web/OutputWaiter.mjs @@ -0,0 +1,497 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils"; +import FileSaver from "file-saver"; + + +/** + * Waiter to handle events related to the output. + */ +class OutputWaiter { + + /** + * OutputWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.dishBuffer = null; + this.dishStr = null; + } + + + /** + * Gets the output string from the output textarea. + * + * @returns {string} + */ + get() { + return document.getElementById("output-text").value; + } + + + /** + * Sets the output in the output textarea. + * + * @param {string|ArrayBuffer} data - The output string/HTML/ArrayBuffer + * @param {string} type - The data type of the output + * @param {number} duration - The length of time (ms) it took to generate the output + * @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer + */ + async set(data, type, duration, preserveBuffer) { + log.debug("Output type: " + type); + const outputText = document.getElementById("output-text"); + const outputHtml = document.getElementById("output-html"); + const outputFile = document.getElementById("output-file"); + const outputHighlighter = document.getElementById("output-highlighter"); + const inputHighlighter = document.getElementById("input-highlighter"); + let scriptElements, lines, length; + + if (!preserveBuffer) { + this.closeFile(); + this.dishStr = null; + document.getElementById("show-file-overlay").style.display = "none"; + } + + switch (type) { + case "html": + outputText.style.display = "none"; + outputHtml.style.display = "block"; + outputFile.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; + + outputText.value = ""; + outputHtml.innerHTML = data; + + // Execute script sections + scriptElements = outputHtml.querySelectorAll("script"); + for (let i = 0; i < scriptElements.length; i++) { + try { + eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval + } catch (err) { + log.error(err); + } + } + + await this.getDishStr(); + length = this.dishStr.length; + lines = this.dishStr.count("\n") + 1; + break; + case "ArrayBuffer": + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; + + outputText.value = ""; + outputHtml.innerHTML = ""; + length = data.byteLength; + + this.setFile(data); + break; + case "string": + default: + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputFile.style.display = "none"; + outputHighlighter.display = "block"; + inputHighlighter.display = "block"; + + outputText.value = Utils.printable(data, true); + outputHtml.innerHTML = ""; + + lines = data.count("\n") + 1; + length = data.length; + this.dishStr = data; + break; + } + + this.manager.highlighter.removeHighlights(); + this.setOutputInfo(length, lines, duration); + this.backgroundMagic(); + } + + + /** + * Shows file details. + * + * @param {ArrayBuffer} buf + */ + setFile(buf) { + this.dishBuffer = buf; + const file = new File([buf], "output.dat"); + + // Display file overlay in output area with details + const fileOverlay = document.getElementById("output-file"), + fileSize = document.getElementById("output-file-size"); + + fileOverlay.style.display = "block"; + fileSize.textContent = file.size.toLocaleString() + " bytes"; + + // Display preview slice in the background + const outputText = document.getElementById("output-text"), + fileSlice = this.dishBuffer.slice(0, 4096); + + outputText.classList.add("blur"); + outputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice)); + } + + + /** + * Removes the output file and nulls its memory. + */ + closeFile() { + this.dishBuffer = null; + document.getElementById("output-file").style.display = "none"; + document.getElementById("output-text").classList.remove("blur"); + } + + + /** + * Handler for file download events. + */ + async downloadFile() { + this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat"); + await this.getDishBuffer(); + const file = new File([this.dishBuffer], this.filename); + if (this.filename) FileSaver.saveAs(file, this.filename, false); + } + + + /** + * Handler for file slice display events. + */ + displayFileSlice() { + const startTime = new Date().getTime(), + showFileOverlay = document.getElementById("show-file-overlay"), + sliceFromEl = document.getElementById("output-file-slice-from"), + sliceToEl = document.getElementById("output-file-slice-to"), + sliceFrom = parseInt(sliceFromEl.value, 10), + sliceTo = parseInt(sliceToEl.value, 10), + str = Utils.arrayBufferToStr(this.dishBuffer.slice(sliceFrom, sliceTo)); + + document.getElementById("output-text").classList.remove("blur"); + showFileOverlay.style.display = "block"; + this.set(str, "string", new Date().getTime() - startTime, true); + } + + + /** + * Handler for show file overlay events. + * + * @param {Event} e + */ + showFileOverlayClick(e) { + const outputFile = document.getElementById("output-file"), + showFileOverlay = e.target; + + document.getElementById("output-text").classList.add("blur"); + outputFile.style.display = "block"; + showFileOverlay.style.display = "none"; + this.setOutputInfo(this.dishBuffer.byteLength, null, 0); + } + + + /** + * Displays information about the output. + * + * @param {number} length - The length of the current output string + * @param {number} lines - The number of the lines in the current output string + * @param {number} duration - The length of time (ms) it took to generate the output + */ + setOutputInfo(length, lines, duration) { + let width = length.toString().length; + width = width < 4 ? 4 : width; + + const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); + const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " "); + + let msg = "time: " + timeStr + "
    length: " + lengthStr; + + if (typeof lines === "number") { + const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); + msg += "
    lines: " + linesStr; + } + + document.getElementById("output-info").innerHTML = msg; + document.getElementById("input-selection-info").innerHTML = ""; + document.getElementById("output-selection-info").innerHTML = ""; + } + + + /** + * Handler for save click events. + * Saves the current output to a file. + */ + saveClick() { + this.downloadFile(); + } + + + /** + * Handler for copy click events. + * Copies the output to the clipboard. + */ + async copyClick() { + await this.getDishStr(); + + // Create invisible textarea to populate with the raw dish string (not the printable version that + // contains dots instead of the actual bytes) + const textarea = document.createElement("textarea"); + textarea.style.position = "fixed"; + textarea.style.top = 0; + textarea.style.left = 0; + textarea.style.width = 0; + textarea.style.height = 0; + textarea.style.border = "none"; + + textarea.value = this.dishStr; + document.body.appendChild(textarea); + + // Select and copy the contents of this textarea + let success = false; + try { + textarea.select(); + success = this.dishStr && document.execCommand("copy"); + } catch (err) { + success = false; + } + + if (success) { + this.app.alert("Copied raw output successfully.", 2000); + } else { + this.app.alert("Sorry, the output could not be copied.", 3000); + } + + // Clean up + document.body.removeChild(textarea); + } + + + /** + * Handler for switch click events. + * Moves the current output into the input textarea. + */ + async switchClick() { + this.switchOrigData = this.manager.input.get(); + document.getElementById("undo-switch").disabled = false; + if (this.dishBuffer) { + this.manager.input.setFile(new File([this.dishBuffer], "output.dat")); + this.manager.input.handleLoaderMessage({ + data: { + progress: 100, + fileBuffer: this.dishBuffer + } + }); + } else { + await this.getDishStr(); + this.app.setInput(this.dishStr); + } + } + + + /** + * Handler for undo switch click events. + * Removes the output from the input and replaces the input that was removed. + */ + undoSwitchClick() { + this.app.setInput(this.switchOrigData); + const undoSwitch = document.getElementById("undo-switch"); + undoSwitch.disabled = true; + $(undoSwitch).tooltip("hide"); + } + + + /** + * Handler for maximise output click events. + * Resizes the output frame to be as large as possible, or restores it to its original size. + */ + maximiseOutputClick(e) { + const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode; + + if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) { + this.app.columnSplitter.collapse(0); + this.app.columnSplitter.collapse(1); + this.app.ioSplitter.collapse(0); + + $(el).attr("data-original-title", "Restore output pane"); + el.querySelector("i").innerHTML = "fullscreen_exit"; + } else { + $(el).attr("data-original-title", "Maximise output pane"); + el.querySelector("i").innerHTML = "fullscreen"; + this.app.resetLayout(); + } + } + + + /** + * Shows or hides the loading icon. + * + * @param {boolean} value + */ + toggleLoader(value) { + const outputLoader = document.getElementById("output-loader"), + outputElement = document.getElementById("output-text"); + + if (value) { + this.manager.controls.hideStaleIndicator(); + this.bakingStatusTimeout = setTimeout(function() { + outputElement.disabled = true; + outputLoader.style.visibility = "visible"; + outputLoader.style.opacity = 1; + this.manager.controls.toggleBakeButtonFunction(true); + }.bind(this), 200); + } else { + clearTimeout(this.bakingStatusTimeout); + outputElement.disabled = false; + outputLoader.style.opacity = 0; + outputLoader.style.visibility = "hidden"; + this.manager.controls.toggleBakeButtonFunction(false); + this.setStatusMsg(""); + } + } + + + /** + * Sets the baking status message value. + * + * @param {string} msg + */ + setStatusMsg(msg) { + const el = document.querySelector("#output-loader .loading-msg"); + + el.textContent = msg; + } + + + /** + * Returns true if the output contains carriage returns + * + * @returns {boolean} + */ + async containsCR() { + await this.getDishStr(); + return this.dishStr.indexOf("\r") >= 0; + } + + + /** + * Retrieves the current dish as a string, returning the cached version if possible. + * + * @returns {string} + */ + async getDishStr() { + if (this.dishStr) return this.dishStr; + + this.dishStr = await new Promise(resolve => { + this.manager.worker.getDishAs(this.app.dish, "string", r => { + resolve(r.value); + }); + }); + return this.dishStr; + } + + + /** + * Retrieves the current dish as an ArrayBuffer, returning the cached version if possible. + * + * @returns {ArrayBuffer} + */ + async getDishBuffer() { + if (this.dishBuffer) return this.dishBuffer; + + this.dishBuffer = await new Promise(resolve => { + this.manager.worker.getDishAs(this.app.dish, "ArrayBuffer", r => { + resolve(r.value); + }); + }); + return this.dishBuffer; + } + + + /** + * Triggers the BackgroundWorker to attempt Magic on the current output. + */ + backgroundMagic() { + this.hideMagicButton(); + if (!this.app.options.autoMagic) return; + + const sample = this.dishStr ? this.dishStr.slice(0, 1000) : + this.dishBuffer ? this.dishBuffer.slice(0, 1000) : ""; + + if (sample.length) { + this.manager.background.magic(sample); + } + } + + + /** + * Handles the results of a background Magic call. + * + * @param {Object[]} options + */ + backgroundMagicResult(options) { + if (!options.length || + !options[0].recipe.length) + return; + + const currentRecipeConfig = this.app.getRecipeConfig(); + const newRecipeConfig = currentRecipeConfig.concat(options[0].recipe); + const opSequence = options[0].recipe.map(o => o.op).join(", "); + + this.showMagicButton(opSequence, options[0].data, newRecipeConfig); + } + + + /** + * Handler for Magic click events. + * + * Loads the Magic recipe. + * + * @fires Manager#statechange + */ + magicClick() { + const magicButton = document.getElementById("magic"); + this.app.setRecipeConfig(JSON.parse(magicButton.getAttribute("data-recipe"))); + window.dispatchEvent(this.manager.statechange); + this.hideMagicButton(); + } + + + /** + * Displays the Magic button with a title and adds a link to a complete recipe. + * + * @param {string} opSequence + * @param {string} result + * @param {Object[]} recipeConfig + */ + showMagicButton(opSequence, result, recipeConfig) { + const magicButton = document.getElementById("magic"); + magicButton.setAttribute("data-original-title", `${opSequence} will produce "${Utils.truncate(result, 30)}"`); + magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, ""); + magicButton.classList.remove("hidden"); + } + + + /** + * Hides the Magic button and resets its values. + */ + hideMagicButton() { + const magicButton = document.getElementById("magic"); + magicButton.classList.add("hidden"); + magicButton.setAttribute("data-recipe", ""); + magicButton.setAttribute("data-original-title", "Magic!"); + } + +} + +export default OutputWaiter; diff --git a/src/web/RecipeWaiter.js b/src/web/RecipeWaiter.js deleted file mode 100755 index be7ace65..00000000 --- a/src/web/RecipeWaiter.js +++ /dev/null @@ -1,467 +0,0 @@ -import HTMLOperation from "./HTMLOperation.js"; -import Sortable from "sortablejs"; -import Utils from "../core/Utils.js"; - - -/** - * Waiter to handle events related to the recipe. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const RecipeWaiter = function(app, manager) { - this.app = app; - this.manager = manager; - this.removeIntent = false; -}; - - -/** - * Sets up the drag and drop capability for operations in the operations and recipe areas. - */ -RecipeWaiter.prototype.initialiseOperationDragNDrop = function() { - const recList = document.getElementById("rec-list"); - - // Recipe list - Sortable.create(recList, { - group: "recipe", - sort: true, - animation: 0, - delay: 0, - filter: ".arg-input,.arg", - preventOnFilter: false, - setData: function(dataTransfer, dragEl) { - dataTransfer.setData("Text", dragEl.querySelector(".arg-title").textContent); - }, - onEnd: function(evt) { - if (this.removeIntent) { - evt.item.remove(); - evt.target.dispatchEvent(this.manager.operationremove); - } - }.bind(this), - onSort: function(evt) { - if (evt.from.id === "rec-list") { - document.dispatchEvent(this.manager.statechange); - } - }.bind(this) - }); - - Sortable.utils.on(recList, "dragover", function() { - this.removeIntent = false; - }.bind(this)); - - Sortable.utils.on(recList, "dragleave", function() { - this.removeIntent = true; - this.app.progress = 0; - }.bind(this)); - - Sortable.utils.on(recList, "touchend", function(e) { - const loc = e.changedTouches[0]; - const target = document.elementFromPoint(loc.clientX, loc.clientY); - - this.removeIntent = !recList.contains(target); - }.bind(this)); - - // Favourites category - document.querySelector("#categories a").addEventListener("dragover", this.favDragover.bind(this)); - document.querySelector("#categories a").addEventListener("dragleave", this.favDragleave.bind(this)); - document.querySelector("#categories a").addEventListener("drop", this.favDrop.bind(this)); -}; - - -/** - * Creates a drag-n-droppable seed list of operations. - * - * @param {element} listEl - The list to initialise - */ -RecipeWaiter.prototype.createSortableSeedList = function(listEl) { - Sortable.create(listEl, { - group: { - name: "recipe", - pull: "clone", - put: false, - }, - sort: false, - setData: function(dataTransfer, dragEl) { - dataTransfer.setData("Text", dragEl.textContent); - }, - onStart: function(evt) { - // Removes popover element and event bindings from the dragged operation but not the - // event bindings from the one left in the operations list. Without manually removing - // these bindings, we cannot re-initialise the popover on the stub operation. - $(evt.item).popover("destroy").removeData("bs.popover").off("mouseenter").off("mouseleave"); - $(evt.clone).off(".popover").removeData("bs.popover"); - evt.item.setAttribute("data-toggle", "popover-disabled"); - }, - onEnd: this.opSortEnd.bind(this) - }); -}; - - -/** - * Handler for operation sort end events. - * Removes the operation from the list if it has been dropped outside. If not, adds it to the list - * at the appropriate place and initialises it. - * - * @fires Manager#operationadd - * @param {event} evt - */ -RecipeWaiter.prototype.opSortEnd = function(evt) { - if (this.removeIntent) { - if (evt.item.parentNode.id === "rec-list") { - evt.item.remove(); - } - return; - } - - // Reinitialise the popover on the original element in the ops list because for some reason it - // gets destroyed and recreated. - this.manager.ops.enableOpsListPopovers(evt.clone); - - if (evt.item.parentNode.id !== "rec-list") { - return; - } - - this.buildRecipeOperation(evt.item); - evt.item.dispatchEvent(this.manager.operationadd); -}; - - -/** - * Handler for favourite dragover events. - * If the element being dragged is an operation, displays a visual cue so that the user knows it can - * be dropped here. - * - * @param {event} e - */ -RecipeWaiter.prototype.favDragover = function(e) { - if (e.dataTransfer.effectAllowed !== "move") - return false; - - e.stopPropagation(); - e.preventDefault(); - if (e.target.className && e.target.className.indexOf("category-title") > -1) { - // Hovering over the a - e.target.classList.add("favourites-hover"); - } else if (e.target.parentNode.className && e.target.parentNode.className.indexOf("category-title") > -1) { - // Hovering over the Edit button - e.target.parentNode.classList.add("favourites-hover"); - } else if (e.target.parentNode.parentNode.className && e.target.parentNode.parentNode.className.indexOf("category-title") > -1) { - // Hovering over the image on the Edit button - e.target.parentNode.parentNode.classList.add("favourites-hover"); - } -}; - - -/** - * Handler for favourite dragleave events. - * Removes the visual cue. - * - * @param {event} e - */ -RecipeWaiter.prototype.favDragleave = function(e) { - e.stopPropagation(); - e.preventDefault(); - document.querySelector("#categories a").classList.remove("favourites-hover"); -}; - - -/** - * Handler for favourite drop events. - * Adds the dragged operation to the favourites list. - * - * @param {event} e - */ -RecipeWaiter.prototype.favDrop = function(e) { - e.stopPropagation(); - e.preventDefault(); - e.target.classList.remove("favourites-hover"); - - const opName = e.dataTransfer.getData("Text"); - this.app.addFavourite(opName); -}; - - -/** - * Handler for ingredient change events. - * - * @fires Manager#statechange - */ -RecipeWaiter.prototype.ingChange = function(e) { - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for disable click events. - * Updates the icon status. - * - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.disableClick = function(e) { - const icon = e.target; - - if (icon.getAttribute("disabled") === "false") { - icon.setAttribute("disabled", "true"); - icon.classList.add("disable-icon-selected"); - icon.parentNode.parentNode.classList.add("disabled"); - } else { - icon.setAttribute("disabled", "false"); - icon.classList.remove("disable-icon-selected"); - icon.parentNode.parentNode.classList.remove("disabled"); - } - - this.app.progress = 0; - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for breakpoint click events. - * Updates the icon status. - * - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.breakpointClick = function(e) { - const bp = e.target; - - if (bp.getAttribute("break") === "false") { - bp.setAttribute("break", "true"); - bp.classList.add("breakpoint-selected"); - } else { - bp.setAttribute("break", "false"); - bp.classList.remove("breakpoint-selected"); - } - - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for operation doubleclick events. - * Removes the operation from the recipe and auto bakes. - * - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.operationDblclick = function(e) { - e.target.remove(); - this.opRemove(e); -}; - - -/** - * Handler for operation child doubleclick events. - * Removes the operation from the recipe. - * - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.operationChildDblclick = function(e) { - e.target.parentNode.remove(); - this.opRemove(e); -}; - - -/** - * Generates a configuration object to represent the current recipe. - * - * @returns {recipeConfig} - */ -RecipeWaiter.prototype.getConfig = function() { - const config = []; - let ingredients, ingList, disabled, bp, item; - const operations = document.querySelectorAll("#rec-list li.operation"); - - for (let i = 0; i < operations.length; i++) { - ingredients = []; - disabled = operations[i].querySelector(".disable-icon"); - bp = operations[i].querySelector(".breakpoint"); - ingList = operations[i].querySelectorAll(".arg"); - - for (let j = 0; j < ingList.length; j++) { - if (ingList[j].getAttribute("type") === "checkbox") { - // checkbox - ingredients[j] = ingList[j].checked; - } else if (ingList[j].classList.contains("toggle-string")) { - // toggleString - ingredients[j] = { - option: ingList[j].previousSibling.children[0].textContent.slice(0, -1), - string: ingList[j].value - }; - } else if (ingList[j].getAttribute("type") === "number") { - // number - ingredients[j] = parseFloat(ingList[j].value, 10); - } else { - // all others - ingredients[j] = ingList[j].value; - } - } - - item = { - op: operations[i].querySelector(".arg-title").textContent, - args: ingredients - }; - - if (disabled && disabled.getAttribute("disabled") === "true") { - item.disabled = true; - } - - if (bp && bp.getAttribute("break") === "true") { - item.breakpoint = true; - } - - config.push(item); - } - - return config; -}; - - -/** - * Moves or removes the breakpoint indicator in the recipe based on the position. - * - * @param {number} position - */ -RecipeWaiter.prototype.updateBreakpointIndicator = function(position) { - const operations = document.querySelectorAll("#rec-list li.operation"); - for (let i = 0; i < operations.length; i++) { - if (i === position) { - operations[i].classList.add("break"); - } else { - operations[i].classList.remove("break"); - } - } -}; - - -/** - * Given an operation stub element, this function converts it into a full recipe element with - * arguments. - * - * @param {element} el - The operation stub element from the operations pane - */ -RecipeWaiter.prototype.buildRecipeOperation = function(el) { - const opName = el.textContent; - const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); - el.innerHTML = op.toFullHtml(); - - if (this.app.operations[opName].flowControl) { - el.classList.add("flow-control-op"); - } - - // Disable auto-bake if this is a manual op - if (op.manualBake && this.app.autoBake_) { - this.manager.controls.setAutoBake(false); - this.app.alert("Auto-Bake is disabled by default when using this operation.", "info", 5000); - } -}; - -/** - * Adds the specified operation to the recipe. - * - * @fires Manager#operationadd - * @param {string} name - The name of the operation to add - * @returns {element} - */ -RecipeWaiter.prototype.addOperation = function(name) { - const item = document.createElement("li"); - - item.classList.add("operation"); - item.innerHTML = name; - this.buildRecipeOperation(item); - document.getElementById("rec-list").appendChild(item); - - item.dispatchEvent(this.manager.operationadd); - return item; -}; - - -/** - * Removes all operations from the recipe. - * - * @fires Manager#operationremove - */ -RecipeWaiter.prototype.clearRecipe = function() { - const recList = document.getElementById("rec-list"); - while (recList.firstChild) { - recList.removeChild(recList.firstChild); - } - recList.dispatchEvent(this.manager.operationremove); -}; - - -/** - * Handler for operation dropdown events from toggleString arguments. - * Sets the selected option as the name of the button. - * - * @param {event} e - */ -RecipeWaiter.prototype.dropdownToggleClick = function(e) { - const el = e.target; - const button = el.parentNode.parentNode.previousSibling; - - button.innerHTML = el.textContent + " "; - this.ingChange(); -}; - - -/** - * Handler for operationadd events. - * - * @listens Manager#operationadd - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.opAdd = function(e) { - log.debug(`'${e.target.querySelector(".arg-title").textContent}' added to recipe`); - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Handler for operationremove events. - * - * @listens Manager#operationremove - * @fires Manager#statechange - * @param {event} e - */ -RecipeWaiter.prototype.opRemove = function(e) { - log.debug("Operation removed from recipe"); - window.dispatchEvent(this.manager.statechange); -}; - - -/** - * Sets register values. - * - * @param {number} opIndex - * @param {number} numPrevRegisters - * @param {string[]} registers - */ -RecipeWaiter.prototype.setRegisters = function(opIndex, numPrevRegisters, registers) { - const op = document.querySelector(`#rec-list .operation:nth-child(${opIndex + 1})`), - prevRegList = op.querySelector(".register-list"); - - // Remove previous div - if (prevRegList) prevRegList.remove(); - - let registerList = []; - for (let i = 0; i < registers.length; i++) { - registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`); - } - const registerListEl = `
    - ${registerList.join("
    ")} -
    `; - - op.insertAdjacentHTML("beforeend", registerListEl); -}; - -export default RecipeWaiter; diff --git a/src/web/RecipeWaiter.mjs b/src/web/RecipeWaiter.mjs new file mode 100755 index 00000000..b913fede --- /dev/null +++ b/src/web/RecipeWaiter.mjs @@ -0,0 +1,522 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import HTMLOperation from "./HTMLOperation"; +import Sortable from "sortablejs"; +import Utils from "../core/Utils"; + + +/** + * Waiter to handle events related to the recipe. + */ +class RecipeWaiter { + + /** + * RecipeWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + this.removeIntent = false; + } + + + /** + * Sets up the drag and drop capability for operations in the operations and recipe areas. + */ + initialiseOperationDragNDrop() { + const recList = document.getElementById("rec-list"); + + // Recipe list + Sortable.create(recList, { + group: "recipe", + sort: true, + animation: 0, + delay: 0, + filter: ".arg", + preventOnFilter: false, + setData: function(dataTransfer, dragEl) { + dataTransfer.setData("Text", dragEl.querySelector(".op-title").textContent); + }, + onEnd: function(evt) { + if (this.removeIntent) { + evt.item.remove(); + evt.target.dispatchEvent(this.manager.operationremove); + } + }.bind(this), + onSort: function(evt) { + if (evt.from.id === "rec-list") { + document.dispatchEvent(this.manager.statechange); + } + }.bind(this) + }); + + Sortable.utils.on(recList, "dragover", function() { + this.removeIntent = false; + }.bind(this)); + + Sortable.utils.on(recList, "dragleave", function() { + this.removeIntent = true; + this.app.progress = 0; + }.bind(this)); + + Sortable.utils.on(recList, "touchend", function(e) { + const loc = e.changedTouches[0]; + const target = document.elementFromPoint(loc.clientX, loc.clientY); + + this.removeIntent = !recList.contains(target); + }.bind(this)); + + // Favourites category + document.querySelector("#categories a").addEventListener("dragover", this.favDragover.bind(this)); + document.querySelector("#categories a").addEventListener("dragleave", this.favDragleave.bind(this)); + document.querySelector("#categories a").addEventListener("drop", this.favDrop.bind(this)); + } + + + /** + * Creates a drag-n-droppable seed list of operations. + * + * @param {element} listEl - The list to initialise + */ + createSortableSeedList(listEl) { + Sortable.create(listEl, { + group: { + name: "recipe", + pull: "clone", + put: false, + }, + sort: false, + setData: function(dataTransfer, dragEl) { + dataTransfer.setData("Text", dragEl.textContent); + }, + onStart: function(evt) { + // Removes popover element and event bindings from the dragged operation but not the + // event bindings from the one left in the operations list. Without manually removing + // these bindings, we cannot re-initialise the popover on the stub operation. + $(evt.item) + .popover("dispose") + .removeData("bs.popover") + .off("mouseenter") + .off("mouseleave") + .attr("data-toggle", "popover-disabled"); + $(evt.clone) + .off(".popover") + .removeData("bs.popover"); + }, + onEnd: this.opSortEnd.bind(this) + }); + } + + + /** + * Handler for operation sort end events. + * Removes the operation from the list if it has been dropped outside. If not, adds it to the list + * at the appropriate place and initialises it. + * + * @fires Manager#operationadd + * @param {event} evt + */ + opSortEnd(evt) { + if (this.removeIntent) { + if (evt.item.parentNode.id === "rec-list") { + evt.item.remove(); + } + return; + } + + // Reinitialise the popover on the original element in the ops list because for some reason it + // gets destroyed and recreated. + this.manager.ops.enableOpsListPopovers(evt.clone); + + if (evt.item.parentNode.id !== "rec-list") { + return; + } + + this.buildRecipeOperation(evt.item); + evt.item.dispatchEvent(this.manager.operationadd); + } + + + /** + * Handler for favourite dragover events. + * If the element being dragged is an operation, displays a visual cue so that the user knows it can + * be dropped here. + * + * @param {event} e + */ + favDragover(e) { + if (e.dataTransfer.effectAllowed !== "move") + return false; + + e.stopPropagation(); + e.preventDefault(); + if (e.target.className && e.target.className.indexOf("category-title") > -1) { + // Hovering over the a + e.target.classList.add("favourites-hover"); + } else if (e.target.parentNode.className && e.target.parentNode.className.indexOf("category-title") > -1) { + // Hovering over the Edit button + e.target.parentNode.classList.add("favourites-hover"); + } else if (e.target.parentNode.parentNode.className && e.target.parentNode.parentNode.className.indexOf("category-title") > -1) { + // Hovering over the image on the Edit button + e.target.parentNode.parentNode.classList.add("favourites-hover"); + } + } + + + /** + * Handler for favourite dragleave events. + * Removes the visual cue. + * + * @param {event} e + */ + favDragleave(e) { + e.stopPropagation(); + e.preventDefault(); + document.querySelector("#categories a").classList.remove("favourites-hover"); + } + + + /** + * Handler for favourite drop events. + * Adds the dragged operation to the favourites list. + * + * @param {event} e + */ + favDrop(e) { + e.stopPropagation(); + e.preventDefault(); + e.target.classList.remove("favourites-hover"); + + const opName = e.dataTransfer.getData("Text"); + this.app.addFavourite(opName); + } + + + /** + * Handler for ingredient change events. + * + * @fires Manager#statechange + */ + ingChange(e) { + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for disable click events. + * Updates the icon status. + * + * @fires Manager#statechange + * @param {event} e + */ + disableClick(e) { + const icon = e.target; + + if (icon.getAttribute("disabled") === "false") { + icon.setAttribute("disabled", "true"); + icon.classList.add("disable-icon-selected"); + icon.parentNode.parentNode.classList.add("disabled"); + } else { + icon.setAttribute("disabled", "false"); + icon.classList.remove("disable-icon-selected"); + icon.parentNode.parentNode.classList.remove("disabled"); + } + + this.app.progress = 0; + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for breakpoint click events. + * Updates the icon status. + * + * @fires Manager#statechange + * @param {event} e + */ + breakpointClick(e) { + const bp = e.target; + + if (bp.getAttribute("break") === "false") { + bp.setAttribute("break", "true"); + bp.classList.add("breakpoint-selected"); + } else { + bp.setAttribute("break", "false"); + bp.classList.remove("breakpoint-selected"); + } + + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for operation doubleclick events. + * Removes the operation from the recipe and auto bakes. + * + * @fires Manager#statechange + * @param {event} e + */ + operationDblclick(e) { + e.target.remove(); + this.opRemove(e); + } + + + /** + * Handler for operation child doubleclick events. + * Removes the operation from the recipe. + * + * @fires Manager#statechange + * @param {event} e + */ + operationChildDblclick(e) { + e.target.parentNode.remove(); + this.opRemove(e); + } + + + /** + * Generates a configuration object to represent the current recipe. + * + * @returns {recipeConfig} + */ + getConfig() { + const config = []; + let ingredients, ingList, disabled, bp, item; + const operations = document.querySelectorAll("#rec-list li.operation"); + + for (let i = 0; i < operations.length; i++) { + ingredients = []; + disabled = operations[i].querySelector(".disable-icon"); + bp = operations[i].querySelector(".breakpoint"); + ingList = operations[i].querySelectorAll(".arg"); + + for (let j = 0; j < ingList.length; j++) { + if (ingList[j].getAttribute("type") === "checkbox") { + // checkbox + ingredients[j] = ingList[j].checked; + } else if (ingList[j].classList.contains("toggle-string")) { + // toggleString + ingredients[j] = { + option: ingList[j].parentNode.parentNode.querySelector("button").textContent, + string: ingList[j].value + }; + } else if (ingList[j].getAttribute("type") === "number") { + // number + ingredients[j] = parseFloat(ingList[j].value, 10); + } else { + // all others + ingredients[j] = ingList[j].value; + } + } + + item = { + op: operations[i].querySelector(".op-title").textContent, + args: ingredients + }; + + if (disabled && disabled.getAttribute("disabled") === "true") { + item.disabled = true; + } + + if (bp && bp.getAttribute("break") === "true") { + item.breakpoint = true; + } + + config.push(item); + } + + return config; + } + + + /** + * Moves or removes the breakpoint indicator in the recipe based on the position. + * + * @param {number} position + */ + updateBreakpointIndicator(position) { + const operations = document.querySelectorAll("#rec-list li.operation"); + for (let i = 0; i < operations.length; i++) { + if (i === position) { + operations[i].classList.add("break"); + } else { + operations[i].classList.remove("break"); + } + } + } + + + /** + * Given an operation stub element, this function converts it into a full recipe element with + * arguments. + * + * @param {element} el - The operation stub element from the operations pane + */ + buildRecipeOperation(el) { + const opName = el.textContent; + const op = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + el.innerHTML = op.toFullHtml(); + + if (this.app.operations[opName].flowControl) { + el.classList.add("flow-control-op"); + } + + // Disable auto-bake if this is a manual op + if (op.manualBake && this.app.autoBake_) { + this.manager.controls.setAutoBake(false); + this.app.alert("Auto-Bake is disabled by default when using this operation.", 5000); + } + } + + /** + * Adds the specified operation to the recipe. + * + * @fires Manager#operationadd + * @param {string} name - The name of the operation to add + * @returns {element} + */ + addOperation(name) { + const item = document.createElement("li"); + + item.classList.add("operation"); + item.innerHTML = name; + this.buildRecipeOperation(item); + document.getElementById("rec-list").appendChild(item); + + item.dispatchEvent(this.manager.operationadd); + return item; + } + + + /** + * Removes all operations from the recipe. + * + * @fires Manager#operationremove + */ + clearRecipe() { + const recList = document.getElementById("rec-list"); + while (recList.firstChild) { + recList.removeChild(recList.firstChild); + } + recList.dispatchEvent(this.manager.operationremove); + } + + + /** + * Handler for operation dropdown events from toggleString arguments. + * Sets the selected option as the name of the button. + * + * @param {event} e + */ + dropdownToggleClick(e) { + e.stopPropagation(); + e.preventDefault(); + + const el = e.target; + const button = el.parentNode.parentNode.querySelector("button"); + + button.innerHTML = el.textContent; + this.ingChange(); + } + + + /** + * Handler for operationadd events. + * + * @listens Manager#operationadd + * @fires Manager#statechange + * @param {event} e + */ + opAdd(e) { + log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`); + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Handler for operationremove events. + * + * @listens Manager#operationremove + * @fires Manager#statechange + * @param {event} e + */ + opRemove(e) { + log.debug("Operation removed from recipe"); + window.dispatchEvent(this.manager.statechange); + } + + + /** + * Sets register values. + * + * @param {number} opIndex + * @param {number} numPrevRegisters + * @param {string[]} registers + */ + setRegisters(opIndex, numPrevRegisters, registers) { + const op = document.querySelector(`#rec-list .operation:nth-child(${opIndex + 1})`), + prevRegList = op.querySelector(".register-list"); + + // Remove previous div + if (prevRegList) prevRegList.remove(); + + const registerList = []; + for (let i = 0; i < registers.length; i++) { + registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`); + } + const registerListEl = `
    + ${registerList.join("
    ")} +
    `; + + op.insertAdjacentHTML("beforeend", registerListEl); + } + + /** + * Adjusts the number of ingredient columns as the width of the recipe changes. + */ + adjustWidth() { + const recList = document.getElementById("rec-list"); + + if (!this.ingredientRuleID) { + this.ingredientRuleID = null; + this.ingredientChildRuleID = null; + + // Find relevant rules in the stylesheet + for (const i in document.styleSheets[0].cssRules) { + if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") { + this.ingredientRuleID = i; + } + if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") { + this.ingredientChildRuleID = i; + } + } + } + + if (!this.ingredientRuleID || !this.ingredientChildRuleID) return; + + const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID], + ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID]; + + if (recList.clientWidth < 450) { + ingredientRule.style.gridTemplateColumns = "auto auto"; + ingredientChildRule.style.gridColumn = "1 / span 2"; + } else if (recList.clientWidth < 620) { + ingredientRule.style.gridTemplateColumns = "auto auto auto"; + ingredientChildRule.style.gridColumn = "1 / span 3"; + } else { + ingredientRule.style.gridTemplateColumns = "auto auto auto auto"; + ingredientChildRule.style.gridColumn = "1 / span 4"; + } + } + +} + +export default RecipeWaiter; diff --git a/src/web/SeasonalWaiter.js b/src/web/SeasonalWaiter.js deleted file mode 100755 index fb671024..00000000 --- a/src/web/SeasonalWaiter.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Waiter to handle seasonal events and easter eggs. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const SeasonalWaiter = function(app, manager) { - this.app = app; - this.manager = manager; -}; - - -/** - * Loads all relevant items depending on the current date. - */ -SeasonalWaiter.prototype.load = function() { - // Konami code - this.kkeys = []; - window.addEventListener("keydown", this.konamiCodeListener.bind(this)); -}; - - -/** - * Listen for the Konami code sequence of keys. Turn the page upside down if they are all heard in - * sequence. - * #konamicode - */ -SeasonalWaiter.prototype.konamiCodeListener = function(e) { - this.kkeys.push(e.keyCode); - const konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; - for (let i = 0; i < this.kkeys.length; i++) { - if (this.kkeys[i] !== konami[i]) { - this.kkeys = []; - break; - } - if (i === konami.length - 1) { - $("body").children().toggleClass("konami"); - this.kkeys = []; - } - } -}; - -export default SeasonalWaiter; diff --git a/src/web/SeasonalWaiter.mjs b/src/web/SeasonalWaiter.mjs new file mode 100755 index 00000000..e6611a89 --- /dev/null +++ b/src/web/SeasonalWaiter.mjs @@ -0,0 +1,56 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Waiter to handle seasonal events and easter eggs. + */ +class SeasonalWaiter { + + /** + * SeasonalWaiter contructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + } + + + /** + * Loads all relevant items depending on the current date. + */ + load() { + // Konami code + this.kkeys = []; + window.addEventListener("keydown", this.konamiCodeListener.bind(this)); + } + + + /** + * Listen for the Konami code sequence of keys. Turn the page upside down if they are all heard in + * sequence. + * #konamicode + */ + konamiCodeListener(e) { + this.kkeys.push(e.keyCode); + const konami = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; + for (let i = 0; i < this.kkeys.length; i++) { + if (this.kkeys[i] !== konami[i]) { + this.kkeys = []; + break; + } + if (i === konami.length - 1) { + $("body").children().toggleClass("konami"); + this.kkeys = []; + } + } + } + +} + +export default SeasonalWaiter; diff --git a/src/web/WindowWaiter.js b/src/web/WindowWaiter.js deleted file mode 100755 index e0d3944c..00000000 --- a/src/web/WindowWaiter.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Waiter to handle events related to the window object. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - */ -const WindowWaiter = function(app) { - this.app = app; -}; - - -/** - * Handler for window resize events. - * Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause - * continuous resetting). - */ -WindowWaiter.prototype.windowResize = function() { - clearTimeout(this.resetLayoutTimeout); - this.resetLayoutTimeout = setTimeout(this.app.resetLayout.bind(this.app), 200); -}; - - -/** - * Handler for window blur events. - * Saves the current time so that we can calculate how long the window was unfocussed for when - * focus is returned. - */ -WindowWaiter.prototype.windowBlur = function() { - this.windowBlurTime = new Date().getTime(); -}; - - -/** - * Handler for window focus events. - * - * 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. - */ -WindowWaiter.prototype.windowFocus = function() { - const unfocusedTime = new Date().getTime() - this.windowBlurTime; - if (unfocusedTime > 60000) { - this.app.silentBake(); - } -}; - -export default WindowWaiter; diff --git a/src/web/WindowWaiter.mjs b/src/web/WindowWaiter.mjs new file mode 100755 index 00000000..a8e124f5 --- /dev/null +++ b/src/web/WindowWaiter.mjs @@ -0,0 +1,62 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +/** + * Waiter to handle events related to the window object. + */ +class WindowWaiter { + + /** + * WindowWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + */ + constructor(app) { + this.app = app; + } + + + /** + * Handler for window resize events. + * Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause + * continuous resetting). + */ + windowResize() { + clearTimeout(this.resetLayoutTimeout); + this.resetLayoutTimeout = setTimeout(this.app.resetLayout.bind(this.app), 200); + } + + + /** + * Handler for window blur events. + * Saves the current time so that we can calculate how long the window was unfocussed for when + * focus is returned. + */ + windowBlur() { + this.windowBlurTime = new Date().getTime(); + } + + + /** + * Handler for window focus events. + * + * 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. + */ + windowFocus() { + const unfocusedTime = new Date().getTime() - this.windowBlurTime; + if (unfocusedTime > 60000) { + this.app.silentBake(); + } + } + +} + +export default WindowWaiter; diff --git a/src/web/WorkerWaiter.js b/src/web/WorkerWaiter.js deleted file mode 100644 index 1473e101..00000000 --- a/src/web/WorkerWaiter.js +++ /dev/null @@ -1,203 +0,0 @@ -import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.js"; - -/** - * Waiter to handle conversations with the ChefWorker. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - * @constructor - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ -const WorkerWaiter = function(app, manager) { - this.app = app; - this.manager = manager; -}; - - -/** - * Sets up the ChefWorker and associated listeners. - */ -WorkerWaiter.prototype.registerChefWorker = function() { - log.debug("Registering new ChefWorker"); - this.chefWorker = new ChefWorker(); - this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this)); - this.setLogLevel(); - - let docURL = document.location.href.split(/[#?]/)[0]; - const index = docURL.lastIndexOf("/"); - if (index > 0) { - docURL = docURL.substring(0, index); - } - this.chefWorker.postMessage({"action": "docURL", "data": docURL}); -}; - - -/** - * Handler for messages sent back by the ChefWorker. - * - * @param {MessageEvent} e - */ -WorkerWaiter.prototype.handleChefMessage = function(e) { - const r = e.data; - log.debug("Receiving '" + r.action + "' from ChefWorker"); - - switch (r.action) { - case "bakeComplete": - this.bakingComplete(r.data); - break; - case "bakeError": - this.app.handleError(r.data); - this.setBakingStatus(false); - break; - case "silentBakeComplete": - break; - case "workerLoaded": - this.app.workerLoaded = true; - log.debug("ChefWorker loaded"); - this.app.loaded(); - break; - case "statusMessage": - this.manager.output.setStatusMsg(r.data); - break; - case "optionUpdate": - log.debug(`Setting ${r.data.option} to ${r.data.value}`); - this.app.options[r.data.option] = r.data.value; - break; - case "setRegisters": - this.manager.recipe.setRegisters(r.data.opIndex, r.data.numPrevRegisters, r.data.registers); - break; - case "highlightsCalculated": - this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction); - break; - default: - log.error("Unrecognised message from ChefWorker", e); - break; - } -}; - - -/** - * Updates the UI to show if baking is in process or not. - * - * @param {bakingStatus} - */ -WorkerWaiter.prototype.setBakingStatus = function(bakingStatus) { - this.app.baking = bakingStatus; - - this.manager.output.toggleLoader(bakingStatus); -}; - - -/** - * Cancels the current bake by terminating the ChefWorker and creating a new one. - */ -WorkerWaiter.prototype.cancelBake = function() { - this.chefWorker.terminate(); - this.registerChefWorker(); - this.setBakingStatus(false); - this.manager.controls.showStaleIndicator(); -}; - - -/** - * Handler for completed bakes. - * - * @param {Object} response - */ -WorkerWaiter.prototype.bakingComplete = function(response) { - this.setBakingStatus(false); - - if (!response) return; - - if (response.error) { - this.app.handleError(response.error); - } - - this.app.progress = response.progress; - this.manager.recipe.updateBreakpointIndicator(response.progress); - this.manager.output.set(response.result, response.type, response.duration); - log.debug("--- Bake complete ---"); -}; - - -/** - * Asks the ChefWorker to bake the current input using the current recipe. - * - * @param {string} input - * @param {Object[]} recipeConfig - * @param {Object} options - * @param {number} progress - * @param {boolean} step - */ -WorkerWaiter.prototype.bake = function(input, recipeConfig, options, progress, step) { - this.setBakingStatus(true); - - this.chefWorker.postMessage({ - action: "bake", - data: { - input: input, - recipeConfig: recipeConfig, - options: options, - progress: progress, - step: step - } - }); -}; - - -/** - * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant - * JavaScript code needed to do a real bake. - * - * @param {Object[]} [recipeConfig] - */ -WorkerWaiter.prototype.silentBake = function(recipeConfig) { - this.chefWorker.postMessage({ - action: "silentBake", - data: { - recipeConfig: recipeConfig - } - }); -}; - - -/** - * Asks the ChefWorker to calculate 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. - */ -WorkerWaiter.prototype.highlight = function(recipeConfig, direction, pos) { - this.chefWorker.postMessage({ - action: "highlight", - data: { - recipeConfig: recipeConfig, - direction: direction, - pos: pos - } - }); -}; - - -/** - * Sets the console log level in the worker. - * - * @param {string} level - */ -WorkerWaiter.prototype.setLogLevel = function(level) { - if (!this.chefWorker) return; - - this.chefWorker.postMessage({ - action: "setLogLevel", - data: log.getLevel() - }); -}; - - -export default WorkerWaiter; diff --git a/src/web/WorkerWaiter.mjs b/src/web/WorkerWaiter.mjs new file mode 100755 index 00000000..7ef72263 --- /dev/null +++ b/src/web/WorkerWaiter.mjs @@ -0,0 +1,239 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker"; + +/** + * Waiter to handle conversations with the ChefWorker. + */ +class WorkerWaiter { + + /** + * WorkerWaiter constructor. + * + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ + constructor(app, manager) { + this.app = app; + this.manager = manager; + + this.callbacks = {}; + this.callbackID = 0; + } + + + /** + * Sets up the ChefWorker and associated listeners. + */ + registerChefWorker() { + log.debug("Registering new ChefWorker"); + this.chefWorker = new ChefWorker(); + this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this)); + this.setLogLevel(); + + let docURL = document.location.href.split(/[#?]/)[0]; + const index = docURL.lastIndexOf("/"); + if (index > 0) { + docURL = docURL.substring(0, index); + } + this.chefWorker.postMessage({"action": "docURL", "data": docURL}); + } + + + /** + * Handler for messages sent back by the ChefWorker. + * + * @param {MessageEvent} e + */ + handleChefMessage(e) { + const r = e.data; + log.debug("Receiving '" + r.action + "' from ChefWorker"); + + switch (r.action) { + case "bakeComplete": + this.bakingComplete(r.data); + break; + case "bakeError": + this.app.handleError(r.data); + this.setBakingStatus(false); + break; + case "dishReturned": + this.callbacks[r.data.id](r.data); + break; + case "silentBakeComplete": + break; + case "workerLoaded": + this.app.workerLoaded = true; + log.debug("ChefWorker loaded"); + this.app.loaded(); + break; + case "statusMessage": + this.manager.output.setStatusMsg(r.data); + break; + case "optionUpdate": + log.debug(`Setting ${r.data.option} to ${r.data.value}`); + this.app.options[r.data.option] = r.data.value; + break; + case "setRegisters": + this.manager.recipe.setRegisters(r.data.opIndex, r.data.numPrevRegisters, r.data.registers); + break; + case "highlightsCalculated": + this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction); + break; + default: + log.error("Unrecognised message from ChefWorker", e); + break; + } + } + + + /** + * Updates the UI to show if baking is in process or not. + * + * @param {bakingStatus} + */ + setBakingStatus(bakingStatus) { + this.app.baking = bakingStatus; + + this.manager.output.toggleLoader(bakingStatus); + } + + + /** + * Cancels the current bake by terminating the ChefWorker and creating a new one. + */ + cancelBake() { + this.chefWorker.terminate(); + this.registerChefWorker(); + this.setBakingStatus(false); + this.manager.controls.showStaleIndicator(); + } + + + /** + * Handler for completed bakes. + * + * @param {Object} response + */ + bakingComplete(response) { + this.setBakingStatus(false); + + if (!response) return; + + if (response.error) { + this.app.handleError(response.error); + } + + this.app.progress = response.progress; + this.app.dish = response.dish; + this.manager.recipe.updateBreakpointIndicator(response.progress); + this.manager.output.set(response.result, response.type, response.duration); + log.debug("--- Bake complete ---"); + } + + + /** + * Asks the ChefWorker to bake the current input using the current recipe. + * + * @param {string} input + * @param {Object[]} recipeConfig + * @param {Object} options + * @param {number} progress + * @param {boolean} step + */ + bake(input, recipeConfig, options, progress, step) { + this.setBakingStatus(true); + + this.chefWorker.postMessage({ + action: "bake", + data: { + input: input, + recipeConfig: recipeConfig, + options: options, + progress: progress, + step: step + } + }); + } + + + /** + * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant + * JavaScript code needed to do a real bake. + * + * @param {Object[]} [recipeConfig] + */ + silentBake(recipeConfig) { + this.chefWorker.postMessage({ + action: "silentBake", + data: { + recipeConfig: recipeConfig + } + }); + } + + + /** + * Asks the ChefWorker to calculate 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. + */ + highlight(recipeConfig, direction, pos) { + this.chefWorker.postMessage({ + action: "highlight", + data: { + recipeConfig: recipeConfig, + direction: direction, + pos: pos + } + }); + } + + + /** + * Asks the ChefWorker to return the dish as the specified type + * + * @param {Dish} dish + * @param {string} type + * @param {Function} callback + */ + getDishAs(dish, type, callback) { + const id = this.callbackID++; + this.callbacks[id] = callback; + this.chefWorker.postMessage({ + action: "getDishAs", + data: { + dish: dish, + type: type, + id: id + } + }); + } + + + /** + * Sets the console log level in the worker. + * + * @param {string} level + */ + setLogLevel(level) { + if (!this.chefWorker) return; + + this.chefWorker.postMessage({ + action: "setLogLevel", + data: log.getLevel() + }); + } + +} + + +export default WorkerWaiter; diff --git a/src/web/html/index.html b/src/web/html/index.html index 5575999a..aa13da7c 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -143,21 +143,19 @@
    - Edit -
    - - -
    +
    -
  • cells. if (firstRowHeader) { - let row = tableData.shift(); - output += "
    CommandShortcut (Win/Linux)Shortcut (Mac)
    Place cursor in search fieldCtrl+${modWinLin}+fCtrl+${modMac}+f
    Place cursor in input boxCtrl+${modWinLin}+iCtrl+${modMac}+i
    Place cursor in output boxCtrl+${modWinLin}+oCtrl+${modMac}+o
    Place cursor in first argument field of the next operation in the recipeCtrl+${modWinLin}+.Ctrl+${modMac}+.
    Place cursor in first argument field of the nth operation in the recipeCtrl+${modWinLin}+[1-9]Ctrl+${modMac}+[1-9]
    Disable current operationCtrl+${modWinLin}+dCtrl+${modMac}+d
    Set/clear breakpointCtrl+${modWinLin}+bCtrl+${modMac}+b
    BakeCtrl+${modWinLin}+SpaceCtrl+${modMac}+Space
    StepCtrl+${modWinLin}+'Ctrl+${modMac}+'
    Clear recipeCtrl+${modWinLin}+cCtrl+${modMac}+c
    Save to fileCtrl+${modWinLin}+sCtrl+${modMac}+s
    Load recipeCtrl+${modWinLin}+lCtrl+${modMac}+l
    Move output to inputCtrl+${modWinLin}+mCtrl+${modMac}+m
    CommandShortcut (Win/Linux)Shortcut (Mac)
    Place cursor in search fieldCtrl+${modWinLin}+fCtrl+${modMac}+f
    Place cursor in input boxCtrl+${modWinLin}+iCtrl+${modMac}+i
    Place cursor in output boxCtrl+${modWinLin}+oCtrl+${modMac}+o
    Place cursor in first argument field of the next operation in the recipeCtrl+${modWinLin}+.Ctrl+${modMac}+.
    Place cursor in first argument field of the nth operation in the recipeCtrl+${modWinLin}+[1-9]Ctrl+${modMac}+[1-9]
    Disable current operationCtrl+${modWinLin}+dCtrl+${modMac}+d
    Set/clear breakpointCtrl+${modWinLin}+bCtrl+${modMac}+b
    BakeCtrl+${modWinLin}+SpaceCtrl+${modMac}+Space
    StepCtrl+${modWinLin}+'Ctrl+${modMac}+'
    Clear recipeCtrl+${modWinLin}+cCtrl+${modMac}+c
    Save to fileCtrl+${modWinLin}+sCtrl+${modMac}+s
    Load recipeCtrl+${modWinLin}+lCtrl+${modMac}+l
    Move output to inputCtrl+${modWinLin}+mCtrl+${modMac}+m