Bladeren bron

Merge branch 'esm'

n1474335 7 jaren geleden
bovenliggende
commit
6de74b8211
100 gewijzigde bestanden met toevoegingen van 8063 en 9207 verwijderingen
  1. 5 0
      .babelrc
  2. 14 0
      .editorconfig
  3. 1 2
      .eslintignore
  4. 3 3
      .eslintrc.json
  5. 4 1
      .gitignore
  6. 1 1
      .travis.yml
  7. 36 0
      CHANGELOG.md
  8. 52 67
      Gruntfile.js
  9. 8 2
      README.md
  10. 500 229
      package-lock.json
  11. 41 32
      package.json
  12. 0 165
      src/core/Chef.js
  13. 198 0
      src/core/Chef.mjs
  14. 40 17
      src/core/ChefWorker.js
  15. 0 274
      src/core/Dish.js
  16. 362 0
      src/core/Dish.mjs
  17. 0 289
      src/core/FlowControl.js
  18. 0 92
      src/core/Ingredient.js
  19. 118 0
      src/core/Ingredient.mjs
  20. 0 174
      src/core/Operation.js
  21. 293 0
      src/core/Operation.mjs
  22. 0 264
      src/core/Recipe.js
  23. 290 0
      src/core/Recipe.mjs
  24. 206 407
      src/core/Utils.mjs
  25. 54 67
      src/core/config/Categories.json
  26. 0 4355
      src/core/config/OperationConfig.js
  27. 0 22
      src/core/config/modules/BSON.js
  28. 0 21
      src/core/config/modules/CharEnc.js
  29. 0 44
      src/core/config/modules/Ciphers.js
  30. 0 44
      src/core/config/modules/Code.js
  31. 0 32
      src/core/config/modules/Compression.js
  32. 0 201
      src/core/config/modules/Default.js
  33. 0 20
      src/core/config/modules/Diff.js
  34. 0 21
      src/core/config/modules/Encodings.js
  35. 0 22
      src/core/config/modules/HTTP.js
  36. 0 56
      src/core/config/modules/Hashing.js
  37. 0 25
      src/core/config/modules/Image.js
  38. 0 25
      src/core/config/modules/JSBN.js
  39. 0 45
      src/core/config/modules/OpModules.js
  40. 0 25
      src/core/config/modules/PGP.js
  41. 0 25
      src/core/config/modules/PublicKey.js
  42. 0 30
      src/core/config/modules/Regex.js
  43. 0 20
      src/core/config/modules/Shellcode.js
  44. 0 23
      src/core/config/modules/URL.js
  45. 148 0
      src/core/config/scripts/generateConfig.mjs
  46. 60 0
      src/core/config/scripts/generateOpsIndex.mjs
  47. 223 0
      src/core/config/scripts/newOperation.mjs
  48. 26 0
      src/core/errors/OperationError.mjs
  49. 139 0
      src/core/lib/Arithmetic.mjs
  50. 48 0
      src/core/lib/BCD.mjs
  51. 22 0
      src/core/lib/Base58.mjs
  52. 141 0
      src/core/lib/Base64.mjs
  53. 117 0
      src/core/lib/BitwiseOp.mjs
  54. 204 0
      src/core/lib/CanvasComponents.mjs
  55. 58 0
      src/core/lib/ChrEnc.mjs
  56. 82 0
      src/core/lib/Ciphers.mjs
  57. 29 0
      src/core/lib/Code.mjs
  58. 313 0
      src/core/lib/DateTime.mjs
  59. 74 0
      src/core/lib/Delim.mjs
  60. 41 0
      src/core/lib/Extract.mjs
  61. 20 0
      src/core/lib/FlowControl.mjs
  62. 28 0
      src/core/lib/Hash.mjs
  63. 110 0
      src/core/lib/Hex.mjs
  64. 557 0
      src/core/lib/IP.mjs
  65. 1268 0
      src/core/lib/Magic.mjs
  66. 117 0
      src/core/lib/PGP.mjs
  67. 72 0
      src/core/lib/PublicKey.mjs
  68. 103 0
      src/core/lib/Rotate.mjs
  69. 19 0
      src/core/lib/Zlib.mjs
  70. 0 186
      src/core/lib/canvascomponents.js
  71. 76 0
      src/core/operations/ADD.mjs
  72. 108 0
      src/core/operations/AESDecrypt.mjs
  73. 106 0
      src/core/operations/AESEncrypt.mjs
  74. 76 0
      src/core/operations/AND.mjs
  75. 46 0
      src/core/operations/AddLineNumbers.mjs
  76. 52 0
      src/core/operations/Adler32Checksum.mjs
  77. 105 0
      src/core/operations/AffineCipherDecode.mjs
  78. 77 0
      src/core/operations/AffineCipherEncode.mjs
  79. 183 0
      src/core/operations/AnalyseHash.mjs
  80. 0 253
      src/core/operations/Arithmetic.js
  81. 66 0
      src/core/operations/AtbashCipher.mjs
  82. 0 215
      src/core/operations/BCD.js
  83. 0 59
      src/core/operations/BSON.js
  84. 50 0
      src/core/operations/BSONDeserialise.mjs
  85. 50 0
      src/core/operations/BSONSerialise.mjs
  86. 0 68
      src/core/operations/Base.js
  87. 0 137
      src/core/operations/Base58.js
  88. 0 347
      src/core/operations/Base64.js
  89. 54 0
      src/core/operations/Bcrypt.mjs
  90. 55 0
      src/core/operations/BcryptCompare.mjs
  91. 48 0
      src/core/operations/BcryptParse.mjs
  92. 124 0
      src/core/operations/BifidCipherDecode.mjs
  93. 129 0
      src/core/operations/BifidCipherEncode.mjs
  94. 75 0
      src/core/operations/BitShiftLeft.mjs
  95. 82 0
      src/core/operations/BitShiftRight.mjs
  96. 0 368
      src/core/operations/BitwiseOp.js
  97. 100 0
      src/core/operations/BlowfishDecrypt.mjs
  98. 101 0
      src/core/operations/BlowfishEncrypt.mjs
  99. 0 432
      src/core/operations/ByteRepr.js
  100. 55 0
      src/core/operations/Bzip2Decompress.mjs

+ 5 - 0
.babelrc

@@ -10,5 +10,10 @@
             "modules": false,
             "useBuiltIns": true
         }]
+    ],
+    "plugins": [
+        ["babel-plugin-transform-builtin-extend", {
+            "globals": ["Error"]
+        }]
     ]
 }

+ 14 - 0
.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

+ 1 - 2
.eslintignore

@@ -1,2 +1 @@
-src/core/lib/**
-src/core/config/MetaConfig.js
+src/core/vendor/**

+ 3 - 3
.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,

+ 4 - 1
.gitignore

@@ -6,4 +6,7 @@ docs/*
 !docs/*.conf.json
 !docs/*.ico
 .vscode
-src/core/config/MetaConfig.js
+src/core/config/modules/*
+src/core/config/OperationConfig.json
+src/core/operations/index.mjs
+

+ 1 - 1
.travis.yml

@@ -1,6 +1,6 @@
 language: node_js
 node_js:
-  - "8.4"
+  - node
 install: npm install
 before_script:
   - npm install -g grunt

+ 36 - 0
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<File>` 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

+ 52 - 67
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"
-        },
     });
 };

+ 8 - 2
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

File diff suppressed because it is too large
+ 500 - 229
package-lock.json


+ 41 - 32
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"
   }
 }

+ 0 - 165
src/core/Chef.js

@@ -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;

+ 198 - 0
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;

+ 40 - 17
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.
  *

+ 0 - 274
src/core/Dish.js

@@ -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;

+ 362 - 0
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<file>":
+                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<File>";
+            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;

+ 0 - 289
src/core/FlowControl.js

@@ -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;

+ 0 - 92
src/core/Ingredient.js

@@ -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;

+ 118 - 0
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;

+ 0 - 174
src/core/Operation.js

@@ -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;

+ 293 - 0
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;

+ 0 - 264
src/core/Recipe.js

@@ -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 +
-                    ".<br><br>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;

+ 290 - 0
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}.<br><br>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;

File diff suppressed because it is too large
+ 206 - 407
src/core/Utils.mjs


+ 54 - 67
src/core/config/Categories.js → src/core/config/Categories.json

@@ -1,30 +1,11 @@
-/**
- * Type definition for a CatConf.
- *
- * @typedef {Object} CatConf
- * @property {string} name - The display name for the category
- * @property {string[]} ops - A list of the operations to be included in this category
- */
-
-
-/**
- * Categories of operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @constant
- * @type {CatConf[]}
- */
-const Categories = [
+[
     {
-        name: "Favourites",
-        ops: []
+        "name": "Favourites",
+        "ops": []
     },
     {
-        name: "Data format",
-        ops: [
+        "name": "Data format",
+        "ops": [
             "To Hexdump",
             "From Hexdump",
             "To Hex",
@@ -66,12 +47,12 @@ const Categories = [
             "Change IP format",
             "Encode text",
             "Decode text",
-            "Swap endianness",
+            "Swap endianness"
         ]
     },
     {
-        name: "Encryption / Encoding",
-        ops: [
+        "name": "Encryption / Encoding",
+        "ops": [
             "AES Encrypt",
             "AES Decrypt",
             "Blowfish Encrypt",
@@ -102,12 +83,12 @@ const Categories = [
             "Derive EVP key",
             "Bcrypt",
             "Scrypt",
-            "Pseudo-Random Number Generator",
+            "Pseudo-Random Number Generator"
         ]
     },
     {
-        name: "Public Key",
-        ops: [
+        "name": "Public Key",
+        "ops": [
             "Parse X.509 certificate",
             "Parse ASN.1 hex string",
             "PEM to Hex",
@@ -118,12 +99,18 @@ const Categories = [
             "PGP Encrypt",
             "PGP Decrypt",
             "PGP Encrypt and Sign",
-            "PGP Decrypt and Verify",
+            "PGP Decrypt and Verify"
         ]
     },
     {
-        name: "Arithmetic / Logic",
-        ops: [
+        "name": "Arithmetic / Logic",
+        "ops": [
+            "Set Union",
+            "Set Intersection",
+            "Set Difference",
+            "Symmetric Difference",
+            "Cartesian Product",
+            "Power Set",
             "XOR",
             "XOR Brute Force",
             "OR",
@@ -142,12 +129,12 @@ const Categories = [
             "Bit shift right",
             "Rotate left",
             "Rotate right",
-            "ROT13",
+            "ROT13"
         ]
     },
     {
-        name: "Networking",
-        ops: [
+        "name": "Networking",
+        "ops": [
             "HTTP request",
             "Strip HTTP headers",
             "Parse User Agent",
@@ -161,20 +148,20 @@ const Categories = [
             "Change IP format",
             "Group IP addresses",
             "Encode NetBIOS Name",
-            "Decode NetBIOS Name",
+            "Decode NetBIOS Name"
         ]
     },
     {
-        name: "Language",
-        ops: [
+        "name": "Language",
+        "ops": [
             "Encode text",
             "Decode text",
-            "Unescape Unicode Characters",
+            "Unescape Unicode Characters"
         ]
     },
     {
-        name: "Utils",
-        ops: [
+        "name": "Utils",
+        "ops": [
             "Diff",
             "Remove whitespace",
             "Remove null bytes",
@@ -210,12 +197,12 @@ const Categories = [
             "Escape string",
             "Unescape string",
             "Pseudo-Random Number Generator",
-            "Sleep",
+            "Sleep"
         ]
     },
     {
-        name: "Date / Time",
-        ops: [
+        "name": "Date / Time",
+        "ops": [
             "Parse DateTime",
             "Translate DateTime Format",
             "From UNIX Timestamp",
@@ -223,12 +210,12 @@ const Categories = [
             "Windows Filetime to UNIX Timestamp",
             "UNIX Timestamp to Windows Filetime",
             "Extract dates",
-            "Sleep",
+            "Sleep"
         ]
     },
     {
-        name: "Extractors",
-        ops: [
+        "name": "Extractors",
+        "ops": [
             "Strings",
             "Extract IP addresses",
             "Extract email addresses",
@@ -241,12 +228,12 @@ const Categories = [
             "XPath expression",
             "JPath expression",
             "CSS selector",
-            "Extract EXIF",
+            "Extract EXIF"
         ]
     },
     {
-        name: "Compression",
-        ops: [
+        "name": "Compression",
+        "ops": [
             "Raw Deflate",
             "Raw Inflate",
             "Zlib Deflate",
@@ -257,12 +244,12 @@ const Categories = [
             "Unzip",
             "Bzip2 Decompress",
             "Tar",
-            "Untar",
+            "Untar"
         ]
     },
     {
-        name: "Hashing",
-        ops: [
+        "name": "Hashing",
+        "ops": [
             "Analyse hash",
             "Generate all hashes",
             "MD2",
@@ -295,12 +282,12 @@ const Categories = [
             "Adler-32 Checksum",
             "CRC-16 Checksum",
             "CRC-32 Checksum",
-            "TCP/IP Checksum",
+            "TCP/IP Checksum"
         ]
     },
     {
-        name: "Code tidy",
-        ops: [
+        "name": "Code tidy",
+        "ops": [
             "Syntax highlighter",
             "Generic Code Beautify",
             "JavaScript Parser",
@@ -325,12 +312,12 @@ const Categories = [
             "To Camel case",
             "To Kebab case",
             "BSON serialise",
-            "BSON deserialise",
+            "BSON deserialise"
         ]
     },
     {
-        name: "Other",
-        ops: [
+        "name": "Other",
+        "ops": [
             "Entropy",
             "Frequency distribution",
             "Chi Square",
@@ -341,16 +328,18 @@ const Categories = [
             "Generate UUID",
             "Generate TOTP",
             "Generate HOTP",
+            "Haversine distance",
             "Render Image",
             "Remove EXIF",
             "Extract EXIF",
             "Numberwang",
-            "XKCD Random Number",
+            "XKCD Random Number"
         ]
     },
     {
-        name: "Flow control",
-        ops: [
+        "name": "Flow control",
+        "ops": [
+            "Magic",
             "Fork",
             "Merge",
             "Register",
@@ -360,7 +349,5 @@ const Categories = [
             "Return",
             "Comment"
         ]
-    },
-];
-
-export default Categories;
+    }
+]

+ 0 - 4355
src/core/config/OperationConfig.js

@@ -1,4355 +0,0 @@
-import Arithmetic from "../operations/Arithmetic.js";
-import Base from "../operations/Base.js";
-import Base58 from "../operations/Base58.js";
-import Base64 from "../operations/Base64.js";
-import BCD from "../operations/BCD.js";
-import BitwiseOp from "../operations/BitwiseOp.js";
-import ByteRepr from "../operations/ByteRepr.js";
-import CharEnc from "../operations/CharEnc.js";
-import Cipher from "../operations/Cipher.js";
-import Code from "../operations/Code.js";
-import Compress from "../operations/Compress.js";
-import Convert from "../operations/Convert.js";
-import DateTime from "../operations/DateTime.js";
-import Diff from "../operations/Diff.js";
-import Endian from "../operations/Endian.js";
-import Entropy from "../operations/Entropy.js";
-import Extract from "../operations/Extract.js";
-import Filetime from "../operations/Filetime.js";
-import FileType from "../operations/FileType.js";
-import Image from "../operations/Image.js";
-import Hash from "../operations/Hash.js";
-import Hexdump from "../operations/Hexdump.js";
-import HTML from "../operations/HTML.js";
-import HTTP from "../operations/HTTP.js";
-import IP from "../operations/IP.js";
-import JS from "../operations/JS.js";
-import MAC from "../operations/MAC.js";
-import MorseCode from "../operations/MorseCode.js";
-import NetBIOS from "../operations/NetBIOS.js";
-import PHP from "../operations/PHP.js";
-import PGP from "../operations/PGP.js";
-import PublicKey from "../operations/PublicKey.js";
-import Punycode from "../operations/Punycode.js";
-import Regex from "../operations/Regex.js";
-import Rotate from "../operations/Rotate.js";
-import SeqUtils from "../operations/SeqUtils.js";
-import Shellcode from "../operations/Shellcode.js";
-import StrUtils from "../operations/StrUtils.js";
-import Tidy from "../operations/Tidy.js";
-import ToTable from "../operations/ToTable.js";
-import Unicode from "../operations/Unicode.js";
-import URL_ from "../operations/URL.js";
-
-
-/**
- * Type definition for an OpConf.
- *
- * @typedef {Object} OpConf
- * @property {string} module - The module to which the operation belongs
- * @property {html} description - A description of the operation with optional HTML tags
- * @property {string} inputType
- * @property {string} outputType
- * @property {Function|boolean} [highlight] - A function to calculate the highlight offset, or true
- *   if the offset does not change
- * @property {Function|boolean} [highlightReverse] - A function to calculate the highlight offset
- *   in reverse, or true if the offset does not change
- * @property {boolean} [flowControl] - True if the operation is for Flow Control
- * @property {ArgConf[]} [args] - A list of configuration objects for the arguments
- */
-
-
-/**
- * Type definition for an ArgConf.
- *
- * @typedef {Object} ArgConf
- * @property {string} name - The display name of the argument
- * @property {string} type - The data type of the argument
- * @property {*} value
- * @property {number[]} [disableArgs] - A list of the indices of the operation's arguments which
- *   should be toggled on or off when this argument is changed
- * @property {boolean} [disabled] - Whether or not this argument starts off disabled
- */
-
-
-/**
- * Operation configuration objects.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @constant
- * @type {Object.<string, OpConf>}
- */
-const OperationConfig = {
-    "Fork": {
-        module: "Default",
-        description: "Split the input data up based on the specified delimiter and run all subsequent operations on each branch separately.<br><br>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.",
-        inputType: "string",
-        outputType: "string",
-        flowControl: true,
-        args: [
-            {
-                name: "Split delimiter",
-                type: "binaryShortString",
-                value: "\\n"
-            },
-            {
-                name: "Merge delimiter",
-                type: "binaryShortString",
-                value: "\\n"
-            },
-            {
-                name: "Ignore errors",
-                type: "boolean",
-                value: false
-            }
-        ]
-    },
-    "Merge": {
-        module: "Default",
-        description: "Consolidate all branches back into a single trunk. The opposite of Fork.",
-        inputType: "string",
-        outputType: "string",
-        flowControl: true,
-        args: []
-    },
-    "Register": {
-        module: "Default",
-        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.<br><br>To use registers in arguments, refer to them using the notation <code>$Rn</code> where n is the register number, starting at 0.<br><br>For example:<br>Input: <code>Test</code><br>Extractor: <code>(.*)</code><br>Argument: <code>$R0</code> becomes <code>Test</code><br><br>Registers can be escaped in arguments using a backslash. e.g. <code>\\$R0</code> would become <code>$R0</code> rather than <code>Test</code>.",
-        inputType: "string",
-        outputType: "string",
-        flowControl: true,
-        args: [
-            {
-                name: "Extractor",
-                type: "binaryString",
-                value: "([\\s\\S]*)"
-            },
-            {
-                name: "Case insensitive",
-                type: "boolean",
-                value: true
-            },
-            {
-                name: "Multiline matching",
-                type: "boolean",
-                value: false
-            },
-        ]
-    },
-    "Jump": {
-        module: "Default",
-        description: "Jump forwards or backwards to the specified Label",
-        inputType: "string",
-        outputType: "string",
-        flowControl: true,
-        args: [
-            {
-                name: "Label name",
-                type: "string",
-                value: ""
-            },
-            {
-                name: "Maximum jumps (if jumping backwards)",
-                type: "number",
-                value: 10
-            }
-        ]
-    },
-    "Conditional Jump": {
-        module: "Default",
-        description: "Conditionally jump forwards or backwards to the specified Label  based on whether the data matches the specified regular expression.",
-        inputType: "string",
-        outputType: "string",
-        flowControl: true,
-        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
-            }
-        ]
-    },
-    "Label": {
-        module: "Default",
-        description: "Provides a location for conditional and fixed jumps to redirect execution to.",
-        inputType: "string",
-        outputType: "string",
-        flowControl: true,
-        args: [
-            {
-                name: "Name",
-                type: "shortString",
-                value: ""
-            }
-        ]
-    },
-    "Return": {
-        module: "Default",
-        description: "End execution of operations at this point in the recipe.",
-        inputType: "string",
-        outputType: "string",
-        flowControl: true,
-        args: []
-    },
-    "Comment": {
-        module: "Default",
-        description: "Provides a place to write comments within the flow of the recipe. This operation has no computational effect.",
-        inputType: "string",
-        outputType: "string",
-        flowControl: true,
-        args: [
-            {
-                name: "",
-                type: "text",
-                value: ""
-            }
-        ]
-    },
-    "From Base64": {
-        module: "Default",
-        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.<br><br>This operation decodes data from an ASCII Base64 string back into its raw format.<br><br>e.g. <code>aGVsbG8=</code> becomes <code>hello</code>",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Alphabet",
-                type: "editableOption",
-                value: Base64.ALPHABET_OPTIONS
-            },
-            {
-                name: "Remove non-alphabet chars",
-                type: "boolean",
-                value: Base64.REMOVE_NON_ALPH_CHARS
-            }
-        ]
-    },
-    "To Base64": {
-        module: "Default",
-        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.<br><br>This operation encodes data in an ASCII Base64 string.<br><br>e.g. <code>hello</code> becomes <code>aGVsbG8=</code>",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Alphabet",
-                type: "editableOption",
-                value: Base64.ALPHABET_OPTIONS
-            },
-        ]
-    },
-    "From Base58": {
-        module: "Default",
-        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.<br><br>This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included) back into its raw form.<br><br>e.g. <code>StV1DL6CwTryKyV</code> becomes <code>hello world</code><br><br>Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc).",
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Alphabet",
-                type: "editableOption",
-                value: Base58.ALPHABET_OPTIONS
-            },
-            {
-                name: "Remove non-alphabet chars",
-                type: "boolean",
-                value: Base58.REMOVE_NON_ALPH_CHARS
-            }
-        ]
-    },
-    "To Base58": {
-        module: "Default",
-        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.<br><br>This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>hello world</code> becomes <code>StV1DL6CwTryKyV</code><br><br>Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc).",
-        inputType: "byteArray",
-        outputType: "string",
-        args: [
-            {
-                name: "Alphabet",
-                type: "editableOption",
-                value: Base58.ALPHABET_OPTIONS
-            },
-        ]
-    },
-    "From Base32": {
-        module: "Default",
-        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.",
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Alphabet",
-                type: "binaryString",
-                value: Base64.BASE32_ALPHABET
-            },
-            {
-                name: "Remove non-alphabet chars",
-                type: "boolean",
-                value: Base64.REMOVE_NON_ALPH_CHARS
-            }
-        ]
-    },
-    "To Base32": {
-        module: "Default",
-        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.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: [
-            {
-                name: "Alphabet",
-                type: "binaryString",
-                value: Base64.BASE32_ALPHABET
-            }
-        ]
-    },
-    "Show Base64 offsets": {
-        module: "Default",
-        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.<br><br>This operation shows all possible offsets for a given string so that each possible encoding can be considered.",
-        inputType: "byteArray",
-        outputType: "html",
-        args: [
-            {
-                name: "Alphabet",
-                type: "binaryString",
-                value: Base64.ALPHABET
-            },
-            {
-                name: "Show variable chars and padding",
-                type: "boolean",
-                value: Base64.OFFSETS_SHOW_VARIABLE
-            }
-        ]
-    },
-    "Disassemble x86": {
-        module: "Shellcode",
-        description: "Disassembly is the process of translating machine language into assembly language.<br><br>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.<br><br>Input should be in hexadecimal.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Bit mode",
-                type: "option",
-                value: Shellcode.MODE
-            },
-            {
-                name: "Compatibility",
-                type: "option",
-                value: Shellcode.COMPATIBILITY
-            },
-            {
-                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
-            }
-        ]
-    },
-    "XOR": {
-        module: "Default",
-        description: "XOR the input with the given key.<br>e.g. <code>fe023da5</code><br><br><strong>Options</strong><br><u>Null preserving:</u> If the current byte is 0x00 or the same as the key, skip it.<br><br><u>Scheme:</u><ul><li>Standard - key is unchanged after each round</li><li>Input differential - key is set to the value of the previous unprocessed byte</li><li>Output differential - key is set to the value of the previous processed byte</li></ul>",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: BitwiseOp.KEY_FORMAT
-            },
-            {
-                name: "Scheme",
-                type: "option",
-                value: BitwiseOp.XOR_SCHEME
-            },
-            {
-                name: "Null preserving",
-                type: "boolean",
-                value: BitwiseOp.XOR_PRESERVE_NULLS
-            }
-        ]
-    },
-    "XOR Brute Force": {
-        module: "Default",
-        description: "Enumerate all possible XOR solutions. Current maximum key length is 2 due to browser performance.<br><br>Optionally enter a string that you expect to find in the plaintext to filter results (crib).",
-        inputType: "byteArray",
-        outputType: "string",
-        args: [
-            {
-                name: "Key length",
-                type: "number",
-                value: BitwiseOp.XOR_BRUTE_KEY_LENGTH
-            },
-            {
-                name: "Sample length",
-                type: "number",
-                value: BitwiseOp.XOR_BRUTE_SAMPLE_LENGTH
-            },
-            {
-                name: "Sample offset",
-                type: "number",
-                value: BitwiseOp.XOR_BRUTE_SAMPLE_OFFSET
-            },
-            {
-                name: "Scheme",
-                type: "option",
-                value: BitwiseOp.XOR_SCHEME
-            },
-            {
-                name: "Null preserving",
-                type: "boolean",
-                value: BitwiseOp.XOR_PRESERVE_NULLS
-            },
-            {
-                name: "Print key",
-                type: "boolean",
-                value: BitwiseOp.XOR_BRUTE_PRINT_KEY
-            },
-            {
-                name: "Output as hex",
-                type: "boolean",
-                value: BitwiseOp.XOR_BRUTE_OUTPUT_HEX
-            },
-            {
-                name: "Crib (known plaintext string)",
-                type: "binaryString",
-                value: ""
-            }
-        ]
-    },
-    "NOT": {
-        module: "Default",
-        description: "Returns the inverse of each byte.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: []
-    },
-    "AND": {
-        module: "Default",
-        description: "AND the input with the given key.<br>e.g. <code>fe023da5</code>",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: BitwiseOp.KEY_FORMAT
-            }
-        ]
-    },
-    "OR": {
-        module: "Default",
-        description: "OR the input with the given key.<br>e.g. <code>fe023da5</code>",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: BitwiseOp.KEY_FORMAT
-            }
-        ]
-    },
-    "ADD": {
-        module: "Default",
-        description: "ADD the input with the given key (e.g. <code>fe023da5</code>), MOD 255",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: BitwiseOp.KEY_FORMAT
-            }
-        ]
-    },
-    "SUB": {
-        module: "Default",
-        description: "SUB the input with the given key (e.g. <code>fe023da5</code>), MOD 255",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: BitwiseOp.KEY_FORMAT
-            }
-        ]
-    },
-    "Sum": {
-        module: "Default",
-        description: "Adds together a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>18.5</code>",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Arithmetic.DELIM_OPTIONS
-            }
-        ]
-    },
-    "Subtract": {
-        module: "Default",
-        description: "Subtracts a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>1.5</code>",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Arithmetic.DELIM_OPTIONS
-            }
-        ]
-    },
-    "Multiply": {
-        module: "Default",
-        description: "Multiplies a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>40</code>",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Arithmetic.DELIM_OPTIONS
-            }
-        ]
-    },
-    "Divide": {
-        module: "Default",
-        description: "Divides a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>2.5</code>",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Arithmetic.DELIM_OPTIONS
-            }
-        ]
-    },
-    "Mean": {
-        module: "Default",
-        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.<br><br>e.g. <code>0x0a 8 .5 .5</code> becomes <code>4.75</code>",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Arithmetic.DELIM_OPTIONS
-            }
-        ]
-    },
-    "Median": {
-        module: "Default",
-        description: "Computes the median of a number list. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 1 .5</code> becomes <code>4.5</code>",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Arithmetic.DELIM_OPTIONS
-            }
-        ]
-    },
-    "Standard Deviation": {
-        module: "Default",
-        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.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>4.089281382128433</code>",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Arithmetic.DELIM_OPTIONS
-            }
-        ]
-    },
-    "To Table": {
-        module: "Default",
-        description: "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.<br><br>Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to <code>\\t</code> to support TSV (Tab Separated Values) or <code>|</code> for PSV (Pipe Separated Values).<br><br>You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter.",
-        inputType: "string",
-        outputType: "html",
-        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: ToTable.FORMATS
-            }
-        ]
-    },
-    "From Hex": {
-        module: "Default",
-        description: "Converts a hexadecimal byte string back into its raw value.<br><br>e.g. <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code> becomes the UTF-8 encoded string <code>Γειά σου</code>",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.FROM_HEX_DELIM_OPTIONS
-            }
-        ]
-    },
-    "To Hex": {
-        module: "Default",
-        description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code>",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.TO_HEX_DELIM_OPTIONS
-            }
-        ]
-    },
-    "From Octal": {
-        module: "Default",
-        description: "Converts an octal byte string back into its raw value.<br><br>e.g. <code>316 223 316 265 316 271 316 254 40 317 203 316 277 317 205</code> becomes the UTF-8 encoded string <code>Γειά σου</code>",
-        highlight: false,
-        highlightReverse: false,
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.DELIM_OPTIONS
-            }
-        ]
-    },
-    "To Octal": {
-        module: "Default",
-        description: "Converts the input string to octal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>316 223 316 265 316 271 316 254 40 317 203 316 277 317 205</code>",
-        highlight: false,
-        highlightReverse: false,
-        inputType: "byteArray",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.DELIM_OPTIONS
-            }
-        ]
-    },
-    "From Charcode": {
-        module: "Default",
-        description: "Converts unicode character codes back into text.<br><br>e.g. <code>0393 03b5 03b9 03ac 20 03c3 03bf 03c5</code> becomes <code>Γειά σου</code>",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.DELIM_OPTIONS
-            },
-            {
-                name: "Base",
-                type: "number",
-                value: ByteRepr.CHARCODE_BASE
-            }
-        ]
-    },
-
-    "To Charcode": {
-        module: "Default",
-        description: "Converts text to its unicode character code equivalent.<br><br>e.g. <code>Γειά σου</code> becomes <code>0393 03b5 03b9 03ac 20 03c3 03bf 03c5</code>",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.DELIM_OPTIONS
-            },
-            {
-                name: "Base",
-                type: "number",
-                value: ByteRepr.CHARCODE_BASE
-            }
-        ]
-    },
-    "From Binary": {
-        module: "Default",
-        description: "Converts a binary string back into its raw form.<br><br>e.g. <code>01001000 01101001</code> becomes <code>Hi</code>",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.BIN_DELIM_OPTIONS
-            }
-        ]
-    },
-    "To Binary": {
-        module: "Default",
-        description: "Displays the input data as a binary string.<br><br>e.g. <code>Hi</code> becomes <code>01001000 01101001</code>",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "byteArray",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.BIN_DELIM_OPTIONS
-            }
-        ]
-    },
-    "From Decimal": {
-        module: "Default",
-        description: "Converts the data from an ordinal integer array back into its raw form.<br><br>e.g. <code>72 101 108 108 111</code> becomes <code>Hello</code>",
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.DELIM_OPTIONS
-            }
-        ]
-    },
-    "To Decimal": {
-        module: "Default",
-        description: "Converts the input data to an ordinal integer array.<br><br>e.g. <code>Hello</code> becomes <code>72 101 108 108 111</code>",
-        inputType: "byteArray",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: ByteRepr.DELIM_OPTIONS
-            }
-        ]
-    },
-    "From Hexdump": {
-        module: "Default",
-        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.",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "string",
-        outputType: "byteArray",
-        args: []
-    },
-    "To Hexdump": {
-        module: "Default",
-        description: "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside.",
-        highlight: "func",
-        highlightReverse: "func",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Width",
-                type: "number",
-                value: Hexdump.WIDTH
-            },
-            {
-                name: "Upper case hex",
-                type: "boolean",
-                value: Hexdump.UPPER_CASE
-            },
-            {
-                name: "Include final length",
-                type: "boolean",
-                value: Hexdump.INCLUDE_FINAL_LENGTH
-            }
-        ]
-    },
-    "From Base": {
-        module: "Default",
-        description: "Converts a number to decimal from a given numerical base.",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Radix",
-                type: "number",
-                value: Base.DEFAULT_RADIX
-            }
-        ]
-    },
-    "To Base": {
-        module: "Default",
-        description: "Converts a decimal number to a given numerical base.",
-        inputType: "BigNumber",
-        outputType: "string",
-        args: [
-            {
-                name: "Radix",
-                type: "number",
-                value: Base.DEFAULT_RADIX
-            }
-        ]
-    },
-    "From HTML Entity": {
-        module: "Default",
-        description: "Converts HTML entities back to characters<br><br>e.g. <code>&amp;<span>amp;</span></code> becomes <code>&amp;</code>", // <span> tags required to stop the browser just printing &
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "To HTML Entity": {
-        module: "Default",
-        description: "Converts characters to HTML entities<br><br>e.g. <code>&amp;</code> becomes <code>&amp;<span>amp;</span></code>", // <span> tags required to stop the browser just printing &
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Convert all characters",
-                type: "boolean",
-                value: HTML.CONVERT_ALL
-            },
-            {
-                name: "Convert to",
-                type: "option",
-                value: HTML.CONVERT_OPTIONS
-            }
-        ]
-    },
-    "Strip HTML tags": {
-        module: "Default",
-        description: "Removes all HTML tags from the input.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Remove indentation",
-                type: "boolean",
-                value: HTML.REMOVE_INDENTATION
-            },
-            {
-                name: "Remove excess line breaks",
-                type: "boolean",
-                value: HTML.REMOVE_LINE_BREAKS
-            }
-        ]
-    },
-    "URL Decode": {
-        module: "URL",
-        description: "Converts URI/URL percent-encoded characters back to their raw values.<br><br>e.g. <code>%3d</code> becomes <code>=</code>",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "URL Encode": {
-        module: "URL",
-        description: "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.<br><br>e.g. <code>=</code> becomes <code>%3d</code>",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Encode all special chars",
-                type: "boolean",
-                value: URL_.ENCODE_ALL
-            }
-        ]
-    },
-    "Parse URI": {
-        module: "URL",
-        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.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Unescape Unicode Characters": {
-        module: "Default",
-        description: "Converts unicode-escaped character notation back into raw characters.<br><br>Supports the prefixes:<ul><li><code>\\u</code></li><li><code>%u</code></li><li><code>U+</code></li></ul>e.g. <code>\\u03c3\\u03bf\\u03c5</code> becomes <code>σου</code>",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Prefix",
-                type: "option",
-                value: Unicode.PREFIXES
-            }
-        ]
-    },
-    "Escape Unicode Characters": {
-        module: "Default",
-        description: "Converts characters to their unicode-escaped notations.<br><br>Supports the prefixes:<ul><li><code>\\u</code></li><li><code>%u</code></li><li><code>U+</code></li></ul>e.g. <code>σου</code> becomes <code>\\u03C3\\u03BF\\u03C5</code>",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Prefix",
-                type: "option",
-                value: Unicode.PREFIXES
-            },
-            {
-                name: "Encode all chars",
-                type: "boolean",
-                value: false
-            },
-            {
-                name: "Padding",
-                type: "number",
-                value: 4
-            },
-            {
-                name: "Uppercase hex",
-                type: "boolean",
-                value: true
-            }
-        ]
-    },
-    "From Quoted Printable": {
-        module: "Default",
-        description: "Converts QP-encoded text back to standard text.",
-        inputType: "string",
-        outputType: "byteArray",
-        args: []
-    },
-    "To Quoted Printable": {
-        module: "Default",
-        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.<br><br>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.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: []
-    },
-    "From Punycode": {
-        module: "Encodings",
-        description: "Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.<br><br>e.g. <code>mnchen-3ya</code> decodes to <code>münchen</code>",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Internationalised domain name",
-                type: "boolean",
-                value: Punycode.IDN
-            }
-        ]
-    },
-    "To Punycode": {
-        module: "Encodings",
-        description: "Punycode is a way to represent Unicode with the limited character subset of ASCII supported by the Domain Name System.<br><br>e.g. <code>münchen</code> encodes to <code>mnchen-3ya</code>",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Internationalised domain name",
-                type: "boolean",
-                value: Punycode.IDN
-            }
-        ]
-    },
-    "From Hex Content": {
-        module: "Default",
-        description: "Translates hexadecimal bytes in text back to raw bytes.<br><br>e.g. <code>foo|3d|bar</code> becomes <code>foo=bar</code>.",
-        inputType: "string",
-        outputType: "byteArray",
-        args: []
-    },
-    "To Hex Content": {
-        module: "Default",
-        description: "Converts special characters in a string to hexadecimal.<br><br>e.g. <code>foo=bar</code> becomes <code>foo|3d|bar</code>.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: [
-            {
-                name: "Convert",
-                type: "option",
-                value: ByteRepr.HEX_CONTENT_CONVERT_WHICH
-            },
-            {
-                name: "Print spaces between bytes",
-                type: "boolean",
-                value: ByteRepr.HEX_CONTENT_SPACES_BETWEEN_BYTES
-            },
-        ]
-    },
-    "Change IP format": {
-        module: "JSBN",
-        description: "Convert an IP address from one format to another, e.g. <code>172.20.23.54</code> to <code>ac141736</code>",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Input format",
-                type: "option",
-                value: IP.IP_FORMAT_LIST
-            },
-            {
-                name: "Output format",
-                type: "option",
-                value: IP.IP_FORMAT_LIST
-            }
-        ]
-    },
-    "Parse IP range": {
-        module: "JSBN",
-        description: "Given a CIDR range (e.g. <code>10.0.0.0/24</code>) or a hyphenated range (e.g. <code>10.0.0.0 - 10.0.1.0</code>), this operation provides network information and enumerates all IP addresses in the range.<br><br>IPv6 is supported but will not be enumerated.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Include network info",
-                type: "boolean",
-                value: IP.INCLUDE_NETWORK_INFO
-            },
-            {
-                name: "Enumerate IP addresses",
-                type: "boolean",
-                value: IP.ENUMERATE_ADDRESSES
-            },
-            {
-                name: "Allow large queries",
-                type: "boolean",
-                value: IP.ALLOW_LARGE_LIST
-            }
-        ]
-    },
-    "Group IP addresses": {
-        module: "JSBN",
-        description: "Groups a list of IP addresses into subnets. Supports both IPv4 and IPv6 addresses.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: IP.DELIM_OPTIONS
-            },
-            {
-                name: "Subnet (CIDR)",
-                type: "number",
-                value: IP.GROUP_CIDR
-            },
-            {
-                name: "Only show the subnets",
-                type: "boolean",
-                value: IP.GROUP_ONLY_SUBNET
-            }
-        ]
-    },
-    "Parse IPv6 address": {
-        module: "JSBN",
-        description: "Displays the longhand and shorthand versions of a valid IPv6 address.<br><br>Recognises all reserved ranges and parses encapsulated or tunnelled addresses including Teredo and 6to4.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Parse IPv4 header": {
-        module: "JSBN",
-        description: "Given an IPv4 header, this operations parses and displays each field in an easily readable format.",
-        inputType: "string",
-        outputType: "html",
-        args: [
-            {
-                name: "Input format",
-                type: "option",
-                value: IP.IP_HEADER_FORMAT
-            }
-        ]
-    },
-    "Encode text": {
-        module: "CharEnc",
-        description: [
-            "Encodes text into the chosen character encoding.",
-            "<br><br>",
-            "Supported charsets are:",
-            "<ul>",
-            Object.keys(CharEnc.IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
-            "</ul>",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Encoding",
-                type: "option",
-                value: Object.keys(CharEnc.IO_FORMAT),
-            },
-        ]
-    },
-    "Decode text": {
-        module: "CharEnc",
-        description: [
-            "Decodes text from the chosen character encoding.",
-            "<br><br>",
-            "Supported charsets are:",
-            "<ul>",
-            Object.keys(CharEnc.IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
-            "</ul>",
-        ].join("\n"),
-        inputType: "byteArray",
-        outputType: "string",
-        args: [
-            {
-                name: "Encoding",
-                type: "option",
-                value: Object.keys(CharEnc.IO_FORMAT),
-            },
-        ]
-    },
-    "AES Decrypt": {
-        module: "Ciphers",
-        description: "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: Cipher.AES_MODES
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT4
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-            {
-                name: "GCM Tag",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-        ]
-    },
-    "AES Encrypt": {
-        module: "Ciphers",
-        description: "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: Cipher.AES_MODES
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT4
-            },
-        ]
-    },
-    "DES Decrypt": {
-        module: "Ciphers",
-        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.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: Cipher.DES_MODES
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT4
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-        ]
-    },
-    "DES Encrypt": {
-        module: "Ciphers",
-        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.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: Cipher.DES_MODES
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT4
-            },
-        ]
-    },
-    "Triple DES Decrypt": {
-        module: "Ciphers",
-        description: "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: Cipher.DES_MODES
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT4
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-        ]
-    },
-    "Triple DES Encrypt": {
-        module: "Ciphers",
-        description: "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: Cipher.DES_MODES
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT4
-            },
-        ]
-    },
-    "Blowfish Decrypt": {
-        module: "Ciphers",
-        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.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: Cipher.BLOWFISH_MODES
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.BLOWFISH_OUTPUT_TYPES
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-        ]
-    },
-    "Blowfish Encrypt": {
-        module: "Ciphers",
-        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.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: Cipher.BLOWFISH_MODES
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.BLOWFISH_OUTPUT_TYPES
-            },
-        ]
-    },
-    "RC4": {
-        module: "Ciphers",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Passphrase",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.RC4_KEY_FORMAT
-            },
-            {
-                name: "Input format",
-                type: "option",
-                value: Cipher.CJS_IO_FORMAT
-            },
-            {
-                name: "Output format",
-                type: "option",
-                value: Cipher.CJS_IO_FORMAT
-            },
-        ]
-    },
-    "RC4 Drop": {
-        module: "Ciphers",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Passphrase",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.RC4_KEY_FORMAT
-            },
-            {
-                name: "Input format",
-                type: "option",
-                value: Cipher.CJS_IO_FORMAT
-            },
-            {
-                name: "Output format",
-                type: "option",
-                value: Cipher.CJS_IO_FORMAT
-            },
-            {
-                name: "Number of bytes to drop",
-                type: "number",
-                value: Cipher.RC4DROP_BYTES
-            },
-        ]
-    },
-    "RC2 Decrypt": {
-        module: "Ciphers",
-        description: "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br><b>IV:</b> 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.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT4
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-        ]
-    },
-    "RC2 Encrypt": {
-        module: "Ciphers",
-        description: "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> 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.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "IV",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-            {
-                name: "Input",
-                type: "option",
-                value: Cipher.IO_FORMAT3
-            },
-            {
-                name: "Output",
-                type: "option",
-                value: Cipher.IO_FORMAT4
-            },
-        ]
-    },
-    "Pseudo-Random Number Generator": {
-        module: "Ciphers",
-        description: "A cryptographically-secure pseudo-random number generator (PRNG).<br><br>This operation uses the browser's built-in <code>crypto.getRandomValues()</code> method if available. If this cannot be found, it falls back to a Fortuna-based PRNG algorithm.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Number of bytes",
-                type: "number",
-                value: Cipher.PRNG_BYTES
-            },
-            {
-                name: "Output as",
-                type: "option",
-                value: Cipher.PRNG_OUTPUT
-            }
-        ]
-    },
-    "Derive PBKDF2 key": {
-        module: "Ciphers",
-        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.<br><br>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.<br><br>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.<br><br>If you leave the salt argument empty, a random salt will be generated.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Passphrase",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT2
-            },
-            {
-                name: "Key size",
-                type: "number",
-                value: Cipher.KDF_KEY_SIZE
-            },
-            {
-                name: "Iterations",
-                type: "number",
-                value: Cipher.KDF_ITERATIONS
-            },
-            {
-                name: "Hashing function",
-                type: "option",
-                value: Cipher.HASHERS
-            },
-            {
-                name: "Salt",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-        ]
-    },
-    "Derive EVP key": {
-        module: "Ciphers",
-        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.<br><br>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.<br><br>If you leave the salt argument empty, a random salt will be generated.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Passphrase",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT2
-            },
-            {
-                name: "Key size",
-                type: "number",
-                value: Cipher.KDF_KEY_SIZE
-            },
-            {
-                name: "Iterations",
-                type: "number",
-                value: Cipher.KDF_ITERATIONS
-            },
-            {
-                name: "Hashing function",
-                type: "option",
-                value: Cipher.HASHERS
-            },
-            {
-                name: "Salt",
-                type: "toggleString",
-                value: "",
-                toggleValues: Cipher.IO_FORMAT1
-            },
-        ]
-    },
-    "Vigenère Encode": {
-        module: "Ciphers",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "string",
-                value: ""
-            }
-        ]
-    },
-    "Vigenère Decode": {
-        module: "Ciphers",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "string",
-                value: ""
-            }
-        ]
-    },
-    "Bifid Cipher Encode": {
-        module: "Ciphers",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Keyword",
-                type: "string",
-                value: ""
-            }
-        ]
-    },
-    "Bifid Cipher Decode": {
-        module: "Ciphers",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Keyword",
-                type: "string",
-                value: ""
-            }
-        ]
-    },
-    "Affine Cipher Encode": {
-        module: "Ciphers",
-        description: "The Affine cipher is a type of monoalphabetic substitution cipher, wherein each letter in an alphabet is mapped to its numeric equivalent, encrypted using simple mathematical function, <code>(ax + b) % 26</code>, and converted back to a letter.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "a",
-                type: "number",
-                value: Cipher.AFFINE_A
-            },
-            {
-                name: "b",
-                type: "number",
-                value: Cipher.AFFINE_B
-            }
-        ]
-    },
-    "Affine Cipher Decode": {
-        module: "Ciphers",
-        description: "The Affine cipher is a type of monoalphabetic substitution cipher. To decrypt, each letter in an alphabet is mapped to its numeric equivalent, decrypted by a mathematical function, and converted back to a letter.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "a",
-                type: "number",
-                value: Cipher.AFFINE_A
-            },
-            {
-                name: "b",
-                type: "number",
-                value: Cipher.AFFINE_B
-            }
-        ]
-    },
-    "Atbash Cipher": {
-        module: "Ciphers",
-        description: "Atbash is a mono-alphabetic substitution cipher originally used to encode the Hebrew alphabet. It has been modified here for use with the Latin alphabet.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Rotate right": {
-        module: "Default",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Amount",
-                type: "number",
-                value: Rotate.ROTATE_AMOUNT
-            },
-            {
-                name: "Carry through",
-                type: "boolean",
-                value: Rotate.ROTATE_CARRY
-            }
-        ]
-    },
-    "Rotate left": {
-        module: "Default",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Amount",
-                type: "number",
-                value: Rotate.ROTATE_AMOUNT
-            },
-            {
-                name: "Carry through",
-                type: "boolean",
-                value: Rotate.ROTATE_CARRY
-            }
-        ]
-    },
-    "ROT13": {
-        module: "Default",
-        description: "A simple caesar substitution cipher which rotates alphabet characters by the specified amount (default 13).",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Rotate lower case chars",
-                type: "boolean",
-                value: Rotate.ROT13_LOWERCASE
-            },
-            {
-                name: "Rotate upper case chars",
-                type: "boolean",
-                value: Rotate.ROT13_UPPERCASE
-            },
-            {
-                name: "Amount",
-                type: "number",
-                value: Rotate.ROT13_AMOUNT
-            },
-        ]
-    },
-    "ROT47": {
-        module: "Default",
-        description: "A slightly more complex variation of a caesar cipher, which includes ASCII characters from 33 '!' to 126 '~'. Default rotation: 47.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Amount",
-                type: "number",
-                value: Rotate.ROT47_AMOUNT
-            },
-        ]
-    },
-    "Strip HTTP headers": {
-        module: "HTTP",
-        description: "Removes HTTP headers from a request or response by looking for the first instance of a double newline.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Parse User Agent": {
-        module: "HTTP",
-        description: "Attempts to identify and categorise information contained in a user-agent string.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Format MAC addresses": {
-        module: "Default",
-        description: "Displays given MAC addresses in multiple different formats.<br><br>Expects addresses in a list separated by newlines, spaces or commas.<br><br>WARNING: There are no validity checks.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Output case",
-                type: "option",
-                value: MAC.OUTPUT_CASE
-            },
-            {
-                name: "No delimiter",
-                type: "boolean",
-                value: MAC.NO_DELIM
-            },
-            {
-                name: "Dash delimiter",
-                type: "boolean",
-                value: MAC.DASH_DELIM
-            },
-            {
-                name: "Colon delimiter",
-                type: "boolean",
-                value: MAC.COLON_DELIM
-            },
-            {
-                name: "Cisco style",
-                type: "boolean",
-                value: MAC.CISCO_STYLE
-            },
-            {
-                name: "IPv6 interface ID",
-                type: "boolean",
-                value: MAC.IPV6_INTERFACE_ID
-            }
-        ]
-    },
-    "Encode NetBIOS Name": {
-        module: "Default",
-        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.<br><br>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.<br><br>This operation carries out the first level of encoding. See RFC 1001 for full details.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Offset",
-                type: "number",
-                value: NetBIOS.OFFSET
-            }
-        ]
-    },
-    "Decode NetBIOS Name": {
-        module: "Default",
-        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.<br><br>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.<br><br>This operation decodes the first level of encoding. See RFC 1001 for full details.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Offset",
-                type: "number",
-                value: NetBIOS.OFFSET
-            }
-        ]
-    },
-    "Offset checker": {
-        module: "Default",
-        description: "Compares multiple inputs (separated by the specified delimiter) and highlights matching characters which appear at the same position in all samples.",
-        inputType: "string",
-        outputType: "html",
-        args: [
-            {
-                name: "Sample delimiter",
-                type: "binaryString",
-                value: StrUtils.OFF_CHK_SAMPLE_DELIMITER
-            }
-        ]
-    },
-    "Remove whitespace": {
-        module: "Default",
-        description: "Optionally removes all spaces, carriage returns, line feeds, tabs and form feeds from the input data.<br><br>This operation also supports the removal of full stops which are sometimes used to represent non-printable bytes in ASCII output.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Spaces",
-                type: "boolean",
-                value: Tidy.REMOVE_SPACES
-            },
-            {
-                name: "Carriage returns (\\r)",
-                type: "boolean",
-                value: Tidy.REMOVE_CARIAGE_RETURNS
-            },
-            {
-                name: "Line feeds (\\n)",
-                type: "boolean",
-                value: Tidy.REMOVE_LINE_FEEDS
-            },
-            {
-                name: "Tabs",
-                type: "boolean",
-                value: Tidy.REMOVE_TABS
-            },
-            {
-                name: "Form feeds (\\f)",
-                type: "boolean",
-                value: Tidy.REMOVE_FORM_FEEDS
-            },
-            {
-                name: "Full stops",
-                type: "boolean",
-                value: Tidy.REMOVE_FULL_STOPS
-            }
-        ]
-    },
-    "Remove null bytes": {
-        module: "Default",
-        description: "Removes all null bytes (<code>0x00</code>) from the input.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: []
-    },
-    "Drop bytes": {
-        module: "Default",
-        description: "Cuts a slice of the specified number of bytes out of the data.",
-        inputType: "ArrayBuffer",
-        outputType: "ArrayBuffer",
-        args: [
-            {
-                name: "Start",
-                type: "number",
-                value: Tidy.DROP_START
-            },
-            {
-                name: "Length",
-                type: "number",
-                value: Tidy.DROP_LENGTH
-            },
-            {
-                name: "Apply to each line",
-                type: "boolean",
-                value: Tidy.APPLY_TO_EACH_LINE
-            }
-        ]
-    },
-    "Take bytes": {
-        module: "Default",
-        description: "Takes a slice of the specified number of bytes from the data.",
-        inputType: "ArrayBuffer",
-        outputType: "ArrayBuffer",
-        args: [
-            {
-                name: "Start",
-                type: "number",
-                value: Tidy.TAKE_START
-            },
-            {
-                name: "Length",
-                type: "number",
-                value: Tidy.TAKE_LENGTH
-            },
-            {
-                name: "Apply to each line",
-                type: "boolean",
-                value: Tidy.APPLY_TO_EACH_LINE
-            }
-        ]
-    },
-    "Pad lines": {
-        module: "Default",
-        description: "Add the specified number of the specified character to the beginning or end of each line",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Position",
-                type: "option",
-                value: Tidy.PAD_POSITION
-            },
-            {
-                name: "Length",
-                type: "number",
-                value: Tidy.PAD_LENGTH
-            },
-            {
-                name: "Character",
-                type: "binaryShortString",
-                value: Tidy.PAD_CHAR
-            }
-        ]
-    },
-    "Reverse": {
-        module: "Default",
-        description: "Reverses the input string.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "By",
-                type: "option",
-                value: SeqUtils.REVERSE_BY
-            }
-        ]
-    },
-    "Sort": {
-        module: "Default",
-        description: "Alphabetically sorts strings separated by the specified delimiter.<br><br>The IP address option supports IPv4 only.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: SeqUtils.DELIMITER_OPTIONS
-            },
-            {
-                name: "Reverse",
-                type: "boolean",
-                value: SeqUtils.SORT_REVERSE
-            },
-            {
-                name: "Order",
-                type: "option",
-                value: SeqUtils.SORT_ORDER
-            }
-        ]
-    },
-    "Unique": {
-        module: "Default",
-        description: "Removes duplicate strings from the input.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: SeqUtils.DELIMITER_OPTIONS
-            }
-        ]
-    },
-    "Count occurrences": {
-        module: "Default",
-        description: "Counts the number of times the provided string occurs in the input.",
-        inputType: "string",
-        outputType: "number",
-        args: [
-            {
-                name: "Search string",
-                type: "toggleString",
-                value: "",
-                toggleValues: SeqUtils.SEARCH_TYPE
-            }
-        ]
-    },
-    "Add line numbers": {
-        module: "Default",
-        description: "Adds line numbers to the output.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Remove line numbers": {
-        module: "Default",
-        description: "Removes line numbers from the output if they can be trivially detected.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Find / Replace": {
-        module: "Regex",
-        description: "Replaces all occurrences of the first string with the second.<br><br> 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).",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Find",
-                type: "toggleString",
-                value: "",
-                toggleValues: Regex.SEARCH_TYPE
-            },
-            {
-                name: "Replace",
-                type: "binaryString",
-                value: ""
-            },
-            {
-                name: "Global match",
-                type: "boolean",
-                value: Regex.FIND_REPLACE_GLOBAL,
-            },
-            {
-                name: "Case insensitive",
-                type: "boolean",
-                value: Regex.FIND_REPLACE_CASE,
-            },
-            {
-                name: "Multiline matching",
-                type: "boolean",
-                value: Regex.FIND_REPLACE_MULTILINE,
-            },
-
-        ]
-    },
-    "To Upper case": {
-        module: "Default",
-        description: "Converts the input string to upper case, optionally limiting scope to only the first character in each word, sentence or paragraph.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Scope",
-                type: "option",
-                value: StrUtils.CASE_SCOPE
-            }
-        ]
-    },
-    "To Lower case": {
-        module: "Default",
-        description: "Converts every character in the input to lower case.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Split": {
-        module: "Default",
-        description: "Splits a string into sections around a given delimiter.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Split delimiter",
-                type: "editableOption",
-                value: StrUtils.SPLIT_DELIM_OPTIONS
-            },
-            {
-                name: "Join delimiter",
-                type: "editableOption",
-                value: StrUtils.JOIN_DELIM_OPTIONS
-            }
-        ]
-    },
-    "Filter": {
-        module: "Default",
-        description: "Splits up the input using the specified delimiter and then filters each branch based on a regular expression.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: StrUtils.DELIMITER_OPTIONS
-            },
-            {
-                name: "Regex",
-                type: "string",
-                value: ""
-            },
-            {
-                name: "Invert condition",
-                type: "boolean",
-                value: false
-            },
-        ]
-    },
-    "Strings": {
-        module: "Regex",
-        description: "Extracts all strings from the input.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Encoding",
-                type: "option",
-                value: Extract.ENCODING_LIST
-            },
-            {
-                name: "Minimum length",
-                type: "number",
-                value: Extract.MIN_STRING_LEN
-            },
-            {
-                name: "Match",
-                type: "option",
-                value: Extract.STRING_MATCH_TYPE
-            },
-            {
-                name: "Display total",
-                type: "boolean",
-                value: Extract.DISPLAY_TOTAL
-            }
-        ]
-    },
-    "Extract IP addresses": {
-        module: "Regex",
-        description: "Extracts all IPv4 and IPv6 addresses.<br><br>Warning: Given a string <code>710.65.0.456</code>, this will match <code>10.65.0.45</code> so always check the original input!",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "IPv4",
-                type: "boolean",
-                value: Extract.INCLUDE_IPV4
-            },
-            {
-                name: "IPv6",
-                type: "boolean",
-                value: Extract.INCLUDE_IPV6
-            },
-            {
-                name: "Remove local IPv4 addresses",
-                type: "boolean",
-                value: Extract.REMOVE_LOCAL
-            },
-            {
-                name: "Display total",
-                type: "boolean",
-                value: Extract.DISPLAY_TOTAL
-            }
-        ]
-    },
-    "Extract email addresses": {
-        module: "Regex",
-        description: "Extracts all email addresses from the input.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Display total",
-                type: "boolean",
-                value: Extract.DISPLAY_TOTAL
-            }
-        ]
-    },
-    "Extract MAC addresses": {
-        module: "Regex",
-        description: "Extracts all Media Access Control (MAC) addresses from the input.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Display total",
-                type: "boolean",
-                value: Extract.DISPLAY_TOTAL
-            }
-        ]
-    },
-    "Extract URLs": {
-        module: "Regex",
-        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.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Display total",
-                type: "boolean",
-                value: Extract.DISPLAY_TOTAL
-            }
-        ]
-    },
-    "Extract domains": {
-        module: "Regex",
-        description: "Extracts domain names.<br>Note that this will not include paths. Use <strong>Extract URLs</strong> to find entire URLs.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Display total",
-                type: "boolean",
-                value: Extract.DISPLAY_TOTAL
-            }
-        ]
-    },
-    "Extract file paths": {
-        module: "Regex",
-        description: "Extracts anything that looks like a Windows or UNIX file path.<br><br>Note that if UNIX is selected, there will likely be a lot of false positives.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Windows",
-                type: "boolean",
-                value: Extract.INCLUDE_WIN_PATH
-            },
-            {
-                name: "UNIX",
-                type: "boolean",
-                value: Extract.INCLUDE_UNIX_PATH
-            },
-            {
-                name: "Display total",
-                type: "boolean",
-                value: Extract.DISPLAY_TOTAL
-            }
-        ]
-    },
-    "Extract dates": {
-        module: "Regex",
-        description: "Extracts dates in the following formats<ul><li><code>yyyy-mm-dd</code></li><li><code>dd/mm/yyyy</code></li><li><code>mm/dd/yyyy</code></li></ul>Dividers can be any of /, -, . or space",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Display total",
-                type: "boolean",
-                value: Extract.DISPLAY_TOTAL
-            }
-        ]
-    },
-    "Regular expression": {
-        module: "Regex",
-        description: "Define your own regular expression (regex) to search the input data with, optionally choosing from a list of pre-defined patterns.<br><br>Supports extended regex syntax including the 'dot matches all' flag, named capture groups, full unicode coverage (including <code>\\p{}</code> categories and scripts as well as astral codes) and recursive matching.",
-        inputType: "string",
-        outputType: "html",
-        args: [
-            {
-                name: "Built in regexes",
-                type: "populateOption",
-                value: Regex.REGEX_PRE_POPULATE,
-                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: Regex.DISPLAY_TOTAL
-            },
-            {
-                name: "Output format",
-                type: "option",
-                value: Regex.OUTPUT_FORMAT
-            },
-        ]
-    },
-    "XPath expression": {
-        module: "Code",
-        description: "Extract information from an XML document with an XPath query",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "XPath",
-                type: "string",
-                value: Code.XPATH_INITIAL
-            },
-            {
-                name: "Result delimiter",
-                type: "binaryShortString",
-                value: Code.XPATH_DELIMITER
-            }
-        ]
-    },
-    "JPath expression": {
-        module: "Code",
-        description: "Extract information from a JSON object with a JPath query.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Query",
-                type: "string",
-                value: Code.JPATH_INITIAL
-            },
-            {
-                name: "Result delimiter",
-                type: "binaryShortString",
-                value: Code.JPATH_DELIMITER
-            }
-        ]
-    },
-    "CSS selector": {
-        module: "Code",
-        description: "Extract information from an HTML document with a CSS selector",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "CSS selector",
-                type: "string",
-                value: Code.CSS_SELECTOR_INITIAL
-            },
-            {
-                name: "Delimiter",
-                type: "binaryShortString",
-                value: Code.CSS_QUERY_DELIMITER
-            },
-        ]
-    },
-    "From UNIX Timestamp": {
-        module: "Default",
-        description: "Converts a UNIX timestamp to a datetime string.<br><br>e.g. <code>978346800</code> becomes <code>Mon 1 January 2001 11:00:00 UTC</code><br><br>A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).",
-        inputType: "number",
-        outputType: "string",
-        args: [
-            {
-                name: "Units",
-                type: "option",
-                value: DateTime.UNITS
-            }
-        ]
-    },
-    "To UNIX Timestamp": {
-        module: "Default",
-        description: "Parses a datetime string in UTC and returns the corresponding UNIX timestamp.<br><br>e.g. <code>Mon 1 January 2001 11:00:00</code> becomes <code>978346800</code><br><br>A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Units",
-                type: "option",
-                value: DateTime.UNITS
-            },
-            {
-                name: "Treat as UTC",
-                type: "boolean",
-                value: DateTime.TREAT_AS_UTC
-            },
-            {
-                name: "Show parsed datetime",
-                type: "boolean",
-                value: true
-            }
-        ]
-    },
-    "Sleep": {
-        module: "Default",
-        description: "Sleep causes the recipe to wait for a specified number of milliseconds before continuing execution.",
-        inputType: "ArrayBuffer",
-        outputType: "ArrayBuffer",
-        args: [
-            {
-                name: "Time (ms)",
-                type: "number",
-                value: 1000
-            }
-        ]
-    },
-    "Windows Filetime to UNIX Timestamp": {
-        module: "Default",
-        description: "Converts a Windows Filetime value to a UNIX timestamp.<br><br>A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.<br><br>A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).<br><br>This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Output units",
-                type: "option",
-                value: Filetime.UNITS
-            },
-            {
-                name: "Input format",
-                type: "option",
-                value: Filetime.FILETIME_FORMATS
-            }
-        ]
-    },
-    "UNIX Timestamp to Windows Filetime": {
-        module: "Default",
-        description: "Converts a UNIX timestamp to a Windows Filetime value.<br><br>A Windows Filetime is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 UTC.<br><br>A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).<br><br>This operation also supports UNIX timestamps in milliseconds, microseconds and nanoseconds.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Input units",
-                type: "option",
-                value: Filetime.UNITS
-            },
-            {
-                name: "Output format",
-                type: "option",
-                value: Filetime.FILETIME_FORMATS
-            }
-        ]
-    },
-    "Translate DateTime Format": {
-        module: "Default",
-        description: "Parses a datetime string in one format and re-writes it in another.<br><br>Run with no input to see the relevant format string examples.",
-        inputType: "string",
-        outputType: "html",
-        args: [
-            {
-                name: "Built in formats",
-                type: "populateOption",
-                value: DateTime.DATETIME_FORMATS,
-                target: 1
-            },
-            {
-                name: "Input format string",
-                type: "binaryString",
-                value: DateTime.INPUT_FORMAT_STRING
-            },
-            {
-                name: "Input timezone",
-                type: "option",
-                value: DateTime.TIMEZONES
-            },
-            {
-                name: "Output format string",
-                type: "binaryString",
-                value: DateTime.OUTPUT_FORMAT_STRING
-            },
-            {
-                name: "Output timezone",
-                type: "option",
-                value: DateTime.TIMEZONES
-            }
-        ]
-    },
-    "Parse DateTime": {
-        module: "Default",
-        description: "Parses a DateTime string in your specified format and displays it in whichever timezone you choose with the following information:<ul><li>Date</li><li>Time</li><li>Period (AM/PM)</li><li>Timezone</li><li>UTC offset</li><li>Daylight Saving Time</li><li>Leap year</li><li>Days in this month</li><li>Day of year</li><li>Week number</li><li>Quarter</li></ul>Run with no input to see format string examples if required.",
-        inputType: "string",
-        outputType: "html",
-        args: [
-            {
-                name: "Built in formats",
-                type: "populateOption",
-                value: DateTime.DATETIME_FORMATS,
-                target: 1
-            },
-            {
-                name: "Input format string",
-                type: "binaryString",
-                value: DateTime.INPUT_FORMAT_STRING
-            },
-            {
-                name: "Input timezone",
-                type: "option",
-                value: DateTime.TIMEZONES
-            },
-        ]
-    },
-    "Convert distance": {
-        module: "Default",
-        description: "Converts a unit of distance to another format.",
-        inputType: "BigNumber",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Input units",
-                type: "option",
-                value: Convert.DISTANCE_UNITS
-            },
-            {
-                name: "Output units",
-                type: "option",
-                value: Convert.DISTANCE_UNITS
-            }
-        ]
-    },
-    "Convert area": {
-        module: "Default",
-        description: "Converts a unit of area to another format.",
-        inputType: "BigNumber",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Input units",
-                type: "option",
-                value: Convert.AREA_UNITS
-            },
-            {
-                name: "Output units",
-                type: "option",
-                value: Convert.AREA_UNITS
-            }
-        ]
-    },
-    "Convert mass": {
-        module: "Default",
-        description: "Converts a unit of mass to another format.",
-        inputType: "BigNumber",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Input units",
-                type: "option",
-                value: Convert.MASS_UNITS
-            },
-            {
-                name: "Output units",
-                type: "option",
-                value: Convert.MASS_UNITS
-            }
-        ]
-    },
-    "Convert speed": {
-        module: "Default",
-        description: "Converts a unit of speed to another format.",
-        inputType: "BigNumber",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Input units",
-                type: "option",
-                value: Convert.SPEED_UNITS
-            },
-            {
-                name: "Output units",
-                type: "option",
-                value: Convert.SPEED_UNITS
-            }
-        ]
-    },
-    "Convert data units": {
-        module: "Default",
-        description: "Converts a unit of data to another format.",
-        inputType: "BigNumber",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Input units",
-                type: "option",
-                value: Convert.DATA_UNITS
-            },
-            {
-                name: "Output units",
-                type: "option",
-                value: Convert.DATA_UNITS
-            }
-        ]
-    },
-    "Raw Deflate": {
-        module: "Compression",
-        description: "Compresses data using the deflate algorithm with no headers.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Compression type",
-                type: "option",
-                value: Compress.COMPRESSION_TYPE
-            }
-        ]
-    },
-    "Raw Inflate": {
-        module: "Compression",
-        description: "Decompresses data which has been compressed using the deflate algorithm with no headers.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Start index",
-                type: "number",
-                value: Compress.INFLATE_INDEX
-            },
-            {
-                name: "Initial output buffer size",
-                type: "number",
-                value: Compress.INFLATE_BUFFER_SIZE
-            },
-            {
-                name: "Buffer expansion type",
-                type: "option",
-                value: Compress.INFLATE_BUFFER_TYPE
-            },
-            {
-                name: "Resize buffer after decompression",
-                type: "boolean",
-                value: Compress.INFLATE_RESIZE
-            },
-            {
-                name: "Verify result",
-                type: "boolean",
-                value: Compress.INFLATE_VERIFY
-            }
-        ]
-    },
-    "Zlib Deflate": {
-        module: "Compression",
-        description: "Compresses data using the deflate algorithm adding zlib headers.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Compression type",
-                type: "option",
-                value: Compress.COMPRESSION_TYPE
-            }
-        ]
-    },
-    "Zlib Inflate": {
-        module: "Compression",
-        description: "Decompresses data which has been compressed using the deflate algorithm with zlib headers.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Start index",
-                type: "number",
-                value: Compress.INFLATE_INDEX
-            },
-            {
-                name: "Initial output buffer size",
-                type: "number",
-                value: Compress.INFLATE_BUFFER_SIZE
-            },
-            {
-                name: "Buffer expansion type",
-                type: "option",
-                value: Compress.INFLATE_BUFFER_TYPE
-            },
-            {
-                name: "Resize buffer after decompression",
-                type: "boolean",
-                value: Compress.INFLATE_RESIZE
-            },
-            {
-                name: "Verify result",
-                type: "boolean",
-                value: Compress.INFLATE_VERIFY
-            }
-        ]
-    },
-    "Gzip": {
-        module: "Compression",
-        description: "Compresses data using the deflate algorithm with gzip headers.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Compression type",
-                type: "option",
-                value: Compress.COMPRESSION_TYPE
-            },
-            {
-                name: "Filename (optional)",
-                type: "string",
-                value: ""
-            },
-            {
-                name: "Comment (optional)",
-                type: "string",
-                value: ""
-            },
-            {
-                name: "Include file checksum",
-                type: "boolean",
-                value: Compress.GZIP_CHECKSUM
-            }
-        ]
-    },
-    "Gunzip": {
-        module: "Compression",
-        description: "Decompresses data which has been compressed using the deflate algorithm with gzip headers.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: []
-    },
-    "Zip": {
-        module: "Compression",
-        description: "Compresses data using the PKZIP algorithm with the given filename.<br><br>No support for multiple files at this time.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Filename",
-                type: "string",
-                value: Compress.PKZIP_FILENAME
-            },
-            {
-                name: "Comment",
-                type: "string",
-                value: ""
-            },
-            {
-                name: "Password",
-                type: "binaryString",
-                value: ""
-            },
-            {
-                name: "Compression method",
-                type: "option",
-                value: Compress.COMPRESSION_METHOD
-            },
-            {
-                name: "Operating system",
-                type: "option",
-                value: Compress.OS
-            },
-            {
-                name: "Compression type",
-                type: "option",
-                value: Compress.COMPRESSION_TYPE
-            }
-        ]
-    },
-    "Unzip": {
-        module: "Compression",
-        description: "Decompresses data using the PKZIP algorithm and displays it per file, with support for passwords.",
-        inputType: "byteArray",
-        outputType: "html",
-        args: [
-            {
-                name: "Password",
-                type: "binaryString",
-                value: ""
-            },
-            {
-                name: "Verify result",
-                type: "boolean",
-                value: Compress.PKUNZIP_VERIFY
-            }
-        ]
-    },
-    "Bzip2 Decompress": {
-        module: "Compression",
-        description: "Decompresses data using the Bzip2 algorithm.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: []
-    },
-    "Generic Code Beautify": {
-        module: "Code",
-        description: "Attempts to pretty print C-style languages such as C, C++, C#, Java, PHP, JavaScript etc.<br><br>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.<br><br>Things which will not work properly:<ul><li>For loop formatting</li><li>Do-While loop formatting</li><li>Switch/Case indentation</li><li>Certain bit shift operators</li></ul>",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "JavaScript Parser": {
-        module: "Code",
-        description: "Returns an Abstract Syntax Tree for valid JavaScript code.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Location info",
-                type: "boolean",
-                value: JS.PARSE_LOC
-            },
-            {
-                name: "Range info",
-                type: "boolean",
-                value: JS.PARSE_RANGE
-            },
-            {
-                name: "Include tokens array",
-                type: "boolean",
-                value: JS.PARSE_TOKENS
-            },
-            {
-                name: "Include comments array",
-                type: "boolean",
-                value: JS.PARSE_COMMENT
-            },
-            {
-                name: "Report errors and try to continue",
-                type: "boolean",
-                value: JS.PARSE_TOLERANT
-            },
-        ]
-    },
-    "JavaScript Beautify": {
-        module: "Code",
-        description: "Parses and pretty prints valid JavaScript code. Also works with JavaScript Object Notation (JSON).",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Indent string",
-                type: "binaryShortString",
-                value: JS.BEAUTIFY_INDENT
-            },
-            {
-                name: "Quotes",
-                type: "option",
-                value: JS.BEAUTIFY_QUOTES
-            },
-            {
-                name: "Semicolons before closing braces",
-                type: "boolean",
-                value: JS.BEAUTIFY_SEMICOLONS
-            },
-            {
-                name: "Include comments",
-                type: "boolean",
-                value: JS.BEAUTIFY_COMMENT
-            },
-        ]
-    },
-    "JavaScript Minify": {
-        module: "Code",
-        description: "Compresses JavaScript code.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "XML Beautify": {
-        module: "Code",
-        description: "Indents and prettifies eXtensible Markup Language (XML) code.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Indent string",
-                type: "binaryShortString",
-                value: Code.BEAUTIFY_INDENT
-            }
-        ]
-    },
-    "JSON Beautify": {
-        module: "Code",
-        description: "Indents and prettifies JavaScript Object Notation (JSON) code.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Indent string",
-                type: "binaryShortString",
-                value: Code.BEAUTIFY_INDENT
-            }
-        ]
-    },
-    "CSS Beautify": {
-        module: "Code",
-        description: "Indents and prettifies Cascading Style Sheets (CSS) code.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Indent string",
-                type: "binaryShortString",
-                value: Code.BEAUTIFY_INDENT
-            }
-        ]
-    },
-    "SQL Beautify": {
-        module: "Code",
-        description: "Indents and prettifies Structured Query Language (SQL) code.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Indent string",
-                type: "binaryShortString",
-                value: Code.BEAUTIFY_INDENT
-            }
-        ]
-    },
-    "XML Minify": {
-        module: "Code",
-        description: "Compresses eXtensible Markup Language (XML) code.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Preserve comments",
-                type: "boolean",
-                value: Code.PRESERVE_COMMENTS
-            }
-        ]
-    },
-    "JSON Minify": {
-        module: "Code",
-        description: "Compresses JavaScript Object Notation (JSON) code.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "CSS Minify": {
-        module: "Code",
-        description: "Compresses Cascading Style Sheets (CSS) code.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Preserve comments",
-                type: "boolean",
-                value: Code.PRESERVE_COMMENTS
-            }
-        ]
-    },
-    "SQL Minify": {
-        module: "Code",
-        description: "Compresses Structured Query Language (SQL) code.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Analyse hash": {
-        module: "Hashing",
-        description: "Tries to determine information about a given hash and suggests which algorithm may have been used to generate it based on its length.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "MD2": {
-        module: "Hashing",
-        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.<br><br>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.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "MD4": {
-        module: "Hashing",
-        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.<br><br>The security of MD4 has been severely compromised.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "MD5": {
-        module: "Hashing",
-        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.<br><br>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.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "MD6": {
-        module: "Hashing",
-        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.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Size",
-                type: "number",
-                value: Hash.MD6_SIZE
-            },
-            {
-                name: "Levels",
-                type: "number",
-                value: Hash.MD6_LEVELS
-            },
-            {
-                name: "Key",
-                type: "string",
-                value: ""
-            }
-        ]
-    },
-    "SHA0": {
-        module: "Hashing",
-        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.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "SHA1": {
-        module: "Hashing",
-        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.<br><br>However, SHA-1's collision resistance has been weakening as new attacks are discovered or improved.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "SHA2": {
-        module: "Hashing",
-        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.<br><br><ul><li>SHA-512 operates on 64-bit words.</li><li>SHA-256 operates on 32-bit words.</li><li>SHA-384 is largely identical to SHA-512 but is truncated to 384 bytes.</li><li>SHA-224 is largely identical to SHA-256 but is truncated to 224 bytes.</li><li>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.</li></ul>",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Size",
-                type: "option",
-                value: Hash.SHA2_SIZE
-            }
-        ]
-    },
-    "SHA3": {
-        module: "Hashing",
-        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.<br><br>SHA-3 is a subset of the broader cryptographic primitive family Keccak designed by Guido Bertoni, Joan Daemen, Michaël Peeters, and Gilles Van Assche, building upon RadioGatún.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Size",
-                type: "option",
-                value: Hash.SHA3_SIZE
-            }
-        ]
-    },
-    "Keccak": {
-        module: "Hashing",
-        description: "The Keccak hash algorithm was designed by Guido Bertoni, Joan Daemen, Michaël Peeters, and Gilles Van Assche, building upon RadioGatún. It was selected as the winner of the SHA-3 design competition.<br><br>This version of the algorithm is Keccak[c=2d] and differs from the SHA-3 specification.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Size",
-                type: "option",
-                value: Hash.KECCAK_SIZE
-            }
-        ]
-    },
-    "Shake": {
-        module: "Hashing",
-        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.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Capacity",
-                type: "option",
-                value: Hash.SHAKE_CAPACITY
-            },
-            {
-                name: "Size",
-                type: "number",
-                value: Hash.SHAKE_SIZE
-            }
-        ]
-
-    },
-    "RIPEMD": {
-        module: "Hashing",
-        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.<br><br>RIPEMD was based upon the design principles used in MD4, and is similar in performance to the more popular SHA-1.<br><br>",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Size",
-                type: "option",
-                value: Hash.RIPEMD_SIZE
-            }
-        ]
-    },
-    "HAS-160": {
-        module: "Hashing",
-        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.<br><br>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.<br><br>The message digest algorithm consists of 80 rounds.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "Whirlpool": {
-        module: "Hashing",
-        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.<br><br>Several variants exist:<ul><li>Whirlpool-0 is the original version released in 2000.</li><li>Whirlpool-T is the first revision, released in 2001, improving the generation of the s-box.</li><li>Wirlpool is the latest revision, released in 2003, fixing a flaw in the difusion matrix.</li></ul>",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Variant",
-                type: "option",
-                value: Hash.WHIRLPOOL_VARIANT
-            }
-        ]
-    },
-    "Snefru": {
-        module: "Hashing",
-        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.<br><br>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.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Rounds",
-                type: "option",
-                value: Hash.SNEFRU_ROUNDS
-            },
-            {
-                name: "Size",
-                type: "option",
-                value: Hash.SNEFRU_SIZE
-            }
-        ]
-    },
-    "SSDEEP": {
-        module: "Hashing",
-        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.<br><br>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.<br><br>This operation is fundamentally the same as the CTPH operation, however their outputs differ in format.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "CTPH": {
-        module: "Hashing",
-        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.<br><br>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'.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Compare SSDEEP hashes": {
-        module: "Hashing",
-        description: "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100.",
-        inputType: "string",
-        outputType: "Number",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Hash.DELIM_OPTIONS
-            }
-        ]
-    },
-    "Compare CTPH hashes": {
-        module: "Hashing",
-        description: "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100.",
-        inputType: "string",
-        outputType: "Number",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: Hash.DELIM_OPTIONS
-            }
-        ]
-    },
-    "HMAC": {
-        module: "Hashing",
-        description: "Keyed-Hash Message Authentication Codes (HMAC) are a mechanism for message authentication using cryptographic hash functions.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Key",
-                type: "binaryString",
-                value: ""
-            },
-            {
-                name: "Hashing function",
-                type: "option",
-                value: Hash.HMAC_FUNCTIONS
-            },
-        ]
-    },
-    "Fletcher-8 Checksum": {
-        module: "Hashing",
-        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.<br><br>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.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: []
-    },
-    "Fletcher-16 Checksum": {
-        module: "Hashing",
-        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.<br><br>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.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: []
-    },
-    "Fletcher-32 Checksum": {
-        module: "Hashing",
-        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.<br><br>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.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: []
-    },
-    "Fletcher-64 Checksum": {
-        module: "Hashing",
-        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.<br><br>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.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: []
-    },
-    "Adler-32 Checksum": {
-        module: "Hashing",
-        description: "Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995, and is a modification of the Fletcher checksum. Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter).<br><br>Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: []
-    },
-    "CRC-32 Checksum": {
-        module: "Hashing",
-        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.<br><br>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.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "CRC-16 Checksum": {
-        module: "Hashing",
-        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.<br><br>The CRC was invented by W. Wesley Peterson in 1961.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "Generate all hashes": {
-        module: "Hashing",
-        description: "Generates all available hashes and checksums for the input.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "Entropy": {
-        module: "Default",
-        description: "Calculates the Shannon entropy of the input data which gives an idea of its randomness. 8 is the maximum.",
-        inputType: "byteArray",
-        outputType: "html",
-        args: [
-            {
-                name: "Chunk size",
-                type: "number",
-                value: Entropy.CHUNK_SIZE
-            }
-        ]
-    },
-    "Frequency distribution": {
-        module: "Default",
-        description: "Displays the distribution of bytes in the data as a graph.",
-        inputType: "ArrayBuffer",
-        outputType: "html",
-        args: [
-            {
-                name: "Show 0%'s",
-                type: "boolean",
-                value: Entropy.FREQ_ZEROS
-            }
-        ]
-    },
-    "Chi Square": {
-        module: "Default",
-        description: "Calculates the Chi Square distribution of values.",
-        inputType: "ArrayBuffer",
-        outputType: "number",
-        args: []
-    },
-    "Numberwang": {
-        module: "Default",
-        description: "Based on the popular gameshow by Mitchell and Webb.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Parse X.509 certificate": {
-        module: "PublicKey",
-        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.<br><br>This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.<br><br>Tags: X509, server hello, handshake",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Input format",
-                type: "option",
-                value: PublicKey.X509_INPUT_FORMAT
-            }
-        ]
-    },
-    "PEM to Hex": {
-        module: "PublicKey",
-        description: "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Hex to PEM": {
-        module: "PublicKey",
-        description: "Converts a hexadecimal DER (Distinguished Encoding Rules) string into PEM (Privacy Enhanced Mail) format.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Header string",
-                type: "string",
-                value: PublicKey.PEM_HEADER_STRING
-            }
-        ]
-    },
-    "Hex to Object Identifier": {
-        module: "PublicKey",
-        description: "Converts a hexadecimal string into an object identifier (OID).",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Object Identifier to Hex": {
-        module: "PublicKey",
-        description: "Converts an object identifier (OID) into a hexadecimal string.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Parse ASN.1 hex string": {
-        module: "PublicKey",
-        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.<br><br>This operation parses arbitrary ASN.1 data and presents the resulting tree.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Starting index",
-                type: "number",
-                value: 0
-            },
-            {
-                name: "Truncate octet strings longer than",
-                type: "number",
-                value: PublicKey.ASN1_TRUNCATE_LENGTH
-            }
-        ]
-    },
-    "Detect File Type": {
-        module: "Default",
-        description: "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.<br><br>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.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "Scan for Embedded Files": {
-        module: "Default",
-        description: "Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.<br><br>WARNING: Files over about 100KB in size will take a VERY long time to process.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [
-            {
-                name: "Ignore common byte sequences",
-                type: "boolean",
-                value: FileType.IGNORE_COMMON_BYTE_SEQUENCES
-            }
-        ]
-    },
-    "Expand alphabet range": {
-        module: "Default",
-        description: "Expand an alphabet range string into a list of the characters in that range.<br><br>e.g. <code>a-z</code> becomes <code>abcdefghijklmnopqrstuvwxyz</code>.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "binaryString",
-                value: ""
-            }
-        ]
-    },
-    "Diff": {
-        module: "Diff",
-        description: "Compares two inputs (separated by the specified delimiter) and highlights the differences between them.",
-        inputType: "string",
-        outputType: "html",
-        args: [
-            {
-                name: "Sample delimiter",
-                type: "binaryString",
-                value: Diff.DIFF_SAMPLE_DELIMITER
-            },
-            {
-                name: "Diff by",
-                type: "option",
-                value: Diff.DIFF_BY
-            },
-            {
-                name: "Show added",
-                type: "boolean",
-                value: true
-            },
-            {
-                name: "Show removed",
-                type: "boolean",
-                value: true
-            },
-            {
-                name: "Ignore whitespace (relevant for word and line)",
-                type: "boolean",
-                value: false
-            }
-        ]
-    },
-    "Parse UNIX file permissions": {
-        module: "Default",
-        description: "Given a UNIX/Linux file permission string in octal or textual format, this operation explains which permissions are granted to which user groups.<br><br>Input should be in either octal (e.g. <code>755</code>) or textual (e.g. <code>drwxr-xr-x</code>) format.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Swap endianness": {
-        module: "Default",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Data format",
-                type: "option",
-                value: Endian.DATA_FORMAT
-            },
-            {
-                name: "Word length (bytes)",
-                type: "number",
-                value: Endian.WORD_LENGTH
-            },
-            {
-                name: "Pad incomplete words",
-                type: "boolean",
-                value: Endian.PAD_INCOMPLETE_WORDS
-            }
-        ]
-    },
-    "Microsoft Script Decoder": {
-        module: "Default",
-        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.<br><br><b>Sample</b><br><br>Encoded:<br><code>#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&amp;@*eEI@#@&amp;@#@&amp;.jm.raY 214Wv:zms/obI0xEAAA==^#~@</code><br><br>Decoded:<br><code>var my_msg = &#34;Testing <1><2><3>!&#34;;\n\nVScript.Echo(my_msg);</code>",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Syntax highlighter": {
-        module: "Code",
-        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.",
-        highlight: true,
-        highlightReverse: true,
-        inputType: "string",
-        outputType: "html",
-        args: [
-            {
-                name: "Language",
-                type: "option",
-                value: Code.LANGUAGES
-            },
-        ]
-    },
-    "TCP/IP Checksum": {
-        module: "Hashing",
-        description: "Calculates the checksum for a TCP (Transport Control Protocol) or IP (Internet Protocol) header from an input of raw bytes.",
-        inputType: "byteArray",
-        outputType: "string",
-        args: []
-    },
-    "Parse colour code": {
-        module: "Default",
-        description: "Converts a colour code in a standard format to other standard formats and displays the colour itself.<br><br><strong>Example inputs</strong><ul><li><code>#d9edf7</code></li><li><code>rgba(217,237,247,1)</code></li><li><code>hsla(200,65%,91%,1)</code></li><li><code>cmyk(0.12, 0.04, 0.00, 0.03)</code></li></ul>",
-        inputType: "string",
-        outputType: "html",
-        args: []
-    },
-    "Generate UUID": {
-        module: "Default",
-        description: "Generates an RFC 4122 version 4 compliant Universally Unique Identifier (UUID), also known as a Globally Unique Identifier (GUID).<br><br>A version 4 UUID relies on random numbers, in this case generated using <code>window.crypto</code> if available and falling back to <code>Math.random</code> if not.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Substitute": {
-        module: "Ciphers",
-        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.<br><br>Enter the bytes you want to replace in the Plaintext field and the bytes to replace them with in the Ciphertext field.<br><br>Non-printable bytes can be specified using string escape notation. For example, a line feed character can be written as either <code>\\n</code> or <code>\\x0a</code>.<br><br>Byte ranges can be specified using a hyphen. For example, the sequence <code>0123456789</code> can be written as <code>0-9</code>.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Plaintext",
-                type: "binaryString",
-                value: Cipher.SUBS_PLAINTEXT
-            },
-            {
-                name: "Ciphertext",
-                type: "binaryString",
-                value: Cipher.SUBS_CIPHERTEXT
-            }
-        ]
-    },
-    "Escape string": {
-        module: "Default",
-        description: "Escapes special characters in a string so that they do not cause conflicts. For example, <code>Don't stop me now</code> becomes <code>Don\\'t stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\n</code> (Line feed/newline)</li><li><code>\\r</code> (Carriage return)</li><li><code>\\t</code> (Horizontal tab)</li><li><code>\\b</code> (Backspace)</li><li><code>\\f</code> (Form feed)</li><li><code>\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\</code> (Backslash)</li><li><code>\\'</code> (Single quote)</li><li><code>\\&quot;</code> (Double quote)</li><li><code>\\unnnn</code> (Unicode character)</li><li><code>\\u{nnnnnn}</code> (Unicode code point)</li></ul>",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Escape level",
-                type: "option",
-                value: StrUtils.ESCAPE_LEVEL
-            },
-            {
-                name: "Escape quote",
-                type: "option",
-                value: StrUtils.QUOTE_TYPES
-            },
-            {
-                name: "JSON compatible",
-                type: "boolean",
-                value: false
-            },
-            {
-                name: "ES6 compatible",
-                type: "boolean",
-                value: true
-            },
-            {
-                name: "Uppercase hex",
-                type: "boolean",
-                value: false
-            }
-        ]
-    },
-    "Unescape string": {
-        module: "Default",
-        description: "Unescapes characters in a string that have been escaped. For example, <code>Don\\'t stop me now</code> becomes <code>Don't stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\n</code> (Line feed/newline)</li><li><code>\\r</code> (Carriage return)</li><li><code>\\t</code> (Horizontal tab)</li><li><code>\\b</code> (Backspace)</li><li><code>\\f</code> (Form feed)</li><li><code>\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\</code> (Backslash)</li><li><code>\\'</code> (Single quote)</li><li><code>\\&quot;</code> (Double quote)</li><li><code>\\unnnn</code> (Unicode character)</li><li><code>\\u{nnnnnn}</code> (Unicode code point)</li></ul>",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "To Morse Code": {
-        module: "Default",
-        description: "Translates alphanumeric characters into International Morse Code.<br><br>Ignores non-Morse characters.<br><br>e.g. <code>SOS</code> becomes <code>... --- ...</code>",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Format options",
-                type: "option",
-                value: MorseCode.FORMAT_OPTIONS
-            },
-            {
-                name: "Letter delimiter",
-                type: "option",
-                value: MorseCode.LETTER_DELIM_OPTIONS
-            },
-            {
-                name: "Word delimiter",
-                type: "option",
-                value: MorseCode.WORD_DELIM_OPTIONS
-            }
-        ]
-    },
-    "From Morse Code": {
-        module: "Default",
-        description: "Translates Morse Code into (upper case) alphanumeric characters.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Letter delimiter",
-                type: "option",
-                value: MorseCode.LETTER_DELIM_OPTIONS
-            },
-            {
-                name: "Word delimiter",
-                type: "option",
-                value: MorseCode.WORD_DELIM_OPTIONS
-            }
-        ]
-    },
-    "Tar": {
-        module: "Compression",
-        description: "Packs the input into a tarball.<br><br>No support for multiple files at this time.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: [
-            {
-                name: "Filename",
-                type: "string",
-                value: Compress.TAR_FILENAME
-            }
-        ]
-    },
-    "Untar": {
-        module: "Compression",
-        description: "Unpacks a tarball and displays it per file.",
-        inputType: "byteArray",
-        outputType: "html",
-        args: [
-        ]
-    },
-    "Head": {
-        module: "Default",
-        description: [
-            "Like the UNIX head utility.",
-            "<br>",
-            "Gets the first n lines.",
-            "<br>",
-            "You can select all but the last n lines by entering a negative value for n.",
-            "<br>",
-            "The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: StrUtils.DELIMITER_OPTIONS
-            },
-            {
-                name: "Number",
-                type: "number",
-                value: 10,
-            },
-        ]
-    },
-    "Tail": {
-        module: "Default",
-        description: [
-            "Like the UNIX tail utility.",
-            "<br>",
-            "Gets the last n lines.",
-            "<br>",
-            "Optionally you can select all lines after line n by entering a negative value for n.",
-            "<br>",
-            "The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "option",
-                value: StrUtils.DELIMITER_OPTIONS
-            },
-            {
-                name: "Number",
-                type: "number",
-                value: 10,
-            },
-        ]
-    },
-    "To Snake case": {
-        module: "Code",
-        description: [
-            "Converts the input string to snake case.",
-            "<br><br>",
-            "Snake case is all lower case with underscores as word boundaries.",
-            "<br><br>",
-            "e.g. this_is_snake_case",
-            "<br><br>",
-            "'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Attempt to be context aware",
-                type: "boolean",
-                value: false,
-            },
-        ]
-    },
-    "To Camel case": {
-        module: "Code",
-        description: [
-            "Converts the input string to camel case.",
-            "<br><br>",
-            "Camel case is all lower case except letters after word boundaries which are uppercase.",
-            "<br><br>",
-            "e.g. thisIsCamelCase",
-            "<br><br>",
-            "'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Attempt to be context aware",
-                type: "boolean",
-                value: false,
-            },
-        ]
-    },
-    "To Kebab case": {
-        module: "Code",
-        description: [
-            "Converts the input string to kebab case.",
-            "<br><br>",
-            "Kebab case is all lower case with dashes as word boundaries.",
-            "<br><br>",
-            "e.g. this-is-kebab-case",
-            "<br><br>",
-            "'Attempt to be context aware' will make the operation attempt to nicely transform variable and function names.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Attempt to be context aware",
-                type: "boolean",
-                value: false,
-            },
-        ]
-    },
-    "Extract EXIF": {
-        module: "Image",
-        description: [
-            "Extracts EXIF data from an image.",
-            "<br><br>",
-            "EXIF data is metadata embedded in images (JPEG, JPG, TIFF) and audio files.",
-            "<br><br>",
-            "EXIF data from photos usually contains information about the image file itself as well as the device used to create it.",
-        ].join("\n"),
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: [],
-    },
-    "Render Image": {
-        module: "Image",
-        description: "Displays the input as an image. Supports the following formats:<br><br><ul><li>jpg/jpeg</li><li>png</li><li>gif</li><li>webp</li><li>bmp</li><li>ico</li></ul>",
-        inputType: "string",
-        outputType: "html",
-        args: [
-            {
-                name: "Input format",
-                type: "option",
-                value: Image.INPUT_FORMAT
-            }
-        ]
-    },
-    "Remove EXIF": {
-        module: "Image",
-        description: [
-            "Removes EXIF data from a JPEG image.",
-            "<br><br>",
-            "EXIF data embedded in photos usually contains information about the image file itself as well as the device used to create it.",
-        ].join("\n"),
-        inputType: "byteArray",
-        outputType: "byteArray",
-        args: []
-    },
-    "HTTP request": {
-        module: "HTTP",
-        description: [
-            "Makes an HTTP request and returns the response.",
-            "<br><br>",
-            "This operation supports different HTTP verbs like GET, POST, PUT, etc.",
-            "<br><br>",
-            "You can add headers line by line in the format <code>Key: Value</code>",
-            "<br><br>",
-            "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"),
-        inputType: "string",
-        outputType: "string",
-        manualBake: true,
-        args: [
-            {
-                name: "Method",
-                type: "option",
-                value: HTTP.METHODS,
-            },
-            {
-                name: "URL",
-                type: "string",
-                value: "",
-            },
-            {
-                name: "Headers",
-                type: "text",
-                value: "",
-            },
-            {
-                name: "Mode",
-                type: "option",
-                value: HTTP.MODE,
-            },
-            {
-                name: "Show response metadata",
-                type: "boolean",
-                value: false,
-            }
-        ]
-    },
-    "From BCD": {
-        module: "Default",
-        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.",
-        inputType: "string",
-        outputType: "BigNumber",
-        args: [
-            {
-                name: "Scheme",
-                type: "option",
-                value: BCD.ENCODING_SCHEME
-            },
-            {
-                name: "Packed",
-                type: "boolean",
-                value: true
-            },
-            {
-                name: "Signed",
-                type: "boolean",
-                value: false
-            },
-            {
-                name: "Input format",
-                type: "option",
-                value: BCD.FORMAT
-            }
-        ]
-
-    },
-    "To BCD": {
-        module: "Default",
-        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",
-        inputType: "BigNumber",
-        outputType: "string",
-        args: [
-            {
-                name: "Scheme",
-                type: "option",
-                value: BCD.ENCODING_SCHEME
-            },
-            {
-                name: "Packed",
-                type: "boolean",
-                value: true
-            },
-            {
-                name: "Signed",
-                type: "boolean",
-                value: false
-            },
-            {
-                name: "Output format",
-                type: "option",
-                value: BCD.FORMAT
-            }
-        ]
-
-    },
-    "Bit shift left": {
-        module: "Default",
-        description: "Shifts the bits in each byte towards the left by the specified amount.",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        highlight: true,
-        highlightReverse: true,
-        args: [
-            {
-                name: "Amount",
-                type: "number",
-                value: 1
-            },
-        ]
-    },
-    "Bit shift right": {
-        module: "Default",
-        description: "Shifts the bits in each byte towards the right by the specified amount.<br><br><i>Logical shifts</i> replace the leftmost bits with zeros.<br><i>Arithmetic shifts</i> preserve the most significant bit (MSB) of the original byte keeping the sign the same (positive or negative).",
-        inputType: "byteArray",
-        outputType: "byteArray",
-        highlight: true,
-        highlightReverse: true,
-        args: [
-            {
-                name: "Amount",
-                type: "number",
-                value: 1
-            },
-            {
-                name: "Type",
-                type: "option",
-                value: BitwiseOp.BIT_SHIFT_TYPE
-            }
-        ]
-    },
-    "Generate TOTP": {
-        module: "Default",
-        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.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.",
-        inputType: "byteArray",
-        outputType: "string",
-        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
-            }
-        ]
-    },
-    "Generate HOTP": {
-        module: "Default",
-        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.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated.",
-        inputType: "byteArray",
-        outputType: "string",
-        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
-            }
-        ]
-    },
-    "PHP Deserialize": {
-        module: "Default",
-        description: "Deserializes PHP serialized data, outputting keyed arrays as JSON.<br><br>This function does not support <code>object</code> tags.<br><br>Example:<br><code>a:2:{s:1:&quot;a&quot;;i:10;i:0;a:1:{s:2:&quot;ab&quot;;b:1;}}</code><br>becomes<br><code>{&quot;a&quot;: 10,0: {&quot;ab&quot;: true}}</code><br><br><u>Output valid JSON:</u> JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Output valid JSON",
-                type: "boolean",
-                value: PHP.OUTPUT_VALID_JSON
-            }
-        ]
-    },
-    "Hamming Distance": {
-        module: "Default",
-        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.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Delimiter",
-                type: "binaryShortString",
-                value: StrUtils.HAMMING_DELIM
-            },
-            {
-                name: "Unit",
-                type: "option",
-                value: StrUtils.HAMMING_UNIT
-            },
-            {
-                name: "Input type",
-                type: "option",
-                value: StrUtils.HAMMING_INPUT_TYPE
-            }
-        ]
-    },
-    "XKCD Random Number": {
-        module: "Default",
-        description: "RFC 1149.5 specifies 4 as the standard IEEE-vetted random number.<br><br><a href='https://xkcd.com/221/'>XKCD #221</a>",
-        inputType: "string",
-        outputType: "number",
-        args: []
-    },
-    "Bcrypt": {
-        module: "Hashing",
-        description: "bcrypt is a password hashing function designed by Niels Provos and David Mazières, 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.<br><br>Enter the password in the input to generate its hash.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Rounds",
-                type: "number",
-                value: Hash.BCRYPT_ROUNDS
-            }
-        ]
-    },
-    "Bcrypt compare": {
-        module: "Hashing",
-        description: "Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Hash",
-                type: "string",
-                value: ""
-            }
-        ]
-    },
-    "Bcrypt parse": {
-        module: "Hashing",
-        description: "Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash.",
-        inputType: "string",
-        outputType: "string",
-        args: []
-    },
-    "Scrypt": {
-        module: "Hashing",
-        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.<br><br>Enter the password in the input to generate its hash.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Salt",
-                type: "toggleString",
-                value: "",
-                toggleValues: Hash.KEY_FORMAT
-            },
-            {
-                name: "Iterations (N)",
-                type: "number",
-                value: Hash.SCRYPT_ITERATIONS
-            },
-            {
-                name: "Memory factor (r)",
-                type: "number",
-                value: Hash.SCRYPT_MEM_FACTOR
-            },
-            {
-                name: "Parallelization factor (p)",
-                type: "number",
-                value: Hash.SCRYPT_PARALLEL_FACTOR
-            },
-            {
-                name: "Key length",
-                type: "number",
-                value: Hash.SCRYPT_KEY_LENGTH
-            },
-        ]
-    },
-    "BSON serialise": {
-        module: "BSON",
-        description: "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.<br><br>Input data should be valid JSON.",
-        inputType: "string",
-        outputType: "ArrayBuffer",
-        args: []
-    },
-    "BSON deserialise": {
-        module: "BSON",
-        description: "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.<br><br>Input data should be in a raw bytes format.",
-        inputType: "ArrayBuffer",
-        outputType: "string",
-        args: []
-    },
-    "Generate PGP Key Pair": {
-        module: "PGP",
-        description: "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.",
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Key type",
-                type: "option",
-                value: PGP.KEY_TYPES
-            },
-            {
-                name: "Password (optional)",
-                type: "string",
-                value: ""
-            },
-            {
-                name: "Name (optional)",
-                type: "string",
-                value: ""
-            },
-            {
-                name: "Email (optional)",
-                type: "string",
-                value: ""
-            },
-        ]
-    },
-    "PGP Encrypt": {
-        module: "PGP",
-        description: [
-            "Input: the message you want to encrypt.",
-            "<br><br>",
-            "Arguments: the ASCII-armoured PGP public key of the recipient.",
-            "<br><br>",
-            "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
-            "<br><br>",
-            "This function uses the Keybase implementation of PGP.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Public key of recipient",
-                type: "text",
-                value: ""
-            },
-        ]
-    },
-    "PGP Decrypt": {
-        module: "PGP",
-        description: [
-            "Input: the ASCII-armoured PGP message you want to decrypt.",
-            "<br><br>",
-            "Arguments: the ASCII-armoured PGP private key of the recipient, ",
-            "(and the private key password if necessary).",
-            "<br><br>",
-            "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
-            "<br><br>",
-            "This function uses the Keybase implementation of PGP.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Private key of recipient",
-                type: "text",
-                value: ""
-            },
-            {
-                name: "Private key passphrase",
-                type: "string",
-                value: ""
-            },
-        ]
-    },
-    "PGP Encrypt and Sign": {
-        module: "PGP",
-        description: [
-            "Input: the cleartext you want to sign.",
-            "<br><br>",
-            "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.",
-            "<br><br>",
-            "This operation uses PGP to produce an encrypted digital signature.",
-            "<br><br>",
-            "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
-            "<br><br>",
-            "This function uses the Keybase implementation of PGP.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Private key of signer",
-                type: "text",
-                value: ""
-            },
-            {
-                name: "Private key passphrase",
-                type: "string",
-                value: ""
-            },
-            {
-                name: "Public key of recipient",
-                type: "text",
-                value: ""
-            },
-        ]
-    },
-    "PGP Decrypt and Verify": {
-        module: "PGP",
-        description: [
-            "Input: the ASCII-armoured encrypted PGP message you want to verify.",
-            "<br><br>",
-            "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).",
-            "<br><br>",
-            "This operation uses PGP to decrypt and verify an encrypted digital signature.",
-            "<br><br>",
-            "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
-            "<br><br>",
-            "This function uses the Keybase implementation of PGP.",
-        ].join("\n"),
-        inputType: "string",
-        outputType: "string",
-        args: [
-            {
-                name: "Public key of signer",
-                type: "text",
-                value: "",
-            },
-            {
-                name: "Private key of recipient",
-                type: "text",
-                value: "",
-            },
-            {
-                name: "Private key password",
-                type: "string",
-                value: "",
-            },
-        ]
-    },
-};
-
-
-/**
- * Exports the OperationConfig JSON object in val-loader format so that it can be loaded
- * into the app without also importing all the dependencies.
- *
- * See https://github.com/webpack-contrib/val-loader
- *
- * @returns {Object}
- */
-function valExport() {
-    return {
-        code: "module.exports = " + JSON.stringify(OperationConfig) + ";"
-    };
-}
-
-export default valExport;
-
-export { OperationConfig };

+ 0 - 22
src/core/config/modules/BSON.js

@@ -1,22 +0,0 @@
-import BSON from "../../operations/BSON.js";
-
-
-/**
- * BSON module.
- *
- * Libraries:
- *  - bson
- *  - buffer
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2018
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.BSON = {
-    "BSON serialise":   BSON.runBSONSerialise,
-    "BSON deserialise": BSON.runBSONDeserialise,
-};
-
-export default OpModules;

+ 0 - 21
src/core/config/modules/CharEnc.js

@@ -1,21 +0,0 @@
-import CharEnc from "../../operations/CharEnc.js";
-
-
-/**
- * CharEnc module.
- *
- * Libraries:
- *  - cptable
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.CharEnc = {
-    "Encode text": CharEnc.runEncode,
-    "Decode text": CharEnc.runDecode,
-};
-
-export default OpModules;

+ 0 - 44
src/core/config/modules/Ciphers.js

@@ -1,44 +0,0 @@
-import Cipher from "../../operations/Cipher.js";
-
-
-/**
- * Ciphers module.
- *
- * Libraries:
- *  - CryptoJS
- *  - Blowfish
- *  - Forge
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Ciphers = {
-    "AES Encrypt":          Cipher.runAesEnc,
-    "AES Decrypt":          Cipher.runAesDec,
-    "Blowfish Encrypt":     Cipher.runBlowfishEnc,
-    "Blowfish Decrypt":     Cipher.runBlowfishDec,
-    "DES Encrypt":          Cipher.runDesEnc,
-    "DES Decrypt":          Cipher.runDesDec,
-    "Triple DES Encrypt":   Cipher.runTripleDesEnc,
-    "Triple DES Decrypt":   Cipher.runTripleDesDec,
-    "Derive PBKDF2 key":    Cipher.runPbkdf2,
-    "Derive EVP key":       Cipher.runEvpkdf,
-    "RC4":                  Cipher.runRc4,
-    "RC4 Drop":             Cipher.runRc4drop,
-    "RC2 Encrypt":          Cipher.runRc2Enc,
-    "RC2 Decrypt":          Cipher.runRc2Dec,
-    "Vigenère Encode":      Cipher.runVigenereEnc,
-    "Vigenère Decode":      Cipher.runVigenereDec,
-    "Bifid Cipher Encode":  Cipher.runBifidEnc,
-    "Bifid Cipher Decode":  Cipher.runBifidDec,
-    "Affine Cipher Encode": Cipher.runAffineEnc,
-    "Affine Cipher Decode": Cipher.runAffineDec,
-    "Atbash Cipher":        Cipher.runAtbash,
-    "Substitute":           Cipher.runSubstitute,
-    "Pseudo-Random Number Generator": Cipher.runPRNG,
-};
-
-export default OpModules;

+ 0 - 44
src/core/config/modules/Code.js

@@ -1,44 +0,0 @@
-import JS from "../../operations/JS.js";
-import Code from "../../operations/Code.js";
-
-
-/**
- * Code module.
- *
- * Libraries:
- *  - lodash
- *  - vkbeautify
- *  - xmldom
- *  - xpath
- *  - jpath
- *  - highlight.js
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Code = {
-    "JavaScript Parser":     JS.runParse,
-    "JavaScript Beautify":   JS.runBeautify,
-    "JavaScript Minify":     JS.runMinify,
-    "Syntax highlighter":    Code.runSyntaxHighlight,
-    "Generic Code Beautify": Code.runGenericBeautify,
-    "JSON Beautify":         Code.runJsonBeautify,
-    "JSON Minify":           Code.runJsonMinify,
-    "XML Beautify":          Code.runXmlBeautify,
-    "XML Minify":            Code.runXmlMinify,
-    "SQL Beautify":          Code.runSqlBeautify,
-    "SQL Minify":            Code.runSqlMinify,
-    "CSS Beautify":          Code.runCssBeautify,
-    "CSS Minify":            Code.runCssMinify,
-    "XPath expression":      Code.runXpath,
-    "CSS selector":          Code.runCSSQuery,
-    "To Snake case":         Code.runToSnakeCase,
-    "To Camel case":         Code.runToCamelCase,
-    "To Kebab case":         Code.runToKebabCase,
-    "JPath expression":      Code.runJpath,
-};
-
-export default OpModules;

+ 0 - 32
src/core/config/modules/Compression.js

@@ -1,32 +0,0 @@
-import Compress from "../../operations/Compress.js";
-
-
-/**
- * Compression module.
- *
- * Libraries:
- *  - zlib.js
- *  - bzip2.js
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Compression = {
-    "Raw Deflate":      Compress.runRawDeflate,
-    "Raw Inflate":      Compress.runRawInflate,
-    "Zlib Deflate":     Compress.runZlibDeflate,
-    "Zlib Inflate":     Compress.runZlibInflate,
-    "Gzip":             Compress.runGzip,
-    "Gunzip":           Compress.runGunzip,
-    "Zip":              Compress.runPkzip,
-    "Unzip":            Compress.runPkunzip,
-    "Bzip2 Decompress": Compress.runBzip2Decompress,
-    "Tar":              Compress.runTar,
-    "Untar":            Compress.runUntar,
-
-};
-
-export default OpModules;

+ 0 - 201
src/core/config/modules/Default.js

@@ -1,201 +0,0 @@
-import FlowControl from "../../FlowControl.js";
-import Arithmetic from "../../operations/Arithmetic.js";
-import Base from "../../operations/Base.js";
-import Base58 from "../../operations/Base58.js";
-import Base64 from "../../operations/Base64.js";
-import BCD from "../../operations/BCD.js";
-import BitwiseOp from "../../operations/BitwiseOp.js";
-import ByteRepr from "../../operations/ByteRepr.js";
-import Convert from "../../operations/Convert.js";
-import DateTime from "../../operations/DateTime.js";
-import Endian from "../../operations/Endian.js";
-import Entropy from "../../operations/Entropy.js";
-import Filetime from "../../operations/Filetime.js";
-import FileType from "../../operations/FileType.js";
-import Hexdump from "../../operations/Hexdump.js";
-import HTML from "../../operations/HTML.js";
-import MAC from "../../operations/MAC.js";
-import MorseCode from "../../operations/MorseCode.js";
-import MS from "../../operations/MS.js";
-import NetBIOS from "../../operations/NetBIOS.js";
-import Numberwang from "../../operations/Numberwang.js";
-import OS from "../../operations/OS.js";
-import OTP from "../../operations/OTP.js";
-import PHP from "../../operations/PHP.js";
-import QuotedPrintable from "../../operations/QuotedPrintable.js";
-import Rotate from "../../operations/Rotate.js";
-import SeqUtils from "../../operations/SeqUtils.js";
-import StrUtils from "../../operations/StrUtils.js";
-import Tidy from "../../operations/Tidy.js";
-import ToTable from "../../operations/ToTable.js";
-import Unicode from "../../operations/Unicode.js";
-import UUID from "../../operations/UUID.js";
-import XKCD from "../../operations/XKCD.js";
-
-
-/**
- * Default module.
- *
- * The Default module is for operations that are expected to be very commonly used or
- * do not require any libraries. This module is loaded into the app at compile time.
- *
- * Libraries:
- *  - Utils.js
- *  - otp
- *  - crypto
- *  - bignumber.js
- *  - jsesc
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Default = {
-    "To Hexdump":           Hexdump.runTo,
-    "From Hexdump":         Hexdump.runFrom,
-    "To Hex":               ByteRepr.runToHex,
-    "From Hex":             ByteRepr.runFromHex,
-    "To Octal":             ByteRepr.runToOct,
-    "From Octal":           ByteRepr.runFromOct,
-    "To Charcode":          ByteRepr.runToCharcode,
-    "From Charcode":        ByteRepr.runFromCharcode,
-    "To Decimal":           ByteRepr.runToDecimal,
-    "From Decimal":         ByteRepr.runFromDecimal,
-    "To Binary":            ByteRepr.runToBinary,
-    "From Binary":          ByteRepr.runFromBinary,
-    "To Hex Content":       ByteRepr.runToHexContent,
-    "From Hex Content":     ByteRepr.runFromHexContent,
-    "To Base64":            Base64.runTo,
-    "From Base64":          Base64.runFrom,
-    "Show Base64 offsets":  Base64.runOffsets,
-    "To Base32":            Base64.runTo32,
-    "From Base32":          Base64.runFrom32,
-    "To Base58":            Base58.runTo,
-    "From Base58":          Base58.runFrom,
-    "To Base":              Base.runTo,
-    "From Base":            Base.runFrom,
-    "To BCD":               BCD.runToBCD,
-    "From BCD":             BCD.runFromBCD,
-    "To HTML Entity":       HTML.runToEntity,
-    "From HTML Entity":     HTML.runFromEntity,
-    "Strip HTML tags":      HTML.runStripTags,
-    "Parse colour code":    HTML.runParseColourCode,
-    "Unescape Unicode Characters": Unicode.runUnescape,
-    "Escape Unicode Characters": Unicode.runEscape,
-    "To Quoted Printable":  QuotedPrintable.runTo,
-    "From Quoted Printable": QuotedPrintable.runFrom,
-    "Swap endianness":      Endian.runSwapEndianness,
-    "ROT13":                Rotate.runRot13,
-    "ROT47":                Rotate.runRot47,
-    "Rotate left":          Rotate.runRotl,
-    "Rotate right":         Rotate.runRotr,
-    "Bit shift left":       BitwiseOp.runBitShiftLeft,
-    "Bit shift right":      BitwiseOp.runBitShiftRight,
-    "XOR":                  BitwiseOp.runXor,
-    "XOR Brute Force":      BitwiseOp.runXorBrute,
-    "OR":                   BitwiseOp.runOr,
-    "NOT":                  BitwiseOp.runNot,
-    "AND":                  BitwiseOp.runAnd,
-    "ADD":                  BitwiseOp.runAdd,
-    "SUB":                  BitwiseOp.runSub,
-    "To Morse Code":        MorseCode.runTo,
-    "From Morse Code":      MorseCode.runFrom,
-    "Format MAC addresses": MAC.runFormat,
-    "Encode NetBIOS Name":  NetBIOS.runEncodeName,
-    "Decode NetBIOS Name":  NetBIOS.runDecodeName,
-    "Offset checker":       StrUtils.runOffsetChecker,
-    "To Upper case":        StrUtils.runUpper,
-    "To Lower case":        StrUtils.runLower,
-    "Split":                StrUtils.runSplit,
-    "Filter":               StrUtils.runFilter,
-    "Escape string":        StrUtils.runEscape,
-    "Unescape string":      StrUtils.runUnescape,
-    "Head":                 StrUtils.runHead,
-    "Tail":                 StrUtils.runTail,
-    "Hamming Distance":     StrUtils.runHamming,
-    "Remove whitespace":    Tidy.runRemoveWhitespace,
-    "Remove null bytes":    Tidy.runRemoveNulls,
-    "Drop bytes":           Tidy.runDropBytes,
-    "Take bytes":           Tidy.runTakeBytes,
-    "Pad lines":            Tidy.runPad,
-    "Reverse":              SeqUtils.runReverse,
-    "Sort":                 SeqUtils.runSort,
-    "Unique":               SeqUtils.runUnique,
-    "Count occurrences":    SeqUtils.runCount,
-    "Add line numbers":     SeqUtils.runAddLineNumbers,
-    "Remove line numbers":  SeqUtils.runRemoveLineNumbers,
-    "Expand alphabet range": SeqUtils.runExpandAlphRange,
-    "Convert distance":     Convert.runDistance,
-    "Convert area":         Convert.runArea,
-    "Convert mass":         Convert.runMass,
-    "Convert speed":        Convert.runSpeed,
-    "Convert data units":   Convert.runDataSize,
-    "Parse UNIX file permissions": OS.runParseUnixPerms,
-    "Parse DateTime":       DateTime.runParse,
-    "Translate DateTime Format": DateTime.runTranslateFormat,
-    "From UNIX Timestamp":  DateTime.runFromUnixTimestamp,
-    "To UNIX Timestamp":    DateTime.runToUnixTimestamp,
-    "Sleep":                DateTime.runSleep,
-    "Microsoft Script Decoder": MS.runDecodeScript,
-    "Entropy":              Entropy.runEntropy,
-    "Frequency distribution": Entropy.runFreqDistrib,
-    "Chi Square":           Entropy.runChiSq,
-    "Detect File Type":     FileType.runDetect,
-    "Scan for Embedded Files": FileType.runScanForEmbeddedFiles,
-    "Generate UUID":        UUID.runGenerateV4,
-    "Numberwang":           Numberwang.run,
-    "Generate TOTP":        OTP.runTOTP,
-    "Generate HOTP":        OTP.runHOTP,
-    "Fork":                 FlowControl.runFork,
-    "Merge":                FlowControl.runMerge,
-    "Register":             FlowControl.runRegister,
-    "Label":                FlowControl.runComment,
-    "Jump":                 FlowControl.runJump,
-    "Conditional Jump":     FlowControl.runCondJump,
-    "Return":               FlowControl.runReturn,
-    "Comment":              FlowControl.runComment,
-    "PHP Deserialize":      PHP.runDeserialize,
-    "Sum":                  Arithmetic.runSum,
-    "Subtract":             Arithmetic.runSub,
-    "Multiply":             Arithmetic.runMulti,
-    "Divide":               Arithmetic.runDiv,
-    "Mean":                 Arithmetic.runMean,
-    "Median":               Arithmetic.runMedian,
-    "Standard Deviation":   Arithmetic.runStdDev,
-    "To Table":             ToTable.runToTable,
-    "Windows Filetime to UNIX Timestamp": Filetime.runFromFiletimeToUnix,
-    "UNIX Timestamp to Windows Filetime": Filetime.runToFiletimeFromUnix,
-    "XKCD Random Number":  XKCD.runRandomNumber,
-
-
-    /*
-        Highlighting functions.
-
-        This is a temporary solution as highlighting should be entirely
-        overhauled at some point.
-    */
-    "From Base64-highlight":          Base64.highlightFrom,
-    "From Base64-highlightReverse":   Base64.highlightTo,
-    "To Base64-highlight":            Base64.highlightTo,
-    "To Base64-highlightReverse":     Base64.highlightFrom,
-    "From Hex-highlight":             ByteRepr.highlightFrom,
-    "From Hex-highlightReverse":      ByteRepr.highlightTo,
-    "To Hex-highlight":               ByteRepr.highlightTo,
-    "To Hex-highlightReverse":        ByteRepr.highlightFrom,
-    "From Charcode-highlight":        ByteRepr.highlightFrom,
-    "From Charcode-highlightReverse": ByteRepr.highlightTo,
-    "To Charcode-highlight":          ByteRepr.highlightTo,
-    "To Charcode-highlightReverse":   ByteRepr.highlightFrom,
-    "From Binary-highlight":          ByteRepr.highlightFromBinary,
-    "From Binary-highlightReverse":   ByteRepr.highlightToBinary,
-    "To Binary-highlight":            ByteRepr.highlightToBinary,
-    "To Binary-highlightReverse":     ByteRepr.highlightFromBinary,
-    "From Hexdump-highlight":         Hexdump.highlightFrom,
-    "From Hexdump-highlightReverse":  Hexdump.highlightTo,
-    "To Hexdump-highlight":           Hexdump.highlightTo,
-    "To Hexdump-highlightReverse":    Hexdump.highlightFrom,
-};
-
-export default OpModules;

+ 0 - 20
src/core/config/modules/Diff.js

@@ -1,20 +0,0 @@
-import Diff from "../../operations/Diff.js";
-
-
-/**
- * Diff module.
- *
- * Libraries:
- *  - JsDIff
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Diff = {
-    "Diff": Diff.runDiff,
-};
-
-export default OpModules;

+ 0 - 21
src/core/config/modules/Encodings.js

@@ -1,21 +0,0 @@
-import Punycode from "../../operations/Punycode.js";
-
-
-/**
- * Encodings module.
- *
- * Libraries:
- *  - punycode
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Encodings = {
-    "To Punycode":   Punycode.runToAscii,
-    "From Punycode": Punycode.runToUnicode,
-};
-
-export default OpModules;

+ 0 - 22
src/core/config/modules/HTTP.js

@@ -1,22 +0,0 @@
-import HTTP from "../../operations/HTTP.js";
-
-
-/**
- * HTTP module.
- *
- * Libraries:
- *  - UAS_parser
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.HTTP = {
-    "HTTP request":       HTTP.runHTTPRequest,
-    "Strip HTTP headers": HTTP.runStripHeaders,
-    "Parse User Agent":   HTTP.runParseUserAgent,
-};
-
-export default OpModules;

+ 0 - 56
src/core/config/modules/Hashing.js

@@ -1,56 +0,0 @@
-import Checksum from "../../operations/Checksum.js";
-import Hash from "../../operations/Hash.js";
-
-
-/**
- * Hashing module.
- *
- * Libraries:
- *  - CryptoApi
- *  - node-md6
- *  - js-sha3
- *  - ./Checksum.js
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Hashing = {
-    "Analyse hash":          Hash.runAnalyse,
-    "Generate all hashes":   Hash.runAll,
-    "MD2":                   Hash.runMD2,
-    "MD4":                   Hash.runMD4,
-    "MD5":                   Hash.runMD5,
-    "MD6":                   Hash.runMD6,
-    "SHA0":                  Hash.runSHA0,
-    "SHA1":                  Hash.runSHA1,
-    "SHA2":                  Hash.runSHA2,
-    "SHA3":                  Hash.runSHA3,
-    "Keccak":                Hash.runKeccak,
-    "Shake":                 Hash.runShake,
-    "RIPEMD":                Hash.runRIPEMD,
-    "HAS-160":               Hash.runHAS,
-    "Whirlpool":             Hash.runWhirlpool,
-    "Snefru":                Hash.runSnefru,
-    "CTPH":                  Hash.runCTPH,
-    "SSDEEP":                Hash.runSSDEEP,
-    "Compare CTPH hashes":   Hash.runCompareCTPH,
-    "Compare SSDEEP hashes": Hash.runCompareSSDEEP,
-    "HMAC":                  Hash.runHMAC,
-    "Bcrypt":                Hash.runBcrypt,
-    "Bcrypt compare":        Hash.runBcryptCompare,
-    "Bcrypt parse":          Hash.runBcryptParse,
-    "Scrypt":                Hash.runScrypt,
-    "Fletcher-8 Checksum":   Checksum.runFletcher8,
-    "Fletcher-16 Checksum":  Checksum.runFletcher16,
-    "Fletcher-32 Checksum":  Checksum.runFletcher32,
-    "Fletcher-64 Checksum":  Checksum.runFletcher64,
-    "Adler-32 Checksum":     Checksum.runAdler32,
-    "CRC-16 Checksum":       Checksum.runCRC16,
-    "CRC-32 Checksum":       Checksum.runCRC32,
-    "TCP/IP Checksum":       Checksum.runTCPIP,
-};
-
-export default OpModules;

+ 0 - 25
src/core/config/modules/Image.js

@@ -1,25 +0,0 @@
-import Image from "../../operations/Image.js";
-
-
-/**
- * Image module.
- *
- * Libraries:
- *  - exif-parser
- *  - remove-exif
- *  - ./FileType.js
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Image = {
-    "Extract EXIF": Image.runExtractEXIF,
-    "Remove EXIF":  Image.runRemoveEXIF,
-    "Render Image": Image.runRenderImage,
-
-};
-
-export default OpModules;

+ 0 - 25
src/core/config/modules/JSBN.js

@@ -1,25 +0,0 @@
-import IP from "../../operations/IP.js";
-
-
-/**
- * JSBN module.
- *
- * Libraries:
- *  - jsbn
- *  - ./Checksum.js
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.JSBN = {
-    "Parse IP range":     IP.runParseIpRange,
-    "Parse IPv6 address": IP.runParseIPv6,
-    "Parse IPv4 header":  IP.runParseIPv4Header,
-    "Change IP format":   IP.runChangeIpFormat,
-    "Group IP addresses": IP.runGroupIps,
-};
-
-export default OpModules;

+ 0 - 45
src/core/config/modules/OpModules.js

@@ -1,45 +0,0 @@
-/**
- * Imports all modules for builds which do not load modules separately.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-
-import OpModules from "./Default.js";
-import BSONModule from "./BSON.js";
-import CharEncModule from "./CharEnc.js";
-import CipherModule from "./Ciphers.js";
-import CodeModule from "./Code.js";
-import CompressionModule from "./Compression.js";
-import DiffModule from "./Diff.js";
-import EncodingModule from "./Encodings.js";
-import HashingModule from "./Hashing.js";
-import HTTPModule from "./HTTP.js";
-import ImageModule from "./Image.js";
-import JSBNModule from "./JSBN.js";
-import PublicKeyModule from "./PublicKey.js";
-import RegexModule from "./Regex.js";
-import ShellcodeModule from "./Shellcode.js";
-import URLModule from "./URL.js";
-
-Object.assign(
-    OpModules,
-    BSONModule,
-    CharEncModule,
-    CipherModule,
-    CodeModule,
-    CompressionModule,
-    DiffModule,
-    EncodingModule,
-    HashingModule,
-    HTTPModule,
-    ImageModule,
-    JSBNModule,
-    PublicKeyModule,
-    RegexModule,
-    ShellcodeModule,
-    URLModule
-);
-
-export default OpModules;

+ 0 - 25
src/core/config/modules/PGP.js

@@ -1,25 +0,0 @@
-import PGP from "../../operations/PGP.js";
-
-
-/**
- * PGP module.
- *
- * Libraries:
- *  - kbpgp
- *
- * @author tlwr [toby@toby.codes]
- * @author Matt C [matt@artemisbot.uk]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.PGP = {
-    "Generate PGP Key Pair":  PGP.runGenerateKeyPair,
-    "PGP Encrypt":            PGP.runEncrypt,
-    "PGP Decrypt":            PGP.runDecrypt,
-    "PGP Encrypt and Sign":   PGP.runSign,
-    "PGP Decrypt and Verify": PGP.runVerify,
-};
-
-export default OpModules;

+ 0 - 25
src/core/config/modules/PublicKey.js

@@ -1,25 +0,0 @@
-import PublicKey from "../../operations/PublicKey.js";
-
-
-/**
- * PublicKey module.
- *
- * Libraries:
- *  - jsrsasign
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.PublicKey = {
-    "Parse X.509 certificate":  PublicKey.runParseX509,
-    "Parse ASN.1 hex string":   PublicKey.runParseAsn1HexString,
-    "PEM to Hex":               PublicKey.runPemToHex,
-    "Hex to PEM":               PublicKey.runHexToPem,
-    "Hex to Object Identifier": PublicKey.runHexToObjectIdentifier,
-    "Object Identifier to Hex": PublicKey.runObjectIdentifierToHex,
-};
-
-export default OpModules;

+ 0 - 30
src/core/config/modules/Regex.js

@@ -1,30 +0,0 @@
-import Extract from "../../operations/Extract.js";
-import Regex from "../../operations/Regex.js";
-
-
-/**
- * Regex module.
- *
- * Libraries:
- *  - XRegExp
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2018
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Regex = {
-    "Regular expression":   Regex.runRegex,
-    "Find / Replace":       Regex.runFindReplace,
-    "Strings":              Extract.runStrings,
-    "Extract IP addresses": Extract.runIp,
-    "Extract email addresses": Extract.runEmail,
-    "Extract MAC addresses": Extract.runMac,
-    "Extract URLs":         Extract.runUrls,
-    "Extract domains":      Extract.runDomains,
-    "Extract file paths":   Extract.runFilePaths,
-    "Extract dates":        Extract.runDates,
-};
-
-export default OpModules;

+ 0 - 20
src/core/config/modules/Shellcode.js

@@ -1,20 +0,0 @@
-import Shellcode from "../../operations/Shellcode.js";
-
-
-/**
- * Shellcode module.
- *
- * Libraries:
- *  - DisassembleX86-64.js
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.Shellcode = {
-    "Disassemble x86": Shellcode.runDisassemble,
-};
-
-export default OpModules;

+ 0 - 23
src/core/config/modules/URL.js

@@ -1,23 +0,0 @@
-import URL_ from "../../operations/URL.js";
-
-
-/**
- * URL module.
- *
- * Libraries:
- *  - Utils.js
- *  - url
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- */
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
-
-OpModules.URL = {
-    "URL Encode": URL_.runTo,
-    "URL Decode": URL_.runFrom,
-    "Parse URI":  URL_.runParse,
-};
-
-export default OpModules;

+ 148 - 0
src/core/config/scripts/generateConfig.mjs

@@ -0,0 +1,148 @@
+/**
+ * This script automatically generates OperationConfig.json, containing metadata
+ * for each operation in the src/core/operations directory.
+ * It also generates modules in the src/core/config/modules directory to separate
+ * out operations into logical collections.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+/*eslint no-console: ["off"] */
+
+import path from "path";
+import fs  from "fs";
+import process from "process";
+import * as Ops from "../../operations/index";
+
+const dir = path.join(process.cwd() + "/src/core/config/");
+if (!fs.existsSync(dir)) {
+    console.log("\nCWD: " + process.cwd());
+    console.log("Error: generateConfig.mjs should be run from the project root");
+    console.log("Example> node --experimental-modules src/core/config/scripts/generateConfig.mjs");
+    process.exit(1);
+}
+
+
+const operationConfig = {},
+    modules = {};
+
+/**
+ * Generate operation config and module lists.
+ */
+for (const opObj in Ops) {
+    const op = new Ops[opObj]();
+
+    operationConfig[op.name] = {
+        module: op.module,
+        description: op.description,
+        inputType: op.inputType,
+        outputType: op.presentType,
+        flowControl: op.flowControl,
+        args: op.args
+    };
+
+    if (op.hasOwnProperty("patterns")) {
+        operationConfig[op.name].patterns = op.patterns;
+    }
+
+    if (!modules.hasOwnProperty(op.module))
+        modules[op.module] = {};
+    modules[op.module][op.name] = opObj;
+}
+
+
+/**
+ * Write OperationConfig.
+ */
+fs.writeFileSync(
+    path.join(dir, "OperationConfig.json"),
+    JSON.stringify(operationConfig, null, 4)
+);
+console.log("Written OperationConfig.json");
+
+
+/**
+ * Write modules.
+ */
+if (!fs.existsSync(path.join(dir, "modules/"))) {
+    fs.mkdirSync(path.join(dir, "modules/"));
+}
+
+for (const module in modules) {
+    let code = `/**
+* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateConfig.mjs
+*
+* @author n1474335 [n1474335@gmail.com]
+* @copyright Crown Copyright ${new Date().getUTCFullYear()}
+* @license Apache-2.0
+*/
+`;
+
+    for (const opName in modules[module]) {
+        const objName = modules[module][opName];
+        code += `import ${objName} from "../../operations/${objName}";\n`;
+    }
+
+    code += `
+const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
+
+OpModules.${module} = {
+`;
+    for (const opName in modules[module]) {
+        const objName = modules[module][opName];
+        code += `    "${opName}": ${objName},\n`;
+    }
+
+    code += `};
+
+export default OpModules;
+`;
+    fs.writeFileSync(
+        path.join(dir, `modules/${module}.mjs`),
+        code
+    );
+    console.log(`Written ${module} module`);
+}
+
+
+/**
+ * Write OpModules wrapper.
+ */
+let opModulesCode = `/**
+* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateConfig.mjs
+*
+* Imports all modules for builds which do not load modules separately.
+*
+* @author n1474335 [n1474335@gmail.com]
+* @copyright Crown Copyright ${new Date().getUTCFullYear()}
+* @license Apache-2.0
+*/
+`;
+
+for (const module in modules) {
+    opModulesCode += `import ${module}Module from "./${module}";\n`;
+}
+
+opModulesCode += `
+const OpModules = {};
+
+Object.assign(
+    OpModules,
+`;
+
+for (const module in modules) {
+    opModulesCode += `    ${module}Module,\n`;
+}
+
+opModulesCode += `);
+
+export default OpModules;
+`;
+
+fs.writeFileSync(
+    path.join(dir, "modules/OpModules.mjs"),
+    opModulesCode
+);
+console.log("Written OpModules.mjs");

+ 60 - 0
src/core/config/scripts/generateOpsIndex.mjs

@@ -0,0 +1,60 @@
+/**
+ * This script automatically generates src/core/operations/index.mjs, containing
+ * imports for all operations in src/core/operations.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+/*eslint no-console: ["off"] */
+
+import path from "path";
+import fs  from "fs";
+import process from "process";
+
+const dir = path.join(process.cwd() + "/src/core/config/");
+if (!fs.existsSync(dir)) {
+    console.log("\nCWD: " + process.cwd());
+    console.log("Error: generateOpsIndex.mjs should be run from the project root");
+    console.log("Example> node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs");
+    process.exit(1);
+}
+
+// Find all operation files
+const opObjs = [];
+fs.readdirSync(path.join(dir, "../operations")).forEach(file => {
+    if (!file.endsWith(".mjs") || file === "index.mjs") return;
+    opObjs.push(file.split(".mjs")[0]);
+});
+
+// Construct index file
+let code = `/**
+* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
+*
+* @author n1474335 [n1474335@gmail.com]
+* @copyright Crown Copyright ${new Date().getUTCFullYear()}
+* @license Apache-2.0
+*/
+`;
+
+opObjs.forEach(obj => {
+    code += `import ${obj} from "./${obj}";\n`;
+});
+
+code += `
+export {
+`;
+
+opObjs.forEach(obj => {
+    code += `    ${obj},\n`;
+});
+
+code += "};\n";
+
+// Write file
+fs.writeFileSync(
+    path.join(dir, "../operations/index.mjs"),
+    code
+);
+console.log("Written operation index.");

+ 223 - 0
src/core/config/scripts/newOperation.mjs

@@ -0,0 +1,223 @@
+/**
+ * Interactive script for generating a new operation template.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+/*eslint no-console: ["off"] */
+
+import prompt from "prompt";
+import colors from "colors";
+import process from "process";
+import fs from "fs";
+import path from "path";
+import EscapeString from "../../operations/EscapeString";
+
+
+const dir = path.join(process.cwd() + "/src/core/operations/");
+if (!fs.existsSync(dir)) {
+    console.log("\nCWD: " + process.cwd());
+    console.log("Error: newOperation.mjs should be run from the project root");
+    console.log("Example> node --experimental-modules src/core/config/scripts/newOperation.mjs");
+    process.exit(1);
+}
+
+const ioTypes = ["string", "byteArray", "number", "html", "ArrayBuffer", "BigNumber", "JSON", "File", "List<File>"];
+
+const schema = {
+    properties: {
+        opName: {
+            description: "The operation name should be short but descriptive.",
+            example: "URL Decode",
+            prompt: "Operation name",
+            type: "string",
+            pattern: /^[\w\s-/().]+$/,
+            required: true,
+            message: "Operation names should consist of letters, numbers or the following symbols: _-/()."
+        },
+        module: {
+            description: `Modules are used to group operations that rely on large libraries. Any operation that is not in the Default module will be loaded in dynamically when it is first called. All operations in the same module will also be loaded at this time. This system prevents the CyberChef web app from getting too bloated and taking a long time to load initially.
+If your operation does not rely on a library, just leave this blank and it will be added to the Default module. If it relies on the same library as other operations, enter the name of the module those operations are in. If it relies on a new large library, enter a new module name (capitalise the first letter).`,
+            example: "Crypto",
+            prompt: "Module",
+            type: "string",
+            pattern: /^[A-Z][A-Za-z\d]+$/,
+            message: "Module names should start with a capital letter and not contain any spaces or symbols.",
+            default: "Default"
+        },
+        description: {
+            description: "The description should explain what the operation is and how it works. It can describe how the arguments should be entered and give examples of expected input and output. HTML markup is supported. Use <code> tags for examples. The description is scanned during searches, so include terms that are likely to be searched for when someone is looking for your operation.",
+            example: "Converts URI/URL percent-encoded characters back to their raw values.<br><br>e.g. <code>%3d</code> becomes <code>=</code>",
+            prompt: "Description",
+            type: "string"
+        },
+        inputType: {
+            description: `The input type defines how the input data will be presented to your operation. Check the project wiki for a full description of each type. The options are: ${ioTypes.join(", ")}.`,
+            example: "string",
+            prompt: "Input type",
+            type: "string",
+            pattern: new RegExp(`^(${ioTypes.join("|")})$`),
+            required: true,
+            message: `The input type should be one of: ${ioTypes.join(", ")}.`
+        },
+        outputType: {
+            description: `The output type tells CyberChef what sort of data you are returning from your operation. Check the project wiki for a full description of each type. The options are: ${ioTypes.join(", ")}.`,
+            example: "string",
+            prompt: "Output type",
+            type: "string",
+            pattern: new RegExp(`^(${ioTypes.join("|")})$`),
+            required: true,
+            message: `The output type should be one of: ${ioTypes.join(", ")}.`
+        },
+        highlight: {
+            description: "If your operation does not change the length of the input in any way, we can enable highlighting. If it does change the length in a predictable way, we may still be able to enable highlighting and calculate the correct offsets. If this is not possible, we will disable highlighting for this operation.",
+            example: "true/false",
+            prompt: "Enable highlighting",
+            type: "boolean",
+            default: "false",
+            message: "Enter true or false to specify if highlighting should be enabled."
+        },
+        authorName: {
+            description: "Your name or username will be added to the @author tag for this operation.",
+            example: "n1474335",
+            prompt: "Username",
+            type: "string"
+        },
+        authorEmail: {
+            description: "Your email address will also be added to the @author tag for this operation.",
+            example: "n1474335@gmail.com",
+            prompt: "Email",
+            type: "string"
+        }
+    }
+};
+
+// Build schema
+for (const prop in schema.properties) {
+    const p = schema.properties[prop];
+    p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
+}
+
+console.log("\n\nThis script will generate a new operation template based on the information you provide. These values can be changed manually later.".yellow);
+
+prompt.message = "";
+prompt.delimiter = ":".green;
+
+prompt.start();
+
+prompt.get(schema, (err, result) => {
+    if (err) {
+        console.log("\nExiting build script.");
+        process.exit(0);
+    }
+
+    const moduleName = result.opName.replace(/\w\S*/g, txt => {
+        return txt.charAt(0).toUpperCase() + txt.substr(1);
+    }).replace(/[\s-()/./]/g, "");
+
+
+    const template = `/**
+ * @author ${result.authorName} [${result.authorEmail}]
+ * @copyright Crown Copyright ${(new Date()).getFullYear()}
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+
+/**
+ * ${result.opName} operation
+ */
+class ${moduleName} extends Operation {
+
+    /**
+     * ${moduleName} constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "${result.opName}";
+        this.module = "${result.module}";
+        this.description = "${(new EscapeString).run(result.description, ["Special chars", "Double"])}";
+        this.inputType = "${result.inputType}";
+        this.outputType = "${result.outputType}";
+        this.args = [
+            /* Example arguments. See the project wiki for full details.
+            {
+                name: "First arg",
+                type: "string",
+                value: "Don't Panic"
+            },
+            {
+                name: "Second arg",
+                type: "number",
+                value: 42
+            }
+            */
+        ];
+    }
+
+    /**
+     * @param {${result.inputType}} input
+     * @param {Object[]} args
+     * @returns {${result.outputType}}
+     */
+    run(input, args) {
+        // const [firstArg, secondArg] = args;
+
+        throw new OperationError("Test");
+    }
+${result.highlight ? `
+    /**
+     * Highlight ${result.opName}
+     *
+     * @param {Object[]} pos
+     * @param {number} pos[].start
+     * @param {number} pos[].end
+     * @param {Object[]} args
+     * @returns {Object[]} pos
+     */
+    highlight(pos, args) {
+        return pos;
+    }
+
+    /**
+     * Highlight ${result.opName} 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 ${moduleName};
+`;
+
+    //console.log(template);
+
+    const filename = path.join(dir, `./${moduleName}.mjs`);
+    if (fs.existsSync(filename)) {
+        console.log(`${filename} already exists. It has NOT been overwritten.`.red);
+        console.log("Choose a different operation name to avoid conflicts.");
+        process.exit(0);
+    }
+    fs.writeFileSync(filename, template);
+
+    console.log(`\nOperation template written to ${colors.green(filename)}`);
+    console.log(`\nNext steps:
+1. Add your operation to ${colors.green("src/core/config/Categories.json")}
+2. Write your operation code.
+3. Write tests in ${colors.green("test/tests/operations/")}
+4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")}
+5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);
+
+});
+

+ 26 - 0
src/core/errors/OperationError.mjs

@@ -0,0 +1,26 @@
+/**
+ * Custom error type for handling operation input errors.
+ * i.e. where the operation can handle the error and print a message to the screen.
+ *
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+class OperationError extends Error {
+    /**
+     * Standard error constructor. Adds no new behaviour.
+     *
+     * @param args - Standard error args
+     */
+    constructor(...args) {
+        super(...args);
+
+        this.type = "OperationError";
+
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(this, OperationError);
+        }
+    }
+}
+
+export default OperationError;

+ 139 - 0
src/core/lib/Arithmetic.mjs

@@ -0,0 +1,139 @@
+/**
+ * @author bwhitn [brian.m.whitney@outlook.com]
+ * @author d98762625 [d98762625@gmailcom]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import BigNumber from "bignumber.js";
+
+
+/**
+ * Converts a string array to a number array.
+ *
+ * @param {string[]} input
+ * @param {string} delim
+ * @returns {BigNumber[]}
+ */
+export function createNumArray(input, delim) {
+    delim = Utils.charRep(delim || "Space");
+    const splitNumbers = input.split(delim);
+    const numbers = [];
+    let num;
+
+    splitNumbers.map((number) => {
+        try {
+            num = BigNumber(number.trim());
+            if (!num.isNaN()) {
+                numbers.push(num);
+            }
+        } catch (err) {
+            // This line is not a valid number
+        }
+    });
+    return numbers;
+}
+
+
+/**
+ * Adds an array of numbers and returns the value.
+ *
+ * @param {BigNumber[]} data
+ * @returns {BigNumber}
+ */
+export function sum(data) {
+    if (data.length > 0) {
+        return data.reduce((acc, curr) => acc.plus(curr));
+    }
+}
+
+
+/**
+ * Subtracts an array of numbers and returns the value.
+ *
+ * @param {BigNumber[]} data
+ * @returns {BigNumber}
+ */
+export function sub(data) {
+    if (data.length > 0) {
+        return data.reduce((acc, curr) => acc.minus(curr));
+    }
+}
+
+
+/**
+ * Multiplies an array of numbers and returns the value.
+ *
+ * @param {BigNumber[]} data
+ * @returns {BigNumber}
+ */
+export function multi(data) {
+    if (data.length > 0) {
+        return data.reduce((acc, curr) => acc.times(curr));
+    }
+}
+
+
+/**
+ * Divides an array of numbers and returns the value.
+ *
+ * @param {BigNumber[]} data
+ * @returns {BigNumber}
+ */
+export function div(data) {
+    if (data.length > 0) {
+        return data.reduce((acc, curr) => acc.div(curr));
+    }
+}
+
+
+/**
+ * Computes mean of a number array and returns the value.
+ *
+ * @param {BigNumber[]} data
+ * @returns {BigNumber}
+ */
+export function mean(data) {
+    if (data.length > 0) {
+        return sum(data).div(data.length);
+    }
+}
+
+
+/**
+ * Computes median of a number array and returns the value.
+ *
+ * @param {BigNumber[]} data
+ * @returns {BigNumber}
+ */
+export function median(data) {
+    if ((data.length % 2) === 0 && data.length > 0) {
+        data.sort(function(a, b){
+            return a.minus(b);
+        });
+        const first = data[Math.floor(data.length / 2)];
+        const second = data[Math.floor(data.length / 2) - 1];
+        return mean([first, second]);
+    } else {
+        return data[Math.floor(data.length / 2)];
+    }
+}
+
+
+/**
+ * Computes standard deviation of a number array and returns the value.
+ *
+ * @param {BigNumber[]} data
+ * @returns {BigNumber}
+ */
+export function stdDev(data) {
+    if (data.length > 0) {
+        const avg = mean(data);
+        let devSum = new BigNumber(0);
+        data.map((datum) => {
+            devSum = devSum.plus(datum.minus(avg).pow(2));
+        });
+        return devSum.div(data.length).sqrt();
+    }
+}

+ 48 - 0
src/core/lib/BCD.mjs

@@ -0,0 +1,48 @@
+/**
+ * Binary Code Decimal resources.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+/**
+ * BCD encoding schemes.
+ */
+export const ENCODING_SCHEME = [
+    "8 4 2 1",
+    "7 4 2 1",
+    "4 2 2 1",
+    "2 4 2 1",
+    "8 4 -2 -1",
+    "Excess-3",
+    "IBM 8 4 2 1",
+];
+
+/**
+ * Lookup table for the binary value of each digit representation.
+ *
+ * I wrote a very nice algorithm to generate 8 4 2 1 encoding programatically,
+ * but unfortunately it's much easier (if less elegant) to use lookup tables
+ * when supporting multiple encoding schemes.
+ *
+ * "Practicality beats purity" - PEP 20
+ *
+ * In some schemes it is possible to represent the same value in multiple ways.
+ * For instance, in 4 2 2 1 encoding, 0100 and 0010 both represent 2. Support
+ * has not yet been added for this.
+ */
+export const ENCODING_LOOKUP = {
+    "8 4 2 1":     [0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+    "7 4 2 1":     [0,  1,  2,  3,  4,  5,  6,  8,  9,  10],
+    "4 2 2 1":     [0,  1,  4,  5,  8,  9,  12, 13, 14, 15],
+    "2 4 2 1":     [0,  1,  2,  3,  4,  11, 12, 13, 14, 15],
+    "8 4 -2 -1":   [0,  7,  6,  5,  4,  11, 10, 9,  8,  15],
+    "Excess-3":    [3,  4,  5,  6,  7,  8,  9,  10, 11, 12],
+    "IBM 8 4 2 1": [10, 1,  2,  3,  4,  5,  6,  7,  8,  9],
+};
+
+/**
+ * BCD formats.
+ */
+export const FORMAT = ["Nibbles", "Bytes", "Raw"];

+ 22 - 0
src/core/lib/Base58.mjs

@@ -0,0 +1,22 @@
+/**
+ * Base58 resources.
+ *
+ * @author tlwr [toby@toby.codes]
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+/**
+ * Base58 alphabet options.
+ */
+export const ALPHABET_OPTIONS = [
+    {
+        name: "Bitcoin",
+        value: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
+    },
+    {
+        name: "Ripple",
+        value: "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
+    },
+];

+ 141 - 0
src/core/lib/Base64.mjs

@@ -0,0 +1,141 @@
+/**
+ * Base64 functions.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+
+
+/**
+ * Base64's the input byte array using the given alphabet, returning a string.
+ *
+ * @param {byteArray|Uint8Array|string} data
+ * @param {string} [alphabet="A-Za-z0-9+/="]
+ * @returns {string}
+ *
+ * @example
+ * // returns "SGVsbG8="
+ * toBase64([72, 101, 108, 108, 111]);
+ *
+ * // returns "SGVsbG8="
+ * toBase64("Hello");
+ */
+export function toBase64(data, alphabet="A-Za-z0-9+/=") {
+    if (!data) return "";
+    if (typeof data == "string") {
+        data = Utils.strToByteArray(data);
+    }
+
+    alphabet = Utils.expandAlphRange(alphabet).join("");
+
+    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="A-Za-z0-9+/="]
+ * @param {string} [returnType="string"] - Either "string" or "byteArray"
+ * @param {boolean} [removeNonAlphChars=true]
+ * @returns {byteArray}
+ *
+ * @example
+ * // returns "Hello"
+ * fromBase64("SGVsbG8=");
+ *
+ * // returns [72, 101, 108, 108, 111]
+ * fromBase64("SGVsbG8=", null, "byteArray");
+ */
+export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) {
+    if (!data) {
+        return returnType === "string" ? "" : [];
+    }
+
+    alphabet = Utils.expandAlphRange(alphabet).join("");
+
+    const output = [];
+    let 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;
+}
+
+
+/**
+ * Base64 alphabets.
+ */
+export const ALPHABET_OPTIONS = [
+    {name: "Standard: A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
+    {name: "URL safe: A-Za-z0-9-_", value: "A-Za-z0-9-_"},
+    {name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
+    {name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
+    {name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
+    {name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
+    {name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
+    {name: "Radix-64: 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
+    {name: "Uuencoding: [space]-_", value: " -_"},
+    {name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
+    {name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
+    {name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
+    {name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
+];

+ 117 - 0
src/core/lib/BitwiseOp.mjs

@@ -0,0 +1,117 @@
+/**
+ * Bitwise operation resources.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+/**
+ * Runs bitwise operations across the input data.
+ *
+ * @param {byteArray} input
+ * @param {byteArray} key
+ * @param {function} func - The bitwise calculation to carry out
+ * @param {boolean} nullPreserving
+ * @param {string} scheme
+ * @returns {byteArray}
+ */
+export function bitOp (input, key, func, nullPreserving, scheme) {
+    if (!key || !key.length) key = [0];
+    const result = [];
+    let 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;
+}
+
+/**
+ * XOR bitwise calculation.
+ *
+ * @param {number} operand
+ * @param {number} key
+ * @returns {number}
+ */
+export function xor(operand, key) {
+    return operand ^ key;
+}
+
+
+/**
+ * NOT bitwise calculation.
+ *
+ * @param {number} operand
+ * @returns {number}
+ */
+export function not(operand, _) {
+    return ~operand & 0xff;
+}
+
+
+/**
+ * AND bitwise calculation.
+ *
+ * @param {number} operand
+ * @param {number} key
+ * @returns {number}
+ */
+export function and(operand, key) {
+    return operand & key;
+}
+
+
+/**
+ * OR bitwise calculation.
+ *
+ * @param {number} operand
+ * @param {number} key
+ * @returns {number}
+ */
+export function or(operand, key) {
+    return operand | key;
+}
+
+
+/**
+ * ADD bitwise calculation.
+ *
+ * @param {number} operand
+ * @param {number} key
+ * @returns {number}
+ */
+export function add(operand, key) {
+    return (operand + key) % 256;
+}
+
+
+/**
+ * SUB bitwise calculation.
+ *
+ * @param {number} operand
+ * @param {number} key
+ * @returns {number}
+ */
+export function sub(operand, key) {
+    const result = operand - key;
+    return (result < 0) ? 256 + result : result;
+}

+ 204 - 0
src/core/lib/CanvasComponents.mjs

@@ -0,0 +1,204 @@
+/**
+ * Various components for drawing diagrams on an HTML5 canvas.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+/**
+ * Draws a line from one point to another
+ *
+ * @param ctx
+ * @param startX
+ * @param startY
+ * @param endX
+ * @param endY
+ */
+export function drawLine(ctx, startX, startY, endX, endY) {
+    ctx.beginPath();
+    ctx.moveTo(startX, startY);
+    ctx.lineTo(endX, endY);
+    ctx.closePath();
+    ctx.stroke();
+}
+
+/**
+ * Draws a bar chart on the canvas.
+ *
+ * @param canvas
+ * @param scores
+ * @param xAxisLabel
+ * @param yAxisLabel
+ * @param numXLabels
+ * @param numYLabels
+ * @param fontSize
+ */
+export function drawBarChart(canvas, scores, xAxisLabel, yAxisLabel, numXLabels, numYLabels, fontSize) {
+    fontSize = fontSize || 15;
+    if (!numXLabels || numXLabels > Math.round(canvas.width / 50)) {
+        numXLabels = Math.round(canvas.width / 50);
+    }
+    if (!numYLabels || numYLabels > Math.round(canvas.width / 50)) {
+        numYLabels = Math.round(canvas.height / 50);
+    }
+
+    // Graph properties
+    const ctx = canvas.getContext("2d"),
+        leftPadding = canvas.width * 0.08,
+        rightPadding = canvas.width * 0.03,
+        topPadding = canvas.height * 0.08,
+        bottomPadding = canvas.height * 0.2,
+        graphHeight = canvas.height - topPadding - bottomPadding,
+        graphWidth = canvas.width - leftPadding - rightPadding,
+        base = topPadding + graphHeight,
+        ceil = topPadding;
+
+    ctx.font = fontSize + "px Arial";
+
+    // Draw axis
+    ctx.lineWidth = "1.0";
+    ctx.strokeStyle = "#444";
+    drawLine(ctx, leftPadding, base, graphWidth + leftPadding, base); // x
+    drawLine(ctx, leftPadding, base, leftPadding, ceil); // y
+
+    // Bar properties
+    const barPadding = graphWidth * 0.003,
+        barWidth = (graphWidth - (barPadding * scores.length)) / scores.length,
+        max = Math.max.apply(Math, scores);
+    let currX = leftPadding + barPadding;
+
+    // Draw bars
+    ctx.fillStyle = "green";
+    for (let i = 0; i < scores.length; i++) {
+        const h = scores[i] / max * graphHeight;
+        ctx.fillRect(currX, base - h, barWidth, h);
+        currX += barWidth + barPadding;
+    }
+
+    // Mark x axis
+    ctx.fillStyle = "black";
+    ctx.textAlign = "center";
+    currX = leftPadding + barPadding;
+    if (numXLabels >= scores.length) {
+        // Mark every score
+        for (let i = 0; i <= scores.length; i++) {
+            ctx.fillText(i, currX, base + (bottomPadding * 0.3));
+            currX += barWidth + barPadding;
+        }
+    } else {
+        // Mark some scores
+        for (let i = 0; i <= numXLabels; i++) {
+            const val = Math.ceil((scores.length / numXLabels) * i);
+            currX = (graphWidth / numXLabels) * i + leftPadding;
+            ctx.fillText(val, currX, base + (bottomPadding * 0.3));
+        }
+    }
+
+    // Mark y axis
+    ctx.textAlign = "right";
+    let currY;
+    if (numYLabels >= max) {
+        // Mark every increment
+        for (let i = 0; i <= max; i++) {
+            currY = base - (i / max * graphHeight) + fontSize / 3;
+            ctx.fillText(i, leftPadding * 0.8, currY);
+        }
+    } else {
+        // Mark some increments
+        for (let i = 0; i <= numYLabels; i++) {
+            const val = Math.ceil((max / numYLabels) * i);
+            currY = base - (val / max * graphHeight) + fontSize / 3;
+            ctx.fillText(val, leftPadding * 0.8, currY);
+        }
+    }
+
+    // Label x axis
+    if (xAxisLabel) {
+        ctx.textAlign = "center";
+        ctx.fillText(xAxisLabel, graphWidth / 2 + leftPadding, base + bottomPadding * 0.8);
+    }
+
+    // Label y axis
+    if (yAxisLabel) {
+        ctx.save();
+        const x = leftPadding * 0.3,
+            y = graphHeight / 2 + topPadding;
+        ctx.translate(x, y);
+        ctx.rotate(-Math.PI / 2);
+        ctx.textAlign = "center";
+        ctx.fillText(yAxisLabel, 0, 0);
+        ctx.restore();
+    }
+}
+
+/**
+ * Draws a scale bar on the canvas.
+ *
+ * @param canvas
+ * @param score
+ * @param max
+ * @param markings
+ */
+export function drawScaleBar(canvas, score, max, markings) {
+    // Bar properties
+    const ctx = canvas.getContext("2d"),
+        leftPadding = canvas.width * 0.01,
+        rightPadding = canvas.width * 0.01,
+        topPadding = canvas.height * 0.1,
+        bottomPadding = canvas.height * 0.35,
+        barHeight = canvas.height - topPadding - bottomPadding,
+        barWidth = canvas.width - leftPadding - rightPadding;
+
+    // Scale properties
+    const proportion = score / max;
+
+    // Draw bar outline
+    ctx.strokeRect(leftPadding, topPadding, barWidth, barHeight);
+
+    // Shade in up to proportion
+    const grad = ctx.createLinearGradient(leftPadding, 0, barWidth + leftPadding, 0);
+    grad.addColorStop(0, "green");
+    grad.addColorStop(0.5, "gold");
+    grad.addColorStop(1, "red");
+    ctx.fillStyle = grad;
+    ctx.fillRect(leftPadding, topPadding, barWidth * proportion, barHeight);
+
+    // Add markings
+    let x0, y0, x1, y1;
+    ctx.fillStyle = "black";
+    ctx.textAlign = "center";
+    ctx.font = "13px Arial";
+    for (let i = 0; i < markings.length; i++) {
+        // Draw min line down
+        x0 = barWidth / max * markings[i].min + leftPadding;
+        y0 = topPadding + barHeight + (bottomPadding * 0.1);
+        x1 = x0;
+        y1 = topPadding + barHeight + (bottomPadding * 0.3);
+        drawLine(ctx, x0, y0, x1, y1);
+
+        // Draw max line down
+        x0 = barWidth / max * markings[i].max + leftPadding;
+        x1 = x0;
+        drawLine(ctx, x0, y0, x1, y1);
+
+        // Join min and max lines
+        x0 = barWidth / max * markings[i].min + leftPadding;
+        y0 = topPadding + barHeight + (bottomPadding * 0.3);
+        x1 = barWidth / max * markings[i].max + leftPadding;
+        y1 = y0;
+        drawLine(ctx, x0, y0, x1, y1);
+
+        // Add label
+        if (markings[i].max >= max * 0.9) {
+            ctx.textAlign = "right";
+            x0 = x1;
+        } else if (markings[i].max <= max * 0.1) {
+            ctx.textAlign = "left";
+        } else {
+            x0 = x0 + (x1 - x0) / 2;
+        }
+        y0 = topPadding + barHeight + (bottomPadding * 0.8);
+        ctx.fillText(markings[i].label, x0, y0);
+    }
+}

+ 58 - 0
src/core/lib/ChrEnc.mjs

@@ -0,0 +1,58 @@
+/**
+ * Character encoding resources.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+/**
+ * Character encoding format mappings.
+ */
+export const 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,
+};
+

+ 82 - 0
src/core/lib/Ciphers.mjs

@@ -0,0 +1,82 @@
+/**
+ * Cipher functions.
+ *
+ * @author Matt C [matt@artemisbot.uk]
+ * @author n1474335 [n1474335@gmail.com]
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ *
+ */
+
+import OperationError from "../errors/OperationError";
+import CryptoJS from "crypto-js";
+
+/**
+ * Affine Cipher Encode operation.
+ *
+ * @author Matt C [matt@artemisbot.uk]
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+export function affineEncode(input, args) {
+    const alphabet = "abcdefghijklmnopqrstuvwxyz",
+        a = args[0],
+        b = args[1];
+    let output = "";
+
+    if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
+        throw new OperationError("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;
+}
+
+/**
+ * Generates a polybius square for the given keyword
+ *
+ * @private
+ * @author Matt C [matt@artemisbot.uk]
+ * @param {string} keyword - Must be upper case
+ * @returns {string}
+ */
+export function genPolybiusSquare (keyword) {
+    const alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ",
+        polArray = `${keyword}${alpha}`.split("").unique(),
+        polybius = [];
+
+    for (let i = 0; i < 5; i++) {
+        polybius[i] = polArray.slice(i*5, i*5 + 5);
+    }
+
+    return polybius;
+}
+
+/**
+ * A mapping of string formats to their classes in the CryptoJS library.
+ *
+ * @private
+ * @constant
+ */
+export const 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,
+};

+ 29 - 0
src/core/lib/Code.mjs

@@ -0,0 +1,29 @@
+/**
+ * Code resources.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+/**
+ * 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}
+ */
+export function replaceVariableNames(input, replacer) {
+    const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
+
+    return input.replace(tokenRegex, (...args) => {
+        const match = args[0],
+            quotes = args[1];
+
+        if (!quotes) {
+            return match;
+        } else {
+            return replacer(match);
+        }
+    });
+}

+ 313 - 0
src/core/lib/DateTime.mjs

@@ -0,0 +1,313 @@
+/**
+ * DateTime resources.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+/**
+ * DateTime units.
+ */
+export const UNITS = ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"];
+
+/**
+ * DateTime formats.
+ */
+export const 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: ""
+    },
+];
+
+/**
+ * MomentJS DateTime formatting examples.
+ */
+export const FORMAT_EXAMPLES = `Format string tokens:
+<table class="table table-striped table-hover table-sm table-bordered" style="font-family: sans-serif">
+  <thead class="thead-dark">
+    <tr>
+      <th>Category</th>
+      <th>Token</th>
+      <th>Output</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><b>Month</b></td>
+      <td>M</td>
+      <td>1 2 ... 11 12</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>Mo</td>
+      <td>1st 2nd ... 11th 12th</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>MM</td>
+      <td>01 02 ... 11 12</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>MMM</td>
+      <td>Jan Feb ... Nov Dec</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>MMMM</td>
+      <td>January February ... November December</td>
+    </tr>
+    <tr>
+      <td><b>Quarter</b></td>
+      <td>Q</td>
+      <td>1 2 3 4</td>
+    </tr>
+    <tr>
+      <td><b>Day of Month</b></td>
+      <td>D</td>
+      <td>1 2 ... 30 31</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>Do</td>
+      <td>1st 2nd ... 30th 31st</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>DD</td>
+      <td>01 02 ... 30 31</td>
+    </tr>
+    <tr>
+      <td><b>Day of Year</b></td>
+      <td>DDD</td>
+      <td>1 2 ... 364 365</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>DDDo</td>
+      <td>1st 2nd ... 364th 365th</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>DDDD</td>
+      <td>001 002 ... 364 365</td>
+    </tr>
+    <tr>
+      <td><b>Day of Week</b></td>
+      <td>d</td>
+      <td>0 1 ... 5 6</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>do</td>
+      <td>0th 1st ... 5th 6th</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>dd</td>
+      <td>Su Mo ... Fr Sa</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>ddd</td>
+      <td>Sun Mon ... Fri Sat</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>dddd</td>
+      <td>Sunday Monday ... Friday Saturday</td>
+    </tr>
+    <tr>
+      <td><b>Day of Week (Locale)</b></td>
+      <td>e</td>
+      <td>0 1 ... 5 6</td>
+    </tr>
+    <tr>
+      <td><b>Day of Week (ISO)</b></td>
+      <td>E</td>
+      <td>1 2 ... 6 7</td>
+    </tr>
+    <tr>
+      <td><b>Week of Year</b></td>
+      <td>w</td>
+      <td>1 2 ... 52 53</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>wo</td>
+      <td>1st 2nd ... 52nd 53rd</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>ww</td>
+      <td>01 02 ... 52 53</td>
+    </tr>
+    <tr>
+      <td><b>Week of Year (ISO)</b></td>
+      <td>W</td>
+      <td>1 2 ... 52 53</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>Wo</td>
+      <td>1st 2nd ... 52nd 53rd</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>WW</td>
+      <td>01 02 ... 52 53</td>
+    </tr>
+    <tr>
+      <td><b>Year</b></td>
+      <td>YY</td>
+      <td>70 71 ... 29 30</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>YYYY</td>
+      <td>1970 1971 ... 2029 2030</td>
+    </tr>
+    <tr>
+      <td><b>Week Year</b></td>
+      <td>gg</td>
+      <td>70 71 ... 29 30</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>gggg</td>
+      <td>1970 1971 ... 2029 2030</td>
+    </tr>
+    <tr>
+      <td><b>Week Year (ISO)</b></td>
+      <td>GG</td>
+      <td>70 71 ... 29 30</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>GGGG</td>
+      <td>1970 1971 ... 2029 2030</td>
+    </tr>
+    <tr>
+      <td><b>AM/PM</b></td>
+      <td>A</td>
+      <td>AM PM</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>a</td>
+      <td>am pm</td>
+    </tr>
+    <tr>
+      <td><b>Hour</b></td>
+      <td>H</td>
+      <td>0 1 ... 22 23</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>HH</td>
+      <td>00 01 ... 22 23</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>h</td>
+      <td>1 2 ... 11 12</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>hh</td>
+      <td>01 02 ... 11 12</td>
+    </tr>
+    <tr>
+      <td><b>Minute</b></td>
+      <td>m</td>
+      <td>0 1 ... 58 59</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>mm</td>
+      <td>00 01 ... 58 59</td>
+    </tr>
+    <tr>
+      <td><b>Second</b></td>
+      <td>s</td>
+      <td>0 1 ... 58 59</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>ss</td>
+      <td>00 01 ... 58 59</td>
+    </tr>
+    <tr>
+      <td><b>Fractional Second</b></td>
+      <td>S</td>
+      <td>0 1 ... 8 9</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>SS</td>
+      <td>00 01 ... 98 99</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>SSS</td>
+      <td>000 001 ... 998 999</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>SSSS ... SSSSSSSSS</td>
+      <td>000[0..] 001[0..] ... 998[0..] 999[0..]</td>
+    </tr>
+    <tr>
+      <td><b>Timezone</b></td>
+      <td>z or zz</td>
+      <td>EST CST ... MST PST</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>Z</td>
+      <td>-07:00 -06:00 ... +06:00 +07:00</td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>ZZ</td>
+      <td>-0700 -0600 ... +0600 +0700</td>
+    </tr>
+    <tr>
+      <td><b>Unix Timestamp</b></td>
+      <td>X</td>
+      <td>1360013296</td>
+    </tr>
+    <tr>
+      <td><b>Unix Millisecond Timestamp</b></td>
+      <td>x</td>
+      <td>1360013296123</td>
+    </tr>
+  </tbody>
+</table>`;
+

+ 74 - 0
src/core/lib/Delim.mjs

@@ -0,0 +1,74 @@
+/**
+ * Various delimiters
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+/**
+ * Generic sequence delimiters.
+ */
+export const DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"];
+
+/**
+ * Binary sequence delimiters.
+ */
+export const BIN_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"];
+
+/**
+ * Letter sequence delimiters.
+ */
+export const LETTER_DELIM_OPTIONS = ["Space", "Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"];
+
+/**
+ * Word sequence delimiters.
+ */
+export const WORD_DELIM_OPTIONS = ["Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"];
+
+/**
+ * Input sequence delimiters.
+ */
+export const INPUT_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"];
+
+/**
+ * Armithmetic sequence delimiters
+ */
+export const ARITHMETIC_DELIM_OPTIONS = ["Line feed", "Space", "Comma", "Semi-colon", "Colon", "CRLF"];
+
+/**
+ * Hash delimiters
+ */
+export const HASH_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma"];
+
+/**
+ * IP delimiters
+ */
+export const IP_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma", "Semi-colon"];
+
+/**
+ * Split delimiters.
+ */
+export const 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: ""}
+];
+
+/**
+ * Join delimiters.
+ */
+export const 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: ""}
+];
+

+ 41 - 0
src/core/lib/Extract.mjs

@@ -0,0 +1,41 @@
+/**
+ * Identifier extraction functions
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ *
+ */
+
+/**
+ * Runs search operations across the input data using regular expressions.
+ *
+ * @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}
+ */
+export function search (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;
+}

+ 20 - 0
src/core/lib/FlowControl.mjs

@@ -0,0 +1,20 @@
+/**
+ * Flow control functions
+ *
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+/**
+ * Returns the index of a label.
+ *
+ * @param {Object} state - The current state of the recipe.
+ * @param {string} name - The label name to look for.
+ * @returns {number}
+ */
+export function getLabelIndex(name, state) {
+    return state.opList.findIndex((operation) => {
+        return (operation.name === "Label") && (name === operation.ingValues[0]);
+    });
+}

+ 28 - 0
src/core/lib/Hash.mjs

@@ -0,0 +1,28 @@
+/**
+ * Hashing resources.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ *
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import CryptoApi from "crypto-api/src/crypto-api";
+
+
+/**
+ * Generic hash function.
+ *
+ * @param {string} name
+ * @param {ArrayBuffer} input
+ * @param {Object} [options={}]
+ * @returns {string}
+ */
+export function runHash(name, input, options={}) {
+    const msg = Utils.arrayBufferToStr(input, false),
+        hasher = CryptoApi.getHasher(name, options);
+    hasher.update(msg);
+    return CryptoApi.encoder.toHex(hasher.finalize());
+}
+

+ 110 - 0
src/core/lib/Hex.mjs

@@ -0,0 +1,110 @@
+/**
+ * Byte representation functions.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+
+
+/**
+ * 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"
+ * toHex([10,20,30]);
+ *
+ * // returns "0a:14:1e"
+ * toHex([10,20,30], ":");
+ */
+export function toHex(data, delim=" ", padding=2) {
+    if (!data) return "";
+
+    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"
+ * toHex([10,20,30]);
+ */
+export function toHexFast(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]
+ * fromHex("0a 14 1e");
+ *
+ * // returns [10,20,30]
+ * fromHex("0a:14:1e", "Colon");
+ */
+export function fromHex(data, delim, byteLen=2) {
+    delim = delim || "Auto";
+    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;
+}
+
+
+/**
+ * To Hexadecimal delimiters.
+ */
+export const TO_HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
+
+
+/**
+ * From Hexadecimal delimiters.
+ */
+export const FROM_HEX_DELIM_OPTIONS = ["Auto"].concat(TO_HEX_DELIM_OPTIONS);

+ 557 - 0
src/core/lib/IP.mjs

@@ -0,0 +1,557 @@
+/**
+ * IP resources.
+ *
+ * @author picapi
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Parses an IPv4 CIDR range (e.g. 192.168.0.0/24) and displays information about it.
+ *
+ * @param {RegExp} cidr
+ * @param {boolean} includeNetworkInfo
+ * @param {boolean} enumerateAddresses
+ * @param {boolean} allowLargeList
+ * @returns {string}
+ */
+export function ipv4CidrRange(cidr, includeNetworkInfo, enumerateAddresses, allowLargeList) {
+    const network = strToIpv4(cidr[1]),
+        cidrRange = parseInt(cidr[2], 10);
+    let output = "";
+
+    if (cidrRange < 0 || cidrRange > 31) {
+        return "IPv4 CIDR must be less than 32";
+    }
+
+    const mask = ~(0xFFFFFFFF >>> cidrRange),
+        ip1 = network & mask,
+        ip2 = ip1 | ~mask;
+
+    if (includeNetworkInfo) {
+        output += "Network: " + ipv4ToStr(network) + "\n";
+        output += "CIDR: " + cidrRange + "\n";
+        output += "Mask: " + ipv4ToStr(mask) + "\n";
+        output += "Range: " + ipv4ToStr(ip1) + " - " + ipv4ToStr(ip2) + "\n";
+        output += "Total addresses in range: " + (((ip2 - ip1) >>> 0) + 1) + "\n\n";
+    }
+
+    if (enumerateAddresses) {
+        if (cidrRange >= 16 || allowLargeList) {
+            output += generateIpv4Range(ip1, ip2).join("\n");
+        } else {
+            output += _LARGE_RANGE_ERROR;
+        }
+    }
+    return output;
+}
+
+/**
+ * Parses an IPv6 CIDR range (e.g. ff00::/48) and displays information about it.
+ *
+ * @param {RegExp} cidr
+ * @param {boolean} includeNetworkInfo
+ * @returns {string}
+ */
+export function ipv6CidrRange(cidr, includeNetworkInfo) {
+    let output = "";
+    const network = strToIpv6(cidr[1]),
+        cidrRange = parseInt(cidr[cidr.length-1], 10);
+
+    if (cidrRange < 0 || cidrRange > 127) {
+        return "IPv6 CIDR must be less than 128";
+    }
+
+    const ip1 = new Array(8),
+        ip2 = new Array(8),
+        total = new Array(128);
+
+    const mask = genIpv6Mask(cidrRange);
+    let totalDiff = "";
+
+
+    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: " + ipv6ToStr(network) + "\n";
+        output += "Shorthand: " + ipv6ToStr(network, true) + "\n";
+        output += "CIDR: " + cidrRange + "\n";
+        output += "Mask: " + ipv6ToStr(mask) + "\n";
+        output += "Range: " + ipv6ToStr(ip1) + " - " + ipv6ToStr(ip2) + "\n";
+        output += "Total addresses in range: " + (parseInt(total.join(""), 2) + 1) + "\n\n";
+    }
+
+    return output;
+}
+
+/**
+ * Parses an IPv4 hyphenated range (e.g. 192.168.0.0 - 192.168.0.255) and displays information
+ * about it.
+ *
+ * @param {RegExp} range
+ * @param {boolean} includeNetworkInfo
+ * @param {boolean} enumerateAddresses
+ * @param {boolean} allowLargeList
+ * @returns {string}
+ */
+export function ipv4HyphenatedRange(range, includeNetworkInfo, enumerateAddresses, allowLargeList) {
+    const ip1 = strToIpv4(range[1]),
+        ip2 = strToIpv4(range[2]);
+
+    let output = "";
+
+    // Calculate mask
+    let diff = ip1 ^ ip2,
+        cidr = 32,
+        mask = 0;
+
+    while (diff !== 0) {
+        diff >>= 1;
+        cidr--;
+        mask = (mask << 1) | 1;
+    }
+
+    mask = ~mask >>> 0;
+    const network = ip1 & mask,
+        subIp1 = network & mask,
+        subIp2 = subIp1 | ~mask;
+
+    if (includeNetworkInfo) {
+        output += `Minimum subnet required to hold this range:
+\tNetwork: ${ipv4ToStr(network)}
+\tCIDR: ${cidr}
+\tMask: ${ipv4ToStr(mask)}
+\tSubnet range: ${ipv4ToStr(subIp1)} - ${ipv4ToStr(subIp2)}
+\tTotal addresses in subnet: ${(((subIp2 - subIp1) >>> 0) + 1)}
+
+Range: ${ipv4ToStr(ip1)} - ${ipv4ToStr(ip2)}
+Total addresses in range: ${(((ip2 - ip1) >>> 0) + 1)}
+
+`;
+    }
+
+    if (enumerateAddresses) {
+        if (((ip2 - ip1) >>> 0) <= 65536 || allowLargeList) {
+            output += generateIpv4Range(ip1, ip2).join("\n");
+        } else {
+            output += _LARGE_RANGE_ERROR;
+        }
+    }
+    return output;
+}
+
+/**
+ * Parses an IPv6 hyphenated range (e.g. ff00:: - ffff::) and displays information about it.
+ *
+ * @param {RegExp} range
+ * @param {boolean} includeNetworkInfo
+ * @returns {string}
+ */
+export function ipv6HyphenatedRange(range, includeNetworkInfo) {
+    const ip1 = strToIpv6(range[1]),
+        ip2 = strToIpv6(range[14]),
+        total = new Array(128).fill();
+
+    let output = "",
+        t = "",
+        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: " + ipv6ToStr(ip1) + " - " + ipv6ToStr(ip2) + "\n";
+        output += "Shorthand range: " + ipv6ToStr(ip1, true) + " - " + 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.
+ *
+ * @param {string} ipStr
+ * @returns {number}
+ *
+ * @example
+ * // returns 168427520
+ * strToIpv4("10.10.0.0");
+ */
+export function strToIpv4(ipStr) {
+    const blocks = ipStr.split("."),
+        numBlocks = parseBlocks(blocks);
+    let 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 new OperationError("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 new OperationError("Block out of range.");
+        }
+        return numBlocks;
+    }
+}
+
+/**
+ * Converts an IPv4 address from numerical format to string format.
+ *
+ * @param {number} ipInt
+ * @returns {string}
+ *
+ * @example
+ * // returns "10.10.0.0"
+ * ipv4ToStr(168427520);
+ */
+export function ipv4ToStr(ipInt) {
+    const 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.
+ *
+ * @param {string} ipStr
+ * @returns {number[]}
+ *
+ * @example
+ * // returns [65280, 0, 0, 0, 0, 0, 4369, 8738]
+ * strToIpv6("ff00::1111:2222");
+ */
+export function strToIpv6(ipStr) {
+    let j = 0;
+    const blocks = ipStr.split(":"),
+        numBlocks = parseBlocks(blocks),
+        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 new OperationError("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 new OperationError("Block out of range.");
+        }
+        return numBlocks;
+    }
+}
+
+/**
+ * Converts an IPv6 address from numerical array format to string format.
+ *
+ * @param {number[]} ipv6
+ * @param {boolean} compact - Whether or not to return the address in shorthand or not
+ * @returns {string}
+ *
+ * @example
+ * // returns "ff00::1111:2222"
+ * ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], true);
+ *
+ * // returns "ff00:0000:0000:0000:0000:0000:1111:2222"
+ * ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], false);
+ */
+export function ipv6ToStr(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.
+ *
+ * @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);
+ */
+export function generateIpv4Range(ip, endIp) {
+    const range = [];
+    if (endIp >= ip) {
+        for (; ip <= endIp; ip++) {
+            range.push(ipv4ToStr(ip));
+        }
+    } else {
+        range[0] = "Second IP address smaller than first.";
+    }
+    return range;
+}
+
+/**
+ * Generates an IPv6 subnet mask given a CIDR value.
+ *
+ * @param {number} cidr
+ * @returns {number[]}
+ */
+export function genIpv6Mask(cidr) {
+    const mask = new Array(8);
+    let 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;
+}
+
+const _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.";
+
+/**
+ * A regular expression that matches an IPv4 address
+ */
+export const IPV4_REGEX = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/;
+
+/**
+ * A regular expression that matches an IPv6 address
+ */
+export const 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;
+
+/**
+ * Lookup table for Internet Protocols.
+ * Taken from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
+ */
+export const 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: ""}
+};

File diff suppressed because it is too large
+ 1268 - 0
src/core/lib/Magic.mjs


+ 117 - 0
src/core/lib/PGP.mjs

@@ -0,0 +1,117 @@
+/**
+ * PGP functions.
+ *
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [matt@artemisbot.uk]
+ * @author n1474335 [n1474335@gmail.com]
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ *
+ */
+
+import OperationError from "../errors/OperationError";
+import kbpgp from "kbpgp";
+import * as es6promisify from "es6-promisify";
+const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
+
+/**
+ * Progress callback
+ */
+export const ASP = 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);
+    }
+});
+
+/**
+ * Get size of subkey
+ *
+ * @param {number} keySize
+ * @returns {number}
+ */
+export function getSubkeySize(keySize) {
+    return {
+        1024: 1024,
+        2048: 1024,
+        4096: 2048,
+        256:   256,
+        384:   256,
+    }[keySize];
+}
+
+/**
+* Import private key and unlock if necessary
+*
+* @param {string} privateKey
+* @param {string} [passphrase]
+* @returns {Object}
+*/
+export async function 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 new OperationError("Did not provide passphrase with locked private key.");
+            }
+        }
+        return key;
+    } catch (err) {
+        throw new OperationError(`Could not import private key: ${err}`);
+    }
+}
+
+/**
+ * Import public key
+ *
+ * @param {string} publicKey
+ * @returns {Object}
+ */
+export async function 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 new OperationError(`Could not import public key: ${err}`);
+    }
+}

+ 72 - 0
src/core/lib/PublicKey.mjs

@@ -0,0 +1,72 @@
+/**
+ * Public key resources.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import { toHex, fromHex } from "./Hex";
+
+/**
+ * Formats Distinguished Name (DN) strings.
+ *
+ * @param {string} dnStr
+ * @param {number} indent
+ * @returns {string}
+ */
+export function formatDnStr (dnStr, indent) {
+    const fields = dnStr.substr(1).replace(/([^\\])\//g, "$1$1/").split(/[^\\]\//);
+    let output = "",
+        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.
+ *
+ * @param {string} byteStr
+ * @param {number} length - Line width
+ * @param {number} indent
+ * @returns {string}
+ */
+export function formatByteStr (byteStr, length, indent) {
+    byteStr = toHex(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);
+}

+ 103 - 0
src/core/lib/Rotate.mjs

@@ -0,0 +1,103 @@
+/**
+ * Bit rotation functions.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ *
+ * @todo Support for UTF16
+ */
+
+
+/**
+ * Runs rotation operations across the input data.
+ *
+ * @param {byteArray} data
+ * @param {number} amount
+ * @param {function} algo - The rotation operation to carry out
+ * @returns {byteArray}
+ */
+export function rot(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 bitwise op.
+ *
+ * @param {byte} b
+ * @returns {byte}
+ */
+export function rotr(b) {
+    const bit = (b & 1) << 7;
+    return (b >> 1) | bit;
+}
+
+/**
+ * Rotate left bitwise op.
+ *
+ * @param {byte} b
+ * @returns {byte}
+ */
+export function rotl(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.
+ *
+ * @param {byteArray} data
+ * @param {number} amount
+ * @returns {byteArray}
+ */
+export function rotrCarry(data, amount) {
+    const result = [];
+    let carryBits = 0,
+        newByte;
+
+    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.
+ *
+ * @param {byteArray} data
+ * @param {number} amount
+ * @returns {byteArray}
+ */
+export function rotlCarry(data, amount) {
+    const result = [];
+    let carryBits = 0,
+        newByte;
+
+    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;
+}

+ 19 - 0
src/core/lib/Zlib.mjs

@@ -0,0 +1,19 @@
+/**
+ * Zlib exports.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min";
+
+const Zlib = zlibAndGzip.Zlib;
+
+export const COMPRESSION_TYPE = ["Dynamic Huffman Coding", "Fixed Huffman Coding", "None (Store)"];
+export const INFLATE_BUFFER_TYPE = ["Adaptive", "Block"];
+export const ZLIB_COMPRESSION_TYPE_LOOKUP = {
+    "Fixed Huffman Coding":   Zlib.Deflate.CompressionType.FIXED,
+    "Dynamic Huffman Coding": Zlib.Deflate.CompressionType.DYNAMIC,
+    "None (Store)":           Zlib.Deflate.CompressionType.NONE,
+};

+ 0 - 186
src/core/lib/canvascomponents.js

@@ -1,186 +0,0 @@
-"use strict";
-
-/**
- * Various components for drawing diagrams on an HTML5 canvas.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @constant
- * @namespace
- */
-const CanvasComponents = {
-
-    drawLine: function(ctx, startX, startY, endX, endY) {
-        ctx.beginPath();
-        ctx.moveTo(startX, startY);
-        ctx.lineTo(endX, endY);
-        ctx.closePath();
-        ctx.stroke();
-    },
-
-    drawBarChart: function(canvas, scores, xAxisLabel, yAxisLabel, numXLabels, numYLabels, fontSize) {
-        fontSize = fontSize || 15;
-        if (!numXLabels || numXLabels > Math.round(canvas.width / 50)) {
-            numXLabels = Math.round(canvas.width / 50);
-        }
-        if (!numYLabels || numYLabels > Math.round(canvas.width / 50)) {
-            numYLabels = Math.round(canvas.height / 50);
-        }
-
-        // Graph properties
-        var ctx = canvas.getContext("2d"),
-            leftPadding = canvas.width * 0.08,
-            rightPadding = canvas.width * 0.03,
-            topPadding = canvas.height * 0.08,
-            bottomPadding = canvas.height * 0.15,
-            graphHeight = canvas.height - topPadding - bottomPadding,
-            graphWidth = canvas.width - leftPadding - rightPadding,
-            base = topPadding + graphHeight,
-            ceil = topPadding;
-
-        ctx.font = fontSize + "px Arial";
-
-        // Draw axis
-        ctx.lineWidth = "1.0";
-        ctx.strokeStyle = "#444";
-        CanvasComponents.drawLine(ctx, leftPadding, base, graphWidth + leftPadding, base); // x
-        CanvasComponents.drawLine(ctx, leftPadding, base, leftPadding, ceil); // y
-
-        // Bar properties
-        var barPadding = graphWidth * 0.003,
-            barWidth = (graphWidth - (barPadding * scores.length)) / scores.length,
-            currX = leftPadding + barPadding,
-            max = Math.max.apply(Math, scores);
-
-        // Draw bars
-        ctx.fillStyle = "green";
-        for (var i = 0; i < scores.length; i++) {
-            var h = scores[i] / max * graphHeight;
-            ctx.fillRect(currX, base - h, barWidth, h);
-            currX += barWidth + barPadding;
-        }
-
-        // Mark x axis
-        ctx.fillStyle = "black";
-        ctx.textAlign = "center";
-        currX = leftPadding + barPadding;
-        if (numXLabels >= scores.length) {
-            // Mark every score
-            for (i = 0; i <= scores.length; i++) {
-                ctx.fillText(i, currX, base + (bottomPadding * 0.3));
-                currX += barWidth + barPadding;
-            }
-        } else {
-            // Mark some scores
-            for (i = 0; i <= numXLabels; i++) {
-                var val = Math.ceil((scores.length / numXLabels) * i);
-                currX = (graphWidth / numXLabels) * i + leftPadding;
-                ctx.fillText(val, currX, base + (bottomPadding * 0.3));
-            }
-        }
-
-        // Mark y axis
-        ctx.textAlign = "right";
-        var currY;
-        if (numYLabels >= max) {
-            // Mark every increment
-            for (i = 0; i <= max; i++) {
-                currY = base - (i / max * graphHeight) + fontSize / 3;
-                ctx.fillText(i, leftPadding * 0.8, currY);
-            }
-        } else {
-            // Mark some increments
-            for (i = 0; i <= numYLabels; i++) {
-                val = Math.ceil((max / numYLabels) * i);
-                currY = base - (val / max * graphHeight) + fontSize / 3;
-                ctx.fillText(val, leftPadding * 0.8, currY);
-            }
-        }
-
-        // Label x axis
-        if (xAxisLabel) {
-            ctx.textAlign = "center";
-            ctx.fillText(xAxisLabel, graphWidth / 2 + leftPadding, base + bottomPadding * 0.8);
-        }
-
-        // Label y axis
-        if (yAxisLabel) {
-            ctx.save();
-            var x = leftPadding * 0.3,
-                y = graphHeight / 2 + topPadding;
-            ctx.translate(x, y);
-            ctx.rotate(-Math.PI / 2);
-            ctx.textAlign = "center";
-            ctx.fillText(yAxisLabel, 0, 0);
-            ctx.restore();
-        }
-    },
-
-    drawScaleBar: function(canvas, score, max, markings) {
-        // Bar properties
-        var ctx = canvas.getContext("2d"),
-            leftPadding = canvas.width * 0.01,
-            rightPadding = canvas.width * 0.01,
-            topPadding = canvas.height * 0.1,
-            bottomPadding = canvas.height * 0.3,
-            barHeight = canvas.height - topPadding - bottomPadding,
-            barWidth = canvas.width - leftPadding - rightPadding;
-
-        // Scale properties
-        var proportion = score / max;
-
-        // Draw bar outline
-        ctx.strokeRect(leftPadding, topPadding, barWidth, barHeight);
-
-        // Shade in up to proportion
-        var grad = ctx.createLinearGradient(leftPadding, 0, barWidth + leftPadding, 0);
-        grad.addColorStop(0, "green");
-        grad.addColorStop(0.5, "gold");
-        grad.addColorStop(1, "red");
-        ctx.fillStyle = grad;
-        ctx.fillRect(leftPadding, topPadding, barWidth * proportion, barHeight);
-
-        // Add markings
-        var x0, y0, x1, y1;
-        ctx.fillStyle = "black";
-        ctx.textAlign = "center";
-        ctx.font = "13px Arial";
-        for (var i = 0; i < markings.length; i++) {
-            // Draw min line down
-            x0 = barWidth / max * markings[i].min + leftPadding;
-            y0 = topPadding + barHeight + (bottomPadding * 0.1);
-            x1 = x0;
-            y1 = topPadding + barHeight + (bottomPadding * 0.3);
-            CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
-
-            // Draw max line down
-            x0 = barWidth / max * markings[i].max + leftPadding;
-            x1 = x0;
-            CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
-
-            // Join min and max lines
-            x0 = barWidth / max * markings[i].min + leftPadding;
-            y0 = topPadding + barHeight + (bottomPadding * 0.3);
-            x1 = barWidth / max * markings[i].max + leftPadding;
-            y1 = y0;
-            CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
-
-            // Add label
-            if (markings[i].max >= max * 0.9) {
-                ctx.textAlign = "right";
-                x0 = x1;
-            } else if (markings[i].max <= max * 0.1) {
-                ctx.textAlign = "left";
-            } else {
-                x0 = x0 + (x1 - x0) / 2;
-            }
-            y0 = topPadding + barHeight + (bottomPadding * 0.8);
-            ctx.fillText(markings[i].label, x0, y0);
-        }
-    },
-
-};
-
-export default CanvasComponents;

+ 76 - 0
src/core/operations/ADD.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, add } from "../lib/BitwiseOp";
+
+/**
+ * ADD operation
+ */
+class ADD extends Operation {
+
+    /**
+     * ADD constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "ADD";
+        this.module = "Default";
+        this.description = "ADD the input with the given key (e.g. <code>fe023da5</code>), 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, add);
+    }
+
+    /**
+     * Highlight ADD
+     *
+     * @param {Object[]} pos
+     * @param {number} pos[].start
+     * @param {number} pos[].end
+     * @param {Object[]} args
+     * @returns {Object[]} pos
+     */
+    highlight(pos, args) {
+        return pos;
+    }
+
+    /**
+     * Highlight ADD 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 ADD;

+ 108 - 0
src/core/operations/AESDecrypt.mjs

@@ -0,0 +1,108 @@
+/**
+ * @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 OperationError from "../errors/OperationError";
+
+/**
+ * AES Decrypt operation
+ */
+class AESDecrypt extends Operation {
+
+    /**
+     * AESDecrypt constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "AES Decrypt";
+        this.module = "Ciphers";
+        this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is 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", "GCM", "ECB"]
+            },
+            {
+                "name": "Input",
+                "type": "option",
+                "value": ["Hex", "Raw"]
+            },
+            {
+                "name": "Output",
+                "type": "option",
+                "value": ["Raw", "Hex"]
+            },
+            {
+                "name": "GCM Tag",
+                "type": "toggleString",
+                "value": "",
+                "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     *
+     * @throws {OperationError} if cannot decrypt input or invalid key length
+     */
+    run(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) {
+            throw new OperationError(`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 {
+            throw new OperationError("Unable to decrypt input with these parameters.");
+        }
+    }
+
+}
+
+export default AESDecrypt;

+ 106 - 0
src/core/operations/AESEncrypt.mjs

@@ -0,0 +1,106 @@
+/**
+ * @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 OperationError from "../errors/OperationError";
+
+/**
+ * AES Encrypt operation
+ */
+class AESEncrypt extends Operation {
+
+    /**
+     * AESEncrypt constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "AES Encrypt";
+        this.module = "Ciphers";
+        this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> 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", "GCM", "ECB"]
+            },
+            {
+                "name": "Input",
+                "type": "option",
+                "value": ["Raw", "Hex"]
+            },
+            {
+                "name": "Output",
+                "type": "option",
+                "value": ["Hex", "Raw"]
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     *
+     * @throws {OperationError} if invalid key length
+     */
+    run(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) {
+            throw new OperationError(`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();
+        }
+    }
+
+}
+
+export default AESEncrypt;

+ 76 - 0
src/core/operations/AND.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, and } from "../lib/BitwiseOp";
+
+/**
+ * AND operation
+ */
+class AND extends Operation {
+
+    /**
+     * AND constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "AND";
+        this.module = "Default";
+        this.description = "AND the input with the given key.<br>e.g. <code>fe023da5</code>";
+        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, and);
+    }
+
+    /**
+     * Highlight AND
+     *
+     * @param {Object[]} pos
+     * @param {number} pos[].start
+     * @param {number} pos[].end
+     * @param {Object[]} args
+     * @returns {Object[]} pos
+     */
+    highlight(pos, args) {
+        return pos;
+    }
+
+    /**
+     * Highlight AND 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 AND;

+ 46 - 0
src/core/operations/AddLineNumbers.mjs

@@ -0,0 +1,46 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * Add line numbers operation
+ */
+class AddLineNumbers extends Operation {
+
+    /**
+     * AddLineNumbers constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Add line numbers";
+        this.module = "Default";
+        this.description = "Adds line numbers to the output.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const lines = input.split("\n"),
+            width = lines.length.toString().length;
+        let output = "";
+
+        for (let n = 0; n < lines.length; n++) {
+            output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
+        }
+        return output.slice(0, output.length-1);
+    }
+
+}
+
+export default AddLineNumbers;

+ 52 - 0
src/core/operations/Adler32Checksum.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";
+
+/**
+ * Adler-32 Checksum operation
+ */
+class Adler32Checksum extends Operation {
+
+    /**
+     * Adler32Checksum constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Adler-32 Checksum";
+        this.module = "Hashing";
+        this.description = "Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995, and is a modification of the Fletcher checksum. Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter).<br><br>Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32.";
+        this.inputType = "byteArray";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {byteArray} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const MOD_ADLER = 65521;
+        let 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);
+    }
+
+}
+
+export default Adler32Checksum;

+ 105 - 0
src/core/operations/AffineCipherDecode.mjs

@@ -0,0 +1,105 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Affine Cipher Decode operation
+ */
+class AffineCipherDecode extends Operation {
+
+    /**
+     * AffineCipherDecode constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Affine Cipher Decode";
+        this.module = "Ciphers";
+        this.description = "The Affine cipher is a type of monoalphabetic substitution cipher. To decrypt, each letter in an alphabet is mapped to its numeric equivalent, decrypted by a mathematical function, and converted back to a letter.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "a",
+                "type": "number",
+                "value": 1
+            },
+            {
+                "name": "b",
+                "type": "number",
+                "value": 0
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     *
+     * @throws {OperationError} if a or b values are invalid
+     */
+    run(input, args) {
+        const alphabet = "abcdefghijklmnopqrstuvwxyz",
+            [a, b] = args,
+            aModInv = Utils.modInv(a, 26); // Calculates modular inverse of a
+        let output = "";
+
+        if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
+            throw new OperationError("The values of a and b can only be integers.");
+        }
+
+        if (Utils.gcd(a, 26) !== 1) {
+            throw new OperationError("The value of `a` must be coprime to 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;
+    }
+
+    /**
+     * Highlight Affine 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 Affine 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 AffineCipherDecode;

+ 77 - 0
src/core/operations/AffineCipherEncode.mjs

@@ -0,0 +1,77 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { affineEncode } from "../lib/Ciphers";
+
+/**
+ * Affine Cipher Encode operation
+ */
+class AffineCipherEncode extends Operation {
+
+    /**
+     * AffineCipherEncode constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Affine Cipher Encode";
+        this.module = "Ciphers";
+        this.description = "The Affine cipher is a type of monoalphabetic substitution cipher, wherein each letter in an alphabet is mapped to its numeric equivalent, encrypted using simple mathematical function, <code>(ax + b) % 26</code>, and converted back to a letter.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "a",
+                "type": "number",
+                "value": 1
+            },
+            {
+                "name": "b",
+                "type": "number",
+                "value": 0
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        return affineEncode(input, args);
+    }
+
+    /**
+     * Highlight Affine 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 Affine 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 AffineCipherEncode;

+ 183 - 0
src/core/operations/AnalyseHash.mjs

@@ -0,0 +1,183 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Analyse hash operation
+ */
+class AnalyseHash extends Operation {
+
+    /**
+     * AnalyseHash constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Analyse hash";
+        this.module = "Hashing";
+        this.description = "Tries to determine information about a given hash and suggests which algorithm may have been used to generate it based on its length.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        input = input.replace(/\s/g, "");
+
+        let output = "",
+            possibleHashFunctions = [];
+        const byteLength = input.length / 2,
+            bitLength = byteLength * 8;
+
+        if (!/^[a-f0-9]+$/i.test(input)) {
+            throw new OperationError("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 AnalyseHash;

+ 0 - 253
src/core/operations/Arithmetic.js

@@ -1,253 +0,0 @@
-import Utils from "../Utils.js";
-import BigNumber from "bignumber.js";
-
-
-/**
- * Math operations on numbers.
- *
- * @author bwhitn [brian.m.whitney@outlook.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @namespace
- */
-const Arithmetic = {
-
-    /**
-     * @constant
-     * @default
-     */
-    DELIM_OPTIONS: ["Line feed", "Space", "Comma", "Semi-colon", "Colon", "CRLF"],
-
-
-    /**
-     * Splits a string based on a delimiter and calculates the sum of numbers.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runSum: function(input, args) {
-        const val = Arithmetic._sum(Arithmetic._createNumArray(input, args[0]));
-        return val instanceof BigNumber ? val : new BigNumber(NaN);
-    },
-
-
-    /**
-     * Splits a string based on a delimiter and subtracts all the numbers.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runSub: function(input, args) {
-        let val = Arithmetic._sub(Arithmetic._createNumArray(input, args[0]));
-        return val instanceof BigNumber ? val : new BigNumber(NaN);
-    },
-
-
-    /**
-     * Splits a string based on a delimiter and multiplies the numbers.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runMulti: function(input, args) {
-        let val = Arithmetic._multi(Arithmetic._createNumArray(input, args[0]));
-        return val instanceof BigNumber ? val : new BigNumber(NaN);
-    },
-
-
-    /**
-     * Splits a string based on a delimiter and divides the numbers.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runDiv: function(input, args) {
-        let val = Arithmetic._div(Arithmetic._createNumArray(input, args[0]));
-        return val instanceof BigNumber ? val : new BigNumber(NaN);
-    },
-
-
-    /**
-     * Splits a string based on a delimiter and computes the mean (average).
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runMean: function(input, args) {
-        let val = Arithmetic._mean(Arithmetic._createNumArray(input, args[0]));
-        return val instanceof BigNumber ? val : new BigNumber(NaN);
-    },
-
-
-    /**
-     * Splits a string based on a delimiter and finds the median.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runMedian: function(input, args) {
-        let val = Arithmetic._median(Arithmetic._createNumArray(input, args[0]));
-        return val instanceof BigNumber ? val : new BigNumber(NaN);
-    },
-
-
-    /**
-     * splits a string based on a delimiter and computes the standard deviation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runStdDev: function(input, args) {
-        let val = Arithmetic._stdDev(Arithmetic._createNumArray(input, args[0]));
-        return val instanceof BigNumber ? val : new BigNumber(NaN);
-    },
-
-
-    /**
-     * Converts a string array to a number array.
-     *
-     * @private
-     * @param {string[]} input
-     * @param {string} delim
-     * @returns {BigNumber[]}
-     */
-    _createNumArray: function(input, delim) {
-        delim = Utils.charRep[delim || "Space"];
-        let splitNumbers = input.split(delim),
-            numbers = [],
-            num;
-
-        for (let i = 0; i < splitNumbers.length; i++) {
-            try {
-                num = BigNumber(splitNumbers[i].trim());
-                if (!num.isNaN()) {
-                    numbers.push(num);
-                }
-            } catch (err) {
-                // This line is not a valid number
-            }
-        }
-        return numbers;
-    },
-
-
-    /**
-     * Adds an array of numbers and returns the value.
-     *
-     * @private
-     * @param {BigNumber[]} data
-     * @returns {BigNumber}
-     */
-    _sum: function(data) {
-        if (data.length > 0) {
-            return data.reduce((acc, curr) => acc.plus(curr));
-        }
-    },
-
-
-    /**
-     * Subtracts an array of numbers and returns the value.
-     *
-     * @private
-     * @param {BigNumber[]} data
-     * @returns {BigNumber}
-     */
-    _sub: function(data) {
-        if (data.length > 0) {
-            return data.reduce((acc, curr) => acc.minus(curr));
-        }
-    },
-
-
-    /**
-     * Multiplies an array of numbers and returns the value.
-     *
-     * @private
-     * @param {BigNumber[]} data
-     * @returns {BigNumber}
-     */
-    _multi: function(data) {
-        if (data.length > 0) {
-            return data.reduce((acc, curr) => acc.times(curr));
-        }
-    },
-
-
-    /**
-     * Divides an array of numbers and returns the value.
-     *
-     * @private
-     * @param {BigNumber[]} data
-     * @returns {BigNumber}
-     */
-    _div: function(data) {
-        if (data.length > 0) {
-            return data.reduce((acc, curr) => acc.div(curr));
-        }
-    },
-
-
-    /**
-     * Computes mean of a number array and returns the value.
-     *
-     * @private
-     * @param {BigNumber[]} data
-     * @returns {BigNumber}
-     */
-    _mean: function(data) {
-        if (data.length > 0) {
-            return Arithmetic._sum(data).div(data.length);
-        }
-    },
-
-
-    /**
-     * Computes median of a number array and returns the value.
-     *
-     * @private
-     * @param {BigNumber[]} data
-     * @returns {BigNumber}
-     */
-    _median: function (data) {
-        if ((data.length % 2) === 0 && data.length > 0) {
-            let first, second;
-            data.sort(function(a, b){
-                return a.minus(b);
-            });
-            first = data[Math.floor(data.length / 2)];
-            second = data[Math.floor(data.length / 2) - 1];
-            return Arithmetic._mean([first, second]);
-        } else {
-            return data[Math.floor(data.length / 2)];
-        }
-    },
-
-
-    /**
-     * Computes standard deviation of a number array and returns the value.
-     *
-     * @private
-     * @param {BigNumber[]} data
-     * @returns {BigNumber}
-     */
-    _stdDev: function (data) {
-        if (data.length > 0) {
-            let avg = Arithmetic._mean(data);
-            let devSum = new BigNumber(0);
-            for (let i = 0; i < data.length; i++) {
-                devSum = devSum.plus(data[i].minus(avg).pow(2));
-            }
-            return devSum.div(data.length).sqrt();
-        }
-    },
-};
-
-export default Arithmetic;

+ 66 - 0
src/core/operations/AtbashCipher.mjs

@@ -0,0 +1,66 @@
+/**
+ * @author Matt C [matt@artemisbot.uk]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { affineEncode } from "../lib/Ciphers";
+
+/**
+ * Atbash Cipher operation
+ */
+class AtbashCipher extends Operation {
+
+    /**
+     * AtbashCipher constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Atbash Cipher";
+        this.module = "Ciphers";
+        this.description = "Atbash is a mono-alphabetic substitution cipher originally used to encode the Hebrew alphabet. It has been modified here for use with the Latin alphabet.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        return affineEncode(input, [25, 25]);
+    }
+
+    /**
+     * Highlight Atbash Cipher
+     *
+     * @param {Object[]} pos
+     * @param {number} pos[].start
+     * @param {number} pos[].end
+     * @param {Object[]} args
+     * @returns {Object[]} pos
+     */
+    highlight(pos, args) {
+        return pos;
+    }
+
+    /**
+     * Highlight Atbash Cipher 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 AtbashCipher;

+ 0 - 215
src/core/operations/BCD.js

@@ -1,215 +0,0 @@
-import Utils from "../Utils.js";
-import BigNumber from "bignumber.js";
-
-
-/**
- * Binary-Coded Decimal operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- *
- * @namespace
- */
-const BCD = {
-
-    /**
-     * @constant
-     * @default
-     */
-    ENCODING_SCHEME: [
-        "8 4 2 1",
-        "7 4 2 1",
-        "4 2 2 1",
-        "2 4 2 1",
-        "8 4 -2 -1",
-        "Excess-3",
-        "IBM 8 4 2 1",
-    ],
-
-    /**
-     * Lookup table for the binary value of each digit representation.
-     *
-     * I wrote a very nice algorithm to generate 8 4 2 1 encoding programatically,
-     * but unfortunately it's much easier (if less elegant) to use lookup tables
-     * when supporting multiple encoding schemes.
-     *
-     * "Practicality beats purity" - PEP 20
-     *
-     * In some schemes it is possible to represent the same value in multiple ways.
-     * For instance, in 4 2 2 1 encoding, 0100 and 0010 both represent 2. Support
-     * has not yet been added for this.
-     *
-     * @constant
-     */
-    ENCODING_LOOKUP: {
-        "8 4 2 1":     [0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
-        "7 4 2 1":     [0,  1,  2,  3,  4,  5,  6,  8,  9,  10],
-        "4 2 2 1":     [0,  1,  4,  5,  8,  9,  12, 13, 14, 15],
-        "2 4 2 1":     [0,  1,  2,  3,  4,  11, 12, 13, 14, 15],
-        "8 4 -2 -1":   [0,  7,  6,  5,  4,  11, 10, 9,  8,  15],
-        "Excess-3":    [3,  4,  5,  6,  7,  8,  9,  10, 11, 12],
-        "IBM 8 4 2 1": [10, 1,  2,  3,  4,  5,  6,  7,  8,  9],
-    },
-
-    /**
-     * @default
-     * @constant
-     */
-    FORMAT: ["Nibbles", "Bytes", "Raw"],
-
-
-    /**
-     * To BCD operation.
-     *
-     * @param {BigNumber} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runToBCD: function(input, args) {
-        if (input.isNaN())
-            return "Invalid input";
-        if (!input.integerValue(BigNumber.ROUND_DOWN).isEqualTo(input))
-            return "Fractional values are not supported by BCD";
-
-        const encoding = BCD.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);
-        }
-    },
-
-
-    /**
-     * From BCD operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runFromBCD: function(input, args) {
-        const encoding = BCD.ENCODING_LOOKUP[args[0]],
-            packed = args[1],
-            signed = args[2],
-            inputFormat = args[3];
-
-        let nibbles = [],
-            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 "Invalid input";
-            let val = encoding.indexOf(n);
-            if (val < 0) throw `Value ${Utils.bin(n, 4)} not in encoding scheme`;
-            output += val.toString();
-        });
-
-        return new BigNumber(output);
-    },
-
-};
-
-export default BCD;

+ 0 - 59
src/core/operations/BSON.js

@@ -1,59 +0,0 @@
-import bsonjs from "bson";
-import {Buffer} from "buffer";
-
-
-/**
- * BSON operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2018
- * @license Apache-2.0
- *
- * @namespace
- */
-const BSON = {
-
-    /**
-     * BSON serialise operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {ArrayBuffer}
-     */
-    runBSONSerialise(input, args) {
-        if (!input) return new ArrayBuffer();
-
-        const bson = new bsonjs();
-
-        try {
-            const data = JSON.parse(input);
-            return bson.serialize(data).buffer;
-        } catch (err) {
-            throw err.toString();
-        }
-    },
-
-
-    /**
-     * BSON deserialise operation.
-     *
-     * @param {ArrayBuffer} input
-     * @param {Object[]} args
-     * @returns {string}
-     *
-     */
-    runBSONDeserialise(input, args) {
-        if (!input.byteLength) return "";
-
-        const bson = new bsonjs();
-
-        try {
-            const data = bson.deserialize(new Buffer(input));
-            return JSON.stringify(data, null, 2);
-        } catch (err) {
-            return err.toString();
-        }
-    },
-};
-
-export default BSON;

+ 50 - 0
src/core/operations/BSONDeserialise.mjs

@@ -0,0 +1,50 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import bsonjs from "bson";
+import OperationError from "../errors/OperationError";
+
+/**
+ * BSON deserialise operation
+ */
+class BSONDeserialise extends Operation {
+
+    /**
+     * BSONDeserialise constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "BSON deserialise";
+        this.module = "BSON";
+        this.description = "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.<br><br>Input data should be in a raw bytes format.";
+        this.inputType = "ArrayBuffer";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {ArrayBuffer} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        if (!input.byteLength) return "";
+
+        const bson = new bsonjs();
+
+        try {
+            const data = bson.deserialize(new Buffer(input));
+            return JSON.stringify(data, null, 2);
+        } catch (err) {
+            throw new OperationError(err.toString());
+        }
+    }
+
+}
+
+export default BSONDeserialise;

+ 50 - 0
src/core/operations/BSONSerialise.mjs

@@ -0,0 +1,50 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import bsonjs from "bson";
+import OperationError from "../errors/OperationError";
+
+/**
+ * BSON serialise operation
+ */
+class BSONSerialise extends Operation {
+
+    /**
+     * BSONSerialise constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "BSON serialise";
+        this.module = "BSON";
+        this.description = "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.<br><br>Input data should be valid JSON.";
+        this.inputType = "string";
+        this.outputType = "ArrayBuffer";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {ArrayBuffer}
+     */
+    run(input, args) {
+        if (!input) return new ArrayBuffer();
+
+        const bson = new bsonjs();
+
+        try {
+            const data = JSON.parse(input);
+            return bson.serialize(data).buffer;
+        } catch (err) {
+            throw new OperationError(err.toString());
+        }
+    }
+
+}
+
+export default BSONSerialise;

+ 0 - 68
src/core/operations/Base.js

@@ -1,68 +0,0 @@
-import BigNumber from "bignumber.js";
-
-/**
- * Numerical base operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @namespace
- */
-const Base = {
-
-    /**
-     * @constant
-     * @default
-     */
-    DEFAULT_RADIX: 36,
-
-    /**
-     * To Base operation.
-     *
-     * @param {BigNumber} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runTo: function(input, args) {
-        if (!input) {
-            throw ("Error: Input must be a number");
-        }
-        const radix = args[0] || Base.DEFAULT_RADIX;
-        if (radix < 2 || radix > 36) {
-            throw "Error: Radix argument must be between 2 and 36";
-        }
-        return input.toString(radix);
-    },
-
-
-    /**
-     * From Base operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {BigNumber}
-     */
-    runFrom: function(input, args) {
-        const radix = args[0] || Base.DEFAULT_RADIX;
-        if (radix < 2 || radix > 36) {
-            throw "Error: Radix argument must be between 2 and 36";
-        }
-
-        let number = input.replace(/\s/g, "").split("."),
-            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 Base;

+ 0 - 137
src/core/operations/Base58.js

@@ -1,137 +0,0 @@
-import Utils from "../Utils.js";
-
-
-/**
- * Base58 operations.
- *
- * @author tlwr [toby@toby.codes]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- *
- * @namespace
- */
-const Base58 = {
-
-    /**
-     * @constant
-     * @default
-     */
-    ALPHABET_OPTIONS: [
-        {
-            name: "Bitcoin",
-            value: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
-        },
-        {
-            name: "Ripple",
-            value: "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
-        },
-    ],
-    /**
-     * @constant
-     * @default
-     */
-    REMOVE_NON_ALPH_CHARS: true,
-
-    /**
-     * To Base58 operation.
-     *
-     * @param {byteArray} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runTo: function(input, args) {
-        let alphabet = args[0] || Base58.ALPHABET_OPTIONS[0].value,
-            result = [0];
-
-        alphabet = Utils.expandAlphRange(alphabet).join("");
-
-        if (alphabet.length !== 58 ||
-            [].unique.call(alphabet).length !== 58) {
-            throw ("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;
-    },
-
-
-    /**
-     * From Base58 operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {byteArray}
-     */
-    runFrom: function(input, args) {
-        let alphabet = args[0] || Base58.ALPHABET_OPTIONS[0].value,
-            removeNonAlphaChars = args[1] === undefined ? true : args[1],
-            result = [0];
-
-        alphabet = Utils.expandAlphRange(alphabet).join("");
-
-        if (alphabet.length !== 58 ||
-            [].unique.call(alphabet).length !== 58) {
-            throw ("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 ("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 Base58;

+ 0 - 347
src/core/operations/Base64.js

@@ -1,347 +0,0 @@
-import Utils from "../Utils.js";
-
-
-/**
- * Base64 operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @namespace
- */
-const Base64 = {
-
-    /**
-     * @constant
-     * @default
-     */
-    ALPHABET: "A-Za-z0-9+/=",
-    /**
-     * @constant
-     * @default
-     */
-    ALPHABET_OPTIONS: [
-        {name: "Standard: A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
-        {name: "URL safe: A-Za-z0-9-_", value: "A-Za-z0-9-_"},
-        {name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
-        {name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
-        {name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
-        {name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
-        {name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
-        {name: "Radix-64: 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
-        {name: "Uuencoding: [space]-_", value: " -_"},
-        {name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
-        {name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
-        {name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
-        {name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
-    ],
-
-    /**
-     * To Base64 operation.
-     *
-     * @param {ArrayBuffer} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runTo: function(input, args) {
-        const alphabet = args[0] || Base64.ALPHABET;
-        return Utils.toBase64(new Uint8Array(input), alphabet);
-    },
-
-
-    /**
-     * @constant
-     * @default
-     */
-    REMOVE_NON_ALPH_CHARS: true,
-
-    /**
-     * From Base64 operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {byteArray}
-     */
-    runFrom: function(input, args) {
-        let alphabet = args[0] || Base64.ALPHABET,
-            removeNonAlphChars = args[1];
-
-        return Utils.fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
-    },
-
-
-    /**
-     * @constant
-     * @default
-     */
-    BASE32_ALPHABET: "A-Z2-7=",
-
-    /**
-     * To Base32 operation.
-     *
-     * @param {byteArray} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runTo32: function(input, args) {
-        if (!input) return "";
-
-        let alphabet = args[0] ?
-                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
-            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;
-    },
-
-
-    /**
-     * From Base32 operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {byteArray}
-     */
-    runFrom32: function(input, args) {
-        if (!input) return [];
-
-        let alphabet = args[0] ?
-                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
-            removeNonAlphChars = args[0];
-
-        let output = [],
-            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;
-    },
-
-
-    /**
-     * @constant
-     * @default
-     */
-    SHOW_IN_BINARY: false,
-    /**
-     * @constant
-     * @default
-     */
-    OFFSETS_SHOW_VARIABLE: true,
-
-    /**
-     * Show Base64 offsets operation.
-     *
-     * @param {byteArray} input
-     * @param {Object[]} args
-     * @returns {html}
-     */
-    runOffsets: function(input, args) {
-        let alphabet = args[0] || Base64.ALPHABET,
-            showVariable = args[1],
-            offset0 = Utils.toBase64(input, alphabet),
-            offset1 = Utils.toBase64([0].concat(input), alphabet),
-            offset2 = Utils.toBase64([0, 0].concat(input), alphabet),
-            len0 = offset0.indexOf("="),
-            len1 = offset1.indexOf("="),
-            len2 = offset2.indexOf("="),
-            script = "<script type='application/javascript'>$('[data-toggle=\"tooltip\"]').tooltip()</script>",
-            staticSection = "",
-            padding = "";
-
-        if (input.length < 1) {
-            return "Please enter a string.";
-        }
-
-        // Highlight offset 0
-        if (len0 % 4 === 2) {
-            staticSection = offset0.slice(0, -3);
-            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet).slice(0, -2)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset0.substr(offset0.length - 3, 1) + "</span>" +
-                "<span class='hl3'>" + offset0.substr(offset0.length - 2) + "</span>";
-        } else if (len0 % 4 === 3) {
-            staticSection = offset0.slice(0, -2);
-            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet).slice(0, -1)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset0.substr(offset0.length - 2, 1) + "</span>" +
-                "<span class='hl3'>" + offset0.substr(offset0.length - 1) + "</span>";
-        } else {
-            staticSection = offset0;
-            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet)) + "'>" +
-                staticSection + "</span>";
-        }
-
-        if (!showVariable) {
-            offset0 = staticSection;
-        }
-
-
-        // Highlight offset 1
-        padding = "<span class='hl3'>" + offset1.substr(0, 1) + "</span>" +
-            "<span class='hl5'>" + offset1.substr(1, 1) + "</span>";
-        offset1 = offset1.substr(2);
-        if (len1 % 4 === 2) {
-            staticSection = offset1.slice(0, -3);
-            offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1, -2)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset1.substr(offset1.length - 3, 1) + "</span>" +
-                "<span class='hl3'>" + offset1.substr(offset1.length - 2) + "</span>";
-        } else if (len1 % 4 === 3) {
-            staticSection = offset1.slice(0, -2);
-            offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1, -1)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset1.substr(offset1.length - 2, 1) + "</span>" +
-                "<span class='hl3'>" + offset1.substr(offset1.length - 1) + "</span>";
-        } else {
-            staticSection = offset1;
-            offset1 = padding +  "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1)) + "'>" +
-                staticSection + "</span>";
-        }
-
-        if (!showVariable) {
-            offset1 = staticSection;
-        }
-
-        // Highlight offset 2
-        padding = "<span class='hl3'>" + offset2.substr(0, 2) + "</span>" +
-            "<span class='hl5'>" + offset2.substr(2, 1) + "</span>";
-        offset2 = offset2.substr(3);
-        if (len2 % 4 === 2) {
-            staticSection = offset2.slice(0, -3);
-            offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset2.substr(offset2.length - 3, 1) + "</span>" +
-                "<span class='hl3'>" + offset2.substr(offset2.length - 2) + "</span>";
-        } else if (len2 % 4 === 3) {
-            staticSection = offset2.slice(0, -2);
-            offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset2.substr(offset2.length - 2, 1) + "</span>" +
-                "<span class='hl3'>" + offset2.substr(offset2.length - 1) + "</span>";
-        } else {
-            staticSection = offset2;
-            offset2 = padding +  "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2)) + "'>" +
-                staticSection + "</span>";
-        }
-
-        if (!showVariable) {
-            offset2 = staticSection;
-        }
-
-        return (showVariable ? "Characters highlighted in <span class='hl5'>green</span> could change if the input is surrounded by more data." +
-            "\nCharacters highlighted in <span class='hl3'>red</span> are for padding purposes only." +
-            "\nUnhighlighted characters are <span data-toggle='tooltip' data-placement='top' title='Tooltip on left'>static</span>." +
-            "\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;

+ 54 - 0
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.<br><br>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;

+ 55 - 0
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;

+ 48 - 0
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;

+ 124 - 0
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;

+ 129 - 0
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;

+ 75 - 0
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;

+ 82 - 0
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.<br><br><i>Logical shifts</i> replace the leftmost bits with zeros.<br><i>Arithmetic shifts</i> 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;

+ 0 - 368
src/core/operations/BitwiseOp.js

@@ -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;

+ 100 - 0
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.<br><br><b>IV:</b> 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;

+ 101 - 0
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.<br><br><b>IV:</b> 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;

+ 0 - 432
src/core/operations/ByteRepr.js

@@ -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;

+ 55 - 0
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;

Some files were not shown because too many files changed in this diff