Ver código fonte

Merge branch 'master' of github.com:gchq/CyberChef into node-lib

d98762625 6 anos atrás
pai
commit
e4ee0fc397
52 arquivos alterados com 4503 adições e 319 exclusões
  1. 1 0
      .eslintignore
  2. 12 0
      CHANGELOG.md
  3. 3 2
      Gruntfile.js
  4. 12 4
      babel.config.js
  5. 653 137
      package-lock.json
  6. 41 32
      package.json
  7. 0 1
      src/core/ChefWorker.js
  8. 40 39
      src/core/Dish.mjs
  9. 66 9
      src/core/Utils.mjs
  10. 8 1
      src/core/config/Categories.json
  11. 7 8
      src/core/dishTranslationTypes/DishBigNumber.mjs
  12. 10 11
      src/core/dishTranslationTypes/DishByteArray.mjs
  13. 7 8
      src/core/dishTranslationTypes/DishFile.mjs
  14. 7 8
      src/core/dishTranslationTypes/DishHTML.mjs
  15. 7 8
      src/core/dishTranslationTypes/DishJSON.mjs
  16. 27 9
      src/core/dishTranslationTypes/DishListFile.mjs
  17. 7 7
      src/core/dishTranslationTypes/DishNumber.mjs
  18. 7 7
      src/core/dishTranslationTypes/DishString.mjs
  19. 5 5
      src/core/dishTranslationTypes/DishTranslationType.mjs
  20. 2 2
      src/core/dishTranslationTypes/index.mjs
  21. 178 0
      src/core/lib/Charts.mjs
  22. 1 1
      src/core/lib/Hex.mjs
  23. 79 0
      src/core/operations/BLAKE2b.mjs
  24. 80 0
      src/core/operations/BLAKE2s.mjs
  25. 1 1
      src/core/operations/ExtractFiles.mjs
  26. 10 0
      src/core/operations/GenerateAllHashes.mjs
  27. 41 0
      src/core/operations/HTMLToText.mjs
  28. 266 0
      src/core/operations/HeatmapChart.mjs
  29. 296 0
      src/core/operations/HexDensityChart.mjs
  30. 1 1
      src/core/operations/JavaScriptParser.mjs
  31. 1 1
      src/core/operations/PEMToHex.mjs
  32. 199 0
      src/core/operations/ScatterChart.mjs
  33. 227 0
      src/core/operations/SeriesChart.mjs
  34. 10 0
      src/node/File.mjs
  35. 1607 0
      src/node/index.mjs
  36. 293 0
      src/web/SeasonalWaiter.mjs
  37. 1 1
      src/web/html/index.html
  38. 0 1
      src/web/index.js
  39. 0 0
      src/web/static/clippy_assets/agents/Clippy/agent.js
  40. BIN
      src/web/static/clippy_assets/agents/Clippy/map.png
  41. 62 0
      src/web/static/clippy_assets/clippy.css
  42. BIN
      src/web/static/clippy_assets/images/border.png
  43. BIN
      src/web/static/clippy_assets/images/tip.png
  44. 1 0
      src/web/stylesheets/index.js
  45. 0 1
      tests/lib/TestRegister.mjs
  46. 50 1
      tests/node/tests/File.mjs
  47. 10 10
      tests/node/tests/nodeApi.mjs
  48. 3 1
      tests/operations/index.mjs
  49. 56 0
      tests/operations/tests/BLAKE2b.mjs
  50. 47 0
      tests/operations/tests/BLAKE2s.mjs
  51. 55 0
      tests/operations/tests/Charts.mjs
  52. 6 2
      webpack.config.js

+ 1 - 0
.eslintignore

@@ -1 +1,2 @@
 src/core/vendor/**
 src/core/vendor/**
+src/web/static/clippy_assets/**

+ 12 - 0
CHANGELOG.md

@@ -2,6 +2,12 @@
 All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
 All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
 
 
 
 
+### [8.29.0] - 2019-03-31
+- 'BLAKE2s' and 'BLAKE2b' hashing operations added [@h345983745] | [#525]
+
+### [8.28.0] - 2019-03-31
+- 'Heatmap Chart', 'Hex Density Chart', 'Scatter Chart' and 'Series Chart' operation added [@artemisbot] [@tlwr] | [#496] [#143]
+
 ### [8.27.0] - 2019-03-14
 ### [8.27.0] - 2019-03-14
 - 'Enigma', 'Typex', 'Bombe' and 'Multiple Bombe' operations added [@s2224834] | [#516]
 - 'Enigma', 'Typex', 'Bombe' and 'Multiple Bombe' operations added [@s2224834] | [#516]
 - See [this wiki article](https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex) for a full explanation of these operations.
 - See [this wiki article](https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex) for a full explanation of these operations.
@@ -118,6 +124,8 @@ All major and minor version changes will be documented in this file. Details of
 
 
 
 
 
 
+[8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0
+[8.28.0]: https://github.com/gchq/CyberChef/releases/tag/v8.28.0
 [8.27.0]: https://github.com/gchq/CyberChef/releases/tag/v8.27.0
 [8.27.0]: https://github.com/gchq/CyberChef/releases/tag/v8.27.0
 [8.26.0]: https://github.com/gchq/CyberChef/releases/tag/v8.26.0
 [8.26.0]: https://github.com/gchq/CyberChef/releases/tag/v8.26.0
 [8.25.0]: https://github.com/gchq/CyberChef/releases/tag/v8.25.0
 [8.25.0]: https://github.com/gchq/CyberChef/releases/tag/v8.25.0
@@ -159,6 +167,7 @@ All major and minor version changes will be documented in this file. Details of
 [@h345983745]: https://github.com/h345983745
 [@h345983745]: https://github.com/h345983745
 [@s2224834]: https://github.com/s2224834
 [@s2224834]: https://github.com/s2224834
 [@artemisbot]: https://github.com/artemisbot
 [@artemisbot]: https://github.com/artemisbot
+[@tlwr]: https://github.com/tlwr
 [@picapi]: https://github.com/picapi
 [@picapi]: https://github.com/picapi
 [@Dachande663]: https://github.com/Dachande663
 [@Dachande663]: https://github.com/Dachande663
 [@JustAnotherMark]: https://github.com/JustAnotherMark
 [@JustAnotherMark]: https://github.com/JustAnotherMark
@@ -175,6 +184,7 @@ All major and minor version changes will be documented in this file. Details of
 
 
 [#95]: https://github.com/gchq/CyberChef/pull/299
 [#95]: https://github.com/gchq/CyberChef/pull/299
 [#173]: https://github.com/gchq/CyberChef/pull/173
 [#173]: https://github.com/gchq/CyberChef/pull/173
+[#143]: https://github.com/gchq/CyberChef/pull/143
 [#224]: https://github.com/gchq/CyberChef/pull/224
 [#224]: https://github.com/gchq/CyberChef/pull/224
 [#239]: https://github.com/gchq/CyberChef/pull/239
 [#239]: https://github.com/gchq/CyberChef/pull/239
 [#248]: https://github.com/gchq/CyberChef/pull/248
 [#248]: https://github.com/gchq/CyberChef/pull/248
@@ -209,5 +219,7 @@ All major and minor version changes will be documented in this file. Details of
 [#468]: https://github.com/gchq/CyberChef/pull/468
 [#468]: https://github.com/gchq/CyberChef/pull/468
 [#476]: https://github.com/gchq/CyberChef/pull/476
 [#476]: https://github.com/gchq/CyberChef/pull/476
 [#489]: https://github.com/gchq/CyberChef/pull/489
 [#489]: https://github.com/gchq/CyberChef/pull/489
+[#496]: https://github.com/gchq/CyberChef/pull/496
 [#506]: https://github.com/gchq/CyberChef/pull/506
 [#506]: https://github.com/gchq/CyberChef/pull/506
 [#516]: https://github.com/gchq/CyberChef/pull/516
 [#516]: https://github.com/gchq/CyberChef/pull/516
+[#525]: https://github.com/gchq/CyberChef/pull/525

+ 3 - 2
Gruntfile.js

@@ -158,7 +158,7 @@ module.exports = function (grunt) {
             },
             },
             configs: ["*.{js,mjs}"],
             configs: ["*.{js,mjs}"],
             core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
             core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
-            web: ["src/web/**/*.{js,mjs}"],
+            web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"],
             node: ["src/node/**/*.{js,mjs}"],
             node: ["src/node/**/*.{js,mjs}"],
             tests: ["tests/**/*.{js,mjs}"],
             tests: ["tests/**/*.{js,mjs}"],
         },
         },
@@ -351,7 +351,8 @@ module.exports = function (grunt) {
                     warningsFilter: [
                     warningsFilter: [
                         /source-map/,
                         /source-map/,
                         /dependency is an expression/,
                         /dependency is an expression/,
-                        /export 'default'/
+                        /export 'default'/,
+                        /Can't resolve 'sodium'/
                     ],
                     ],
                 }
                 }
             },
             },

+ 12 - 4
babel.config.js

@@ -11,14 +11,22 @@ module.exports = function(api) {
                     "node": "6.5"
                     "node": "6.5"
                 },
                 },
                 "modules": false,
                 "modules": false,
-                "useBuiltIns": "entry"
+                "useBuiltIns": "entry",
+                "corejs": 3
             }]
             }]
         ],
         ],
         "plugins": [
         "plugins": [
             "babel-plugin-syntax-dynamic-import",
             "babel-plugin-syntax-dynamic-import",
-            ["babel-plugin-transform-builtin-extend", {
-                "globals": ["Error"]
-            }]
+            [
+                "babel-plugin-transform-builtin-extend", {
+                    "globals": ["Error"]
+                }
+            ],
+            [
+                "@babel/plugin-transform-runtime", {
+                    "regenerator": true
+                }
+            ]
         ]
         ]
     };
     };
 };
 };

Diferenças do arquivo suprimidas por serem muito extensas
+ 653 - 137
package-lock.json


+ 41 - 32
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "cyberchef",
   "name": "cyberchef",
-  "version": "8.27.0",
+  "version": "8.29.1",
   "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
   "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
   "author": "n1474335 <n1474335@gmail.com>",
   "author": "n1474335 <n1474335@gmail.com>",
   "homepage": "https://gchq.github.io/CyberChef",
   "homepage": "https://gchq.github.io/CyberChef",
@@ -31,20 +31,21 @@
   "module": "src/node/index.mjs",
   "module": "src/node/index.mjs",
   "bugs": "https://github.com/gchq/CyberChef/issues",
   "bugs": "https://github.com/gchq/CyberChef/issues",
   "devDependencies": {
   "devDependencies": {
-    "@babel/core": "^7.2.2",
-    "@babel/preset-env": "^7.2.3",
-    "autoprefixer": "^9.4.3",
+    "@babel/core": "^7.4.0",
+    "@babel/plugin-transform-runtime": "^7.4.0",
+    "@babel/preset-env": "^7.4.2",
+    "autoprefixer": "^9.5.0",
     "babel-eslint": "^10.0.1",
     "babel-eslint": "^10.0.1",
-    "babel-loader": "^8.0.4",
+    "babel-loader": "^8.0.5",
     "babel-plugin-syntax-dynamic-import": "^6.18.0",
     "babel-plugin-syntax-dynamic-import": "^6.18.0",
-    "bootstrap": "^4.2.1",
-    "chromedriver": "^2.45.0",
+    "babel-polyfill": "^6.26.0",
+    "chromedriver": "^2.46.0",
     "colors": "^1.3.3",
     "colors": "^1.3.3",
-    "css-loader": "^2.1.0",
-    "eslint": "^5.12.1",
+    "css-loader": "^2.1.1",
+    "eslint": "^5.15.3",
     "exports-loader": "^0.7.0",
     "exports-loader": "^0.7.0",
     "file-loader": "^3.0.1",
     "file-loader": "^3.0.1",
-    "grunt": "^1.0.3",
+    "grunt": "^1.0.4",
     "grunt-accessibility": "~6.0.0",
     "grunt-accessibility": "~6.0.0",
     "grunt-chmod": "~1.1.1",
     "grunt-chmod": "~1.1.1",
     "grunt-concurrent": "^2.3.1",
     "grunt-concurrent": "^2.3.1",
@@ -61,74 +62,82 @@
     "ink-docstrap": "^1.3.2",
     "ink-docstrap": "^1.3.2",
     "jsdoc-babel": "^0.5.0",
     "jsdoc-babel": "^0.5.0",
     "mini-css-extract-plugin": "^0.5.0",
     "mini-css-extract-plugin": "^0.5.0",
-    "nightwatch": "^1.0.18",
+    "nightwatch": "^1.0.19",
     "node-sass": "^4.11.0",
     "node-sass": "^4.11.0",
-    "postcss-css-variables": "^0.11.0",
+    "postcss-css-variables": "^0.12.0",
     "postcss-import": "^12.0.1",
     "postcss-import": "^12.0.1",
     "postcss-loader": "^3.0.0",
     "postcss-loader": "^3.0.0",
     "prompt": "^1.0.0",
     "prompt": "^1.0.0",
     "sass-loader": "^7.1.0",
     "sass-loader": "^7.1.0",
     "sitemap": "^2.1.0",
     "sitemap": "^2.1.0",
     "style-loader": "^0.23.1",
     "style-loader": "^0.23.1",
-    "uglifyjs-webpack-plugin": "^2.0.1",
     "svg-url-loader": "^2.3.2",
     "svg-url-loader": "^2.3.2",
+    "uglifyjs-webpack-plugin": "^2.0.1",
     "url-loader": "^1.1.2",
     "url-loader": "^1.1.2",
-    "web-resource-inliner": "^4.2.1",
-    "webpack": "^4.28.3",
-    "webpack-bundle-analyzer": "^3.0.3",
-    "webpack-dev-server": "^3.1.14",
+    "web-resource-inliner": "^4.3.1",
+    "webpack": "^4.29.6",
+    "webpack-bundle-analyzer": "^3.1.0",
+    "webpack-dev-server": "^3.2.1",
     "webpack-node-externals": "^1.7.2",
     "webpack-node-externals": "^1.7.2",
     "worker-loader": "^2.0.0"
     "worker-loader": "^2.0.0"
   },
   },
   "dependencies": {
   "dependencies": {
+    "@babel/polyfill": "^7.4.0",
+    "@babel/runtime": "^7.4.2",
     "arrive": "^2.4.1",
     "arrive": "^2.4.1",
     "babel-plugin-transform-builtin-extend": "1.1.2",
     "babel-plugin-transform-builtin-extend": "1.1.2",
-    "babel-polyfill": "^6.26.0",
     "bcryptjs": "^2.4.3",
     "bcryptjs": "^2.4.3",
-    "bignumber.js": "^8.0.2",
+    "bignumber.js": "^8.1.1",
+    "blakejs": "^1.1.0",
+    "bootstrap": "4.2.1",
     "bootstrap-colorpicker": "^2.5.3",
     "bootstrap-colorpicker": "^2.5.3",
     "bootstrap-material-design": "^4.1.1",
     "bootstrap-material-design": "^4.1.1",
-    "bson": "^4.0.1",
+    "bson": "^4.0.2",
     "chi-squared": "^1.1.0",
     "chi-squared": "^1.1.0",
+    "clippyjs": "0.0.3",
+    "core-js": "^3.0.0",
     "crypto-api": "^0.8.3",
     "crypto-api": "^0.8.3",
     "crypto-js": "^3.1.9-1",
     "crypto-js": "^3.1.9-1",
     "ctph.js": "0.0.5",
     "ctph.js": "0.0.5",
-    "diff": "^3.5.0",
+    "d3": "^4.9.1",
+    "d3-hexbin": "^0.2.2",
+    "diff": "^4.0.1",
     "es6-promisify": "^6.0.1",
     "es6-promisify": "^6.0.1",
-    "escodegen": "^1.11.0",
+    "escodegen": "^1.11.1",
     "esmangle": "^1.0.1",
     "esmangle": "^1.0.1",
     "esprima": "^4.0.1",
     "esprima": "^4.0.1",
     "exif-parser": "^0.1.12",
     "exif-parser": "^0.1.12",
-    "file-saver": "^2.0.0",
+    "file-saver": "^2.0.1",
     "geodesy": "^1.1.3",
     "geodesy": "^1.1.3",
-    "highlight.js": "^9.13.1",
+    "highlight.js": "^9.15.6",
     "jimp": "^0.6.0",
     "jimp": "^0.6.0",
     "jquery": "^3.3.1",
     "jquery": "^3.3.1",
     "js-crc": "^0.2.0",
     "js-crc": "^0.2.0",
     "js-sha3": "^0.8.0",
     "js-sha3": "^0.8.0",
     "jsesc": "^2.5.2",
     "jsesc": "^2.5.2",
-    "jsonpath": "^1.0.0",
-    "jsonwebtoken": "^8.4.0",
-    "jsqr": "^1.1.1",
+    "jsonpath": "^1.0.1",
+    "jsonwebtoken": "^8.5.1",
+    "jsqr": "^1.2.0",
     "jsrsasign": "8.0.12",
     "jsrsasign": "8.0.12",
-    "kbpgp": "^2.0.82",
+    "kbpgp": "2.1.0",
     "libyara-wasm": "0.0.12",
     "libyara-wasm": "0.0.12",
     "lodash": "^4.17.11",
     "lodash": "^4.17.11",
     "loglevel": "^1.6.1",
     "loglevel": "^1.6.1",
     "loglevel-message-prefix": "^3.0.0",
     "loglevel-message-prefix": "^3.0.0",
-    "moment": "^2.23.0",
+    "moment": "^2.24.0",
     "moment-timezone": "^0.5.23",
     "moment-timezone": "^0.5.23",
     "ngeohash": "^0.6.3",
     "ngeohash": "^0.6.3",
-    "node-forge": "^0.7.6",
+    "node-forge": "^0.8.2",
     "node-md6": "^0.1.0",
     "node-md6": "^0.1.0",
+    "nodom": "^2.2.0",
     "notepack.io": "^2.2.0",
     "notepack.io": "^2.2.0",
     "nwmatcher": "^1.4.4",
     "nwmatcher": "^1.4.4",
     "otp": "^0.1.3",
     "otp": "^0.1.3",
-    "popper.js": "^1.14.6",
+    "popper.js": "^1.14.7",
     "qr-image": "^3.2.0",
     "qr-image": "^3.2.0",
     "scryptsy": "^2.0.0",
     "scryptsy": "^2.0.0",
     "snackbarjs": "^1.1.0",
     "snackbarjs": "^1.1.0",
-    "sortablejs": "^1.8.0-rc1",
+    "sortablejs": "^1.8.4",
     "split.js": "^1.5.10",
     "split.js": "^1.5.10",
     "ssdeep.js": "0.0.2",
     "ssdeep.js": "0.0.2",
     "ua-parser-js": "^0.7.19",
     "ua-parser-js": "^0.7.19",

+ 0 - 1
src/core/ChefWorker.js

@@ -6,7 +6,6 @@
  * @license Apache-2.0
  * @license Apache-2.0
  */
  */
 
 
-import "babel-polyfill";
 import Chef from "./Chef";
 import Chef from "./Chef";
 import OperationConfig from "./config/OperationConfig.json";
 import OperationConfig from "./config/OperationConfig.json";
 import OpModules from "./config/modules/OpModules";
 import OpModules from "./config/modules/OpModules";

+ 40 - 39
src/core/Dish.mjs

@@ -11,7 +11,7 @@ import BigNumber from "bignumber.js";
 import log from "loglevel";
 import log from "loglevel";
 
 
 import {
 import {
-    DishArrayBuffer,
+    DishByteArray,
     DishBigNumber,
     DishBigNumber,
     DishFile,
     DishFile,
     DishHTML,
     DishHTML,
@@ -199,7 +199,6 @@ class Dish {
         return clone.get(type, notUTF8);
         return clone.get(type, notUTF8);
     }
     }
 
 
-
     /**
     /**
      * Validates that the value is the type that has been specified.
      * Validates that the value is the type that has been specified.
      * May have to disable parts of BYTE_ARRAY validation if it effects performance.
      * May have to disable parts of BYTE_ARRAY validation if it effects performance.
@@ -351,16 +350,17 @@ class Dish {
 
 
         // Node environment => translate is sync
         // Node environment => translate is sync
         if (Utils.isNode()) {
         if (Utils.isNode()) {
-            this._toByteArray();
-            this._fromByteArray(toType, notUTF8);
+            this._toArrayBuffer();
+            this.type = Dish.ARRAY_BUFFER;
+            this._fromArrayBuffer(toType, notUTF8);
 
 
         // Browser environment => translate is async
         // Browser environment => translate is async
         } else {
         } else {
             return new Promise((resolve, reject) => {
             return new Promise((resolve, reject) => {
-                this._toByteArray()
-                    .then(() => this.type = Dish.BYTE_ARRAY)
+                this._toArrayBuffer()
+                    .then(() => this.type = Dish.ARRAY_BUFFER)
                     .then(() => {
                     .then(() => {
-                        this._fromByteArray(toType);
+                        this._fromArrayBuffer(toType);
                         resolve();
                         resolve();
                     })
                     })
                     .catch(reject);
                     .catch(reject);
@@ -376,37 +376,37 @@ class Dish {
      *
      *
      * @returns {Promise || undefined}
      * @returns {Promise || undefined}
      */
      */
-    _toByteArray() {
+    _toArrayBuffer() {
         // Using 'bind' here to allow this.value to be mutated within translation functions
         // Using 'bind' here to allow this.value to be mutated within translation functions
         const toByteArrayFuncs = {
         const toByteArrayFuncs = {
             browser: {
             browser: {
-                [Dish.STRING]:          () => Promise.resolve(DishString.toByteArray.bind(this)()),
-                [Dish.NUMBER]:          () => Promise.resolve(DishNumber.toByteArray.bind(this)()),
-                [Dish.HTML]:            () => Promise.resolve(DishHTML.toByteArray.bind(this)()),
-                [Dish.ARRAY_BUFFER]:    () => Promise.resolve(DishArrayBuffer.toByteArray.bind(this)()),
-                [Dish.BIG_NUMBER]:      () => Promise.resolve(DishBigNumber.toByteArray.bind(this)()),
-                [Dish.JSON]:            () => Promise.resolve(DishJSON.toByteArray.bind(this)()),
-                [Dish.FILE]:            () => DishFile.toByteArray.bind(this)(),
-                [Dish.LIST_FILE]:       () => DishListFile.toByteArray.bind(this)(),
-                [Dish.BYTE_ARRAY]:      () => Promise.resolve(),
+                [Dish.STRING]:          () => Promise.resolve(DishString.toArrayBuffer.bind(this)()),
+                [Dish.NUMBER]:          () => Promise.resolve(DishNumber.toArrayBuffer.bind(this)()),
+                [Dish.HTML]:            () => Promise.resolve(DishHTML.toArrayBuffer.bind(this)()),
+                [Dish.ARRAY_BUFFER]:    () => Promise.resolve(),
+                [Dish.BIG_NUMBER]:      () => Promise.resolve(DishBigNumber.toArrayBuffer.bind(this)()),
+                [Dish.JSON]:            () => Promise.resolve(DishJSON.toArrayBuffer.bind(this)()),
+                [Dish.FILE]:            () => DishFile.toArrayBuffer.bind(this)(),
+                [Dish.LIST_FILE]:       () => DishListFile.toArrayBuffer.bind(this)(),
+                [Dish.BYTE_ARRAY]:      () => Promise.resolve(DishByteArray.toArrayBuffer.bind(this)()),
             },
             },
             node: {
             node: {
-                [Dish.STRING]:          () => DishString.toByteArray.bind(this)(),
-                [Dish.NUMBER]:          () => DishNumber.toByteArray.bind(this)(),
-                [Dish.HTML]:            () => DishHTML.toByteArray.bind(this)(),
-                [Dish.ARRAY_BUFFER]:    () => DishArrayBuffer.toByteArray.bind(this)(),
-                [Dish.BIG_NUMBER]:      () => DishBigNumber.toByteArray.bind(this)(),
-                [Dish.JSON]:            () => DishJSON.toByteArray.bind(this)(),
-                [Dish.FILE]:            () => DishFile.toByteArray.bind(this)(),
-                [Dish.LIST_FILE]:       () => DishListFile.toByteArray.bind(this)(),
-                [Dish.BYTE_ARRAY]:      () => {},
+                [Dish.STRING]:          () => DishString.toArrayBuffer.bind(this)(),
+                [Dish.NUMBER]:          () => DishNumber.toArrayBuffer.bind(this)(),
+                [Dish.HTML]:            () => DishHTML.toArrayBuffer.bind(this)(),
+                [Dish.ARRAY_BUFFER]:    () => {},
+                [Dish.BIG_NUMBER]:      () => DishBigNumber.toArrayBuffer.bind(this)(),
+                [Dish.JSON]:            () => DishJSON.toArrayBuffer.bind(this)(),
+                [Dish.FILE]:            () => DishFile.toArrayBuffer.bind(this)(),
+                [Dish.LIST_FILE]:       () => DishListFile.toArrayBuffer.bind(this)(),
+                [Dish.BYTE_ARRAY]:      () => DishByteArray.toArrayBuffer.bind(this)(),
             }
             }
         };
         };
 
 
         try {
         try {
             return toByteArrayFuncs[Utils.isNode() && "node" || "browser"][this.type]();
             return toByteArrayFuncs[Utils.isNode() && "node" || "browser"][this.type]();
         } catch (err) {
         } catch (err) {
-            throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to byteArray: ${err}`);
+            throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to ArrayBuffer: ${err}`);
         }
         }
     }
     }
 
 
@@ -416,33 +416,34 @@ class Dish {
      * @param {number} toType - the Dish enum to convert to
      * @param {number} toType - the Dish enum to convert to
      * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
      * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
     */
     */
-    _fromByteArray(toType, notUTF8) {
-        const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8;
+    _fromArrayBuffer(toType, notUTF8) {
 
 
         // Using 'bind' here to allow this.value to be mutated within translation functions
         // Using 'bind' here to allow this.value to be mutated within translation functions
         const toTypeFunctions = {
         const toTypeFunctions = {
-            [Dish.STRING]:          () => DishString.fromByteArray.bind(this)(byteArrayToStr),
-            [Dish.NUMBER]:          () => DishNumber.fromByteArray.bind(this)(byteArrayToStr),
-            [Dish.HTML]:            () => DishHTML.fromByteArray.bind(this)(byteArrayToStr),
-            [Dish.ARRAY_BUFFER]:    () => DishArrayBuffer.fromByteArray.bind(this)(),
-            [Dish.BIG_NUMBER]:      () => DishBigNumber.fromByteArray.bind(this)(byteArrayToStr),
-            [Dish.JSON]:            () => DishJSON.fromByteArray.bind(this)(byteArrayToStr),
-            [Dish.FILE]:            () => DishFile.fromByteArray.bind(this)(),
-            [Dish.LIST_FILE]:       () => DishListFile.fromByteArray.bind(this)(),
-            [Dish.BYTE_ARRAY]:      () => {},
+            [Dish.STRING]:          () => DishString.fromArrayBuffer.bind(this)(notUTF8),
+            [Dish.NUMBER]:          () => DishNumber.fromArrayBuffer.bind(this)(notUTF8),
+            [Dish.HTML]:            () => DishHTML.fromArrayBuffer.bind(this)(notUTF8),
+            [Dish.ARRAY_BUFFER]:    () => {},
+            [Dish.BIG_NUMBER]:      () => DishBigNumber.fromArrayBuffer.bind(this)(notUTF8),
+            [Dish.JSON]:            () => DishJSON.fromArrayBuffer.bind(this)(notUTF8),
+            [Dish.FILE]:            () => DishFile.fromArrayBuffer.bind(this)(),
+            [Dish.LIST_FILE]:       () => DishListFile.fromArrayBuffer.bind(this)(),
+            [Dish.BYTE_ARRAY]:      () => DishByteArray.fromArrayBuffer.bind(this)(),
         };
         };
 
 
         try {
         try {
             toTypeFunctions[toType]();
             toTypeFunctions[toType]();
             this.type = toType;
             this.type = toType;
         } catch (err) {
         } catch (err) {
-            throw new DishError(`Error translating from byteArray to ${Dish.enumLookup(toType)}: ${err}`);
+            throw new DishError(`Error translating from ArrayBuffer to ${Dish.enumLookup(toType)}: ${err}`);
         }
         }
     }
     }
 
 
 }
 }
 
 
 
 
+
+
 /**
 /**
  * Dish data type enum for byte arrays.
  * Dish data type enum for byte arrays.
  * @readonly
  * @readonly

+ 66 - 9
src/core/Utils.mjs

@@ -366,6 +366,61 @@ class Utils {
     }
     }
 
 
 
 
+    /**
+     * Converts a string to an ArrayBuffer.
+     * Treats the string as UTF-8 if any values are over 255.
+     *
+     * @param {string} str
+     * @returns {ArrayBuffer}
+     *
+     * @example
+     * // returns [72,101,108,108,111]
+     * Utils.strToArrayBuffer("Hello");
+     *
+     * // returns [228,189,160,229,165,189]
+     * Utils.strToArrayBuffer("你好");
+     */
+    static strToArrayBuffer(str) {
+        const arr = new Uint8Array(str.length);
+        let i = str.length, b;
+        while (i--) {
+            b = str.charCodeAt(i);
+            arr[i] = b;
+            // If any of the bytes are over 255, read as UTF-8
+            if (b > 255) return Utils.strToUtf8ArrayBuffer(str);
+        }
+        return arr.buffer;
+    }
+
+
+    /**
+     * Converts a string to a UTF-8 ArrayBuffer.
+     *
+     * @param {string} str
+     * @returns {ArrayBuffer}
+     *
+     * @example
+     * // returns [72,101,108,108,111]
+     * Utils.strToUtf8ArrayBuffer("Hello");
+     *
+     * // returns [228,189,160,229,165,189]
+     * Utils.strToUtf8ArrayBuffer("你好");
+     */
+    static strToUtf8ArrayBuffer(str) {
+        const utf8Str = utf8.encode(str);
+
+        if (str.length !== utf8Str.length) {
+            if (ENVIRONMENT_IS_WORKER()) {
+                self.setOption("attemptHighlight", false);
+            } else if (ENVIRONMENT_IS_WEB()) {
+                window.app.options.attemptHighlight = false;
+            }
+        }
+
+        return Utils.strToArrayBuffer(utf8Str);
+    }
+
+
     /**
     /**
      * Converts a string to a byte array.
      * Converts a string to a byte array.
      * Treats the string as UTF-8 if any values are over 255.
      * Treats the string as UTF-8 if any values are over 255.
@@ -458,7 +513,7 @@ class Utils {
     /**
     /**
      * Attempts to convert a byte array to a UTF-8 string.
      * Attempts to convert a byte array to a UTF-8 string.
      *
      *
-     * @param {byteArray} byteArray
+     * @param {byteArray|Uint8Array} byteArray
      * @returns {string}
      * @returns {string}
      *
      *
      * @example
      * @example
@@ -503,6 +558,7 @@ class Utils {
     static byteArrayToChars(byteArray) {
     static byteArrayToChars(byteArray) {
         if (!byteArray) return "";
         if (!byteArray) return "";
         let str = "";
         let str = "";
+        // String concatenation appears to be faster than an array join
         for (let i = 0; i < byteArray.length;) {
         for (let i = 0; i < byteArray.length;) {
             str += String.fromCharCode(byteArray[i++]);
             str += String.fromCharCode(byteArray[i++]);
         }
         }
@@ -522,8 +578,8 @@ class Utils {
      * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
      * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
      */
      */
     static arrayBufferToStr(arrayBuffer, utf8=true) {
     static arrayBufferToStr(arrayBuffer, utf8=true) {
-        const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer));
-        return utf8 ? Utils.byteArrayToUtf8(byteArray) : Utils.byteArrayToChars(byteArray);
+        const arr = new Uint8Array(arrayBuffer);
+        return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr);
     }
     }
 
 
 
 
@@ -970,17 +1026,15 @@ class Utils {
         }
         }
     }
     }
 
 
-    /** */
+    /**
+     * 
+     */
     static readFileSync(file) {
     static readFileSync(file) {
         if (!Utils.isNode()) {
         if (!Utils.isNode()) {
             throw new TypeError("Browser environment cannot support readFileSync");
             throw new TypeError("Browser environment cannot support readFileSync");
         }
         }
-        let bytes = [];
-        for (const byte of file.data.values()) {
-            bytes = bytes.concat(byte);
-        }
 
 
-        return bytes;
+        return file.data.buffer;
     }
     }
 
 
 
 
@@ -1040,9 +1094,11 @@ class Utils {
     static charRep(token) {
     static charRep(token) {
         return {
         return {
             "Space":         " ",
             "Space":         " ",
+            "Percent":       "%",
             "Comma":         ",",
             "Comma":         ",",
             "Semi-colon":    ";",
             "Semi-colon":    ";",
             "Colon":         ":",
             "Colon":         ":",
+            "Tab":           "\t",
             "Line feed":     "\n",
             "Line feed":     "\n",
             "CRLF":          "\r\n",
             "CRLF":          "\r\n",
             "Forward slash": "/",
             "Forward slash": "/",
@@ -1064,6 +1120,7 @@ class Utils {
     static regexRep(token) {
     static regexRep(token) {
         return {
         return {
             "Space":         /\s+/g,
             "Space":         /\s+/g,
+            "Percent":       /%/g,
             "Comma":         /,/g,
             "Comma":         /,/g,
             "Semi-colon":    /;/g,
             "Semi-colon":    /;/g,
             "Colon":         /:/g,
             "Colon":         /:/g,

+ 8 - 1
src/core/config/Categories.json

@@ -297,6 +297,8 @@
             "HAS-160",
             "HAS-160",
             "Whirlpool",
             "Whirlpool",
             "Snefru",
             "Snefru",
+            "BLAKE2b",
+            "BLAKE2s",
             "SSDEEP",
             "SSDEEP",
             "CTPH",
             "CTPH",
             "Compare SSDEEP hashes",
             "Compare SSDEEP hashes",
@@ -378,7 +380,11 @@
             "Image Filter",
             "Image Filter",
             "Contain Image",
             "Contain Image",
             "Cover Image",
             "Cover Image",
-            "Image Hue/Saturation/Lightness"
+            "Image Hue/Saturation/Lightness",
+            "Hex Density chart",
+            "Scatter chart",
+            "Series chart",
+            "Heatmap chart"
         ]
         ]
     },
     },
     {
     {
@@ -395,6 +401,7 @@
             "Generate QR Code",
             "Generate QR Code",
             "Parse QR Code",
             "Parse QR Code",
             "Haversine distance",
             "Haversine distance",
+            "HTML To Text",
             "Generate Lorem Ipsum",
             "Generate Lorem Ipsum",
             "Numberwang",
             "Numberwang",
             "XKCD Random Number"
             "XKCD Random Number"

+ 7 - 8
src/core/dishTranslationTypes/DishBigNumber.mjs

@@ -14,23 +14,22 @@ import BigNumber from "bignumber.js";
 class DishBigNumber extends DishTranslationType {
 class DishBigNumber extends DishTranslationType {
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to a ArrayBuffer
      * @param {BigNumber} value
      * @param {BigNumber} value
      */
      */
-    static toByteArray() {
+    static toArrayBuffer() {
         DishBigNumber.checkForValue(this.value);
         DishBigNumber.checkForValue(this.value);
-        this.value = BigNumber.isBigNumber(this.value) ? Utils.strToByteArray(this.value.toFixed()) : [];
+        this.value = BigNumber.isBigNumber(this.value) ? Utils.strToArrayBuffer(this.value.toFixed()) : new ArrayBuffer;
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {ByteArray} value
-     * @param {function} byteArrayToStr
+     * convert the given value from a ArrayBuffer
+     * @param {boolean} notUTF8
      */
      */
-    static fromByteArray(byteArrayToStr) {
+    static fromArrayBuffer(notUTF8) {
         DishBigNumber.checkForValue(this.value);
         DishBigNumber.checkForValue(this.value);
         try {
         try {
-            this.value = new BigNumber(byteArrayToStr(this.value));
+            this.value = new BigNumber(Utils.arrayBufferToStr(this.value, !notUTF8));
         } catch (err) {
         } catch (err) {
             this.value = new BigNumber(NaN);
             this.value = new BigNumber(NaN);
         }
         }

+ 10 - 11
src/core/dishTranslationTypes/DishArrayBuffer.mjs → src/core/dishTranslationTypes/DishByteArray.mjs

@@ -9,24 +9,23 @@ import DishTranslationType from "./DishTranslationType";
 /**
 /**
  * Translation methods for ArrayBuffer Dishes
  * Translation methods for ArrayBuffer Dishes
  */
  */
-class DishArrayBuffer extends DishTranslationType {
+class DishByteArray extends DishTranslationType {
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to a ArrayBuffer
      */
      */
-    static toByteArray() {
-        DishArrayBuffer.checkForValue(this.value);
-        this.value = Array.prototype.slice.call(new Uint8Array(this.value));
+    static toArrayBuffer() {
+        DishByteArray.checkForValue(this.value);
+        this.value = new Uint8Array(this.value).buffer;
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {function} byteArrayToStr
+     * convert the given value from a ArrayBuffer
      */
      */
-    static fromByteArray() {
-        DishArrayBuffer.checkForValue(this.value);
-        this.value = new Uint8Array(this.value).buffer;
+    static fromArrayBuffer() {
+        DishByteArray.checkForValue(this.value);
+        this.value = Array.prototype.slice.call(new Uint8Array(this.value));
     }
     }
 }
 }
 
 
-export default DishArrayBuffer;
+export default DishByteArray;

+ 7 - 8
src/core/dishTranslationTypes/DishFile.mjs

@@ -13,17 +13,18 @@ import Utils from "../Utils";
 class DishFile extends DishTranslationType {
 class DishFile extends DishTranslationType {
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to an ArrayBuffer
      * @param {File} value
      * @param {File} value
      */
      */
-    static toByteArray() {
+    static toArrayBuffer() {
         DishFile.checkForValue(this.value);
         DishFile.checkForValue(this.value);
         if (Utils.isNode()) {
         if (Utils.isNode()) {
+            // TODO
             this.value = Utils.readFileSync(this.value);
             this.value = Utils.readFileSync(this.value);
         } else {
         } else {
             return new Promise((resolve, reject) => {
             return new Promise((resolve, reject) => {
                 Utils.readFile(this.value)
                 Utils.readFile(this.value)
-                    .then(v => this.value = Array.prototype.slice.call(v))
+                    .then(v => this.value = v.buffer)
                     .then(resolve)
                     .then(resolve)
                     .catch(reject);
                     .catch(reject);
             });
             });
@@ -31,13 +32,11 @@ class DishFile extends DishTranslationType {
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {ByteArray} value
-     * @param {function} byteArrayToStr
+     * convert the given value from an ArrayBuffer
      */
      */
-    static fromByteArray() {
+    static fromArrayBuffer() {
         DishFile.checkForValue(this.value);
         DishFile.checkForValue(this.value);
-        this.value = new File(this.value, "file.txt");
+        this.value = new File(this.value, "unknown");
     }
     }
 }
 }
 
 

+ 7 - 8
src/core/dishTranslationTypes/DishHTML.mjs

@@ -14,21 +14,20 @@ import DishString from "./DishString";
 class DishHTML extends DishTranslationType {
 class DishHTML extends DishTranslationType {
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to a ArrayBuffer
      * @param {String} value
      * @param {String} value
      */
      */
-    static toByteArray() {
+    static toArrayBuffer() {
         DishHTML.checkForValue(this.value);
         DishHTML.checkForValue(this.value);
-        this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : [];
+        this.value = this.value ? Utils.strToArrayBuffer(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : new ArrayBuffer;
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {function} byteArrayToStr
+     * convert the given value from a ArrayBuffer
+     * @param {boolean} notUTF8
      */
      */
-    static fromByteArray(byteArrayToStr) {
-        DishHTML.checkForValue(this.value);
-        DishString.fromByteArray(this.value, byteArrayToStr);
+    static fromArrayBuffer(notUTF8) {
+        DishString.fromByteArray(this.value, notUTF8);
     }
     }
 }
 }
 
 

+ 7 - 8
src/core/dishTranslationTypes/DishJSON.mjs

@@ -13,21 +13,20 @@ import Utils from "../Utils";
 class DishJSON extends DishTranslationType {
 class DishJSON extends DishTranslationType {
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to a ArrayBuffer
      */
      */
-    static toByteArray() {
+    static toArrayBuffer() {
         DishJSON.checkForValue(this.value);
         DishJSON.checkForValue(this.value);
-        this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value, null, 4)) : [];
+        this.value = this.value ? Utils.strToArrayBuffer(JSON.stringify(this.value, null, 4)) : new ArrayBuffer;
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {ByteArray} value
-     * @param {function} byteArrayToStr
+     * convert the given value from a ArrayBuffer
+     * @param {boolean} notUTF8
      */
      */
-    static fromByteArray(byteArrayToStr) {
+    static fromArrayBuffer(notUTF8) {
         DishJSON.checkForValue(this.value);
         DishJSON.checkForValue(this.value);
-        this.value = JSON.parse(byteArrayToStr(this.value));
+        this.value = JSON.parse(Utils.arrayBufferToStr(this.value, !notUTF8));
     }
     }
 }
 }
 
 

+ 27 - 9
src/core/dishTranslationTypes/DishListFile.mjs

@@ -13,30 +13,48 @@ import Utils from "../Utils";
 class DishListFile extends DishTranslationType {
 class DishListFile extends DishTranslationType {
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to a ArrayBuffer
      */
      */
-    static toByteArray() {
+    static toArrayBuffer() {
         DishListFile.checkForValue(this.value);
         DishListFile.checkForValue(this.value);
         if (Utils.isNode()) {
         if (Utils.isNode()) {
+            // TODO
             this.value = [].concat.apply([], this.value.map(f => Utils.readFileSync(f)).map(b => Array.prototype.slice.call(b)));
             this.value = [].concat.apply([], this.value.map(f => Utils.readFileSync(f)).map(b => Array.prototype.slice.call(b)));
         } else {
         } else {
             return new Promise((resolve, reject) => {
             return new Promise((resolve, reject) => {
-                Promise.all(this.value.map(async f => Utils.readFile(f)))
-                    .then(values => this.value = values.map(b => [].concat.apply([], Array.prototype.slice.call(b))))
-                    .then(resolve)
-                    .catch(reject);
+                resolve(DishListFile.concatenateTypedArrays(...this.value).buffer);
             });
             });
         }
         }
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {function} byteArrayToStr
+     * convert the given value from a ArrayBuffer
      */
      */
-    static fromByteArray() {
+    static fromArrayBuffer() {
         DishListFile.checkForValue(this.value);
         DishListFile.checkForValue(this.value);
         this.value = [new File(this.value, "unknown")];
         this.value = [new File(this.value, "unknown")];
     }
     }
+
+
+    /**
+     * Concatenates a list of Uint8Arrays together
+     *
+     * @param {Uint8Array[]} arrays
+     * @returns {Uint8Array}
+     */
+    static concatenateTypedArrays(...arrays) {
+        let totalLength = 0;
+        for (const arr of arrays) {
+            totalLength += arr.length;
+        }
+        const result = new Uint8Array(totalLength);
+        let offset = 0;
+        for (const arr of arrays) {
+            result.set(arr, offset);
+            offset += arr.length;
+        }
+        return result;
+    }
 }
 }
 
 
 export default DishListFile;
 export default DishListFile;

+ 7 - 7
src/core/dishTranslationTypes/DishNumber.mjs

@@ -14,20 +14,20 @@ import Utils from "../Utils";
 class DishNumber extends DishTranslationType {
 class DishNumber extends DishTranslationType {
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to a ArrayBuffer
      */
      */
-    static toByteArray() {
+    static toArrayBuffer() {
         DishNumber.checkForValue(this.value);
         DishNumber.checkForValue(this.value);
-        this.value = typeof this.value === "number" ? Utils.strToByteArray(this.value.toString()) : [];
+        this.value = typeof this.value === "number" ? Utils.strToArrayBuffer(this.value.toString()) : new ArrayBuffer;
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {function} byteArrayToStr
+     * convert the given value from a ArrayBuffer
+     * @param {boolean} notUTF8
      */
      */
-    static fromByteArray(byteArrayToStr) {
+    static fromArrayBuffer(notUTF8) {
         DishNumber.checkForValue(this.value);
         DishNumber.checkForValue(this.value);
-        this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0;
+        this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value, !notUTF8)) : 0;
     }
     }
 }
 }
 
 

+ 7 - 7
src/core/dishTranslationTypes/DishString.mjs

@@ -14,20 +14,20 @@ import Utils from "../Utils";
 class DishString extends DishTranslationType {
 class DishString extends DishTranslationType {
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to a ArrayBuffer
      */
      */
-    static toByteArray() {
+    static toArrayBuffer() {
         DishString.checkForValue(this.value);
         DishString.checkForValue(this.value);
-        this.value = this.value ? Utils.strToByteArray(this.value) : [];
+        this.value = this.value ? Utils.strToArrayBuffer(this.value) : new ArrayBuffer;
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {function} byteArrayToStr
+     * convert the given value from a ArrayBuffer
+     * @param {boolean} notUTF8
      */
      */
-    static fromByteArray(byteArrayToStr) {
+    static fromArrayBuffer(notUTF8) {
         DishString.checkForValue(this.value);
         DishString.checkForValue(this.value);
-        this.value = this.value ? byteArrayToStr(this.value) : "";
+        this.value = this.value ? Utils.arrayBufferToStr(this.value, !notUTF8) : "";
     }
     }
 }
 }
 
 

+ 5 - 5
src/core/dishTranslationTypes/DishTranslationType.mjs

@@ -20,18 +20,18 @@ class DishTranslationType {
     }
     }
 
 
     /**
     /**
-     * convert the given value to a ByteArray
+     * convert the given value to a ArrayBuffer
      * @param {*} value
      * @param {*} value
      */
      */
-    static toByteArray() {
+    static toArrayBuffer() {
         throw new Error("toByteArray has not been implemented");
         throw new Error("toByteArray has not been implemented");
     }
     }
 
 
     /**
     /**
-     * convert the given value from a ByteArray
-     * @param {function} byteArrayToStr
+     * convert the given value from a ArrayBuffer
+     * @param {boolean} notUTF8
      */
      */
-    static fromByteArray(byteArrayToStr=undefined) {
+    static fromArrayBuffer(notUTF8=undefined) {
         throw new Error("toType has not been implemented");
         throw new Error("toType has not been implemented");
     }
     }
 }
 }

+ 2 - 2
src/core/dishTranslationTypes/index.mjs

@@ -5,7 +5,7 @@
  */
  */
 
 
 
 
-import DishArrayBuffer from "./DishArrayBuffer";
+import DishByteArray from "./DishByteArray";
 import DishBigNumber from "./DishBigNumber";
 import DishBigNumber from "./DishBigNumber";
 import DishFile from "./DishFile";
 import DishFile from "./DishFile";
 import DishHTML from "./DishHTML";
 import DishHTML from "./DishHTML";
@@ -15,7 +15,7 @@ import DishNumber from "./DishNumber";
 import DishString from "./DishString";
 import DishString from "./DishString";
 
 
 export {
 export {
-    DishArrayBuffer,
+    DishByteArray,
     DishBigNumber,
     DishBigNumber,
     DishFile,
     DishFile,
     DishHTML,
     DishHTML,

+ 178 - 0
src/core/lib/Charts.mjs

@@ -0,0 +1,178 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [me@mitt.dev]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import OperationError from "../errors/OperationError";
+
+/**
+ * @constant
+ * @default
+ */
+export const RECORD_DELIMITER_OPTIONS = ["Line feed", "CRLF"];
+
+
+/**
+ * @constant
+ * @default
+ */
+export const FIELD_DELIMITER_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Tab"];
+
+
+/**
+ * Default from colour
+ *
+ * @constant
+ * @default
+ */
+export const COLOURS = {
+    min: "white",
+    max: "black"
+};
+
+
+/**
+ * Gets values from input for a plot.
+ *
+ * @param {string} input
+ * @param {string} recordDelimiter
+ * @param {string} fieldDelimiter
+ * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
+ * @param {number} length
+ * @returns {Object[]}
+ */
+export function getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) {
+    let headings;
+    const values = [];
+
+    input
+        .split(recordDelimiter)
+        .forEach((row, rowIndex) => {
+            const split = row.split(fieldDelimiter);
+            if (split.length !== length) throw new OperationError(`Each row must have length ${length}.`);
+
+            if (columnHeadingsAreIncluded && rowIndex === 0) {
+                headings = split;
+            } else {
+                values.push(split);
+            }
+        });
+    return { headings, values };
+}
+
+
+/**
+ * Gets values from input for a scatter plot.
+ *
+ * @param {string} input
+ * @param {string} recordDelimiter
+ * @param {string} fieldDelimiter
+ * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
+ * @returns {Object[]}
+ */
+export function getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
+    let { headings, values } = getValues(
+        input,
+        recordDelimiter,
+        fieldDelimiter,
+        columnHeadingsAreIncluded,
+        2
+    );
+
+    if (headings) {
+        headings = {x: headings[0], y: headings[1]};
+    }
+
+    values = values.map(row => {
+        const x = parseFloat(row[0], 10),
+            y = parseFloat(row[1], 10);
+
+        if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
+        if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
+
+        return [x, y];
+    });
+
+    return { headings, values };
+}
+
+
+/**
+ * Gets values from input for a scatter plot with colour from the third column.
+ *
+ * @param {string} input
+ * @param {string} recordDelimiter
+ * @param {string} fieldDelimiter
+ * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
+ * @returns {Object[]}
+ */
+export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
+    let { headings, values } = getValues(
+        input,
+        recordDelimiter, fieldDelimiter,
+        columnHeadingsAreIncluded,
+        3
+    );
+
+    if (headings) {
+        headings = {x: headings[0], y: headings[1]};
+    }
+
+    values = values.map(row => {
+        const x = parseFloat(row[0], 10),
+            y = parseFloat(row[1], 10),
+            colour = row[2];
+
+        if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
+        if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
+
+        return [x, y, colour];
+    });
+
+    return { headings, values };
+}
+
+/**
+ * Gets values from input for a time series plot.
+ *
+ * @param {string} input
+ * @param {string} recordDelimiter
+ * @param {string} fieldDelimiter
+ * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
+ * @returns {Object[]}
+ */
+export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
+    const { values } = getValues(
+        input,
+        recordDelimiter, fieldDelimiter,
+        false,
+        3
+    );
+
+    let xValues = new Set();
+    const series = {};
+
+    values.forEach(row => {
+        const serie = row[0],
+            xVal = row[1],
+            val = parseFloat(row[2], 10);
+
+        if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
+
+        xValues.add(xVal);
+        if (typeof series[serie] === "undefined") series[serie] = {};
+        series[serie][xVal] = val;
+    });
+
+    xValues = new Array(...xValues);
+
+    const seriesList = [];
+    for (const seriesName in series) {
+        const serie = series[seriesName];
+        seriesList.push({name: seriesName, data: serie});
+    }
+
+    return { xValues, series: seriesList };
+}

+ 1 - 1
src/core/lib/Hex.mjs

@@ -100,7 +100,7 @@ export function fromHex(data, delim="Auto", byteLen=2) {
 /**
 /**
  * To Hexadecimal delimiters.
  * To Hexadecimal delimiters.
  */
  */
-export const TO_HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
+export const TO_HEX_DELIM_OPTIONS = ["Space", "Percent", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
 
 
 
 
 /**
 /**

+ 79 - 0
src/core/operations/BLAKE2b.mjs

@@ -0,0 +1,79 @@
+/**
+ * @author h345983745
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import blakejs from "blakejs";
+import OperationError from "../errors/OperationError";
+import Utils from "../Utils";
+import { toBase64 } from "../lib/Base64";
+
+/**
+ * BLAKE2b operation
+ */
+class BLAKE2b extends Operation {
+
+    /**
+     * BLAKE2b constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "BLAKE2b";
+        this.module = "Hashing";
+        this.description = `Performs BLAKE2b hashing on the input.  
+        <br><br> BLAKE2b is a flavour of the BLAKE cryptographic hash function that is optimized for 64-bit platforms and produces digests of any size between 1 and 64 bytes.
+        <br><br> Supports the use of an optional key.`;
+        this.infoURL = "https://wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2b_algorithm";
+        this.inputType = "ArrayBuffer";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Size",
+                "type": "option",
+                "value": ["512", "384", "256", "160", "128"]
+            }, {
+                "name": "Output Encoding",
+                "type": "option",
+                "value": ["Hex", "Base64", "Raw"]
+            }, {
+                "name": "Key",
+                "type": "toggleString",
+                "value": "",
+                "toggleValues": ["UTF8", "Decimal", "Base64", "Hex", "Latin1"]
+            }
+        ];
+    }
+
+    /**
+     * @param {ArrayBuffer} input
+     * @param {Object[]} args
+     * @returns {string} The input having been hashed with BLAKE2b in the encoding format speicifed.
+     */
+    run(input, args) {
+        const [outSize, outFormat] = args;
+        let key = Utils.convertToByteArray(args[2].string || "", args[2].option);
+        if (key.length === 0) {
+            key = null;
+        } else if (key.length > 64) {
+            throw new OperationError(["Key cannot be greater than 64 bytes", "It is currently " + key.length + " bytes."].join("\n"));
+        }
+
+        input = new Uint8Array(input);
+        switch (outFormat) {
+            case "Hex":
+                return blakejs.blake2bHex(input, key, outSize / 8);
+            case "Base64":
+                return toBase64(blakejs.blake2b(input, key, outSize / 8));
+            case "Raw":
+                return Utils.arrayBufferToStr(blakejs.blake2b(input, key, outSize / 8).buffer);
+            default:
+                return new OperationError("Unsupported Output Type");
+        }
+    }
+
+}
+
+export default BLAKE2b;

+ 80 - 0
src/core/operations/BLAKE2s.mjs

@@ -0,0 +1,80 @@
+/**
+ * @author h345983745
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import blakejs from "blakejs";
+import OperationError from "../errors/OperationError";
+import Utils from "../Utils";
+import { toBase64 } from "../lib/Base64";
+
+/**
+ * BLAKE2s Operation
+ */
+class BLAKE2s extends Operation {
+
+    /**
+     * BLAKE2s constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "BLAKE2s";
+        this.module = "Hashing";
+        this.description = `Performs BLAKE2s hashing on the input.  
+        <br><br>BLAKE2s is a flavour of the BLAKE cryptographic hash function that is optimized for 8- to 32-bit platforms and produces digests of any size between 1 and 32 bytes.
+        <br><br>Supports the use of an optional key.`;
+        this.infoURL = "https://wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2";
+        this.inputType = "ArrayBuffer";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Size",
+                "type": "option",
+                "value": ["256", "160", "128"]
+            }, {
+                "name": "Output Encoding",
+                "type": "option",
+                "value": ["Hex", "Base64", "Raw"]
+            },
+            {
+                "name": "Key",
+                "type": "toggleString",
+                "value": "",
+                "toggleValues": ["UTF8", "Decimal", "Base64", "Hex", "Latin1"]
+            }
+        ];
+    }
+
+    /**
+     * @param {ArrayBuffer} input
+     * @param {Object[]} args
+     * @returns {string} The input having been hashed with BLAKE2s in the encoding format speicifed.
+     */
+    run(input, args) {
+        const [outSize, outFormat] = args;
+        let key = Utils.convertToByteArray(args[2].string || "", args[2].option);
+        if (key.length === 0) {
+            key = null;
+        } else if (key.length > 32) {
+            throw new OperationError(["Key cannot be greater than 32 bytes", "It is currently " + key.length + " bytes."].join("\n"));
+        }
+
+        input = new Uint8Array(input);
+        switch (outFormat) {
+            case "Hex":
+                return blakejs.blake2sHex(input, key, outSize / 8);
+            case "Base64":
+                return toBase64(blakejs.blake2s(input, key, outSize / 8));
+            case "Raw":
+                return Utils.arrayBufferToStr(blakejs.blake2s(input, key, outSize / 8).buffer);
+            default:
+                return new OperationError("Unsupported Output Type");
+        }
+    }
+
+}
+
+export default BLAKE2s;

+ 1 - 1
src/core/operations/ExtractFiles.mjs

@@ -23,7 +23,7 @@ class ExtractFiles extends Operation {
 
 
         this.name = "Extract Files";
         this.name = "Extract Files";
         this.module = "Default";
         this.module = "Default";
-        this.description = "TODO";
+        this.description = "Performs file carving to attempt to extract files from the input.<br><br>This operation is currently capable of carving out the following formats:<ul><li>JPG</li><li>EXE</li><li>ZIP</li><li>PDF</li><li>PNG</li><li>BMP</li><li>FLV</li><li>RTF</li><li>DOCX, PPTX, XLSX</li><li>EPUB</li><li>GZIP</li><li>ZLIB</li><li>ELF, BIN, AXF, O, PRX, SO</li></ul>";
         this.infoURL = "https://forensicswiki.org/wiki/File_Carving";
         this.infoURL = "https://forensicswiki.org/wiki/File_Carving";
         this.inputType = "ArrayBuffer";
         this.inputType = "ArrayBuffer";
         this.outputType = "List<File>";
         this.outputType = "List<File>";

+ 10 - 0
src/core/operations/GenerateAllHashes.mjs

@@ -28,6 +28,8 @@ import Fletcher64Checksum from "./Fletcher64Checksum";
 import Adler32Checksum from "./Adler32Checksum";
 import Adler32Checksum from "./Adler32Checksum";
 import CRC16Checksum from "./CRC16Checksum";
 import CRC16Checksum from "./CRC16Checksum";
 import CRC32Checksum from "./CRC32Checksum";
 import CRC32Checksum from "./CRC32Checksum";
+import BLAKE2b from "./BLAKE2b";
+import BLAKE2s from "./BLAKE2s";
 
 
 /**
 /**
  * Generate all hashes operation
  * Generate all hashes operation
@@ -86,6 +88,14 @@ class GenerateAllHashes extends Operation {
                 "\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) +
                 "\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) +
                 "\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) +
                 "\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) +
                 "\nWhirlpool:   " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) +
                 "\nWhirlpool:   " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) +
+                "\nBLAKE2b-128: " + (new BLAKE2b).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
+                "\nBLAKE2b-160: " + (new BLAKE2b).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
+                "\nBLAKE2b-256: " + (new BLAKE2b).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
+                "\nBLAKE2b-384: " + (new BLAKE2b).run(arrayBuffer, ["384", "Hex", {string: "", option: "UTF8"}]) +
+                "\nBLAKE2b-512: " + (new BLAKE2b).run(arrayBuffer, ["512", "Hex", {string: "", option: "UTF8"}]) +
+                "\nBLAKE2s-128: " + (new BLAKE2s).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
+                "\nBLAKE2s-160: " + (new BLAKE2s).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
+                "\nBLAKE2s-256: " + (new BLAKE2s).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
                 "\nSSDEEP:      " + (new SSDEEP()).run(str) +
                 "\nSSDEEP:      " + (new SSDEEP()).run(str) +
                 "\nCTPH:        " + (new CTPH()).run(str) +
                 "\nCTPH:        " + (new CTPH()).run(str) +
                 "\n\nChecksums:" +
                 "\n\nChecksums:" +

+ 41 - 0
src/core/operations/HTMLToText.mjs

@@ -0,0 +1,41 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [me@mitt.dev]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * HTML To Text operation
+ */
+class HTMLToText extends Operation {
+
+    /**
+     * HTMLToText constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "HTML To Text";
+        this.module = "Default";
+        this.description = "Converts an HTML output from an operation to a readable string instead of being rendered in the DOM.";
+        this.infoURL = "";
+        this.inputType = "html";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {html} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        return input;
+    }
+
+}
+
+export default HTMLToText;

+ 266 - 0
src/core/operations/HeatmapChart.mjs

@@ -0,0 +1,266 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [me@mitt.dev]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import * as d3temp from "d3";
+import * as nodomtemp from "nodom";
+import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts";
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+import Utils from "../Utils";
+
+const d3 = d3temp.default ? d3temp.default : d3temp;
+const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;
+
+/**
+ * Heatmap chart operation
+ */
+class HeatmapChart extends Operation {
+
+    /**
+     * HeatmapChart constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Heatmap chart";
+        this.module = "Charts";
+        this.description = "A heatmap is a graphical representation of data where the individual values contained in a matrix are represented as colors.";
+        this.infoURL = "https://wikipedia.org/wiki/Heat_map";
+        this.inputType = "string";
+        this.outputType = "html";
+        this.args = [
+            {
+                name: "Record delimiter",
+                type: "option",
+                value: RECORD_DELIMITER_OPTIONS,
+            },
+            {
+                name: "Field delimiter",
+                type: "option",
+                value: FIELD_DELIMITER_OPTIONS,
+            },
+            {
+                name: "Number of vertical bins",
+                type: "number",
+                value: 25,
+            },
+            {
+                name: "Number of horizontal bins",
+                type: "number",
+                value: 25,
+            },
+            {
+                name: "Use column headers as labels",
+                type: "boolean",
+                value: true,
+            },
+            {
+                name: "X label",
+                type: "string",
+                value: "",
+            },
+            {
+                name: "Y label",
+                type: "string",
+                value: "",
+            },
+            {
+                name: "Draw bin edges",
+                type: "boolean",
+                value: false,
+            },
+            {
+                name: "Min colour value",
+                type: "string",
+                value: COLOURS.min,
+            },
+            {
+                name: "Max colour value",
+                type: "string",
+                value: COLOURS.max,
+            },
+        ];
+    }
+
+    /**
+     * Heatmap chart operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    run(input, args) {
+        const recordDelimiter = Utils.charRep(args[0]),
+            fieldDelimiter = Utils.charRep(args[1]),
+            vBins = args[2],
+            hBins = args[3],
+            columnHeadingsAreIncluded = args[4],
+            drawEdges = args[7],
+            minColour = args[8],
+            maxColour = args[9],
+            dimension = 500;
+        if (vBins <= 0) throw new OperationError("Number of vertical bins must be greater than 0");
+        if (hBins <= 0) throw new OperationError("Number of horizontal bins must be greater than 0");
+
+        let xLabel = args[5],
+            yLabel = args[6];
+        const { headings, values } = getScatterValues(
+            input,
+            recordDelimiter,
+            fieldDelimiter,
+            columnHeadingsAreIncluded
+        );
+
+        if (headings) {
+            xLabel = headings.x;
+            yLabel = headings.y;
+        }
+
+        const document = new nodom.Document();
+        let svg = document.createElement("svg");
+        svg = d3.select(svg)
+            .attr("width", "100%")
+            .attr("height", "100%")
+            .attr("viewBox", `0 0 ${dimension} ${dimension}`);
+
+        const margin = {
+                top: 10,
+                right: 0,
+                bottom: 40,
+                left: 30,
+            },
+            width = dimension - margin.left - margin.right,
+            height = dimension - margin.top - margin.bottom,
+            binWidth = width / hBins,
+            binHeight = height/ vBins,
+            marginedSpace = svg.append("g")
+                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+        const bins = this.getHeatmapPacking(values, vBins, hBins),
+            maxCount = Math.max(...bins.map(row => {
+                const lengths = row.map(cell => cell.length);
+                return Math.max(...lengths);
+            }));
+
+        const xExtent = d3.extent(values, d => d[0]),
+            yExtent = d3.extent(values, d => d[1]);
+
+        const xAxis = d3.scaleLinear()
+            .domain(xExtent)
+            .range([0, width]);
+        const yAxis = d3.scaleLinear()
+            .domain(yExtent)
+            .range([height, 0]);
+
+        const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour))
+            .domain([0, maxCount]);
+
+        marginedSpace.append("clipPath")
+            .attr("id", "clip")
+            .append("rect")
+            .attr("width", width)
+            .attr("height", height);
+
+        marginedSpace.append("g")
+            .attr("class", "bins")
+            .attr("clip-path", "url(#clip)")
+            .selectAll("g")
+            .data(bins)
+            .enter()
+            .append("g")
+            .selectAll("rect")
+            .data(d => d)
+            .enter()
+            .append("rect")
+            .attr("x", (d) => binWidth * d.x)
+            .attr("y", (d) => (height - binHeight * (d.y + 1)))
+            .attr("width", binWidth)
+            .attr("height", binHeight)
+            .attr("fill", (d) => colour(d.length))
+            .attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none")
+            .attr("stroke-width", drawEdges ? "0.5" : "none")
+            .append("title")
+            .text(d => {
+                const count = d.length,
+                    perc = 100.0 * d.length / values.length,
+                    tooltip = `Count: ${count}\n
+                               Percentage: ${perc.toFixed(2)}%\n
+                    `.replace(/\s{2,}/g, "\n");
+                return tooltip;
+            });
+
+        marginedSpace.append("g")
+            .attr("class", "axis axis--y")
+            .call(d3.axisLeft(yAxis).tickSizeOuter(-width));
+
+        svg.append("text")
+            .attr("transform", "rotate(-90)")
+            .attr("y", -margin.left)
+            .attr("x", -(height / 2))
+            .attr("dy", "1em")
+            .style("text-anchor", "middle")
+            .text(yLabel);
+
+        marginedSpace.append("g")
+            .attr("class", "axis axis--x")
+            .attr("transform", "translate(0," + height + ")")
+            .call(d3.axisBottom(xAxis).tickSizeOuter(-height));
+
+        svg.append("text")
+            .attr("x", width / 2)
+            .attr("y", dimension)
+            .style("text-anchor", "middle")
+            .text(xLabel);
+
+        return svg._groups[0][0].outerHTML;
+    }
+
+    /**
+     * Packs a list of x, y coordinates into a number of bins for use in a heatmap.
+     *
+     * @param {Object[]} points
+     * @param {number} number of vertical bins
+     * @param {number} number of horizontal bins
+     * @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points
+     */
+    getHeatmapPacking(values, vBins, hBins) {
+        const xBounds = d3.extent(values, d => d[0]),
+            yBounds = d3.extent(values, d => d[1]),
+            bins = [];
+
+        if (xBounds[0] === xBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum X coordinate.";
+        if (yBounds[0] === yBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum Y coordinate.";
+
+        for (let y = 0; y < vBins; y++) {
+            bins.push([]);
+            for (let x = 0; x < hBins; x++) {
+                const item = [];
+                item.y = y;
+                item.x = x;
+
+                bins[y].push(item);
+            } // x
+        } // y
+
+        const epsilon = 0.000000001; // This is to clamp values that are exactly the maximum;
+
+        values.forEach(v => {
+            const fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]),
+                fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]),
+                y = Math.floor(vBins * fractionOfY),
+                x = Math.floor(hBins * fractionOfX);
+
+            bins[y][x].push({x: v[0], y: v[1]});
+        });
+
+        return bins;
+    }
+
+}
+
+export default HeatmapChart;

+ 296 - 0
src/core/operations/HexDensityChart.mjs

@@ -0,0 +1,296 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [me@mitt.dev]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import * as d3temp from "d3";
+import * as d3hexbintemp from "d3-hexbin";
+import * as nodomtemp from "nodom";
+import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts";
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+const d3 = d3temp.default ? d3temp.default : d3temp;
+const d3hexbin = d3hexbintemp.default ? d3hexbintemp.default : d3hexbintemp;
+const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;
+
+
+/**
+ * Hex Density chart operation
+ */
+class HexDensityChart extends Operation {
+
+    /**
+     * HexDensityChart constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Hex Density chart";
+        this.module = "Charts";
+        this.description = "Hex density charts are used in a similar way to scatter charts, however rather than rendering tens of thousands of points, it groups the points into a few hundred hexagons to show the distribution.";
+        this.inputType = "string";
+        this.outputType = "html";
+        this.args = [
+            {
+                name: "Record delimiter",
+                type: "option",
+                value: RECORD_DELIMITER_OPTIONS,
+            },
+            {
+                name: "Field delimiter",
+                type: "option",
+                value: FIELD_DELIMITER_OPTIONS,
+            },
+            {
+                name: "Pack radius",
+                type: "number",
+                value: 25,
+            },
+            {
+                name: "Draw radius",
+                type: "number",
+                value: 15,
+            },
+            {
+                name: "Use column headers as labels",
+                type: "boolean",
+                value: true,
+            },
+            {
+                name: "X label",
+                type: "string",
+                value: "",
+            },
+            {
+                name: "Y label",
+                type: "string",
+                value: "",
+            },
+            {
+                name: "Draw hexagon edges",
+                type: "boolean",
+                value: false,
+            },
+            {
+                name: "Min colour value",
+                type: "string",
+                value: COLOURS.min,
+            },
+            {
+                name: "Max colour value",
+                type: "string",
+                value: COLOURS.max,
+            },
+            {
+                name: "Draw empty hexagons within data boundaries",
+                type: "boolean",
+                value: false,
+            }
+        ];
+    }
+
+
+    /**
+     * Hex Bin chart operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    run(input, args) {
+        const recordDelimiter = Utils.charRep(args[0]),
+            fieldDelimiter = Utils.charRep(args[1]),
+            packRadius = args[2],
+            drawRadius = args[3],
+            columnHeadingsAreIncluded = args[4],
+            drawEdges = args[7],
+            minColour = args[8],
+            maxColour = args[9],
+            drawEmptyHexagons = args[10],
+            dimension = 500;
+
+        let xLabel = args[5],
+            yLabel = args[6];
+        const { headings, values } = getScatterValues(
+            input,
+            recordDelimiter,
+            fieldDelimiter,
+            columnHeadingsAreIncluded
+        );
+
+        if (headings) {
+            xLabel = headings.x;
+            yLabel = headings.y;
+        }
+
+        const document = new nodom.Document();
+        let svg = document.createElement("svg");
+        svg = d3.select(svg)
+            .attr("width", "100%")
+            .attr("height", "100%")
+            .attr("viewBox", `0 0 ${dimension} ${dimension}`);
+
+        const margin = {
+                top: 10,
+                right: 0,
+                bottom: 40,
+                left: 30,
+            },
+            width = dimension - margin.left - margin.right,
+            height = dimension - margin.top - margin.bottom,
+            marginedSpace = svg.append("g")
+                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+        const hexbin = d3hexbin.hexbin()
+            .radius(packRadius)
+            .extent([0, 0], [width, height]);
+
+        const hexPoints = hexbin(values),
+            maxCount = Math.max(...hexPoints.map(b => b.length));
+
+        const xExtent = d3.extent(hexPoints, d => d.x),
+            yExtent = d3.extent(hexPoints, d => d.y);
+        xExtent[0] -= 2 * packRadius;
+        xExtent[1] += 3 * packRadius;
+        yExtent[0] -= 2 * packRadius;
+        yExtent[1] += 2 * packRadius;
+
+        const xAxis = d3.scaleLinear()
+            .domain(xExtent)
+            .range([0, width]);
+        const yAxis = d3.scaleLinear()
+            .domain(yExtent)
+            .range([height, 0]);
+
+        const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour))
+            .domain([0, maxCount]);
+
+        marginedSpace.append("clipPath")
+            .attr("id", "clip")
+            .append("rect")
+            .attr("width", width)
+            .attr("height", height);
+
+        if (drawEmptyHexagons) {
+            marginedSpace.append("g")
+                .attr("class", "empty-hexagon")
+                .selectAll("path")
+                .data(this.getEmptyHexagons(hexPoints, packRadius))
+                .enter()
+                .append("path")
+                .attr("d", d => {
+                    return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`;
+                })
+                .attr("fill", (d) => colour(0))
+                .attr("stroke", drawEdges ? "black" : "none")
+                .attr("stroke-width", drawEdges ? "0.5" : "none")
+                .append("title")
+                .text(d => {
+                    const count = 0,
+                        perc = 0,
+                        tooltip = `Count: ${count}\n
+                                Percentage: ${perc.toFixed(2)}%\n
+                                Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n
+                        `.replace(/\s{2,}/g, "\n");
+                    return tooltip;
+                });
+        }
+
+        marginedSpace.append("g")
+            .attr("class", "hexagon")
+            .attr("clip-path", "url(#clip)")
+            .selectAll("path")
+            .data(hexPoints)
+            .enter()
+            .append("path")
+            .attr("d", d => {
+                return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`;
+            })
+            .attr("fill", (d) => colour(d.length))
+            .attr("stroke", drawEdges ? "black" : "none")
+            .attr("stroke-width", drawEdges ? "0.5" : "none")
+            .append("title")
+            .text(d => {
+                const count = d.length,
+                    perc = 100.0 * d.length / values.length,
+                    CX = d.x,
+                    CY = d.y,
+                    xMin = Math.min(...d.map(d => d[0])),
+                    xMax = Math.max(...d.map(d => d[0])),
+                    yMin = Math.min(...d.map(d => d[1])),
+                    yMax = Math.max(...d.map(d => d[1])),
+                    tooltip = `Count: ${count}\n
+                               Percentage: ${perc.toFixed(2)}%\n
+                               Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n
+                               Min X: ${xMin.toFixed(2)}\n
+                               Max X: ${xMax.toFixed(2)}\n
+                               Min Y: ${yMin.toFixed(2)}\n
+                               Max Y: ${yMax.toFixed(2)}
+                    `.replace(/\s{2,}/g, "\n");
+                return tooltip;
+            });
+
+        marginedSpace.append("g")
+            .attr("class", "axis axis--y")
+            .call(d3.axisLeft(yAxis).tickSizeOuter(-width));
+
+        svg.append("text")
+            .attr("transform", "rotate(-90)")
+            .attr("y", -margin.left)
+            .attr("x", -(height / 2))
+            .attr("dy", "1em")
+            .style("text-anchor", "middle")
+            .text(yLabel);
+
+        marginedSpace.append("g")
+            .attr("class", "axis axis--x")
+            .attr("transform", "translate(0," + height + ")")
+            .call(d3.axisBottom(xAxis).tickSizeOuter(-height));
+
+        svg.append("text")
+            .attr("x", width / 2)
+            .attr("y", dimension)
+            .style("text-anchor", "middle")
+            .text(xLabel);
+
+        return svg._groups[0][0].outerHTML;
+    }
+
+
+    /**
+     * Hex Bin chart operation.
+     *
+     * @param {Object[]} - centres
+     * @param {number} - radius
+     * @returns {Object[]}
+     */
+    getEmptyHexagons(centres, radius) {
+        const emptyCentres = [],
+            boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)],
+            hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius,
+            hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius;
+        let indent = false;
+
+        for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) {
+            for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) {
+                let cx = x;
+                const cy = y;
+
+                if (indent && x >= boundingRect[0][1]) break;
+                if (indent) cx += hexagonCenterToEdge;
+
+                emptyCentres.push({x: cx, y: cy});
+            }
+            indent = !indent;
+        }
+
+        return emptyCentres;
+    }
+
+}
+
+export default HexDensityChart;

+ 1 - 1
src/core/operations/JavaScriptParser.mjs

@@ -21,7 +21,7 @@ class JavaScriptParser extends Operation {
         this.name = "JavaScript Parser";
         this.name = "JavaScript Parser";
         this.module = "Code";
         this.module = "Code";
         this.description = "Returns an Abstract Syntax Tree for valid JavaScript code.";
         this.description = "Returns an Abstract Syntax Tree for valid JavaScript code.";
-        this.infoURL = "https://en.wikipedia.org/wiki/Abstract_syntax_tree";
+        this.infoURL = "https://wikipedia.org/wiki/Abstract_syntax_tree";
         this.inputType = "string";
         this.inputType = "string";
         this.outputType = "string";
         this.outputType = "string";
         this.args = [
         this.args = [

+ 1 - 1
src/core/operations/PEMToHex.mjs

@@ -21,7 +21,7 @@ class PEMToHex extends Operation {
         this.name = "PEM to Hex";
         this.name = "PEM to Hex";
         this.module = "PublicKey";
         this.module = "PublicKey";
         this.description = "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string.";
         this.description = "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string.";
-        this.infoURL = "https://en.wikipedia.org/wiki/X.690#DER_encoding";
+        this.infoURL = "https://wikipedia.org/wiki/X.690#DER_encoding";
         this.inputType = "string";
         this.inputType = "string";
         this.outputType = "string";
         this.outputType = "string";
         this.args = [];
         this.args = [];

+ 199 - 0
src/core/operations/ScatterChart.mjs

@@ -0,0 +1,199 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [me@mitt.dev]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import * as d3temp from "d3";
+import * as nodomtemp from "nodom";
+import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts";
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+const d3 = d3temp.default ? d3temp.default : d3temp;
+const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;
+
+/**
+ * Scatter chart operation
+ */
+class ScatterChart extends Operation {
+
+    /**
+     * ScatterChart constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Scatter chart";
+        this.module = "Charts";
+        this.description = "Plots two-variable data as single points on a graph.";
+        this.infoURL = "https://wikipedia.org/wiki/Scatter_plot";
+        this.inputType = "string";
+        this.outputType = "html";
+        this.args = [
+            {
+                name: "Record delimiter",
+                type: "option",
+                value: RECORD_DELIMITER_OPTIONS,
+            },
+            {
+                name: "Field delimiter",
+                type: "option",
+                value: FIELD_DELIMITER_OPTIONS,
+            },
+            {
+                name: "Use column headers as labels",
+                type: "boolean",
+                value: true,
+            },
+            {
+                name: "X label",
+                type: "string",
+                value: "",
+            },
+            {
+                name: "Y label",
+                type: "string",
+                value: "",
+            },
+            {
+                name: "Colour",
+                type: "string",
+                value: COLOURS.max,
+            },
+            {
+                name: "Point radius",
+                type: "number",
+                value: 10,
+            },
+            {
+                name: "Use colour from third column",
+                type: "boolean",
+                value: false,
+            }
+        ];
+    }
+
+    /**
+     * Scatter chart operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    run(input, args) {
+        const recordDelimiter = Utils.charRep(args[0]),
+            fieldDelimiter = Utils.charRep(args[1]),
+            columnHeadingsAreIncluded = args[2],
+            fillColour = args[5],
+            radius = args[6],
+            colourInInput = args[7],
+            dimension = 500;
+
+        let xLabel = args[3],
+            yLabel = args[4];
+
+        const dataFunction = colourInInput ? getScatterValuesWithColour : getScatterValues;
+        const { headings, values } = dataFunction(
+            input,
+            recordDelimiter,
+            fieldDelimiter,
+            columnHeadingsAreIncluded
+        );
+
+        if (headings) {
+            xLabel = headings.x;
+            yLabel = headings.y;
+        }
+
+        const document = new nodom.Document();
+        let svg = document.createElement("svg");
+        svg = d3.select(svg)
+            .attr("width", "100%")
+            .attr("height", "100%")
+            .attr("viewBox", `0 0 ${dimension} ${dimension}`);
+
+        const margin = {
+                top: 10,
+                right: 0,
+                bottom: 40,
+                left: 30,
+            },
+            width = dimension - margin.left - margin.right,
+            height = dimension - margin.top - margin.bottom,
+            marginedSpace = svg.append("g")
+                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+        const xExtent = d3.extent(values, d => d[0]),
+            xDelta = xExtent[1] - xExtent[0],
+            yExtent = d3.extent(values, d => d[1]),
+            yDelta = yExtent[1] - yExtent[0],
+            xAxis = d3.scaleLinear()
+                .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)])
+                .range([0, width]),
+            yAxis = d3.scaleLinear()
+                .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)])
+                .range([height, 0]);
+
+        marginedSpace.append("clipPath")
+            .attr("id", "clip")
+            .append("rect")
+            .attr("width", width)
+            .attr("height", height);
+
+        marginedSpace.append("g")
+            .attr("class", "points")
+            .attr("clip-path", "url(#clip)")
+            .selectAll("circle")
+            .data(values)
+            .enter()
+            .append("circle")
+            .attr("cx", (d) => xAxis(d[0]))
+            .attr("cy", (d) => yAxis(d[1]))
+            .attr("r", d => radius)
+            .attr("fill", d => {
+                return colourInInput ? d[2] : fillColour;
+            })
+            .attr("stroke", "rgba(0, 0, 0, 0.5)")
+            .attr("stroke-width", "0.5")
+            .append("title")
+            .text(d => {
+                const x = d[0],
+                    y = d[1],
+                    tooltip = `X: ${x}\n
+                               Y: ${y}\n
+                    `.replace(/\s{2,}/g, "\n");
+                return tooltip;
+            });
+
+        marginedSpace.append("g")
+            .attr("class", "axis axis--y")
+            .call(d3.axisLeft(yAxis).tickSizeOuter(-width));
+
+        svg.append("text")
+            .attr("transform", "rotate(-90)")
+            .attr("y", -margin.left)
+            .attr("x", -(height / 2))
+            .attr("dy", "1em")
+            .style("text-anchor", "middle")
+            .text(yLabel);
+
+        marginedSpace.append("g")
+            .attr("class", "axis axis--x")
+            .attr("transform", "translate(0," + height + ")")
+            .call(d3.axisBottom(xAxis).tickSizeOuter(-height));
+
+        svg.append("text")
+            .attr("x", width / 2)
+            .attr("y", dimension)
+            .style("text-anchor", "middle")
+            .text(xLabel);
+
+        return svg._groups[0][0].outerHTML;
+    }
+
+}
+
+export default ScatterChart;

+ 227 - 0
src/core/operations/SeriesChart.mjs

@@ -0,0 +1,227 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [me@mitt.dev]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import * as d3temp from "d3";
+import * as nodomtemp from "nodom";
+import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts";
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+const d3 = d3temp.default ? d3temp.default : d3temp;
+const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;
+
+/**
+ * Series chart operation
+ */
+class SeriesChart extends Operation {
+
+    /**
+     * SeriesChart constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Series chart";
+        this.module = "Charts";
+        this.description = "A time series graph is a line graph of repeated measurements taken over regular time intervals.";
+        this.inputType = "string";
+        this.outputType = "html";
+        this.args = [
+            {
+                name: "Record delimiter",
+                type: "option",
+                value: RECORD_DELIMITER_OPTIONS,
+            },
+            {
+                name: "Field delimiter",
+                type: "option",
+                value: FIELD_DELIMITER_OPTIONS,
+            },
+            {
+                name: "X label",
+                type: "string",
+                value: "",
+            },
+            {
+                name: "Point radius",
+                type: "number",
+                value: 1,
+            },
+            {
+                name: "Series colours",
+                type: "string",
+                value: "mediumseagreen, dodgerblue, tomato",
+            },
+        ];
+    }
+
+    /**
+     * Series chart operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    run(input, args) {
+        const recordDelimiter = Utils.charRep(args[0]),
+            fieldDelimiter = Utils.charRep(args[1]),
+            xLabel = args[2],
+            pipRadius = args[3],
+            seriesColours = args[4].split(","),
+            svgWidth = 500,
+            interSeriesPadding = 20,
+            xAxisHeight = 50,
+            seriesLabelWidth = 50,
+            seriesHeight = 100,
+            seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding;
+
+        const { xValues, series } = getSeriesValues(input, recordDelimiter, fieldDelimiter),
+            allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight),
+            svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding;
+
+        const document = new nodom.Document();
+        let svg = document.createElement("svg");
+        svg = d3.select(svg)
+            .attr("width", "100%")
+            .attr("height", "100%")
+            .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
+
+        const xAxis = d3.scalePoint()
+            .domain(xValues)
+            .range([0, seriesWidth]);
+
+        svg.append("g")
+            .attr("class", "axis axis--x")
+            .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`)
+            .call(
+                d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => {
+                    return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0;
+                }))
+            );
+
+        svg.append("text")
+            .attr("x", svgWidth / 2)
+            .attr("y", xAxisHeight / 2)
+            .style("text-anchor", "middle")
+            .text(xLabel);
+
+        const tooltipText = {},
+            tooltipAreaWidth = seriesWidth / xValues.length;
+
+        xValues.forEach(x => {
+            const tooltip = [];
+
+            series.forEach(serie => {
+                const y = serie.data[x];
+                if (typeof y === "undefined") return;
+
+                tooltip.push(`${serie.name}: ${y}`);
+            });
+
+            tooltipText[x] = tooltip.join("\n");
+        });
+
+        const chartArea = svg.append("g")
+            .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`);
+
+        chartArea
+            .append("g")
+            .selectAll("rect")
+            .data(xValues)
+            .enter()
+            .append("rect")
+            .attr("x", x => {
+                return xAxis(x) - (tooltipAreaWidth / 2);
+            })
+            .attr("y", 0)
+            .attr("width", tooltipAreaWidth)
+            .attr("height", allSeriesHeight)
+            .attr("stroke", "none")
+            .attr("fill", "transparent")
+            .append("title")
+            .text(x => {
+                return `${x}\n
+                    --\n
+                    ${tooltipText[x]}\n
+                `.replace(/\s{2,}/g, "\n");
+            });
+
+        const yAxesArea = svg.append("g")
+            .attr("transform", `translate(0, ${xAxisHeight})`);
+
+        series.forEach((serie, seriesIndex) => {
+            const yExtent = d3.extent(Object.values(serie.data)),
+                yAxis = d3.scaleLinear()
+                    .domain(yExtent)
+                    .range([seriesHeight, 0]);
+
+            const seriesGroup = chartArea
+                .append("g")
+                .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`);
+
+            let path = "";
+            xValues.forEach((x, xIndex) => {
+                let nextX = xValues[xIndex + 1],
+                    y = serie.data[x],
+                    nextY= serie.data[nextX];
+
+                if (typeof y === "undefined" || typeof nextY === "undefined") return;
+
+                x = xAxis(x); nextX = xAxis(nextX);
+                y = yAxis(y); nextY = yAxis(nextY);
+
+                path += `M ${x} ${y} L ${nextX} ${nextY} z `;
+            });
+
+            seriesGroup
+                .append("path")
+                .attr("d", path)
+                .attr("fill", "none")
+                .attr("stroke", seriesColours[seriesIndex % seriesColours.length])
+                .attr("stroke-width", "1");
+
+            xValues.forEach(x => {
+                const y = serie.data[x];
+                if (typeof y === "undefined") return;
+
+                seriesGroup
+                    .append("circle")
+                    .attr("cx", xAxis(x))
+                    .attr("cy", yAxis(y))
+                    .attr("r", pipRadius)
+                    .attr("fill", seriesColours[seriesIndex % seriesColours.length])
+                    .append("title")
+                    .text(d => {
+                        return `${x}\n
+                            --\n
+                            ${tooltipText[x]}\n
+                        `.replace(/\s{2,}/g, "\n");
+                    });
+            });
+
+            yAxesArea
+                .append("g")
+                .attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)
+                .attr("class", "axis axis--y")
+                .call(d3.axisLeft(yAxis).ticks(5));
+
+            yAxesArea
+                .append("g")
+                .attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`)
+                .append("text")
+                .style("text-anchor", "middle")
+                .attr("transform", "rotate(-90)")
+                .text(serie.name);
+        });
+
+        return svg._groups[0][0].outerHTML;
+    }
+
+}
+
+export default SeriesChart;

+ 10 - 0
src/node/File.mjs

@@ -27,10 +27,20 @@ class File {
      * @param {Object} stats (optional) - file stats e.g. lastModified
      * @param {Object} stats (optional) - file stats e.g. lastModified
      */
      */
     constructor(data, name="", stats={}) {
     constructor(data, name="", stats={}) {
+
+        if (!Array.isArray(data)) {
+            data = [data];
+        }
+
         const buffers = data.map((d) => {
         const buffers = data.map((d) => {
             if (d instanceof File) {
             if (d instanceof File) {
                 return Buffer.from(d.data);
                 return Buffer.from(d.data);
             }
             }
+
+            if (d instanceof ArrayBuffer) {
+                return Buffer.from(d);
+            }
+
             return Buffer.from(d);
             return Buffer.from(d);
         });
         });
         const totalLength = buffers.reduce((p, c) => p + c.length, 0);
         const totalLength = buffers.reduce((p, c) => p + c.length, 0);

+ 1607 - 0
src/node/index.mjs

@@ -0,0 +1,1607 @@
+/**
+* THIS FILE IS AUTOMATICALLY GENERATED BY src/node/config/scripts/generateNodeIndex.mjs
+*
+* @author d98762625 [d98762625@gmail.com]
+* @copyright Crown Copyright 2019
+* @license Apache-2.0
+*/
+
+/* eslint camelcase: 0 */
+
+
+import "babel-polyfill";
+import NodeDish from "./NodeDish";
+import { _wrap, help, bake, _explainExludedFunction } from "./api";
+import {
+    // import as core_ to avoid name clashes after wrap.
+    A1Z26CipherDecode as core_A1Z26CipherDecode,
+    A1Z26CipherEncode as core_A1Z26CipherEncode,
+    ADD as core_ADD,
+    AESDecrypt as core_AESDecrypt,
+    AESEncrypt as core_AESEncrypt,
+    AND as core_AND,
+    AddLineNumbers as core_AddLineNumbers,
+    Adler32Checksum as core_Adler32Checksum,
+    AffineCipherDecode as core_AffineCipherDecode,
+    AffineCipherEncode as core_AffineCipherEncode,
+    AnalyseHash as core_AnalyseHash,
+    AtbashCipher as core_AtbashCipher,
+    BLAKE2b as core_BLAKE2b,
+    BLAKE2s as core_BLAKE2s,
+    BSONDeserialise as core_BSONDeserialise,
+    BSONSerialise as core_BSONSerialise,
+    Bcrypt as core_Bcrypt,
+    BcryptCompare as core_BcryptCompare,
+    BcryptParse as core_BcryptParse,
+    BifidCipherDecode as core_BifidCipherDecode,
+    BifidCipherEncode as core_BifidCipherEncode,
+    BitShiftLeft as core_BitShiftLeft,
+    BitShiftRight as core_BitShiftRight,
+    BlowfishDecrypt as core_BlowfishDecrypt,
+    BlowfishEncrypt as core_BlowfishEncrypt,
+    BlurImage as core_BlurImage,
+    Bombe as core_Bombe,
+    Bzip2Decompress as core_Bzip2Decompress,
+    CRC16Checksum as core_CRC16Checksum,
+    CRC32Checksum as core_CRC32Checksum,
+    CSSBeautify as core_CSSBeautify,
+    CSSMinify as core_CSSMinify,
+    CSSSelector as core_CSSSelector,
+    CSVToJSON as core_CSVToJSON,
+    CTPH as core_CTPH,
+    CartesianProduct as core_CartesianProduct,
+    ChangeIPFormat as core_ChangeIPFormat,
+    ChiSquare as core_ChiSquare,
+    CitrixCTX1Decode as core_CitrixCTX1Decode,
+    CitrixCTX1Encode as core_CitrixCTX1Encode,
+    CompareCTPHHashes as core_CompareCTPHHashes,
+    CompareSSDEEPHashes as core_CompareSSDEEPHashes,
+    ContainImage as core_ContainImage,
+    ConvertArea as core_ConvertArea,
+    ConvertCoordinateFormat as core_ConvertCoordinateFormat,
+    ConvertDataUnits as core_ConvertDataUnits,
+    ConvertDistance as core_ConvertDistance,
+    ConvertMass as core_ConvertMass,
+    ConvertSpeed as core_ConvertSpeed,
+    CountOccurrences as core_CountOccurrences,
+    CoverImage as core_CoverImage,
+    CropImage as core_CropImage,
+    DESDecrypt as core_DESDecrypt,
+    DESEncrypt as core_DESEncrypt,
+    DNSOverHTTPS as core_DNSOverHTTPS,
+    DechunkHTTPResponse as core_DechunkHTTPResponse,
+    DecodeNetBIOSName as core_DecodeNetBIOSName,
+    DecodeText as core_DecodeText,
+    DefangURL as core_DefangURL,
+    DeriveEVPKey as core_DeriveEVPKey,
+    DerivePBKDF2Key as core_DerivePBKDF2Key,
+    Diff as core_Diff,
+    DisassembleX86 as core_DisassembleX86,
+    DitherImage as core_DitherImage,
+    Divide as core_Divide,
+    DropBytes as core_DropBytes,
+    EncodeNetBIOSName as core_EncodeNetBIOSName,
+    EncodeText as core_EncodeText,
+    Enigma as core_Enigma,
+    Entropy as core_Entropy,
+    EscapeString as core_EscapeString,
+    EscapeUnicodeCharacters as core_EscapeUnicodeCharacters,
+    ExpandAlphabetRange as core_ExpandAlphabetRange,
+    ExtractDates as core_ExtractDates,
+    ExtractDomains as core_ExtractDomains,
+    ExtractEXIF as core_ExtractEXIF,
+    ExtractEmailAddresses as core_ExtractEmailAddresses,
+    ExtractFilePaths as core_ExtractFilePaths,
+    ExtractFiles as core_ExtractFiles,
+    ExtractIPAddresses as core_ExtractIPAddresses,
+    ExtractMACAddresses as core_ExtractMACAddresses,
+    ExtractURLs as core_ExtractURLs,
+    Filter as core_Filter,
+    FindReplace as core_FindReplace,
+    Fletcher16Checksum as core_Fletcher16Checksum,
+    Fletcher32Checksum as core_Fletcher32Checksum,
+    Fletcher64Checksum as core_Fletcher64Checksum,
+    Fletcher8Checksum as core_Fletcher8Checksum,
+    FlipImage as core_FlipImage,
+    FormatMACAddresses as core_FormatMACAddresses,
+    FrequencyDistribution as core_FrequencyDistribution,
+    FromBCD as core_FromBCD,
+    FromBase as core_FromBase,
+    FromBase32 as core_FromBase32,
+    FromBase58 as core_FromBase58,
+    FromBase62 as core_FromBase62,
+    FromBase64 as core_FromBase64,
+    FromBase85 as core_FromBase85,
+    FromBinary as core_FromBinary,
+    FromBraille as core_FromBraille,
+    FromCaseInsensitiveRegex as core_FromCaseInsensitiveRegex,
+    FromCharcode as core_FromCharcode,
+    FromDecimal as core_FromDecimal,
+    FromHTMLEntity as core_FromHTMLEntity,
+    FromHex as core_FromHex,
+    FromHexContent as core_FromHexContent,
+    FromHexdump as core_FromHexdump,
+    FromMessagePack as core_FromMessagePack,
+    FromMorseCode as core_FromMorseCode,
+    FromOctal as core_FromOctal,
+    FromPunycode as core_FromPunycode,
+    FromQuotedPrintable as core_FromQuotedPrintable,
+    FromUNIXTimestamp as core_FromUNIXTimestamp,
+    GenerateAllHashes as core_GenerateAllHashes,
+    GenerateHOTP as core_GenerateHOTP,
+    GenerateLoremIpsum as core_GenerateLoremIpsum,
+    GeneratePGPKeyPair as core_GeneratePGPKeyPair,
+    GenerateQRCode as core_GenerateQRCode,
+    GenerateTOTP as core_GenerateTOTP,
+    GenerateUUID as core_GenerateUUID,
+    GenericCodeBeautify as core_GenericCodeBeautify,
+    GroupIPAddresses as core_GroupIPAddresses,
+    Gunzip as core_Gunzip,
+    Gzip as core_Gzip,
+    HAS160 as core_HAS160,
+    HMAC as core_HMAC,
+    HTMLToText as core_HTMLToText,
+    HTTPRequest as core_HTTPRequest,
+    HammingDistance as core_HammingDistance,
+    HaversineDistance as core_HaversineDistance,
+    Head as core_Head,
+    HeatmapChart as core_HeatmapChart,
+    HexDensityChart as core_HexDensityChart,
+    HexToObjectIdentifier as core_HexToObjectIdentifier,
+    HexToPEM as core_HexToPEM,
+    ImageBrightnessContrast as core_ImageBrightnessContrast,
+    ImageFilter as core_ImageFilter,
+    ImageHueSaturationLightness as core_ImageHueSaturationLightness,
+    ImageOpacity as core_ImageOpacity,
+    InvertImage as core_InvertImage,
+    JPathExpression as core_JPathExpression,
+    JSONBeautify as core_JSONBeautify,
+    JSONMinify as core_JSONMinify,
+    JSONToCSV as core_JSONToCSV,
+    JWTDecode as core_JWTDecode,
+    JWTSign as core_JWTSign,
+    JWTVerify as core_JWTVerify,
+    Keccak as core_Keccak,
+    MD2 as core_MD2,
+    MD4 as core_MD4,
+    MD5 as core_MD5,
+    MD6 as core_MD6,
+    Magic as core_Magic,
+    Mean as core_Mean,
+    Median as core_Median,
+    MicrosoftScriptDecoder as core_MicrosoftScriptDecoder,
+    MultipleBombe as core_MultipleBombe,
+    Multiply as core_Multiply,
+    NOT as core_NOT,
+    NormaliseImage as core_NormaliseImage,
+    Numberwang as core_Numberwang,
+    OR as core_OR,
+    ObjectIdentifierToHex as core_ObjectIdentifierToHex,
+    OffsetChecker as core_OffsetChecker,
+    PEMToHex as core_PEMToHex,
+    PGPDecrypt as core_PGPDecrypt,
+    PGPDecryptAndVerify as core_PGPDecryptAndVerify,
+    PGPEncrypt as core_PGPEncrypt,
+    PGPEncryptAndSign as core_PGPEncryptAndSign,
+    PHPDeserialize as core_PHPDeserialize,
+    PadLines as core_PadLines,
+    ParseASN1HexString as core_ParseASN1HexString,
+    ParseColourCode as core_ParseColourCode,
+    ParseDateTime as core_ParseDateTime,
+    ParseIPRange as core_ParseIPRange,
+    ParseIPv4Header as core_ParseIPv4Header,
+    ParseIPv6Address as core_ParseIPv6Address,
+    ParseQRCode as core_ParseQRCode,
+    ParseTLV as core_ParseTLV,
+    ParseUNIXFilePermissions as core_ParseUNIXFilePermissions,
+    ParseURI as core_ParseURI,
+    ParseUserAgent as core_ParseUserAgent,
+    ParseX509Certificate as core_ParseX509Certificate,
+    PlayMedia as core_PlayMedia,
+    PowerSet as core_PowerSet,
+    PseudoRandomNumberGenerator as core_PseudoRandomNumberGenerator,
+    RC2Decrypt as core_RC2Decrypt,
+    RC2Encrypt as core_RC2Encrypt,
+    RC4 as core_RC4,
+    RC4Drop as core_RC4Drop,
+    RIPEMD as core_RIPEMD,
+    ROT13 as core_ROT13,
+    ROT47 as core_ROT47,
+    RawDeflate as core_RawDeflate,
+    RawInflate as core_RawInflate,
+    Register as core_Register,
+    RegularExpression as core_RegularExpression,
+    RemoveDiacritics as core_RemoveDiacritics,
+    RemoveEXIF as core_RemoveEXIF,
+    RemoveLineNumbers as core_RemoveLineNumbers,
+    RemoveNullBytes as core_RemoveNullBytes,
+    RemoveWhitespace as core_RemoveWhitespace,
+    ResizeImage as core_ResizeImage,
+    Return as core_Return,
+    Reverse as core_Reverse,
+    RotateImage as core_RotateImage,
+    RotateLeft as core_RotateLeft,
+    RotateRight as core_RotateRight,
+    SHA0 as core_SHA0,
+    SHA1 as core_SHA1,
+    SHA2 as core_SHA2,
+    SHA3 as core_SHA3,
+    SQLBeautify as core_SQLBeautify,
+    SQLMinify as core_SQLMinify,
+    SSDEEP as core_SSDEEP,
+    SUB as core_SUB,
+    ScanForEmbeddedFiles as core_ScanForEmbeddedFiles,
+    ScatterChart as core_ScatterChart,
+    Scrypt as core_Scrypt,
+    SeriesChart as core_SeriesChart,
+    SetDifference as core_SetDifference,
+    SetIntersection as core_SetIntersection,
+    SetUnion as core_SetUnion,
+    Shake as core_Shake,
+    ShowBase64Offsets as core_ShowBase64Offsets,
+    Sleep as core_Sleep,
+    Snefru as core_Snefru,
+    Sort as core_Sort,
+    Split as core_Split,
+    SplitColourChannels as core_SplitColourChannels,
+    StandardDeviation as core_StandardDeviation,
+    Strings as core_Strings,
+    StripHTMLTags as core_StripHTMLTags,
+    StripHTTPHeaders as core_StripHTTPHeaders,
+    Subsection as core_Subsection,
+    Substitute as core_Substitute,
+    Subtract as core_Subtract,
+    Sum as core_Sum,
+    SwapEndianness as core_SwapEndianness,
+    SymmetricDifference as core_SymmetricDifference,
+    TCPIPChecksum as core_TCPIPChecksum,
+    Tail as core_Tail,
+    TakeBytes as core_TakeBytes,
+    Tar as core_Tar,
+    TextEncodingBruteForce as core_TextEncodingBruteForce,
+    ToBCD as core_ToBCD,
+    ToBase as core_ToBase,
+    ToBase32 as core_ToBase32,
+    ToBase58 as core_ToBase58,
+    ToBase62 as core_ToBase62,
+    ToBase64 as core_ToBase64,
+    ToBase85 as core_ToBase85,
+    ToBinary as core_ToBinary,
+    ToBraille as core_ToBraille,
+    ToCamelCase as core_ToCamelCase,
+    ToCaseInsensitiveRegex as core_ToCaseInsensitiveRegex,
+    ToCharcode as core_ToCharcode,
+    ToDecimal as core_ToDecimal,
+    ToHTMLEntity as core_ToHTMLEntity,
+    ToHex as core_ToHex,
+    ToHexContent as core_ToHexContent,
+    ToHexdump as core_ToHexdump,
+    ToKebabCase as core_ToKebabCase,
+    ToLowerCase as core_ToLowerCase,
+    ToMessagePack as core_ToMessagePack,
+    ToMorseCode as core_ToMorseCode,
+    ToOctal as core_ToOctal,
+    ToPunycode as core_ToPunycode,
+    ToQuotedPrintable as core_ToQuotedPrintable,
+    ToSnakeCase as core_ToSnakeCase,
+    ToTable as core_ToTable,
+    ToUNIXTimestamp as core_ToUNIXTimestamp,
+    ToUpperCase as core_ToUpperCase,
+    TranslateDateTimeFormat as core_TranslateDateTimeFormat,
+    TripleDESDecrypt as core_TripleDESDecrypt,
+    TripleDESEncrypt as core_TripleDESEncrypt,
+    Typex as core_Typex,
+    UNIXTimestampToWindowsFiletime as core_UNIXTimestampToWindowsFiletime,
+    URLDecode as core_URLDecode,
+    URLEncode as core_URLEncode,
+    UnescapeString as core_UnescapeString,
+    UnescapeUnicodeCharacters as core_UnescapeUnicodeCharacters,
+    Unique as core_Unique,
+    Untar as core_Untar,
+    Unzip as core_Unzip,
+    VigenèreDecode as core_VigenèreDecode,
+    VigenèreEncode as core_VigenèreEncode,
+    Whirlpool as core_Whirlpool,
+    WindowsFiletimeToUNIXTimestamp as core_WindowsFiletimeToUNIXTimestamp,
+    XKCDRandomNumber as core_XKCDRandomNumber,
+    XMLBeautify as core_XMLBeautify,
+    XMLMinify as core_XMLMinify,
+    XOR as core_XOR,
+    XORBruteForce as core_XORBruteForce,
+    XPathExpression as core_XPathExpression,
+    YARARules as core_YARARules,
+    Zip as core_Zip,
+    ZlibDeflate as core_ZlibDeflate,
+    ZlibInflate as core_ZlibInflate,
+
+} from "../core/operations/index";
+
+// Define global environment functions
+global.ENVIRONMENT_IS_WORKER = function() {
+    return typeof importScripts === "function";
+};
+global.ENVIRONMENT_IS_NODE = function() {
+    return typeof process === "object" && typeof require === "function";
+};
+global.ENVIRONMENT_IS_WEB = function() {
+    return typeof window === "object";
+};
+
+/**
+ * generateChef
+ *
+ * Creates decapitalised, wrapped ops in chef object for default export.
+ */
+function generateChef() {
+    return {
+        "A1Z26CipherDecode": _wrap(core_A1Z26CipherDecode),
+        "A1Z26CipherEncode": _wrap(core_A1Z26CipherEncode),
+        "ADD": _wrap(core_ADD),
+        "AESDecrypt": _wrap(core_AESDecrypt),
+        "AESEncrypt": _wrap(core_AESEncrypt),
+        "AND": _wrap(core_AND),
+        "addLineNumbers": _wrap(core_AddLineNumbers),
+        "adler32Checksum": _wrap(core_Adler32Checksum),
+        "affineCipherDecode": _wrap(core_AffineCipherDecode),
+        "affineCipherEncode": _wrap(core_AffineCipherEncode),
+        "analyseHash": _wrap(core_AnalyseHash),
+        "atbashCipher": _wrap(core_AtbashCipher),
+        "BLAKE2b": _wrap(core_BLAKE2b),
+        "BLAKE2s": _wrap(core_BLAKE2s),
+        "BSONDeserialise": _wrap(core_BSONDeserialise),
+        "BSONSerialise": _wrap(core_BSONSerialise),
+        "bcrypt": _wrap(core_Bcrypt),
+        "bcryptCompare": _wrap(core_BcryptCompare),
+        "bcryptParse": _wrap(core_BcryptParse),
+        "bifidCipherDecode": _wrap(core_BifidCipherDecode),
+        "bifidCipherEncode": _wrap(core_BifidCipherEncode),
+        "bitShiftLeft": _wrap(core_BitShiftLeft),
+        "bitShiftRight": _wrap(core_BitShiftRight),
+        "blowfishDecrypt": _wrap(core_BlowfishDecrypt),
+        "blowfishEncrypt": _wrap(core_BlowfishEncrypt),
+        "blurImage": _wrap(core_BlurImage),
+        "bombe": _wrap(core_Bombe),
+        "bzip2Decompress": _wrap(core_Bzip2Decompress),
+        "CRC16Checksum": _wrap(core_CRC16Checksum),
+        "CRC32Checksum": _wrap(core_CRC32Checksum),
+        "CSSBeautify": _wrap(core_CSSBeautify),
+        "CSSMinify": _wrap(core_CSSMinify),
+        "CSSSelector": _wrap(core_CSSSelector),
+        "CSVToJSON": _wrap(core_CSVToJSON),
+        "CTPH": _wrap(core_CTPH),
+        "cartesianProduct": _wrap(core_CartesianProduct),
+        "changeIPFormat": _wrap(core_ChangeIPFormat),
+        "chiSquare": _wrap(core_ChiSquare),
+        "citrixCTX1Decode": _wrap(core_CitrixCTX1Decode),
+        "citrixCTX1Encode": _wrap(core_CitrixCTX1Encode),
+        "compareCTPHHashes": _wrap(core_CompareCTPHHashes),
+        "compareSSDEEPHashes": _wrap(core_CompareSSDEEPHashes),
+        "containImage": _wrap(core_ContainImage),
+        "convertArea": _wrap(core_ConvertArea),
+        "convertCoordinateFormat": _wrap(core_ConvertCoordinateFormat),
+        "convertDataUnits": _wrap(core_ConvertDataUnits),
+        "convertDistance": _wrap(core_ConvertDistance),
+        "convertMass": _wrap(core_ConvertMass),
+        "convertSpeed": _wrap(core_ConvertSpeed),
+        "countOccurrences": _wrap(core_CountOccurrences),
+        "coverImage": _wrap(core_CoverImage),
+        "cropImage": _wrap(core_CropImage),
+        "DESDecrypt": _wrap(core_DESDecrypt),
+        "DESEncrypt": _wrap(core_DESEncrypt),
+        "DNSOverHTTPS": _wrap(core_DNSOverHTTPS),
+        "dechunkHTTPResponse": _wrap(core_DechunkHTTPResponse),
+        "decodeNetBIOSName": _wrap(core_DecodeNetBIOSName),
+        "decodeText": _wrap(core_DecodeText),
+        "defangURL": _wrap(core_DefangURL),
+        "deriveEVPKey": _wrap(core_DeriveEVPKey),
+        "derivePBKDF2Key": _wrap(core_DerivePBKDF2Key),
+        "diff": _wrap(core_Diff),
+        "disassembleX86": _wrap(core_DisassembleX86),
+        "ditherImage": _wrap(core_DitherImage),
+        "divide": _wrap(core_Divide),
+        "dropBytes": _wrap(core_DropBytes),
+        "encodeNetBIOSName": _wrap(core_EncodeNetBIOSName),
+        "encodeText": _wrap(core_EncodeText),
+        "enigma": _wrap(core_Enigma),
+        "entropy": _wrap(core_Entropy),
+        "escapeString": _wrap(core_EscapeString),
+        "escapeUnicodeCharacters": _wrap(core_EscapeUnicodeCharacters),
+        "expandAlphabetRange": _wrap(core_ExpandAlphabetRange),
+        "extractDates": _wrap(core_ExtractDates),
+        "extractDomains": _wrap(core_ExtractDomains),
+        "extractEXIF": _wrap(core_ExtractEXIF),
+        "extractEmailAddresses": _wrap(core_ExtractEmailAddresses),
+        "extractFilePaths": _wrap(core_ExtractFilePaths),
+        "extractFiles": _wrap(core_ExtractFiles),
+        "extractIPAddresses": _wrap(core_ExtractIPAddresses),
+        "extractMACAddresses": _wrap(core_ExtractMACAddresses),
+        "extractURLs": _wrap(core_ExtractURLs),
+        "filter": _wrap(core_Filter),
+        "findReplace": _wrap(core_FindReplace),
+        "fletcher16Checksum": _wrap(core_Fletcher16Checksum),
+        "fletcher32Checksum": _wrap(core_Fletcher32Checksum),
+        "fletcher64Checksum": _wrap(core_Fletcher64Checksum),
+        "fletcher8Checksum": _wrap(core_Fletcher8Checksum),
+        "flipImage": _wrap(core_FlipImage),
+        "formatMACAddresses": _wrap(core_FormatMACAddresses),
+        "frequencyDistribution": _wrap(core_FrequencyDistribution),
+        "fromBCD": _wrap(core_FromBCD),
+        "fromBase": _wrap(core_FromBase),
+        "fromBase32": _wrap(core_FromBase32),
+        "fromBase58": _wrap(core_FromBase58),
+        "fromBase62": _wrap(core_FromBase62),
+        "fromBase64": _wrap(core_FromBase64),
+        "fromBase85": _wrap(core_FromBase85),
+        "fromBinary": _wrap(core_FromBinary),
+        "fromBraille": _wrap(core_FromBraille),
+        "fromCaseInsensitiveRegex": _wrap(core_FromCaseInsensitiveRegex),
+        "fromCharcode": _wrap(core_FromCharcode),
+        "fromDecimal": _wrap(core_FromDecimal),
+        "fromHTMLEntity": _wrap(core_FromHTMLEntity),
+        "fromHex": _wrap(core_FromHex),
+        "fromHexContent": _wrap(core_FromHexContent),
+        "fromHexdump": _wrap(core_FromHexdump),
+        "fromMessagePack": _wrap(core_FromMessagePack),
+        "fromMorseCode": _wrap(core_FromMorseCode),
+        "fromOctal": _wrap(core_FromOctal),
+        "fromPunycode": _wrap(core_FromPunycode),
+        "fromQuotedPrintable": _wrap(core_FromQuotedPrintable),
+        "fromUNIXTimestamp": _wrap(core_FromUNIXTimestamp),
+        "generateAllHashes": _wrap(core_GenerateAllHashes),
+        "generateHOTP": _wrap(core_GenerateHOTP),
+        "generateLoremIpsum": _wrap(core_GenerateLoremIpsum),
+        "generatePGPKeyPair": _wrap(core_GeneratePGPKeyPair),
+        "generateQRCode": _wrap(core_GenerateQRCode),
+        "generateTOTP": _wrap(core_GenerateTOTP),
+        "generateUUID": _wrap(core_GenerateUUID),
+        "genericCodeBeautify": _wrap(core_GenericCodeBeautify),
+        "groupIPAddresses": _wrap(core_GroupIPAddresses),
+        "gunzip": _wrap(core_Gunzip),
+        "gzip": _wrap(core_Gzip),
+        "HAS160": _wrap(core_HAS160),
+        "HMAC": _wrap(core_HMAC),
+        "HTMLToText": _wrap(core_HTMLToText),
+        "HTTPRequest": _wrap(core_HTTPRequest),
+        "hammingDistance": _wrap(core_HammingDistance),
+        "haversineDistance": _wrap(core_HaversineDistance),
+        "head": _wrap(core_Head),
+        "heatmapChart": _wrap(core_HeatmapChart),
+        "hexDensityChart": _wrap(core_HexDensityChart),
+        "hexToObjectIdentifier": _wrap(core_HexToObjectIdentifier),
+        "hexToPEM": _wrap(core_HexToPEM),
+        "imageBrightnessContrast": _wrap(core_ImageBrightnessContrast),
+        "imageFilter": _wrap(core_ImageFilter),
+        "imageHueSaturationLightness": _wrap(core_ImageHueSaturationLightness),
+        "imageOpacity": _wrap(core_ImageOpacity),
+        "invertImage": _wrap(core_InvertImage),
+        "JPathExpression": _wrap(core_JPathExpression),
+        "JSONBeautify": _wrap(core_JSONBeautify),
+        "JSONMinify": _wrap(core_JSONMinify),
+        "JSONToCSV": _wrap(core_JSONToCSV),
+        "JWTDecode": _wrap(core_JWTDecode),
+        "JWTSign": _wrap(core_JWTSign),
+        "JWTVerify": _wrap(core_JWTVerify),
+        "keccak": _wrap(core_Keccak),
+        "MD2": _wrap(core_MD2),
+        "MD4": _wrap(core_MD4),
+        "MD5": _wrap(core_MD5),
+        "MD6": _wrap(core_MD6),
+        "magic": _wrap(core_Magic),
+        "mean": _wrap(core_Mean),
+        "median": _wrap(core_Median),
+        "microsoftScriptDecoder": _wrap(core_MicrosoftScriptDecoder),
+        "multipleBombe": _wrap(core_MultipleBombe),
+        "multiply": _wrap(core_Multiply),
+        "NOT": _wrap(core_NOT),
+        "normaliseImage": _wrap(core_NormaliseImage),
+        "numberwang": _wrap(core_Numberwang),
+        "OR": _wrap(core_OR),
+        "objectIdentifierToHex": _wrap(core_ObjectIdentifierToHex),
+        "offsetChecker": _wrap(core_OffsetChecker),
+        "PEMToHex": _wrap(core_PEMToHex),
+        "PGPDecrypt": _wrap(core_PGPDecrypt),
+        "PGPDecryptAndVerify": _wrap(core_PGPDecryptAndVerify),
+        "PGPEncrypt": _wrap(core_PGPEncrypt),
+        "PGPEncryptAndSign": _wrap(core_PGPEncryptAndSign),
+        "PHPDeserialize": _wrap(core_PHPDeserialize),
+        "padLines": _wrap(core_PadLines),
+        "parseASN1HexString": _wrap(core_ParseASN1HexString),
+        "parseColourCode": _wrap(core_ParseColourCode),
+        "parseDateTime": _wrap(core_ParseDateTime),
+        "parseIPRange": _wrap(core_ParseIPRange),
+        "parseIPv4Header": _wrap(core_ParseIPv4Header),
+        "parseIPv6Address": _wrap(core_ParseIPv6Address),
+        "parseQRCode": _wrap(core_ParseQRCode),
+        "parseTLV": _wrap(core_ParseTLV),
+        "parseUNIXFilePermissions": _wrap(core_ParseUNIXFilePermissions),
+        "parseURI": _wrap(core_ParseURI),
+        "parseUserAgent": _wrap(core_ParseUserAgent),
+        "parseX509Certificate": _wrap(core_ParseX509Certificate),
+        "playMedia": _wrap(core_PlayMedia),
+        "powerSet": _wrap(core_PowerSet),
+        "pseudoRandomNumberGenerator": _wrap(core_PseudoRandomNumberGenerator),
+        "RC2Decrypt": _wrap(core_RC2Decrypt),
+        "RC2Encrypt": _wrap(core_RC2Encrypt),
+        "RC4": _wrap(core_RC4),
+        "RC4Drop": _wrap(core_RC4Drop),
+        "RIPEMD": _wrap(core_RIPEMD),
+        "ROT13": _wrap(core_ROT13),
+        "ROT47": _wrap(core_ROT47),
+        "rawDeflate": _wrap(core_RawDeflate),
+        "rawInflate": _wrap(core_RawInflate),
+        "register": _wrap(core_Register),
+        "regularExpression": _wrap(core_RegularExpression),
+        "removeDiacritics": _wrap(core_RemoveDiacritics),
+        "removeEXIF": _wrap(core_RemoveEXIF),
+        "removeLineNumbers": _wrap(core_RemoveLineNumbers),
+        "removeNullBytes": _wrap(core_RemoveNullBytes),
+        "removeWhitespace": _wrap(core_RemoveWhitespace),
+        "resizeImage": _wrap(core_ResizeImage),
+        "Return": _wrap(core_Return),
+        "reverse": _wrap(core_Reverse),
+        "rotateImage": _wrap(core_RotateImage),
+        "rotateLeft": _wrap(core_RotateLeft),
+        "rotateRight": _wrap(core_RotateRight),
+        "SHA0": _wrap(core_SHA0),
+        "SHA1": _wrap(core_SHA1),
+        "SHA2": _wrap(core_SHA2),
+        "SHA3": _wrap(core_SHA3),
+        "SQLBeautify": _wrap(core_SQLBeautify),
+        "SQLMinify": _wrap(core_SQLMinify),
+        "SSDEEP": _wrap(core_SSDEEP),
+        "SUB": _wrap(core_SUB),
+        "scanForEmbeddedFiles": _wrap(core_ScanForEmbeddedFiles),
+        "scatterChart": _wrap(core_ScatterChart),
+        "scrypt": _wrap(core_Scrypt),
+        "seriesChart": _wrap(core_SeriesChart),
+        "setDifference": _wrap(core_SetDifference),
+        "setIntersection": _wrap(core_SetIntersection),
+        "setUnion": _wrap(core_SetUnion),
+        "shake": _wrap(core_Shake),
+        "showBase64Offsets": _wrap(core_ShowBase64Offsets),
+        "sleep": _wrap(core_Sleep),
+        "snefru": _wrap(core_Snefru),
+        "sort": _wrap(core_Sort),
+        "split": _wrap(core_Split),
+        "splitColourChannels": _wrap(core_SplitColourChannels),
+        "standardDeviation": _wrap(core_StandardDeviation),
+        "strings": _wrap(core_Strings),
+        "stripHTMLTags": _wrap(core_StripHTMLTags),
+        "stripHTTPHeaders": _wrap(core_StripHTTPHeaders),
+        "subsection": _wrap(core_Subsection),
+        "substitute": _wrap(core_Substitute),
+        "subtract": _wrap(core_Subtract),
+        "sum": _wrap(core_Sum),
+        "swapEndianness": _wrap(core_SwapEndianness),
+        "symmetricDifference": _wrap(core_SymmetricDifference),
+        "TCPIPChecksum": _wrap(core_TCPIPChecksum),
+        "tail": _wrap(core_Tail),
+        "takeBytes": _wrap(core_TakeBytes),
+        "tar": _wrap(core_Tar),
+        "textEncodingBruteForce": _wrap(core_TextEncodingBruteForce),
+        "toBCD": _wrap(core_ToBCD),
+        "toBase": _wrap(core_ToBase),
+        "toBase32": _wrap(core_ToBase32),
+        "toBase58": _wrap(core_ToBase58),
+        "toBase62": _wrap(core_ToBase62),
+        "toBase64": _wrap(core_ToBase64),
+        "toBase85": _wrap(core_ToBase85),
+        "toBinary": _wrap(core_ToBinary),
+        "toBraille": _wrap(core_ToBraille),
+        "toCamelCase": _wrap(core_ToCamelCase),
+        "toCaseInsensitiveRegex": _wrap(core_ToCaseInsensitiveRegex),
+        "toCharcode": _wrap(core_ToCharcode),
+        "toDecimal": _wrap(core_ToDecimal),
+        "toHTMLEntity": _wrap(core_ToHTMLEntity),
+        "toHex": _wrap(core_ToHex),
+        "toHexContent": _wrap(core_ToHexContent),
+        "toHexdump": _wrap(core_ToHexdump),
+        "toKebabCase": _wrap(core_ToKebabCase),
+        "toLowerCase": _wrap(core_ToLowerCase),
+        "toMessagePack": _wrap(core_ToMessagePack),
+        "toMorseCode": _wrap(core_ToMorseCode),
+        "toOctal": _wrap(core_ToOctal),
+        "toPunycode": _wrap(core_ToPunycode),
+        "toQuotedPrintable": _wrap(core_ToQuotedPrintable),
+        "toSnakeCase": _wrap(core_ToSnakeCase),
+        "toTable": _wrap(core_ToTable),
+        "toUNIXTimestamp": _wrap(core_ToUNIXTimestamp),
+        "toUpperCase": _wrap(core_ToUpperCase),
+        "translateDateTimeFormat": _wrap(core_TranslateDateTimeFormat),
+        "tripleDESDecrypt": _wrap(core_TripleDESDecrypt),
+        "tripleDESEncrypt": _wrap(core_TripleDESEncrypt),
+        "typex": _wrap(core_Typex),
+        "UNIXTimestampToWindowsFiletime": _wrap(core_UNIXTimestampToWindowsFiletime),
+        "URLDecode": _wrap(core_URLDecode),
+        "URLEncode": _wrap(core_URLEncode),
+        "unescapeString": _wrap(core_UnescapeString),
+        "unescapeUnicodeCharacters": _wrap(core_UnescapeUnicodeCharacters),
+        "unique": _wrap(core_Unique),
+        "untar": _wrap(core_Untar),
+        "unzip": _wrap(core_Unzip),
+        "vigenèreDecode": _wrap(core_VigenèreDecode),
+        "vigenèreEncode": _wrap(core_VigenèreEncode),
+        "whirlpool": _wrap(core_Whirlpool),
+        "windowsFiletimeToUNIXTimestamp": _wrap(core_WindowsFiletimeToUNIXTimestamp),
+        "XKCDRandomNumber": _wrap(core_XKCDRandomNumber),
+        "XMLBeautify": _wrap(core_XMLBeautify),
+        "XMLMinify": _wrap(core_XMLMinify),
+        "XOR": _wrap(core_XOR),
+        "XORBruteForce": _wrap(core_XORBruteForce),
+        "XPathExpression": _wrap(core_XPathExpression),
+        "YARARules": _wrap(core_YARARules),
+        "zip": _wrap(core_Zip),
+        "zlibDeflate": _wrap(core_ZlibDeflate),
+        "zlibInflate": _wrap(core_ZlibInflate),
+        "fork": _explainExludedFunction("Fork"),
+        "merge": _explainExludedFunction("Merge"),
+        "jump": _explainExludedFunction("Jump"),
+        "conditionalJump": _explainExludedFunction("ConditionalJump"),
+        "label": _explainExludedFunction("Label"),
+        "comment": _explainExludedFunction("Comment"),
+        "javaScriptBeautify": _explainExludedFunction("JavaScriptBeautify"),
+        "javaScriptMinify": _explainExludedFunction("JavaScriptMinify"),
+        "javaScriptParser": _explainExludedFunction("JavaScriptParser"),
+        "renderImage": _explainExludedFunction("RenderImage"),
+        "syntaxHighlighter": _explainExludedFunction("SyntaxHighlighter"),
+        "detectFileType": _explainExludedFunction("DetectFileType"),
+    };
+}
+
+const chef = generateChef();
+// Add some additional features to chef object.
+chef.help = help;
+chef.Dish = NodeDish;
+
+// Define consts here so we can add to top-level export - wont allow
+// export of chef property.
+const A1Z26CipherDecode = chef.A1Z26CipherDecode;
+const A1Z26CipherEncode = chef.A1Z26CipherEncode;
+const ADD = chef.ADD;
+const AESDecrypt = chef.AESDecrypt;
+const AESEncrypt = chef.AESEncrypt;
+const AND = chef.AND;
+const addLineNumbers = chef.addLineNumbers;
+const adler32Checksum = chef.adler32Checksum;
+const affineCipherDecode = chef.affineCipherDecode;
+const affineCipherEncode = chef.affineCipherEncode;
+const analyseHash = chef.analyseHash;
+const atbashCipher = chef.atbashCipher;
+const BLAKE2b = chef.BLAKE2b;
+const BLAKE2s = chef.BLAKE2s;
+const BSONDeserialise = chef.BSONDeserialise;
+const BSONSerialise = chef.BSONSerialise;
+const bcrypt = chef.bcrypt;
+const bcryptCompare = chef.bcryptCompare;
+const bcryptParse = chef.bcryptParse;
+const bifidCipherDecode = chef.bifidCipherDecode;
+const bifidCipherEncode = chef.bifidCipherEncode;
+const bitShiftLeft = chef.bitShiftLeft;
+const bitShiftRight = chef.bitShiftRight;
+const blowfishDecrypt = chef.blowfishDecrypt;
+const blowfishEncrypt = chef.blowfishEncrypt;
+const blurImage = chef.blurImage;
+const bombe = chef.bombe;
+const bzip2Decompress = chef.bzip2Decompress;
+const CRC16Checksum = chef.CRC16Checksum;
+const CRC32Checksum = chef.CRC32Checksum;
+const CSSBeautify = chef.CSSBeautify;
+const CSSMinify = chef.CSSMinify;
+const CSSSelector = chef.CSSSelector;
+const CSVToJSON = chef.CSVToJSON;
+const CTPH = chef.CTPH;
+const cartesianProduct = chef.cartesianProduct;
+const changeIPFormat = chef.changeIPFormat;
+const chiSquare = chef.chiSquare;
+const citrixCTX1Decode = chef.citrixCTX1Decode;
+const citrixCTX1Encode = chef.citrixCTX1Encode;
+const comment = chef.comment;
+const compareCTPHHashes = chef.compareCTPHHashes;
+const compareSSDEEPHashes = chef.compareSSDEEPHashes;
+const conditionalJump = chef.conditionalJump;
+const containImage = chef.containImage;
+const convertArea = chef.convertArea;
+const convertCoordinateFormat = chef.convertCoordinateFormat;
+const convertDataUnits = chef.convertDataUnits;
+const convertDistance = chef.convertDistance;
+const convertMass = chef.convertMass;
+const convertSpeed = chef.convertSpeed;
+const countOccurrences = chef.countOccurrences;
+const coverImage = chef.coverImage;
+const cropImage = chef.cropImage;
+const DESDecrypt = chef.DESDecrypt;
+const DESEncrypt = chef.DESEncrypt;
+const DNSOverHTTPS = chef.DNSOverHTTPS;
+const dechunkHTTPResponse = chef.dechunkHTTPResponse;
+const decodeNetBIOSName = chef.decodeNetBIOSName;
+const decodeText = chef.decodeText;
+const defangURL = chef.defangURL;
+const deriveEVPKey = chef.deriveEVPKey;
+const derivePBKDF2Key = chef.derivePBKDF2Key;
+const detectFileType = chef.detectFileType;
+const diff = chef.diff;
+const disassembleX86 = chef.disassembleX86;
+const ditherImage = chef.ditherImage;
+const divide = chef.divide;
+const dropBytes = chef.dropBytes;
+const encodeNetBIOSName = chef.encodeNetBIOSName;
+const encodeText = chef.encodeText;
+const enigma = chef.enigma;
+const entropy = chef.entropy;
+const escapeString = chef.escapeString;
+const escapeUnicodeCharacters = chef.escapeUnicodeCharacters;
+const expandAlphabetRange = chef.expandAlphabetRange;
+const extractDates = chef.extractDates;
+const extractDomains = chef.extractDomains;
+const extractEXIF = chef.extractEXIF;
+const extractEmailAddresses = chef.extractEmailAddresses;
+const extractFilePaths = chef.extractFilePaths;
+const extractFiles = chef.extractFiles;
+const extractIPAddresses = chef.extractIPAddresses;
+const extractMACAddresses = chef.extractMACAddresses;
+const extractURLs = chef.extractURLs;
+const filter = chef.filter;
+const findReplace = chef.findReplace;
+const fletcher16Checksum = chef.fletcher16Checksum;
+const fletcher32Checksum = chef.fletcher32Checksum;
+const fletcher64Checksum = chef.fletcher64Checksum;
+const fletcher8Checksum = chef.fletcher8Checksum;
+const flipImage = chef.flipImage;
+const fork = chef.fork;
+const formatMACAddresses = chef.formatMACAddresses;
+const frequencyDistribution = chef.frequencyDistribution;
+const fromBCD = chef.fromBCD;
+const fromBase = chef.fromBase;
+const fromBase32 = chef.fromBase32;
+const fromBase58 = chef.fromBase58;
+const fromBase62 = chef.fromBase62;
+const fromBase64 = chef.fromBase64;
+const fromBase85 = chef.fromBase85;
+const fromBinary = chef.fromBinary;
+const fromBraille = chef.fromBraille;
+const fromCaseInsensitiveRegex = chef.fromCaseInsensitiveRegex;
+const fromCharcode = chef.fromCharcode;
+const fromDecimal = chef.fromDecimal;
+const fromHTMLEntity = chef.fromHTMLEntity;
+const fromHex = chef.fromHex;
+const fromHexContent = chef.fromHexContent;
+const fromHexdump = chef.fromHexdump;
+const fromMessagePack = chef.fromMessagePack;
+const fromMorseCode = chef.fromMorseCode;
+const fromOctal = chef.fromOctal;
+const fromPunycode = chef.fromPunycode;
+const fromQuotedPrintable = chef.fromQuotedPrintable;
+const fromUNIXTimestamp = chef.fromUNIXTimestamp;
+const generateAllHashes = chef.generateAllHashes;
+const generateHOTP = chef.generateHOTP;
+const generateLoremIpsum = chef.generateLoremIpsum;
+const generatePGPKeyPair = chef.generatePGPKeyPair;
+const generateQRCode = chef.generateQRCode;
+const generateTOTP = chef.generateTOTP;
+const generateUUID = chef.generateUUID;
+const genericCodeBeautify = chef.genericCodeBeautify;
+const groupIPAddresses = chef.groupIPAddresses;
+const gunzip = chef.gunzip;
+const gzip = chef.gzip;
+const HAS160 = chef.HAS160;
+const HMAC = chef.HMAC;
+const HTMLToText = chef.HTMLToText;
+const HTTPRequest = chef.HTTPRequest;
+const hammingDistance = chef.hammingDistance;
+const haversineDistance = chef.haversineDistance;
+const head = chef.head;
+const heatmapChart = chef.heatmapChart;
+const hexDensityChart = chef.hexDensityChart;
+const hexToObjectIdentifier = chef.hexToObjectIdentifier;
+const hexToPEM = chef.hexToPEM;
+const imageBrightnessContrast = chef.imageBrightnessContrast;
+const imageFilter = chef.imageFilter;
+const imageHueSaturationLightness = chef.imageHueSaturationLightness;
+const imageOpacity = chef.imageOpacity;
+const invertImage = chef.invertImage;
+const JPathExpression = chef.JPathExpression;
+const JSONBeautify = chef.JSONBeautify;
+const JSONMinify = chef.JSONMinify;
+const JSONToCSV = chef.JSONToCSV;
+const JWTDecode = chef.JWTDecode;
+const JWTSign = chef.JWTSign;
+const JWTVerify = chef.JWTVerify;
+const javaScriptBeautify = chef.javaScriptBeautify;
+const javaScriptMinify = chef.javaScriptMinify;
+const javaScriptParser = chef.javaScriptParser;
+const jump = chef.jump;
+const keccak = chef.keccak;
+const label = chef.label;
+const MD2 = chef.MD2;
+const MD4 = chef.MD4;
+const MD5 = chef.MD5;
+const MD6 = chef.MD6;
+const magic = chef.magic;
+const mean = chef.mean;
+const median = chef.median;
+const merge = chef.merge;
+const microsoftScriptDecoder = chef.microsoftScriptDecoder;
+const multipleBombe = chef.multipleBombe;
+const multiply = chef.multiply;
+const NOT = chef.NOT;
+const normaliseImage = chef.normaliseImage;
+const numberwang = chef.numberwang;
+const OR = chef.OR;
+const objectIdentifierToHex = chef.objectIdentifierToHex;
+const offsetChecker = chef.offsetChecker;
+const PEMToHex = chef.PEMToHex;
+const PGPDecrypt = chef.PGPDecrypt;
+const PGPDecryptAndVerify = chef.PGPDecryptAndVerify;
+const PGPEncrypt = chef.PGPEncrypt;
+const PGPEncryptAndSign = chef.PGPEncryptAndSign;
+const PHPDeserialize = chef.PHPDeserialize;
+const padLines = chef.padLines;
+const parseASN1HexString = chef.parseASN1HexString;
+const parseColourCode = chef.parseColourCode;
+const parseDateTime = chef.parseDateTime;
+const parseIPRange = chef.parseIPRange;
+const parseIPv4Header = chef.parseIPv4Header;
+const parseIPv6Address = chef.parseIPv6Address;
+const parseQRCode = chef.parseQRCode;
+const parseTLV = chef.parseTLV;
+const parseUNIXFilePermissions = chef.parseUNIXFilePermissions;
+const parseURI = chef.parseURI;
+const parseUserAgent = chef.parseUserAgent;
+const parseX509Certificate = chef.parseX509Certificate;
+const playMedia = chef.playMedia;
+const powerSet = chef.powerSet;
+const pseudoRandomNumberGenerator = chef.pseudoRandomNumberGenerator;
+const RC2Decrypt = chef.RC2Decrypt;
+const RC2Encrypt = chef.RC2Encrypt;
+const RC4 = chef.RC4;
+const RC4Drop = chef.RC4Drop;
+const RIPEMD = chef.RIPEMD;
+const ROT13 = chef.ROT13;
+const ROT47 = chef.ROT47;
+const rawDeflate = chef.rawDeflate;
+const rawInflate = chef.rawInflate;
+const register = chef.register;
+const regularExpression = chef.regularExpression;
+const removeDiacritics = chef.removeDiacritics;
+const removeEXIF = chef.removeEXIF;
+const removeLineNumbers = chef.removeLineNumbers;
+const removeNullBytes = chef.removeNullBytes;
+const removeWhitespace = chef.removeWhitespace;
+const renderImage = chef.renderImage;
+const resizeImage = chef.resizeImage;
+const Return = chef.Return;
+const reverse = chef.reverse;
+const rotateImage = chef.rotateImage;
+const rotateLeft = chef.rotateLeft;
+const rotateRight = chef.rotateRight;
+const SHA0 = chef.SHA0;
+const SHA1 = chef.SHA1;
+const SHA2 = chef.SHA2;
+const SHA3 = chef.SHA3;
+const SQLBeautify = chef.SQLBeautify;
+const SQLMinify = chef.SQLMinify;
+const SSDEEP = chef.SSDEEP;
+const SUB = chef.SUB;
+const scanForEmbeddedFiles = chef.scanForEmbeddedFiles;
+const scatterChart = chef.scatterChart;
+const scrypt = chef.scrypt;
+const seriesChart = chef.seriesChart;
+const setDifference = chef.setDifference;
+const setIntersection = chef.setIntersection;
+const setUnion = chef.setUnion;
+const shake = chef.shake;
+const showBase64Offsets = chef.showBase64Offsets;
+const sleep = chef.sleep;
+const snefru = chef.snefru;
+const sort = chef.sort;
+const split = chef.split;
+const splitColourChannels = chef.splitColourChannels;
+const standardDeviation = chef.standardDeviation;
+const strings = chef.strings;
+const stripHTMLTags = chef.stripHTMLTags;
+const stripHTTPHeaders = chef.stripHTTPHeaders;
+const subsection = chef.subsection;
+const substitute = chef.substitute;
+const subtract = chef.subtract;
+const sum = chef.sum;
+const swapEndianness = chef.swapEndianness;
+const symmetricDifference = chef.symmetricDifference;
+const syntaxHighlighter = chef.syntaxHighlighter;
+const TCPIPChecksum = chef.TCPIPChecksum;
+const tail = chef.tail;
+const takeBytes = chef.takeBytes;
+const tar = chef.tar;
+const textEncodingBruteForce = chef.textEncodingBruteForce;
+const toBCD = chef.toBCD;
+const toBase = chef.toBase;
+const toBase32 = chef.toBase32;
+const toBase58 = chef.toBase58;
+const toBase62 = chef.toBase62;
+const toBase64 = chef.toBase64;
+const toBase85 = chef.toBase85;
+const toBinary = chef.toBinary;
+const toBraille = chef.toBraille;
+const toCamelCase = chef.toCamelCase;
+const toCaseInsensitiveRegex = chef.toCaseInsensitiveRegex;
+const toCharcode = chef.toCharcode;
+const toDecimal = chef.toDecimal;
+const toHTMLEntity = chef.toHTMLEntity;
+const toHex = chef.toHex;
+const toHexContent = chef.toHexContent;
+const toHexdump = chef.toHexdump;
+const toKebabCase = chef.toKebabCase;
+const toLowerCase = chef.toLowerCase;
+const toMessagePack = chef.toMessagePack;
+const toMorseCode = chef.toMorseCode;
+const toOctal = chef.toOctal;
+const toPunycode = chef.toPunycode;
+const toQuotedPrintable = chef.toQuotedPrintable;
+const toSnakeCase = chef.toSnakeCase;
+const toTable = chef.toTable;
+const toUNIXTimestamp = chef.toUNIXTimestamp;
+const toUpperCase = chef.toUpperCase;
+const translateDateTimeFormat = chef.translateDateTimeFormat;
+const tripleDESDecrypt = chef.tripleDESDecrypt;
+const tripleDESEncrypt = chef.tripleDESEncrypt;
+const typex = chef.typex;
+const UNIXTimestampToWindowsFiletime = chef.UNIXTimestampToWindowsFiletime;
+const URLDecode = chef.URLDecode;
+const URLEncode = chef.URLEncode;
+const unescapeString = chef.unescapeString;
+const unescapeUnicodeCharacters = chef.unescapeUnicodeCharacters;
+const unique = chef.unique;
+const untar = chef.untar;
+const unzip = chef.unzip;
+const vigenèreDecode = chef.vigenèreDecode;
+const vigenèreEncode = chef.vigenèreEncode;
+const whirlpool = chef.whirlpool;
+const windowsFiletimeToUNIXTimestamp = chef.windowsFiletimeToUNIXTimestamp;
+const XKCDRandomNumber = chef.XKCDRandomNumber;
+const XMLBeautify = chef.XMLBeautify;
+const XMLMinify = chef.XMLMinify;
+const XOR = chef.XOR;
+const XORBruteForce = chef.XORBruteForce;
+const XPathExpression = chef.XPathExpression;
+const YARARules = chef.YARARules;
+const zip = chef.zip;
+const zlibDeflate = chef.zlibDeflate;
+const zlibInflate = chef.zlibInflate;
+
+
+// Define array of all operations to create register for bake.
+const operations = [
+    A1Z26CipherDecode,
+    A1Z26CipherEncode,
+    ADD,
+    AESDecrypt,
+    AESEncrypt,
+    AND,
+    addLineNumbers,
+    adler32Checksum,
+    affineCipherDecode,
+    affineCipherEncode,
+    analyseHash,
+    atbashCipher,
+    BLAKE2b,
+    BLAKE2s,
+    BSONDeserialise,
+    BSONSerialise,
+    bcrypt,
+    bcryptCompare,
+    bcryptParse,
+    bifidCipherDecode,
+    bifidCipherEncode,
+    bitShiftLeft,
+    bitShiftRight,
+    blowfishDecrypt,
+    blowfishEncrypt,
+    blurImage,
+    bombe,
+    bzip2Decompress,
+    CRC16Checksum,
+    CRC32Checksum,
+    CSSBeautify,
+    CSSMinify,
+    CSSSelector,
+    CSVToJSON,
+    CTPH,
+    cartesianProduct,
+    changeIPFormat,
+    chiSquare,
+    citrixCTX1Decode,
+    citrixCTX1Encode,
+    comment,
+    compareCTPHHashes,
+    compareSSDEEPHashes,
+    conditionalJump,
+    containImage,
+    convertArea,
+    convertCoordinateFormat,
+    convertDataUnits,
+    convertDistance,
+    convertMass,
+    convertSpeed,
+    countOccurrences,
+    coverImage,
+    cropImage,
+    DESDecrypt,
+    DESEncrypt,
+    DNSOverHTTPS,
+    dechunkHTTPResponse,
+    decodeNetBIOSName,
+    decodeText,
+    defangURL,
+    deriveEVPKey,
+    derivePBKDF2Key,
+    detectFileType,
+    diff,
+    disassembleX86,
+    ditherImage,
+    divide,
+    dropBytes,
+    encodeNetBIOSName,
+    encodeText,
+    enigma,
+    entropy,
+    escapeString,
+    escapeUnicodeCharacters,
+    expandAlphabetRange,
+    extractDates,
+    extractDomains,
+    extractEXIF,
+    extractEmailAddresses,
+    extractFilePaths,
+    extractFiles,
+    extractIPAddresses,
+    extractMACAddresses,
+    extractURLs,
+    filter,
+    findReplace,
+    fletcher16Checksum,
+    fletcher32Checksum,
+    fletcher64Checksum,
+    fletcher8Checksum,
+    flipImage,
+    fork,
+    formatMACAddresses,
+    frequencyDistribution,
+    fromBCD,
+    fromBase,
+    fromBase32,
+    fromBase58,
+    fromBase62,
+    fromBase64,
+    fromBase85,
+    fromBinary,
+    fromBraille,
+    fromCaseInsensitiveRegex,
+    fromCharcode,
+    fromDecimal,
+    fromHTMLEntity,
+    fromHex,
+    fromHexContent,
+    fromHexdump,
+    fromMessagePack,
+    fromMorseCode,
+    fromOctal,
+    fromPunycode,
+    fromQuotedPrintable,
+    fromUNIXTimestamp,
+    generateAllHashes,
+    generateHOTP,
+    generateLoremIpsum,
+    generatePGPKeyPair,
+    generateQRCode,
+    generateTOTP,
+    generateUUID,
+    genericCodeBeautify,
+    groupIPAddresses,
+    gunzip,
+    gzip,
+    HAS160,
+    HMAC,
+    HTMLToText,
+    HTTPRequest,
+    hammingDistance,
+    haversineDistance,
+    head,
+    heatmapChart,
+    hexDensityChart,
+    hexToObjectIdentifier,
+    hexToPEM,
+    imageBrightnessContrast,
+    imageFilter,
+    imageHueSaturationLightness,
+    imageOpacity,
+    invertImage,
+    JPathExpression,
+    JSONBeautify,
+    JSONMinify,
+    JSONToCSV,
+    JWTDecode,
+    JWTSign,
+    JWTVerify,
+    javaScriptBeautify,
+    javaScriptMinify,
+    javaScriptParser,
+    jump,
+    keccak,
+    label,
+    MD2,
+    MD4,
+    MD5,
+    MD6,
+    magic,
+    mean,
+    median,
+    merge,
+    microsoftScriptDecoder,
+    multipleBombe,
+    multiply,
+    NOT,
+    normaliseImage,
+    numberwang,
+    OR,
+    objectIdentifierToHex,
+    offsetChecker,
+    PEMToHex,
+    PGPDecrypt,
+    PGPDecryptAndVerify,
+    PGPEncrypt,
+    PGPEncryptAndSign,
+    PHPDeserialize,
+    padLines,
+    parseASN1HexString,
+    parseColourCode,
+    parseDateTime,
+    parseIPRange,
+    parseIPv4Header,
+    parseIPv6Address,
+    parseQRCode,
+    parseTLV,
+    parseUNIXFilePermissions,
+    parseURI,
+    parseUserAgent,
+    parseX509Certificate,
+    playMedia,
+    powerSet,
+    pseudoRandomNumberGenerator,
+    RC2Decrypt,
+    RC2Encrypt,
+    RC4,
+    RC4Drop,
+    RIPEMD,
+    ROT13,
+    ROT47,
+    rawDeflate,
+    rawInflate,
+    register,
+    regularExpression,
+    removeDiacritics,
+    removeEXIF,
+    removeLineNumbers,
+    removeNullBytes,
+    removeWhitespace,
+    renderImage,
+    resizeImage,
+    Return,
+    reverse,
+    rotateImage,
+    rotateLeft,
+    rotateRight,
+    SHA0,
+    SHA1,
+    SHA2,
+    SHA3,
+    SQLBeautify,
+    SQLMinify,
+    SSDEEP,
+    SUB,
+    scanForEmbeddedFiles,
+    scatterChart,
+    scrypt,
+    seriesChart,
+    setDifference,
+    setIntersection,
+    setUnion,
+    shake,
+    showBase64Offsets,
+    sleep,
+    snefru,
+    sort,
+    split,
+    splitColourChannels,
+    standardDeviation,
+    strings,
+    stripHTMLTags,
+    stripHTTPHeaders,
+    subsection,
+    substitute,
+    subtract,
+    sum,
+    swapEndianness,
+    symmetricDifference,
+    syntaxHighlighter,
+    TCPIPChecksum,
+    tail,
+    takeBytes,
+    tar,
+    textEncodingBruteForce,
+    toBCD,
+    toBase,
+    toBase32,
+    toBase58,
+    toBase62,
+    toBase64,
+    toBase85,
+    toBinary,
+    toBraille,
+    toCamelCase,
+    toCaseInsensitiveRegex,
+    toCharcode,
+    toDecimal,
+    toHTMLEntity,
+    toHex,
+    toHexContent,
+    toHexdump,
+    toKebabCase,
+    toLowerCase,
+    toMessagePack,
+    toMorseCode,
+    toOctal,
+    toPunycode,
+    toQuotedPrintable,
+    toSnakeCase,
+    toTable,
+    toUNIXTimestamp,
+    toUpperCase,
+    translateDateTimeFormat,
+    tripleDESDecrypt,
+    tripleDESEncrypt,
+    typex,
+    UNIXTimestampToWindowsFiletime,
+    URLDecode,
+    URLEncode,
+    unescapeString,
+    unescapeUnicodeCharacters,
+    unique,
+    untar,
+    unzip,
+    vigenèreDecode,
+    vigenèreEncode,
+    whirlpool,
+    windowsFiletimeToUNIXTimestamp,
+    XKCDRandomNumber,
+    XMLBeautify,
+    XMLMinify,
+    XOR,
+    XORBruteForce,
+    XPathExpression,
+    YARARules,
+    zip,
+    zlibDeflate,
+    zlibInflate,
+];
+
+const prebaked = bake(operations);
+chef.bake = prebaked;
+export default chef;
+
+// Operations as top level exports.
+export {
+    operations,
+    A1Z26CipherDecode,
+    A1Z26CipherEncode,
+    ADD,
+    AESDecrypt,
+    AESEncrypt,
+    AND,
+    addLineNumbers,
+    adler32Checksum,
+    affineCipherDecode,
+    affineCipherEncode,
+    analyseHash,
+    atbashCipher,
+    BLAKE2b,
+    BLAKE2s,
+    BSONDeserialise,
+    BSONSerialise,
+    bcrypt,
+    bcryptCompare,
+    bcryptParse,
+    bifidCipherDecode,
+    bifidCipherEncode,
+    bitShiftLeft,
+    bitShiftRight,
+    blowfishDecrypt,
+    blowfishEncrypt,
+    blurImage,
+    bombe,
+    bzip2Decompress,
+    CRC16Checksum,
+    CRC32Checksum,
+    CSSBeautify,
+    CSSMinify,
+    CSSSelector,
+    CSVToJSON,
+    CTPH,
+    cartesianProduct,
+    changeIPFormat,
+    chiSquare,
+    citrixCTX1Decode,
+    citrixCTX1Encode,
+    comment,
+    compareCTPHHashes,
+    compareSSDEEPHashes,
+    conditionalJump,
+    containImage,
+    convertArea,
+    convertCoordinateFormat,
+    convertDataUnits,
+    convertDistance,
+    convertMass,
+    convertSpeed,
+    countOccurrences,
+    coverImage,
+    cropImage,
+    DESDecrypt,
+    DESEncrypt,
+    DNSOverHTTPS,
+    dechunkHTTPResponse,
+    decodeNetBIOSName,
+    decodeText,
+    defangURL,
+    deriveEVPKey,
+    derivePBKDF2Key,
+    detectFileType,
+    diff,
+    disassembleX86,
+    ditherImage,
+    divide,
+    dropBytes,
+    encodeNetBIOSName,
+    encodeText,
+    enigma,
+    entropy,
+    escapeString,
+    escapeUnicodeCharacters,
+    expandAlphabetRange,
+    extractDates,
+    extractDomains,
+    extractEXIF,
+    extractEmailAddresses,
+    extractFilePaths,
+    extractFiles,
+    extractIPAddresses,
+    extractMACAddresses,
+    extractURLs,
+    filter,
+    findReplace,
+    fletcher16Checksum,
+    fletcher32Checksum,
+    fletcher64Checksum,
+    fletcher8Checksum,
+    flipImage,
+    fork,
+    formatMACAddresses,
+    frequencyDistribution,
+    fromBCD,
+    fromBase,
+    fromBase32,
+    fromBase58,
+    fromBase62,
+    fromBase64,
+    fromBase85,
+    fromBinary,
+    fromBraille,
+    fromCaseInsensitiveRegex,
+    fromCharcode,
+    fromDecimal,
+    fromHTMLEntity,
+    fromHex,
+    fromHexContent,
+    fromHexdump,
+    fromMessagePack,
+    fromMorseCode,
+    fromOctal,
+    fromPunycode,
+    fromQuotedPrintable,
+    fromUNIXTimestamp,
+    generateAllHashes,
+    generateHOTP,
+    generateLoremIpsum,
+    generatePGPKeyPair,
+    generateQRCode,
+    generateTOTP,
+    generateUUID,
+    genericCodeBeautify,
+    groupIPAddresses,
+    gunzip,
+    gzip,
+    HAS160,
+    HMAC,
+    HTMLToText,
+    HTTPRequest,
+    hammingDistance,
+    haversineDistance,
+    head,
+    heatmapChart,
+    hexDensityChart,
+    hexToObjectIdentifier,
+    hexToPEM,
+    imageBrightnessContrast,
+    imageFilter,
+    imageHueSaturationLightness,
+    imageOpacity,
+    invertImage,
+    JPathExpression,
+    JSONBeautify,
+    JSONMinify,
+    JSONToCSV,
+    JWTDecode,
+    JWTSign,
+    JWTVerify,
+    javaScriptBeautify,
+    javaScriptMinify,
+    javaScriptParser,
+    jump,
+    keccak,
+    label,
+    MD2,
+    MD4,
+    MD5,
+    MD6,
+    magic,
+    mean,
+    median,
+    merge,
+    microsoftScriptDecoder,
+    multipleBombe,
+    multiply,
+    NOT,
+    normaliseImage,
+    numberwang,
+    OR,
+    objectIdentifierToHex,
+    offsetChecker,
+    PEMToHex,
+    PGPDecrypt,
+    PGPDecryptAndVerify,
+    PGPEncrypt,
+    PGPEncryptAndSign,
+    PHPDeserialize,
+    padLines,
+    parseASN1HexString,
+    parseColourCode,
+    parseDateTime,
+    parseIPRange,
+    parseIPv4Header,
+    parseIPv6Address,
+    parseQRCode,
+    parseTLV,
+    parseUNIXFilePermissions,
+    parseURI,
+    parseUserAgent,
+    parseX509Certificate,
+    playMedia,
+    powerSet,
+    pseudoRandomNumberGenerator,
+    RC2Decrypt,
+    RC2Encrypt,
+    RC4,
+    RC4Drop,
+    RIPEMD,
+    ROT13,
+    ROT47,
+    rawDeflate,
+    rawInflate,
+    register,
+    regularExpression,
+    removeDiacritics,
+    removeEXIF,
+    removeLineNumbers,
+    removeNullBytes,
+    removeWhitespace,
+    renderImage,
+    resizeImage,
+    Return,
+    reverse,
+    rotateImage,
+    rotateLeft,
+    rotateRight,
+    SHA0,
+    SHA1,
+    SHA2,
+    SHA3,
+    SQLBeautify,
+    SQLMinify,
+    SSDEEP,
+    SUB,
+    scanForEmbeddedFiles,
+    scatterChart,
+    scrypt,
+    seriesChart,
+    setDifference,
+    setIntersection,
+    setUnion,
+    shake,
+    showBase64Offsets,
+    sleep,
+    snefru,
+    sort,
+    split,
+    splitColourChannels,
+    standardDeviation,
+    strings,
+    stripHTMLTags,
+    stripHTTPHeaders,
+    subsection,
+    substitute,
+    subtract,
+    sum,
+    swapEndianness,
+    symmetricDifference,
+    syntaxHighlighter,
+    TCPIPChecksum,
+    tail,
+    takeBytes,
+    tar,
+    textEncodingBruteForce,
+    toBCD,
+    toBase,
+    toBase32,
+    toBase58,
+    toBase62,
+    toBase64,
+    toBase85,
+    toBinary,
+    toBraille,
+    toCamelCase,
+    toCaseInsensitiveRegex,
+    toCharcode,
+    toDecimal,
+    toHTMLEntity,
+    toHex,
+    toHexContent,
+    toHexdump,
+    toKebabCase,
+    toLowerCase,
+    toMessagePack,
+    toMorseCode,
+    toOctal,
+    toPunycode,
+    toQuotedPrintable,
+    toSnakeCase,
+    toTable,
+    toUNIXTimestamp,
+    toUpperCase,
+    translateDateTimeFormat,
+    tripleDESDecrypt,
+    tripleDESEncrypt,
+    typex,
+    UNIXTimestampToWindowsFiletime,
+    URLDecode,
+    URLEncode,
+    unescapeString,
+    unescapeUnicodeCharacters,
+    unique,
+    untar,
+    unzip,
+    vigenèreDecode,
+    vigenèreEncode,
+    whirlpool,
+    windowsFiletimeToUNIXTimestamp,
+    XKCDRandomNumber,
+    XMLBeautify,
+    XMLMinify,
+    XOR,
+    XORBruteForce,
+    XPathExpression,
+    YARARules,
+    zip,
+    zlibDeflate,
+    zlibInflate,
+    NodeDish as Dish,
+    prebaked as bake,
+    help,
+};

+ 293 - 0
src/web/SeasonalWaiter.mjs

@@ -4,6 +4,10 @@
  * @license Apache-2.0
  * @license Apache-2.0
  */
  */
 
 
+import clippy from "clippyjs";
+import "./static/clippy_assets/agents/Clippy/agent.js";
+import clippyMap from "./static/clippy_assets/agents/Clippy/map.png";
+
 /**
 /**
  * Waiter to handle seasonal events and easter eggs.
  * Waiter to handle seasonal events and easter eggs.
  */
  */
@@ -18,6 +22,8 @@ class SeasonalWaiter {
     constructor(app, manager) {
     constructor(app, manager) {
         this.app = app;
         this.app = app;
         this.manager = manager;
         this.manager = manager;
+
+        this.clippyAgent = null;
     }
     }
 
 
 
 
@@ -28,6 +34,14 @@ class SeasonalWaiter {
         // Konami code
         // Konami code
         this.kkeys = [];
         this.kkeys = [];
         window.addEventListener("keydown", this.konamiCodeListener.bind(this));
         window.addEventListener("keydown", this.konamiCodeListener.bind(this));
+
+        // Clippy
+        const now = new Date();
+        if (now.getMonth() === 3 && now.getDate() === 1) {
+            this.addClippyOption();
+            this.manager.addDynamicListener(".option-item #clippy", "change", this.setupClippy, this);
+            this.setupClippy();
+        }
     }
     }
 
 
 
 
@@ -51,6 +65,285 @@ class SeasonalWaiter {
         }
         }
     }
     }
 
 
+    /**
+     * Creates an option in the Options menu for turning Clippy on or off
+     */
+    addClippyOption() {
+        const optionsBody = document.getElementById("options-body"),
+            optionItem = document.createElement("span");
+
+        optionItem.className = "bmd-form-group is-filled";
+        optionItem.innerHTML = `<div class="checkbox option-item">
+            <label for="clippy">
+                <input type="checkbox" option="clippy" id="clippy" checked="">
+                Use the Clippy helper
+            </label>
+        </div>`;
+        optionsBody.appendChild(optionItem);
+
+        if (!this.app.options.hasOwnProperty("clippy")) {
+            this.app.options.clippy = true;
+        }
+
+        this.manager.options.load();
+    }
+
+    /**
+     * Sets up Clippy for April Fools Day
+     */
+    setupClippy() {
+        // Destroy any previous agents
+        if (this.clippyAgent) {
+            this.clippyAgent.closeBalloonImmediately();
+            this.clippyAgent.hide();
+        }
+
+        if (!this.app.options.clippy) {
+            if (this.clippyTimeouts) this.clippyTimeouts.forEach(t => clearTimeout(t));
+            return;
+        }
+
+        // Set base path to # to prevent external network requests
+        const clippyAssets = "#";
+        // Shim the library to prevent external network requests
+        shimClippy(clippy);
+
+        const self = this;
+        clippy.load("Clippy", (agent) => {
+            shimClippyAgent(agent);
+            self.clippyAgent = agent;
+            agent.show();
+            agent.speak("Hello, I'm Clippy, your personal cyber assistant!");
+        }, undefined, clippyAssets);
+
+        // Watch for the Auto Magic button appearing
+        const magic = document.getElementById("magic");
+        const observer = new MutationObserver((mutationsList, observer) => {
+            // Read in message and recipe
+            let msg, recipe;
+            for (const mutation of mutationsList) {
+                if (mutation.attributeName === "data-original-title") {
+                    msg = magic.getAttribute("data-original-title");
+                }
+                if (mutation.attributeName === "data-recipe") {
+                    recipe = magic.getAttribute("data-recipe");
+                }
+            }
+
+            // Close balloon if it is currently showing a magic hint
+            const balloon = self.clippyAgent._balloon._balloon;
+            if (balloon.is(":visible") && balloon.text().indexOf("That looks like encoded data") >= 0) {
+                self.clippyAgent._balloon.hide(true);
+                this.clippyAgent._balloon._hidden = true;
+            }
+
+            // If a recipe was found, get Clippy to tell the user
+            if (recipe) {
+                recipe = this.manager.controls.generateStateUrl(true, true, JSON.parse(recipe));
+                msg = `That looks like encoded data!<br><br>${msg}<br><br>Click <a class="clippyMagicRecipe" href="${recipe}">here</a> to load this recipe.`;
+
+                // Stop current balloon activity immediately and trigger speak again
+                this.clippyAgent.closeBalloonImmediately();
+                self.clippyAgent.speak(msg, true);
+                // self.clippyAgent._queue.next();
+            }
+        });
+        observer.observe(document.getElementById("magic"), {attributes: true});
+
+        // Play animations for various things
+        this.manager.addListeners("#search", "click", () => {
+            this.clippyAgent.play("Searching");
+        }, this);
+        this.manager.addListeners("#save,#save-to-file", "click", () => {
+            this.clippyAgent.play("Save");
+        }, this);
+        this.manager.addListeners("#clr-recipe,#clr-io", "click", () => {
+            this.clippyAgent.play("EmptyTrash");
+        }, this);
+        this.manager.addListeners("#bake", "click", e => {
+            if (e.target.closest("button").textContent.toLowerCase().indexOf("bake") >= 0) {
+                this.clippyAgent.play("Thinking");
+            } else {
+                this.clippyAgent.play("EmptyTrash");
+            }
+            this.clippyAgent._queue.clear();
+        }, this);
+        this.manager.addListeners("#input-text", "keydown", () => {
+            this.clippyAgent.play("Writing");
+            this.clippyAgent._queue.clear();
+        }, this);
+        this.manager.addDynamicListener("a.clippyMagicRecipe", "click", (e) => {
+            this.clippyAgent.play("Congratulate");
+        }, this);
+
+        this.clippyTimeouts = [];
+        // Show challenge after timeout
+        this.clippyTimeouts.push(setTimeout(() => {
+            const hex = "1f 8b 08 00 ae a1 9b 5c 00 ff 05 40 a1 12 00 10 0c fd 26 61 5b 76 aa 9d 26 a8 02 02 37 84 f7 fb bb c5 a4 5f 22 c6 09 e5 6e c5 4c 2d 3f e9 30 a6 ea 41 a2 f2 ac 1c 00 00 00";
+            self.clippyAgent.speak(`How about a fun challenge?<br><br>Try decoding this (click to load):<br><a href="#recipe=[]&input=${encodeURIComponent(btoa(hex))}">${hex}</a>`, true);
+            self.clippyAgent.play("GetAttention");
+        }, 1 * 60 * 1000));
+
+        this.clippyTimeouts.push(setTimeout(() => {
+            self.clippyAgent.speak("<i>Did you know?</i><br><br>You can load files into CyberChef up to around 500MB using drag and drop or the load file button.", 15000);
+            self.clippyAgent.play("Wave");
+        }, 2 * 60 * 1000));
+
+        this.clippyTimeouts.push(setTimeout(() => {
+            self.clippyAgent.speak("<i>Did you know?</i><br><br>You can use the 'Fork' operation to split up your input and run the recipe over each branch separately.<br><br><a class='clippyMagicRecipe' href=\"#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&amp;input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA\">Here's an example</a>.", 15000);
+            self.clippyAgent.play("Print");
+        }, 3 * 60 * 1000));
+
+        this.clippyTimeouts.push(setTimeout(() => {
+            self.clippyAgent.speak("<i>Did you know?</i><br><br>The 'Magic' operation uses a number of methods to detect encoded data and the operations which can be used to make sense of it. A technical description of these methods can be found <a href=\"https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic\">here</a>.", 15000);
+            self.clippyAgent.play("Alert");
+        }, 4 * 60 * 1000));
+
+        this.clippyTimeouts.push(setTimeout(() => {
+            self.clippyAgent.speak("<i>Did you know?</i><br><br>You can use parts of the input as arguments to operations.<br><br><a class='clippyMagicRecipe' href=\"#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')&amp;input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ\">Click here for an example</a>.", 15000);
+            self.clippyAgent.play("CheckingSomething");
+        }, 5 * 60 * 1000));
+    }
+
+}
+
+
+/**
+ * Shims various ClippyJS functions to modify behaviour.
+ *
+ * @param {Clippy} clippy - The Clippy library
+ */
+function shimClippy(clippy) {
+    // Shim _loadSounds so that it doesn't actually try to load any sounds
+    clippy.load._loadSounds = function _loadSounds (name, path) {
+        let dfd = clippy.load._sounds[name];
+        if (dfd) return dfd;
+
+        // set dfd if not defined
+        dfd = clippy.load._sounds[name] = $.Deferred();
+
+        // Resolve immediately without loading
+        dfd.resolve({});
+
+        return dfd.promise();
+    };
+
+    // Shim _loadMap so that it uses the local copy
+    clippy.load._loadMap = function _loadMap (path) {
+        let dfd = clippy.load._maps[path];
+        if (dfd) return dfd;
+
+        // set dfd if not defined
+        dfd = clippy.load._maps[path] = $.Deferred();
+
+        const src = clippyMap;
+        const img = new Image();
+
+        img.onload = dfd.resolve;
+        img.onerror = dfd.reject;
+
+        // start loading the map;
+        img.setAttribute("src", src);
+
+        return dfd.promise();
+    };
+
+    // Make sure we don't request the remote map
+    clippy.Animator.prototype._setupElement = function _setupElement (el) {
+        const frameSize = this._data.framesize;
+        el.css("display", "none");
+        el.css({ width: frameSize[0], height: frameSize[1] });
+        el.css("background", "url('" + clippyMap + "') no-repeat");
+
+        return el;
+    };
+}
+
+/**
+ * Shims various ClippyJS Agent functions to modify behaviour.
+ *
+ * @param {Agent} agent - The Clippy Agent
+ */
+function shimClippyAgent(agent) {
+    // Turn off all sounds
+    agent._animator._playSound = () => {};
+
+    // Improve speak function to support HTML markup
+    const self = agent._balloon;
+    agent._balloon.speak = (complete, text, hold) => {
+        self._hidden = false;
+        self.show();
+        const c = self._content;
+        // set height to auto
+        c.height("auto");
+        c.width("auto");
+        // add the text
+        c.html(text);
+        // set height
+        c.height(c.height());
+        c.width(c.width());
+        c.text("");
+        self.reposition();
+
+        self._complete = complete;
+        self._sayWords(text, hold, complete);
+        if (hold) agent._queue.next();
+    };
+
+    // Improve the _sayWords function to allow HTML and support timeouts
+    agent._balloon.WORD_SPEAK_TIME = 60;
+    agent._balloon._sayWords = (text, hold, complete) => {
+        self._active = true;
+        self._hold = hold;
+        const words = text.split(/[^\S-]/);
+        const time = self.WORD_SPEAK_TIME;
+        const el = self._content;
+        let idx = 1;
+        clearTimeout(self.holdTimeout);
+
+        self._addWord = $.proxy(function () {
+            if (!self._active) return;
+            if (idx > words.length) {
+                delete self._addWord;
+                self._active = false;
+                if (!self._hold) {
+                    complete();
+                    self.hide();
+                } else if (typeof hold === "number") {
+                    self.holdTimeout = setTimeout(() => {
+                        self._hold = false;
+                        complete();
+                        self.hide();
+                    }, hold);
+                }
+            } else {
+                el.html(words.slice(0, idx).join(" "));
+                idx++;
+                self._loop = window.setTimeout($.proxy(self._addWord, self), time);
+            }
+        }, self);
+
+        self._addWord();
+    };
+
+    // Add break-word to balloon CSS
+    agent._balloon._balloon.css("word-break", "break-word");
+
+    // Close the balloon on click (unless it was a link)
+    agent._balloon._balloon.click(e => {
+        if (e.target.nodeName !== "A") {
+            agent._balloon.hide(true);
+            agent._balloon._hidden = true;
+        }
+    });
+
+    // Add function to immediately close the balloon even if it is currently doing something
+    agent.closeBalloonImmediately = () => {
+        agent._queue.clear();
+        agent._balloon.hide(true);
+        agent._balloon._hidden = true;
+        agent._queue.next();
+    };
 }
 }
 
 
 export default SeasonalWaiter;
 export default SeasonalWaiter;

+ 1 - 1
src/web/html/index.html

@@ -591,7 +591,7 @@
                                     What sort of things can I do with CyberChef?
                                     What sort of things can I do with CyberChef?
                                 </a>
                                 </a>
                                 <div class="collapse" id="faq-examples">
                                 <div class="collapse" id="faq-examples">
-                                    <p>There are around 200 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
+                                    <p>There are around 300 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
                                     <ul>
                                     <ul>
                                         <li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
                                         <li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
                                         <li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>
                                         <li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>

+ 0 - 1
src/web/index.js

@@ -8,7 +8,6 @@
 import "./stylesheets/index.js";
 import "./stylesheets/index.js";
 
 
 // Libs
 // Libs
-import "babel-polyfill";
 import "arrive";
 import "arrive";
 import "snackbarjs";
 import "snackbarjs";
 import "bootstrap-material-design";
 import "bootstrap-material-design";

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
src/web/static/clippy_assets/agents/Clippy/agent.js


BIN
src/web/static/clippy_assets/agents/Clippy/map.png


+ 62 - 0
src/web/static/clippy_assets/clippy.css

@@ -0,0 +1,62 @@
+.clippy, .clippy-balloon {
+    position: fixed;
+    z-index: 1000;
+    cursor: pointer;
+}
+
+.clippy-balloon {
+
+    background: #FFC;
+    color: black;
+    padding: 8px;
+    border: 1px solid black;
+    border-radius: 5px;
+
+}
+
+.clippy-content {
+    max-width: 200px;
+    min-width: 120px;
+    font-family: "Microsoft Sans", sans-serif;
+    font-size: 10pt;
+}
+
+.clippy-tip {
+    width: 10px;
+    height: 16px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAMAAAAlvKiEAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRF///MAAAA////52QwgAAAAAN0Uk5T//8A18oNQQAAAGxJREFUeNqs0kEOwCAIRFHn3//QTUU6xMyyxii+jQosrTPkyPEM6IN3FtzIRk1U4dFeKWQiH6pRRowMVKEmvronEynkwj0uZJgR22+YLopPSo9P34wJSamLSU7lSIWLJU7NkNomNlhqxUeAAQC+TQLZyEuJBwAAAABJRU5ErkJggg==) no-repeat;
+    position: absolute;
+}
+
+.clippy-top-left .clippy-tip {
+    top: 100%;
+    margin-top: 0px;
+    left: 100%;
+    margin-left: -50px;
+}
+
+.clippy-top-right .clippy-tip {
+    top: 100%;
+    margin-top: 0px;
+    left: 0;
+    margin-left: 50px;
+    background-position: -10px 0;
+
+}
+
+.clippy-bottom-right .clippy-tip {
+    top: 0;
+    margin-top: -16px;
+    left: 0;
+    margin-left: 50px;
+    background-position: -10px -16px;
+}
+
+.clippy-bottom-left .clippy-tip {
+    top: 0;
+    margin-top: -16px;
+    left: 100%;
+    margin-left: -50px;
+    background-position: 0px -16px;
+}
+

BIN
src/web/static/clippy_assets/images/border.png


BIN
src/web/static/clippy_assets/images/tip.png


+ 1 - 0
src/web/stylesheets/index.js

@@ -8,6 +8,7 @@
 
 
 /* Libraries */
 /* Libraries */
 import "highlight.js/styles/vs.css";
 import "highlight.js/styles/vs.css";
+import "../static/clippy_assets/clippy.css";
 
 
 /* Frameworks */
 /* Frameworks */
 import "./vendors/bootstrap.scss";
 import "./vendors/bootstrap.scss";

+ 0 - 1
tests/lib/TestRegister.mjs

@@ -48,7 +48,6 @@ class TestRegister {
      * Runs all the tests in the register.
      * Runs all the tests in the register.
      */
      */
     runTests () {
     runTests () {
-        console.log("run tests");
         return Promise.all(
         return Promise.all(
             this.tests.map(function(test, i) {
             this.tests.map(function(test, i) {
                 const chef = new Chef();
                 const chef = new Chef();

+ 50 - 1
tests/node/tests/File.mjs

@@ -2,7 +2,8 @@ import assert from "assert";
 import it from "../assertionHandler";
 import it from "../assertionHandler";
 import TestRegister from "../../lib/TestRegister";
 import TestRegister from "../../lib/TestRegister";
 import File from "../../../src/node/File";
 import File from "../../../src/node/File";
-import {zip} from "../../../src/node/index";
+import {zip, Dish} from "../../../src/node/index";
+import { DishBigNumber } from "../../../src/core/dishTranslationTypes/index.mjs";
 
 
 TestRegister.addApiTests([
 TestRegister.addApiTests([
     it("File: should exist", () => {
     it("File: should exist", () => {
@@ -31,4 +32,52 @@ TestRegister.addApiTests([
         const file =  new File([uint8Array], "sample.txt");
         const file =  new File([uint8Array], "sample.txt");
         assert.strictEqual(file.type, "application/unknown");
         assert.strictEqual(file.type, "application/unknown");
     }),
     }),
+
+    it("File: should be able to make a dish from it", () => {
+        const uint8Array = new Uint8Array(Buffer.from("hello"));
+        const file =  new File([uint8Array], "sample.txt");
+        try {
+            const dish = new Dish(file, 7);
+            assert.ok(dish.valid());
+        } catch (e) {
+            assert.fail(e.message);
+        }
+    }),
+
+    it("File: should allow dish to translate to ArrayBuffer", () => {
+        const uint8Array = new Uint8Array(Buffer.from("hello"));
+        const file =  new File([uint8Array], "sample.txt");
+        try {
+            const dish = new Dish(file, 7);
+            assert.ok(dish.value);
+
+            dish.get(4);
+            assert.strictEqual(dish.type, 4);
+            assert.ok(dish.valid());
+
+        } catch (e) {
+            assert.fail(e.message);
+        }
+    }),
+
+    it("File: should allow dish to translate from ArrayBuffer to File", () => {
+        const uint8Array = new Uint8Array(Buffer.from("hello"));
+        const file =  new File([uint8Array], "sample.txt");
+        try {
+            const dish = new Dish(file, 7);
+            assert.ok(dish.value);
+
+            // translate to ArrayBuffer
+            dish.get(4);
+            assert.ok(dish.valid());
+
+            // translate back to File
+            dish.get(7);
+            assert.ok(dish.valid());
+
+        } catch (e) {
+            assert.fail(e.message);
+        }
+    })
+
 ]);
 ]);

+ 10 - 10
tests/node/tests/nodeApi.mjs

@@ -400,8 +400,8 @@ TestRegister.addApiTests([
     }),
     }),
 
 
     it("Operation arguments: should be accessible from operation object if op has array arg", () => {
     it("Operation arguments: should be accessible from operation object if op has array arg", () => {
-        assert.ok(chef.toCharcode.argOptions);
-        assert.deepEqual(chef.unzip.argOptions, {
+        assert.ok(chef.toCharcode.args);
+        assert.deepEqual(chef.unzip.args, {
             password: {
             password: {
                 type: "binaryString",
                 type: "binaryString",
                 value: "",
                 value: "",
@@ -414,20 +414,20 @@ TestRegister.addApiTests([
     }),
     }),
 
 
     it("Operation arguments: should have key for each argument in operation", () => {
     it("Operation arguments: should have key for each argument in operation", () => {
-        assert.ok(chef.convertDistance.argOptions.inputUnits);
-        assert.ok(chef.convertDistance.argOptions.outputUnits);
+        assert.ok(chef.convertDistance.args.inputUnits);
+        assert.ok(chef.convertDistance.args.outputUnits);
 
 
-        assert.strictEqual(chef.bitShiftRight.argOptions.amount.type, "number");
-        assert.strictEqual(chef.bitShiftRight.argOptions.amount.value, 1);
-        assert.strictEqual(chef.bitShiftRight.argOptions.type.type, "option");
-        assert.ok(Array.isArray(chef.bitShiftRight.argOptions.type.options));
+        assert.strictEqual(chef.bitShiftRight.args.amount.type, "number");
+        assert.strictEqual(chef.bitShiftRight.args.amount.value, 1);
+        assert.strictEqual(chef.bitShiftRight.args.type.type, "option");
+        assert.ok(Array.isArray(chef.bitShiftRight.args.type.options));
 
 
     }),
     }),
 
 
     it("Operation arguments: should list all options excluding subheadings", () => {
     it("Operation arguments: should list all options excluding subheadings", () => {
         // First element (subheading) removed
         // First element (subheading) removed
-        assert.equal(chef.convertDistance.argOptions.inputUnits.options[0], "Nanometres (nm)");
-        assert.equal(chef.defangURL.argOptions.process.options[1], "Only full URLs");
+        assert.equal(chef.convertDistance.args.inputUnits.options[0], "Nanometres (nm)");
+        assert.equal(chef.defangURL.args.process.options[1], "Only full URLs");
     })
     })
 
 
 ]);
 ]);

+ 3 - 1
tests/operations/index.mjs

@@ -10,7 +10,6 @@
  * @copyright Crown Copyright 2017
  * @copyright Crown Copyright 2017
  * @license Apache-2.0
  * @license Apache-2.0
  */
  */
-import "babel-polyfill";
 
 
 import {
 import {
     setLongTestFailure,
     setLongTestFailure,
@@ -38,6 +37,7 @@ import "./tests/BitwiseOp";
 import "./tests/ByteRepr";
 import "./tests/ByteRepr";
 import "./tests/CartesianProduct";
 import "./tests/CartesianProduct";
 import "./tests/CharEnc";
 import "./tests/CharEnc";
+import "./tests/Charts";
 import "./tests/Checksum";
 import "./tests/Checksum";
 import "./tests/Ciphers";
 import "./tests/Ciphers";
 import "./tests/Code";
 import "./tests/Code";
@@ -92,6 +92,8 @@ import "./tests/Enigma";
 import "./tests/Bombe";
 import "./tests/Bombe";
 import "./tests/MultipleBombe";
 import "./tests/MultipleBombe";
 import "./tests/Typex";
 import "./tests/Typex";
+import "./tests/BLAKE2b";
+import "./tests/BLAKE2s";
 
 
 // Cannot test operations that use the File type yet
 // Cannot test operations that use the File type yet
 //import "./tests/SplitColourChannels";
 //import "./tests/SplitColourChannels";

+ 56 - 0
tests/operations/tests/BLAKE2b.mjs

@@ -0,0 +1,56 @@
+/**
+ * BitwiseOp tests
+ *
+ * @author h345983745
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+import TestRegister from "../TestRegister";
+
+TestRegister.addTests([
+    {
+        name: "BLAKE2b: 512 - Hello World",
+        input: "Hello World",
+        expectedOutput: "4386a08a265111c9896f56456e2cb61a64239115c4784cf438e36cc851221972da3fb0115f73cd02486254001f878ab1fd126aac69844ef1c1ca152379d0a9bd",
+        recipeConfig: [
+            { "op": "BLAKE2b",
+                "args": ["512", "Hex", {string: "", option: "UTF8"}] }
+        ]
+    },
+    {
+        name: "BLAKE2b: 384 - Hello World",
+        input: "Hello World",
+        expectedOutput: "4d388e82ca8f866e606b6f6f0be910abd62ad6e98c0adfc27cf35acf948986d5c5b9c18b6f47261e1e679eb98edf8e2d",
+        recipeConfig: [
+            { "op": "BLAKE2b",
+                "args": ["384", "Hex", {string: "", option: "UTF8"}] }
+        ]
+    },
+    {
+        name: "BLAKE2b: 256 - Hello World",
+        input: "Hello World",
+        expectedOutput: "1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00",
+        recipeConfig: [
+            { "op": "BLAKE2b",
+                "args": ["256", "Hex", {string: "", option: "UTF8"}] }
+        ]
+    },
+    {
+        name: "BLAKE2b: 160 - Hello World",
+        input: "Hello World",
+        expectedOutput: "6a8489e6fd6e51fae12ab271ec7fc8134dd5d737",
+        recipeConfig: [
+            { "op": "BLAKE2b",
+                "args": ["160", "Hex", {string: "", option: "UTF8"}] }
+        ]
+    },
+    {
+        name: "BLAKE2b: Key Test",
+        input: "message data",
+        expectedOutput: "3d363ff7401e02026f4a4687d4863ced",
+        recipeConfig: [
+            { "op": "BLAKE2b",
+                "args": ["128", "Hex", {string: "pseudorandom key", option: "UTF8"}] }
+        ]
+    }
+]);

+ 47 - 0
tests/operations/tests/BLAKE2s.mjs

@@ -0,0 +1,47 @@
+/**
+ * BitwiseOp tests
+ *
+ * @author h345983745
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+import TestRegister from "../TestRegister";
+
+TestRegister.addTests([
+    {
+        name: "BLAKE2s: 256 - Hello World",
+        input: "Hello World",
+        expectedOutput: "7706af019148849e516f95ba630307a2018bb7bf03803eca5ed7ed2c3c013513",
+        recipeConfig: [
+            { "op": "BLAKE2s",
+                "args": ["256", "Hex", {string: "", option: "UTF8"}] }
+        ]
+    },
+    {
+        name: "BLAKE2s: 160 - Hello World",
+        input: "Hello World",
+        expectedOutput: "0e4fcfc2ee0097ac1d72d70b595a39e09a3c7c7e",
+        recipeConfig: [
+            { "op": "BLAKE2s",
+                "args": ["160", "Hex", {string: "", option: "UTF8"}] }
+        ]
+    },
+    {
+        name: "BLAKE2s: 128 - Hello World",
+        input: "Hello World",
+        expectedOutput: "9964ee6f36126626bf864363edfa96f6",
+        recipeConfig: [
+            { "op": "BLAKE2s",
+                "args": ["128", "Hex", {string: "", option: "UTF8"}] }
+        ]
+    },
+    {
+        name: "BLAKE2s: Key Test",
+        input: "Hello World",
+        expectedOutput: "9964ee6f36126626bf864363edfa96f6",
+        recipeConfig: [
+            { "op": "BLAKE2s",
+                "args": ["128", "Hex", {string: "", option: "UTF8"}] }
+        ]
+    }
+]);

+ 55 - 0
tests/operations/tests/Charts.mjs

@@ -0,0 +1,55 @@
+/**
+ * Chart tests.
+ *
+ * @author Matt C [me@mitt.dev]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+import TestRegister from "../TestRegister";
+
+TestRegister.addTests([
+    {
+        name: "Scatter chart",
+        input: "100 100\n200 200\n300 300\n400 400\n500 500",
+        expectedMatch: /^<svg width/,
+        recipeConfig: [
+            {
+                "op": "Scatter chart",
+                "args": ["Line feed", "Space", false, "time", "stress", "black", 5, false]
+            }
+        ],
+    },
+    {
+        name: "Hex density chart",
+        input: "100 100\n200 200\n300 300\n400 400\n500 500",
+        expectedMatch: /^<svg width/,
+        recipeConfig: [
+            {
+                "op": "Hex Density chart",
+                "args": ["Line feed", "Space", 25, 15, true, "", "", true, "white", "black", true]
+            }
+        ],
+    },
+    {
+        name: "Series chart",
+        input: "100 100 100\n200 200 200\n300 300 300\n400 400 400\n500 500 500",
+        expectedMatch: /^<svg width/,
+        recipeConfig: [
+            {
+                "op": "Series chart",
+                "args": ["Line feed", "Space", "", 1, "mediumseagreen, dodgerblue, tomato"]
+            }
+        ],
+    },
+    {
+        name: "Heatmap chart",
+        input: "100 100\n200 200\n300 300\n400 400\n500 500",
+        expectedMatch: /^<svg width/,
+        recipeConfig: [
+            {
+                "op": "Heatmap chart",
+                "args": ["Line feed", "Space", 25, 25, true, "", "", false, "white", "black"]
+            }
+        ],
+    },
+]);

+ 6 - 2
webpack.config.js

@@ -133,11 +133,15 @@ module.exports = {
         warningsFilter: [
         warningsFilter: [
             /source-map/,
             /source-map/,
             /dependency is an expression/,
             /dependency is an expression/,
-            /export 'default'/
+            /export 'default'/,
+            /Can't resolve 'sodium'/
         ],
         ],
     },
     },
     node: {
     node: {
-        fs: "empty"
+        fs: "empty",
+        "child_process": "empty",
+        net: "empty",
+        tls: "empty"
     },
     },
     performance: {
     performance: {
         hints: false
         hints: false

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff