Browse Source

Merge branch 'master' into feature-hashing

n1474335 7 years ago
parent
commit
7966b2bde6
50 changed files with 2377 additions and 1550 deletions
  1. 1 0
      .eslintignore
  2. 4 1
      .eslintrc.json
  3. 1 0
      .gitignore
  4. 163 132
      Gruntfile.js
  5. 448 114
      package-lock.json
  6. 7 2
      package.json
  7. 35 8
      src/core/Chef.js
  8. 178 0
      src/core/ChefWorker.js
  9. 0 27
      src/core/FlowControl.js
  10. 16 5
      src/core/Operation.js
  11. 34 3
      src/core/Recipe.js
  12. 14 5
      src/core/Utils.js
  13. 123 122
      src/core/config/OperationConfig.js
  14. 22 0
      src/core/config/modules/CharEnc.js
  15. 42 0
      src/core/config/modules/Ciphers.js
  16. 44 0
      src/core/config/modules/Code.js
  17. 32 0
      src/core/config/modules/Compression.js
  18. 191 0
      src/core/config/modules/Default.js
  19. 20 0
      src/core/config/modules/Diff.js
  20. 21 0
      src/core/config/modules/Encodings.js
  21. 22 0
      src/core/config/modules/HTTP.js
  22. 45 0
      src/core/config/modules/Hashing.js
  23. 25 0
      src/core/config/modules/Image.js
  24. 28 0
      src/core/config/modules/JSBN.js
  25. 37 0
      src/core/config/modules/OpModules.js
  26. 25 0
      src/core/config/modules/PublicKey.js
  27. 41 17
      src/core/operations/BitwiseOp.js
  28. 3 5
      src/core/operations/ByteRepr.js
  29. 0 81
      src/core/operations/DateTime.js
  30. 94 0
      src/core/operations/Diff.js
  31. 0 33
      src/core/operations/Extract.js
  32. 99 0
      src/core/operations/Filetime.js
  33. 1 1
      src/core/operations/Hexdump.js
  34. 0 699
      src/core/operations/PublicKey.js
  35. 0 78
      src/core/operations/StrUtils.js
  36. 67 86
      src/web/App.js
  37. 65 7
      src/web/ControlsWaiter.js
  38. 22 70
      src/web/HighlighterWaiter.js
  39. 4 6
      src/web/InputWaiter.js
  40. 4 1
      src/web/Manager.js
  41. 1 3
      src/web/OperationsWaiter.js
  42. 9 3
      src/web/OptionsWaiter.js
  43. 40 0
      src/web/OutputWaiter.js
  44. 180 0
      src/web/WorkerWaiter.js
  45. 14 12
      src/web/html/index.html
  46. 1 2
      src/web/index.js
  47. 1 1
      src/web/stylesheets/layout/_controls.css
  48. 29 16
      src/web/stylesheets/layout/_io.css
  49. 8 10
      src/web/stylesheets/preloader.css
  50. 116 0
      webpack.config.js

+ 1 - 0
.eslintignore

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

+ 4 - 1
.eslintrc.json

@@ -97,6 +97,9 @@
 
         "COMPILE_TIME": false,
         "COMPILE_MSG": false,
-        "PKG_VERSION": false
+        "PKG_VERSION": false,
+        "ENVIRONMENT_IS_WORKER": false,
+        "ENVIRONMENT_IS_NODE": false,
+        "ENVIRONMENT_IS_WEB": false
     }
 }

+ 1 - 0
.gitignore

@@ -6,3 +6,4 @@ docs/*
 !docs/*.conf.json
 !docs/*.ico
 .vscode
+src/core/config/MetaConfig.js

+ 163 - 132
Gruntfile.js

@@ -1,8 +1,18 @@
+"use strict";
+
 const webpack = require("webpack");
-const ExtractTextPlugin = require("extract-text-webpack-plugin");
 const HtmlWebpackPlugin = require("html-webpack-plugin");
 const NodeExternals = require("webpack-node-externals");
 const Inliner = require("web-resource-inliner");
+const fs = require("fs");
+
+/**
+ * Grunt configuration for building the app in various formats.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
 
 module.exports = function (grunt) {
     grunt.file.defaultEncoding = "utf8";
@@ -11,15 +21,15 @@ module.exports = function (grunt) {
     // Tasks
     grunt.registerTask("dev",
         "A persistent task which creates a development build whenever source files are modified.",
-        ["clean:dev", "webpack:webDev"]);
+        ["clean:dev", "concurrent:dev"]);
 
     grunt.registerTask("node",
         "Compiles CyberChef into a single NodeJS module.",
-        ["clean:node", "webpack:node", "chmod:build"]);
+        ["clean:node", "webpack:metaConf", "webpack:node", "chmod:build"]);
 
     grunt.registerTask("test",
         "A task which runs all the tests in test/tests.",
-        ["clean:test", "webpack:tests", "execute:test"]);
+        ["clean:test", "webpack:metaConf", "webpack:tests", "execute:test"]);
 
     grunt.registerTask("docs",
         "Compiles documentation in the /docs directory.",
@@ -27,7 +37,7 @@ module.exports = function (grunt) {
 
     grunt.registerTask("prod",
         "Creates a production-ready build. Use the --msg flag to add a compile message.",
-        ["eslint", "clean:prod", "webpack:webProd", "inline", "chmod"]);
+        ["eslint", "clean:prod", "webpack:metaConf", "webpack:web", "inline", "chmod"]);
 
     grunt.registerTask("default",
         "Lints the code base",
@@ -35,8 +45,10 @@ module.exports = function (grunt) {
 
     grunt.registerTask("inline",
         "Compiles a production build of CyberChef into a single, portable web page.",
-        runInliner);
+        ["webpack:webInline", "runInliner", "clean:inlineScripts"]);
+
 
+    grunt.registerTask("runInliner", runInliner);
     grunt.registerTask("doc", "docs");
     grunt.registerTask("tests", "test");
     grunt.registerTask("lint", "eslint");
@@ -52,31 +64,28 @@ module.exports = function (grunt) {
     grunt.loadNpmTasks("grunt-exec");
     grunt.loadNpmTasks("grunt-execute");
     grunt.loadNpmTasks("grunt-accessibility");
+    grunt.loadNpmTasks("grunt-concurrent");
 
 
     // Project configuration
     const compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC",
-        banner = "/**\n" +
-            "* CyberChef - The Cyber Swiss Army Knife\n" +
-            "*\n" +
-            "* @copyright Crown Copyright 2016\n" +
-            "* @license Apache-2.0\n" +
-            "*\n" +
-            "*   Copyright 2016 Crown Copyright\n" +
-            "*\n" +
-            '* Licensed under the Apache License, Version 2.0 (the "License");\n' +
-            "* you may not use this file except in compliance with the License.\n" +
-            "* You may obtain a copy of the License at\n" +
-            "*\n" +
-            "*     http://www.apache.org/licenses/LICENSE-2.0\n" +
-            "*\n" +
-            "* Unless required by applicable law or agreed to in writing, software\n" +
-            '* distributed under the License is distributed on an "AS IS" BASIS,\n' +
-            "* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
-            "* See the License for the specific language governing permissions and\n" +
-            "* limitations under the License.\n" +
-            "*/\n",
-        pkg = grunt.file.readJSON("package.json");
+        pkg = grunt.file.readJSON("package.json"),
+        webpackConfig = require("./webpack.config.js"),
+        BUILD_CONSTANTS = {
+            COMPILE_TIME: JSON.stringify(compileTime),
+            COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
+            PKG_VERSION: JSON.stringify(pkg.version),
+            ENVIRONMENT_IS_WORKER: function() {
+                return typeof importScripts === "function";
+            },
+            ENVIRONMENT_IS_NODE: function() {
+                return typeof process === "object" && typeof require === "function";
+            },
+            ENVIRONMENT_IS_WEB: function() {
+                return typeof window === "object";
+            }
+        },
+        moduleEntryPoints = listEntryModules();
 
     /**
      * Compiles a production build of CyberChef into a single, portable web page.
@@ -105,20 +114,36 @@ module.exports = function (grunt) {
         });
     }
 
+    /**
+     * Generates an entry list for all the modules.
+     */
+    function listEntryModules() {
+        const path = "./src/core/config/modules/";
+        let entryModules = {};
+
+        fs.readdirSync(path).forEach(file => {
+            if (file !== "Default.js" && file !== "OpModules.js")
+                entryModules[file.split(".js")[0]] = path + file;
+        });
+
+        return entryModules;
+    }
+
     grunt.initConfig({
         clean: {
-            dev: ["build/dev/*"],
-            prod: ["build/prod/*"],
-            test: ["build/test/*"],
-            node: ["build/node/*"],
+            dev: ["build/dev/*", "src/core/config/MetaConfig.js"],
+            prod: ["build/prod/*", "src/core/config/MetaConfig.js"],
+            test: ["build/test/*", "src/core/config/MetaConfig.js"],
+            node: ["build/node/*", "src/core/config/MetaConfig.js"],
             docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
+            inlineScripts: ["build/prod/scripts.js"],
         },
         eslint: {
             options: {
                 configFile: "./.eslintrc.json"
             },
             configs: ["Gruntfile.js"],
-            core: ["src/core/**/*.js", "!src/core/lib/**/*"],
+            core: ["src/core/**/*.js", "!src/core/lib/**/*", "!src/core/config/MetaConfig.js"],
             web: ["src/web/**/*.js"],
             node: ["src/node/**/*.js"],
             tests: ["test/**/*.js"],
@@ -135,9 +160,16 @@ module.exports = function (grunt) {
                 src: [
                     "src/**/*.js",
                     "!src/core/lib/**/*",
+                    "!src/core/config/MetaConfig.js"
                 ],
             }
         },
+        concurrent: {
+            options: {
+                logConcurrentOutput: true
+            },
+            dev: ["webpack:metaConfDev", "webpack-dev-server:start"]
+        },
         accessibility: {
             options: {
                 accessibilityLevel: "WCAG2A",
@@ -151,114 +183,47 @@ module.exports = function (grunt) {
             }
         },
         webpack: {
-            options: {
-                plugins: [
-                    new webpack.ProvidePlugin({
-                        $: "jquery",
-                        jQuery: "jquery",
-                        moment: "moment-timezone"
-                    }),
-                    new webpack.BannerPlugin({
-                        banner: banner,
-                        raw: true,
-                        entryOnly: true
-                    }),
-                    new webpack.DefinePlugin({
-                        COMPILE_TIME: JSON.stringify(compileTime),
-                        COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
-                        PKG_VERSION: JSON.stringify(pkg.version)
-                    }),
-                    new ExtractTextPlugin("styles.css"),
-                ],
-                resolve: {
-                    alias: {
-                        jquery: "jquery/src/jquery"
-                    }
-                },
-                module: {
-                    rules: [
-                        {
-                            test: /\.js$/,
-                            exclude: /node_modules/,
-                            loader: "babel-loader?compact=false"
-                        },
-                        {
-                            test: /\.css$/,
-                            use: ExtractTextPlugin.extract({
-                                use: [
-                                    { loader: "css-loader?minimize" },
-                                    { loader: "postcss-loader" },
-                                ]
-                            })
-                        },
-                        {
-                            test: /\.less$/,
-                            use: ExtractTextPlugin.extract({
-                                use: [
-                                    { loader: "css-loader?minimize" },
-                                    { loader: "postcss-loader" },
-                                    { loader: "less-loader" }
-                                ]
-                            })
-                        },
-                        {
-                            test: /\.(ico|eot|ttf|woff|woff2)$/,
-                            loader: "url-loader",
-                            options: {
-                                limit: 10000
-                            }
-                        },
-                        { // First party images are saved as files to be cached
-                            test: /\.(png|jpg|gif|svg)$/,
-                            exclude: /node_modules/,
-                            loader: "file-loader",
-                            options: {
-                                name: "images/[name].[ext]"
-                            }
-                        },
-                        { // Third party images are inlined
-                            test: /\.(png|jpg|gif|svg)$/,
-                            exclude: /web\/static/,
-                            loader: "url-loader",
-                            options: {
-                                limit: 10000
-                            }
-                        },
-                    ]
-                },
-                stats: {
-                    children: false,
-                    warningsFilter: /source-map/
+            options: webpackConfig,
+            metaConf: {
+                target: "node",
+                entry: "./src/core/config/OperationConfig.js",
+                output: {
+                    filename: "MetaConfig.js",
+                    path: __dirname + "/src/core/config/",
+                    library: "MetaConfig",
+                    libraryTarget: "commonjs2",
+                    libraryExport: "default"
                 },
-                node: {
-                    fs: "empty"
-                }
+                externals: [NodeExternals()],
             },
-            webDev: {
-                target: "web",
-                entry: "./src/web/index.js",
+            metaConfDev: {
+                target: "node",
+                entry: "./src/core/config/OperationConfig.js",
                 output: {
-                    filename: "scripts.js",
-                    path: __dirname + "/build/dev"
+                    filename: "MetaConfig.js",
+                    path: __dirname + "/src/core/config/",
+                    library: "MetaConfig",
+                    libraryTarget: "commonjs2",
+                    libraryExport: "default"
                 },
-                plugins: [
-                    new HtmlWebpackPlugin({
-                        filename: "index.html",
-                        template: "./src/web/html/index.html",
-                        compileTime: compileTime,
-                        version: pkg.version,
-                    })
-                ],
+                externals: [NodeExternals()],
                 watch: true
             },
-            webProd: {
+            web: {
                 target: "web",
-                entry: "./src/web/index.js",
+                entry: Object.assign({
+                    main: "./src/web/index.js"
+                }, moduleEntryPoints),
                 output: {
-                    filename: "scripts.js",
                     path: __dirname + "/build/prod"
                 },
+                resolve: {
+                    alias: {
+                        "./config/modules/OpModules.js": "./config/modules/Default.js"
+                    }
+                },
                 plugins: [
+                    new webpack.DefinePlugin(BUILD_CONSTANTS),
                     new webpack.optimize.UglifyJsPlugin({
                         compress: {
                             "screw_ie8": true,
@@ -268,9 +233,10 @@ module.exports = function (grunt) {
                         },
                         comments: false,
                     }),
-                    new HtmlWebpackPlugin({ // Main version
+                    new HtmlWebpackPlugin({
                         filename: "index.html",
                         template: "./src/web/html/index.html",
+                        chunks: ["main"],
                         compileTime: compileTime,
                         version: pkg.version,
                         minify: {
@@ -280,7 +246,27 @@ module.exports = function (grunt) {
                             minifyCSS: true
                         }
                     }),
-                    new HtmlWebpackPlugin({ // Inline version
+                ]
+            },
+            webInline: {
+                target: "web",
+                entry: "./src/web/index.js",
+                output: {
+                    filename: "scripts.js",
+                    path: __dirname + "/build/prod"
+                },
+                plugins: [
+                    new webpack.DefinePlugin(BUILD_CONSTANTS),
+                    new webpack.optimize.UglifyJsPlugin({
+                        compress: {
+                            "screw_ie8": true,
+                            "dead_code": true,
+                            "unused": true,
+                            "warnings": false
+                        },
+                        comments: false,
+                    }),
+                    new HtmlWebpackPlugin({
                         filename: "cyberchef.htm",
                         template: "./src/web/html/index.html",
                         compileTime: compileTime,
@@ -302,7 +288,10 @@ module.exports = function (grunt) {
                 output: {
                     filename: "index.js",
                     path: __dirname + "/build/test"
-                }
+                },
+                plugins: [
+                    new webpack.DefinePlugin(BUILD_CONSTANTS)
+                ]
             },
             node: {
                 target: "node",
@@ -313,6 +302,48 @@ module.exports = function (grunt) {
                     path: __dirname + "/build/node",
                     library: "CyberChef",
                     libraryTarget: "commonjs2"
+                },
+                plugins: [
+                    new webpack.DefinePlugin(BUILD_CONSTANTS)
+                ]
+            }
+        },
+        "webpack-dev-server": {
+            options: {
+                webpack: webpackConfig,
+                host: "0.0.0.0",
+                disableHostCheck: true,
+                overlay: true,
+                inline: false,
+                clientLogLevel: "error",
+                stats: {
+                    children: false,
+                    chunks: false,
+                    modules: false,
+                    warningsFilter: /source-map/,
+                }
+            },
+            start: {
+                webpack: {
+                    target: "web",
+                    entry: Object.assign({
+                        main: "./src/web/index.js"
+                    }, moduleEntryPoints),
+                    resolve: {
+                        alias: {
+                            "./config/modules/OpModules.js": "./config/modules/Default.js"
+                        }
+                    },
+                    plugins: [
+                        new webpack.DefinePlugin(BUILD_CONSTANTS),
+                        new HtmlWebpackPlugin({
+                            filename: "index.html",
+                            template: "./src/web/html/index.html",
+                            chunks: ["main"],
+                            compileTime: compileTime,
+                            version: pkg.version,
+                        })
+                    ]
                 }
             }
         },

File diff suppressed because it is too large
+ 448 - 114
package-lock.json


+ 7 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "cyberchef",
-  "version": "5.20.0",
+  "version": "6.0.2",
   "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
   "author": "n1474335 <n1474335@gmail.com>",
   "homepage": "https://gchq.github.io/CyberChef",
@@ -40,6 +40,7 @@
     "grunt": ">=1.0.1",
     "grunt-accessibility": "~5.0.0",
     "grunt-chmod": "~1.1.1",
+    "grunt-concurrent": "^2.3.1",
     "grunt-contrib-clean": "~1.1.0",
     "grunt-contrib-copy": "~1.0.0",
     "grunt-eslint": "^20.1.0",
@@ -58,9 +59,12 @@
     "postcss-loader": "^2.0.6",
     "style-loader": "^0.18.2",
     "url-loader": "^0.5.9",
+    "val-loader": "^1.0.2",
     "web-resource-inliner": "^4.1.1",
     "webpack": "^3.5.6",
-    "webpack-node-externals": "^1.6.0"
+    "webpack-dev-server": "^2.5.0",
+    "webpack-node-externals": "^1.6.0",
+    "worker-loader": "^0.8.0"
   },
   "dependencies": {
     "babel-polyfill": "^6.26.0",
@@ -95,6 +99,7 @@
     "zlibjs": "^0.3.1"
   },
   "scripts": {
+    "start": "grunt dev",
     "build": "grunt prod",
     "test": "grunt test",
     "docs": "grunt docs"

+ 35 - 8
src/core/Chef.js

@@ -30,7 +30,6 @@ const Chef = function() {
  * @returns {string} response.result - The output of the recipe
  * @returns {string} response.type - The data type of the result
  * @returns {number} response.progress - The position that we have got to in the recipe
- * @returns {number} response.options - The app options object (which may have been changed)
  * @returns {number} response.duration - The number of ms it took to execute the recipe
  * @returns {number} response.error - The error object thrown by a failed operation (false if no error)
 */
@@ -40,12 +39,7 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress,
         containsFc = recipe.containsFlowControl(),
         error      = false;
 
-    // Reset attemptHighlight flag
-    if (options.hasOwnProperty("attemptHighlight")) {
-        options.attemptHighlight = true;
-    }
-
-    if (containsFc) options.attemptHighlight = false;
+    if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
 
     // Clean up progress
     if (progress >= recipeConfig.length) {
@@ -86,7 +80,6 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress,
             this.dish.get(Dish.STRING),
         type: Dish.enumLookup(this.dish.type),
         progress: progress,
-        options: options,
         duration: new Date().getTime() - startTime,
         error: error
     };
@@ -123,4 +116,38 @@ Chef.prototype.silentBake = function(recipeConfig) {
     return new Date().getTime() - startTime;
 };
 
+
+/**
+ * Calculates highlight offsets if possible.
+ *
+ * @param {Object[]} recipeConfig
+ * @param {string} direction
+ * @param {Object} pos - The position object for the highlight.
+ * @param {number} pos.start - The start offset.
+ * @param {number} pos.end - The end offset.
+ * @returns {Object}
+ */
+Chef.prototype.calculateHighlights = function(recipeConfig, direction, pos) {
+    const recipe = new Recipe(recipeConfig);
+    const highlights = recipe.generateHighlightList();
+
+    if (!highlights) return false;
+
+    for (let i = 0; i < highlights.length; i++) {
+        // Remove multiple highlights before processing again
+        pos = [pos[0]];
+
+        const func = direction === "forward" ? highlights[i].f : highlights[i].b;
+
+        if (typeof func == "function") {
+            pos = func(pos, highlights[i].args);
+        }
+    }
+
+    return {
+        pos: pos,
+        direction: direction
+    };
+};
+
 export default Chef;

+ 178 - 0
src/core/ChefWorker.js

@@ -0,0 +1,178 @@
+/**
+ * Web Worker to handle communications between the front-end and the core.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import "babel-polyfill";
+import Chef from "./Chef.js";
+import OperationConfig from "./config/MetaConfig.js";
+import OpModules from "./config/modules/Default.js";
+
+
+// Set up Chef instance
+self.chef = new Chef();
+
+self.OpModules = OpModules;
+self.OperationConfig = OperationConfig;
+
+// Tell the app that the worker has loaded and is ready to operate
+self.postMessage({
+    action: "workerLoaded",
+    data: {}
+});
+
+/**
+ * Respond to message from parent thread.
+ *
+ * Messages should have the following format:
+ * {
+ *     action: "bake" | "silentBake",
+ *     data: {
+ *         input: {string},
+ *         recipeConfig: {[Object]},
+ *         options: {Object},
+ *         progress: {number},
+ *         step: {boolean}
+ *     } | undefined
+ * }
+ */
+self.addEventListener("message", function(e) {
+    // Handle message
+    const r = e.data;
+    switch (r.action) {
+        case "bake":
+            bake(r.data);
+            break;
+        case "silentBake":
+            silentBake(r.data);
+            break;
+        case "docURL":
+            // Used to set the URL of the current document so that scripts can be
+            // imported into an inline worker.
+            self.docURL = r.data;
+            break;
+        case "highlight":
+            calculateHighlights(
+                r.data.recipeConfig,
+                r.data.direction,
+                r.data.pos
+            );
+            break;
+        default:
+            break;
+    }
+});
+
+
+/**
+ * Baking handler
+ *
+ * @param {Object} data
+ */
+async function bake(data) {
+    // Ensure the relevant modules are loaded
+    loadRequiredModules(data.recipeConfig);
+
+    try {
+        const response = await self.chef.bake(
+            data.input,          // The user's input
+            data.recipeConfig,   // The configuration of the recipe
+            data.options,        // Options set by the user
+            data.progress,       // The current position in the recipe
+            data.step            // Whether or not to take one step or execute the whole recipe
+        );
+
+        self.postMessage({
+            action: "bakeSuccess",
+            data: response
+        });
+    } catch (err) {
+        self.postMessage({
+            action: "bakeError",
+            data: err.message
+        });
+    }
+}
+
+
+/**
+ * Silent baking handler
+ */
+function silentBake(data) {
+    const duration = self.chef.silentBake(data.recipeConfig);
+
+    self.postMessage({
+        action: "silentBakeComplete",
+        data: duration
+    });
+}
+
+
+/**
+ * Checks that all required modules are loaded and loads them if not.
+ *
+ * @param {Object} recipeConfig
+ */
+function loadRequiredModules(recipeConfig) {
+    recipeConfig.forEach(op => {
+        let module = self.OperationConfig[op.op].module;
+
+        if (!OpModules.hasOwnProperty(module)) {
+            console.log("Loading module " + module);
+            self.sendStatusMessage("Loading module " + module);
+            self.importScripts(self.docURL + "/" + module + ".js");
+        }
+    });
+}
+
+
+/**
+ * Calculates highlight offsets if possible.
+ *
+ * @param {Object[]} recipeConfig
+ * @param {string} direction
+ * @param {Object} pos - The position object for the highlight.
+ * @param {number} pos.start - The start offset.
+ * @param {number} pos.end - The end offset.
+ */
+function calculateHighlights(recipeConfig, direction, pos) {
+    pos = self.chef.calculateHighlights(recipeConfig, direction, pos);
+
+    self.postMessage({
+        action: "highlightsCalculated",
+        data: pos
+    });
+}
+
+
+/**
+ * Send status update to the app.
+ *
+ * @param {string} msg
+ */
+self.sendStatusMessage = function(msg) {
+    self.postMessage({
+        action: "statusMessage",
+        data: msg
+    });
+};
+
+
+/**
+ * Send an option value update to the app.
+ *
+ * @param {string} option
+ * @param {*} value
+ */
+self.setOption = function(option, value) {
+    self.postMessage({
+        action: "optionUpdate",
+        data: {
+            option: option,
+            value: value
+        }
+    });
+};

+ 0 - 27
src/core/FlowControl.js

@@ -13,22 +13,6 @@ import Dish from "./Dish.js";
  */
 const FlowControl = {
 
-    /**
-     * @constant
-     * @default
-     */
-    FORK_DELIM: "\\n",
-    /**
-     * @constant
-     * @default
-     */
-    MERGE_DELIM: "\\n",
-    /**
-     * @constant
-     * @default
-     */
-    FORK_IGNORE_ERRORS: false,
-
     /**
      * Fork operation.
      *
@@ -106,17 +90,6 @@ const FlowControl = {
     },
 
 
-    /**
-     * @constant
-     * @default
-     */
-    JUMP_NUM: 0,
-    /**
-     * @constant
-     * @default
-     */
-    MAX_JUMPS: 10,
-
     /**
      * Jump operation.
      *

+ 16 - 5
src/core/Operation.js

@@ -1,5 +1,7 @@
 import Dish from "./Dish.js";
 import Ingredient from "./Ingredient.js";
+import OperationConfig from "./config/MetaConfig.js";
+import OpModules from "./config/modules/OpModules.js";
 
 
 /**
@@ -11,10 +13,10 @@ import Ingredient from "./Ingredient.js";
  *
  * @class
  * @param {string} operationName
- * @param {Object} operationConfig
  */
-const Operation = function(operationName, operationConfig) {
+const Operation = function(operationName) {
     this.name             = operationName;
+    this.module           = "";
     this.description      = "";
     this.inputType        = -1;
     this.outputType       = -1;
@@ -25,8 +27,8 @@ const Operation = function(operationName, operationConfig) {
     this.disabled         = false;
     this.ingList          = [];
 
-    if (operationConfig) {
-        this._parseConfig(operationConfig);
+    if (OperationConfig.hasOwnProperty(this.name)) {
+        this._parseConfig(OperationConfig[this.name]);
     }
 };
 
@@ -38,19 +40,28 @@ const Operation = function(operationName, operationConfig) {
  * @param {Object} operationConfig
  */
 Operation.prototype._parseConfig = function(operationConfig) {
+    this.module           = operationConfig.module;
     this.description      = operationConfig.description;
     this.inputType        = Dish.typeEnum(operationConfig.inputType);
     this.outputType       = Dish.typeEnum(operationConfig.outputType);
-    this.run              = operationConfig.run;
     this.highlight        = operationConfig.highlight;
     this.highlightReverse = operationConfig.highlightReverse;
     this.flowControl      = operationConfig.flowControl;
+    this.run              = OpModules[this.module][this.name];
 
     for (let a = 0; a < operationConfig.args.length; a++) {
         const ingredientConfig = operationConfig.args[a];
         const ingredient = new Ingredient(ingredientConfig);
         this.addIngredient(ingredient);
     }
+
+    if (this.highlight === "func") {
+        this.highlight = OpModules[this.module][`${this.name}-highlight`];
+    }
+
+    if (this.highlightReverse === "func") {
+        this.highlightReverse = OpModules[this.module][`${this.name}-highlightReverse`];
+    }
 };
 
 

+ 34 - 3
src/core/Recipe.js

@@ -1,5 +1,4 @@
 import Operation from "./Operation.js";
-import OperationConfig from "./config/OperationConfig.js";
 
 
 /**
@@ -30,8 +29,7 @@ const Recipe = function(recipeConfig) {
 Recipe.prototype._parseConfig = function(recipeConfig) {
     for (let c = 0; c < recipeConfig.length; c++) {
         const operationName = recipeConfig[c].op;
-        const operationConfig = OperationConfig[operationName];
-        const operation = new Operation(operationName, operationConfig);
+        const operation = new Operation(operationName);
         operation.setIngValues(recipeConfig[c].args);
         operation.setBreakpoint(recipeConfig[c].breakpoint);
         operation.setDisabled(recipeConfig[c].disabled);
@@ -217,4 +215,37 @@ Recipe.prototype.fromString = function(recipeStr) {
     this._parseConfig(recipeConfig);
 };
 
+
+/**
+ * Generates a list of all the highlight functions assigned to operations in the recipe, if the
+ * entire recipe supports highlighting.
+ *
+ * @returns {Object[]} highlights
+ * @returns {function} highlights[].f
+ * @returns {function} highlights[].b
+ * @returns {Object[]} highlights[].args
+ */
+Recipe.prototype.generateHighlightList = function() {
+    const highlights = [];
+
+    for (let i = 0; i < this.opList.length; i++) {
+        let op = this.opList[i];
+        if (op.isDisabled()) continue;
+
+        // If any breakpoints are set, do not attempt to highlight
+        if (op.isBreakpoint()) return false;
+
+        // If any of the operations do not support highlighting, fail immediately.
+        if (op.highlight === false || op.highlight === undefined) return false;
+
+        highlights.push({
+            f: op.highlight,
+            b: op.highlightReverse,
+            args: op.getIngValues()
+        });
+    }
+
+    return highlights;
+};
+
 export default Recipe;

+ 14 - 5
src/core/Utils.js

@@ -234,7 +234,7 @@ const Utils = {
      * @returns {string}
      */
     printable: function(str, preserveWs) {
-        if (typeof window !== "undefined" && window.app && !window.app.options.treatAsUtf8) {
+        if (ENVIRONMENT_IS_WEB() && window.app && !window.app.options.treatAsUtf8) {
             str = Utils.byteArrayToChars(Utils.strToByteArray(str));
         }
 
@@ -384,8 +384,12 @@ const Utils = {
         let wordArray = CryptoJS.enc.Utf8.parse(str),
             byteArray = Utils.wordArrayToByteArray(wordArray);
 
-        if (typeof window !== "undefined" && str.length !== wordArray.sigBytes) {
-            window.app.options.attemptHighlight = false;
+        if (str.length !== wordArray.sigBytes) {
+            if (ENVIRONMENT_IS_WORKER()) {
+                self.setOption("attemptHighlight", false);
+            } else if (ENVIRONMENT_IS_WEB()) {
+                window.app.options.attemptHighlight = false;
+            }
         }
         return byteArray;
     },
@@ -448,8 +452,13 @@ const Utils = {
             let wordArray = new CryptoJS.lib.WordArray.init(words, byteArray.length),
                 str = CryptoJS.enc.Utf8.stringify(wordArray);
 
-            if (typeof window !== "undefined" && str.length !== wordArray.sigBytes)
-                window.app.options.attemptHighlight = false;
+            if (str.length !== wordArray.sigBytes) {
+                if (ENVIRONMENT_IS_WORKER()) {
+                    self.setOption("attemptHighlight", false);
+                } else if (ENVIRONMENT_IS_WEB()) {
+                    window.app.options.attemptHighlight = false;
+                }
+            }
             return str;
         } catch (err) {
             // If it fails, treat it as ANSI

File diff suppressed because it is too large
+ 123 - 122
src/core/config/OperationConfig.js


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 41 - 17
src/core/operations/BitwiseOp.js

@@ -1,5 +1,4 @@
 import Utils from "../Utils.js";
-import CryptoJS from "crypto-js";
 
 
 /**
@@ -92,7 +91,7 @@ const BitwiseOp = {
      * @constant
      * @default
      */
-    XOR_BRUTE_KEY_LENGTH: ["1", "2"],
+    XOR_BRUTE_KEY_LENGTH: 1,
     /**
      * @constant
      * @default
@@ -122,37 +121,62 @@ const BitwiseOp = {
      * @returns {string}
      */
     runXorBrute: function (input, args) {
-        const keyLength = parseInt(args[0], 10),
+        const keyLength = args[0],
             sampleLength = args[1],
             sampleOffset = args[2],
             scheme = args[3],
             nullPreserving = args[4],
             printKey = args[5],
             outputHex = args[6],
-            crib = args[7];
+            crib = args[7].toLowerCase();
 
-        let output = "",
+        let output = [],
             result,
             resultUtf8,
-            regex;
+            record = "";
 
         input = input.slice(sampleOffset, sampleOffset + sampleLength);
 
-        if (crib !== "") {
-            regex = new RegExp(crib, "im");
-        }
-
+        if (ENVIRONMENT_IS_WORKER())
+            self.sendStatusMessage("Calculating " + Math.pow(256, keyLength) + " values...");
+
+        /**
+         * Converts an integer to an array of bytes expressing that number.
+         *
+         * @param {number} int
+         * @param {number} len - Length of the resulting array
+         * @returns {array}
+         */
+        const intToByteArray = (int, len) => {
+            let res = Array(len).fill(0);
+            for (let i = len - 1; i >= 0; i--) {
+                res[i] = int & 0xff;
+                int = int >>> 8;
+            }
+            return res;
+        };
 
         for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) {
-            result = BitwiseOp._bitOp(input, Utils.fromHex(key.toString(16)), BitwiseOp._xor, nullPreserving, scheme);
+            if (key % 10000 === 0 && ENVIRONMENT_IS_WORKER()) {
+                self.sendStatusMessage("Calculating " + l + " values... " + Math.floor(key / l * 100) + "%");
+            }
+
+            result = BitwiseOp._bitOp(input, intToByteArray(key, keyLength), BitwiseOp._xor, nullPreserving, scheme);
             resultUtf8 = Utils.byteArrayToUtf8(result);
-            if (crib !== "" && resultUtf8.search(regex) === -1) continue;
-            if (printKey) output += "Key = " + Utils.hex(key, (2*keyLength)) + ": ";
-            if (outputHex) output += Utils.toHex(result) + "\n";
-            else output += Utils.printable(resultUtf8, false) + "\n";
-            if (printKey) output += "\n";
+            record = "";
+
+            if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue;
+            if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": ";
+            if (outputHex) {
+                record += Utils.toHex(result);
+            } else {
+                record += Utils.printable(resultUtf8, false);
+            }
+
+            output.push(record);
         }
-        return output;
+
+        return output.join("\n");
     },
 
 

+ 3 - 5
src/core/operations/ByteRepr.js

@@ -119,11 +119,11 @@ const ByteRepr = {
                 else if (ordinal < 4294967296) padding = 8;
                 else padding = 2;
 
-                if (padding > 2 && app) app.options.attemptHighlight = false;
+                if (padding > 2 && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
 
                 output += Utils.hex(ordinal, padding) + delim;
             } else {
-                if (app) app.options.attemptHighlight = false;
+                if (ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
                 output += ordinal.toString(base) + delim;
             }
         }
@@ -149,9 +149,7 @@ const ByteRepr = {
             throw "Error: Base argument must be between 2 and 36";
         }
 
-        if (base !== 16 && app) {
-            app.options.attemptHighlight = false;
-        }
+        if (base !== 16 && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
 
         // Split into groups of 2 if the whole string is concatenated and
         // too long to be a single character

+ 0 - 81
src/core/operations/DateTime.js

@@ -1,5 +1,3 @@
-import {BigInteger} from "jsbn";
-
 /**
  * Date and time operations.
  *
@@ -80,85 +78,6 @@ const DateTime = {
     },
 
 
-    /**
-     * Windows Filetime to Unix Timestamp operation.
-     *
-     * @author bwhitn [brian.m.whitney@outlook.com]
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runFromFiletimeToUnix: function(input, args) {
-        let units = args[0],
-            format = args[1];
-
-        if (format === "Hex") {
-            input = new BigInteger(input, 16);
-        } else {
-            input = new BigInteger(input);
-        }
-
-        input = input.subtract(new BigInteger("116444736000000000"));
-
-        if (units === "Seconds (s)"){
-            input = input.divide(new BigInteger("10000000"));
-        } else if (units === "Milliseconds (ms)") {
-            input = input.divide(new BigInteger("10000"));
-        } else if (units === "Microseconds (μs)") {
-            input = input.divide(new BigInteger("10"));
-        } else if (units === "Nanoseconds (ns)") {
-            input = input.multiply(new BigInteger("100"));
-        } else {
-            throw "Unrecognised unit";
-        }
-
-        return input.toString();
-    },
-
-
-    /**
-     * Unix Timestamp to Windows Filetime operation.
-     *
-     * @author bwhitn [brian.m.whitney@outlook.com]
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runToFiletimeFromUnix: function(input, args) {
-        let units = args[0],
-            format = args[1];
-
-        input = new BigInteger(input);
-
-        if (units === "Seconds (s)"){
-            input = input.multiply(new BigInteger("10000000"));
-        } else if (units === "Milliseconds (ms)") {
-            input = input.multiply(new BigInteger("10000"));
-        } else if (units === "Microseconds (μs)") {
-            input = input.multiply(new BigInteger("10"));
-        } else if (units === "Nanoseconds (ns)") {
-            input = input.divide(new BigInteger("100"));
-        } else {
-            throw "Unrecognised unit";
-        }
-
-        input = input.add(new BigInteger("116444736000000000"));
-
-        if (format === "Hex"){
-            return input.toString(16);
-        } else {
-            return input.toString();
-        }
-    },
-
-
-    /**
-     * @constant
-     * @default
-     */
-    FILETIME_FORMATS: ["Decimal", "Hex"],
-
-
     /**
      * @constant
      * @default

+ 94 - 0
src/core/operations/Diff.js

@@ -0,0 +1,94 @@
+import Utils from "../Utils.js";
+import * as JsDiff from "diff";
+
+
+/**
+ * Diff operations.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ *
+ * @namespace
+ */
+const Diff = {
+
+    /**
+     * @constant
+     * @default
+     */
+    DIFF_SAMPLE_DELIMITER: "\\n\\n",
+    /**
+     * @constant
+     * @default
+     */
+    DIFF_BY: ["Character", "Word", "Line", "Sentence", "CSS", "JSON"],
+
+    /**
+     * Diff operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    runDiff: function(input, args) {
+        let sampleDelim = args[0],
+            diffBy = args[1],
+            showAdded = args[2],
+            showRemoved = args[3],
+            ignoreWhitespace = args[4],
+            samples = input.split(sampleDelim),
+            output = "",
+            diff;
+
+        if (!samples || samples.length !== 2) {
+            return "Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?";
+        }
+
+        switch (diffBy) {
+            case "Character":
+                diff = JsDiff.diffChars(samples[0], samples[1]);
+                break;
+            case "Word":
+                if (ignoreWhitespace) {
+                    diff = JsDiff.diffWords(samples[0], samples[1]);
+                } else {
+                    diff = JsDiff.diffWordsWithSpace(samples[0], samples[1]);
+                }
+                break;
+            case "Line":
+                if (ignoreWhitespace) {
+                    diff = JsDiff.diffTrimmedLines(samples[0], samples[1]);
+                } else {
+                    diff = JsDiff.diffLines(samples[0], samples[1]);
+                }
+                break;
+            case "Sentence":
+                diff = JsDiff.diffSentences(samples[0], samples[1]);
+                break;
+            case "CSS":
+                diff = JsDiff.diffCss(samples[0], samples[1]);
+                break;
+            case "JSON":
+                diff = JsDiff.diffJson(samples[0], samples[1]);
+                break;
+            default:
+                return "Invalid 'Diff by' option.";
+        }
+
+        for (let i = 0; i < diff.length; i++) {
+            if (diff[i].added) {
+                if (showAdded) output += "<span class='hl5'>" + Utils.escapeHtml(diff[i].value) + "</span>";
+            } else if (diff[i].removed) {
+                if (showRemoved) output += "<span class='hl3'>" + Utils.escapeHtml(diff[i].value) + "</span>";
+            } else {
+                output += Utils.escapeHtml(diff[i].value);
+            }
+        }
+
+        return output;
+    },
+
+};
+
+export default Diff;

+ 0 - 33
src/core/operations/Extract.js

@@ -258,39 +258,6 @@ const Extract = {
         return Extract._search(input, regex, null, displayTotal);
     },
 
-
-    /**
-     * Extract all identifiers operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runAllIdents: function(input, args) {
-        let output = "";
-        output += "IP addresses\n";
-        output += Extract.runIp(input, [true, true, false]);
-
-        output += "\nEmail addresses\n";
-        output += Extract.runEmail(input, []);
-
-        output += "\nMAC addresses\n";
-        output += Extract.runMac(input, []);
-
-        output += "\nURLs\n";
-        output += Extract.runUrls(input, []);
-
-        output += "\nDomain names\n";
-        output += Extract.runDomains(input, []);
-
-        output += "\nFile paths\n";
-        output += Extract.runFilePaths(input, [true, true]);
-
-        output += "\nDates\n";
-        output += Extract.runDates(input, []);
-        return output;
-    },
-
 };
 
 export default Extract;

+ 99 - 0
src/core/operations/Filetime.js

@@ -0,0 +1,99 @@
+import {BigInteger} from "jsbn";
+
+/**
+ * Windows Filetime operations.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ * @namespace
+ */
+const Filetime = {
+
+    /**
+     * @constant
+     * @default
+     */
+    UNITS: ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"],
+
+    /**
+     * @constant
+     * @default
+     */
+    FILETIME_FORMATS: ["Decimal", "Hex"],
+
+    /**
+     * Windows Filetime to Unix Timestamp operation.
+     *
+     * @author bwhitn [brian.m.whitney@outlook.com]
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    runFromFiletimeToUnix: function(input, args) {
+        let units = args[0],
+            format = args[1];
+
+        if (format === "Hex") {
+            input = new BigInteger(input, 16);
+        } else {
+            input = new BigInteger(input);
+        }
+
+        input = input.subtract(new BigInteger("116444736000000000"));
+
+        if (units === "Seconds (s)"){
+            input = input.divide(new BigInteger("10000000"));
+        } else if (units === "Milliseconds (ms)") {
+            input = input.divide(new BigInteger("10000"));
+        } else if (units === "Microseconds (μs)") {
+            input = input.divide(new BigInteger("10"));
+        } else if (units === "Nanoseconds (ns)") {
+            input = input.multiply(new BigInteger("100"));
+        } else {
+            throw "Unrecognised unit";
+        }
+
+        return input.toString();
+    },
+
+
+    /**
+     * Unix Timestamp to Windows Filetime operation.
+     *
+     * @author bwhitn [brian.m.whitney@outlook.com]
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    runToFiletimeFromUnix: function(input, args) {
+        let units = args[0],
+            format = args[1];
+
+        input = new BigInteger(input);
+
+        if (units === "Seconds (s)"){
+            input = input.multiply(new BigInteger("10000000"));
+        } else if (units === "Milliseconds (ms)") {
+            input = input.multiply(new BigInteger("10000"));
+        } else if (units === "Microseconds (μs)") {
+            input = input.multiply(new BigInteger("10"));
+        } else if (units === "Nanoseconds (ns)") {
+            input = input.divide(new BigInteger("100"));
+        } else {
+            throw "Unrecognised unit";
+        }
+
+        input = input.add(new BigInteger("116444736000000000"));
+
+        if (format === "Hex"){
+            return input.toString(16);
+        } else {
+            return input.toString();
+        }
+    },
+
+};
+
+export default Filetime;

+ 1 - 1
src/core/operations/Hexdump.js

@@ -92,7 +92,7 @@ const Hexdump = {
         const w = (width - 13) / 4;
         // w should be the specified width of the hexdump and therefore a round number
         if (Math.floor(w) !== w || input.indexOf("\r") !== -1 || output.indexOf(13) !== -1) {
-            if (app) app.options.attemptHighlight = false;
+            if (ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
         }
         return output;
     },

+ 0 - 699
src/core/operations/PublicKey.js

@@ -341,702 +341,3 @@ ${extensions}`;
 };
 
 export default PublicKey;
-
-
-/**
- * Overwrite DN attribute lookup in jsrasign library with a much more complete version from
- * https://github.com/nfephp-org/nfephp/blob/master/libs/Common/Certificate/Oids.php
- *
- * Various duplicates commented out.
- *
- * @constant
- */
-r.X509.DN_ATTRHEX = {
-    "0603550403": "commonName",
-    "0603550404": "surname",
-    "0603550406": "countryName",
-    "0603550407": "localityName",
-    "0603550408": "stateOrProvinceName",
-    "0603550409": "streetAddress",
-    "060355040a": "organizationName",
-    "060355040b": "organizationalUnitName",
-    "060355040c": "title",
-    "0603550414": "telephoneNumber",
-    "060355042a": "givenName",
-    // "0603551d0e" : "id-ce-subjectKeyIdentifier",
-    // "0603551d0f" : "id-ce-keyUsage",
-    // "0603551d11" : "id-ce-subjectAltName",
-    // "0603551d13" : "id-ce-basicConstraints",
-    // "0603551d14" : "id-ce-cRLNumber",
-    // "0603551d1f" : "id-ce-CRLDistributionPoints",
-    // "0603551d20" : "id-ce-certificatePolicies",
-    // "0603551d23" : "id-ce-authorityKeyIdentifier",
-    // "0603551d25" : "id-ce-extKeyUsage",
-    // "06032a864886f70d010901" : "Email",
-    // "06032a864886f70d010101" : "RSAEncryption",
-    // "06032a864886f70d010102" : "md2WithRSAEncryption",
-    // "06032a864886f70d010104" : "md5withRSAEncryption",
-    // "06032a864886f70d010105" : "SHA-1WithRSAEncryption",
-    // "06032a8648ce380403" : "id-dsa-with-sha-1",
-    // "06032b06010505070302" : "idKpClientAuth",
-    // "06032b06010505070304" : "idKpSecurityemail",
-    "06032b06010505070201": "idCertificatePolicies",
-    "06036086480186f8420101": "netscape-cert-type",
-    "06036086480186f8420102": "netscape-base-url",
-    "06036086480186f8420103": "netscape-revocation-url",
-    "06036086480186f8420104": "netscape-ca-revocation-url",
-    "06036086480186f8420107": "netscape-cert-renewal-url",
-    "06036086480186f8420108": "netscape-ca-policy-url",
-    "06036086480186f842010c": "netscape-ssl-server-name",
-    "06036086480186f842010d": "netscape-comment",
-    "0603604c010201": "A1",
-    "0603604c010203": "A3",
-    "0603604c01020110": "Certification Practice Statement pointer",
-    "0603604c010301": "Dados do cert parte 1",
-    "0603604c010305": "Dados do cert parte 2",
-    "0603604c010306": "Dados do cert parte 3",
-    "06030992268993f22c640119": "domainComponent",
-    "06032a24a0f2a07d01010a": "Signet pilot",
-    "06032a24a0f2a07d01010b": "Signet intraNet",
-    "06032a24a0f2a07d010102": "Signet personal",
-    "06032a24a0f2a07d010114": "Signet securityPolicy",
-    "06032a24a0f2a07d010103": "Signet business",
-    "06032a24a0f2a07d010104": "Signet legal",
-    "06032a24a497a35301640101": "Certificates Australia policyIdentifier",
-    "06032a85702201": "seis-cp",
-    "06032a8570220101": "SEIS certificatePolicy-s10",
-    "06032a85702202": "SEIS pe",
-    "06032a85702203": "SEIS at",
-    "06032a8570220301": "SEIS at-personalIdentifier",
-    "06032a8648ce380201": "holdinstruction-none",
-    "06032a8648ce380202": "holdinstruction-callissuer",
-    "06032a8648ce380203": "holdinstruction-reject",
-    "06032a8648ce380401": "dsa",
-    "06032a8648ce380403": "dsaWithSha1",
-    "06032a8648ce3d01": "fieldType",
-    "06032a8648ce3d0101": "prime-field",
-    "06032a8648ce3d0102": "characteristic-two-field",
-    "06032a8648ce3d010201": "ecPublicKey",
-    "06032a8648ce3d010203": "characteristic-two-basis",
-    "06032a8648ce3d01020301": "onBasis",
-    "06032a8648ce3d01020302": "tpBasis",
-    "06032a8648ce3d01020303": "ppBasis",
-    "06032a8648ce3d02": "publicKeyType",
-    "06032a8648ce3d0201": "ecPublicKey",
-    "06032a8648ce3e0201": "dhPublicNumber",
-    "06032a864886f67d07": "nsn",
-    "06032a864886f67d0741": "nsn-ce",
-    "06032a864886f67d074100": "entrustVersInfo",
-    "06032a864886f67d0742": "nsn-alg",
-    "06032a864886f67d07420a": "cast5CBC",
-    "06032a864886f67d07420b": "cast5MAC",
-    "06032a864886f67d07420c": "pbeWithMD5AndCAST5-CBC",
-    "06032a864886f67d07420d": "passwordBasedMac",
-    "06032a864886f67d074203": "cast3CBC",
-    "06032a864886f67d0743": "nsn-oc",
-    "06032a864886f67d074300": "entrustUser",
-    "06032a864886f67d0744": "nsn-at",
-    "06032a864886f67d074400": "entrustCAInfo",
-    "06032a864886f67d07440a": "attributeCertificate",
-    "06032a864886f70d0101": "pkcs-1",
-    "06032a864886f70d010101": "rsaEncryption",
-    "06032a864886f70d010102": "md2withRSAEncryption",
-    "06032a864886f70d010103": "md4withRSAEncryption",
-    "06032a864886f70d010104": "md5withRSAEncryption",
-    "06032a864886f70d010105": "sha1withRSAEncryption",
-    "06032a864886f70d010106": "rsaOAEPEncryptionSET",
-    "06032a864886f70d010910020b": "SMIMEEncryptionKeyPreference",
-    "06032a864886f70d010c": "pkcs-12",
-    "06032a864886f70d010c01": "pkcs-12-PbeIds",
-    "06032a864886f70d010c0101": "pbeWithSHAAnd128BitRC4",
-    "06032a864886f70d010c0102": "pbeWithSHAAnd40BitRC4",
-    "06032a864886f70d010c0103": "pbeWithSHAAnd3-KeyTripleDES-CBC",
-    "06032a864886f70d010c0104": "pbeWithSHAAnd2-KeyTripleDES-CBC",
-    "06032a864886f70d010c0105": "pbeWithSHAAnd128BitRC2-CBC",
-    "06032a864886f70d010c0106": "pbeWithSHAAnd40BitRC2-CBC",
-    "06032a864886f70d010c0a": "pkcs-12Version1",
-    "06032a864886f70d010c0a01": "pkcs-12BadIds",
-    "06032a864886f70d010c0a0101": "pkcs-12-keyBag",
-    "06032a864886f70d010c0a0102": "pkcs-12-pkcs-8ShroudedKeyBag",
-    "06032a864886f70d010c0a0103": "pkcs-12-certBag",
-    "06032a864886f70d010c0a0104": "pkcs-12-crlBag",
-    "06032a864886f70d010c0a0105": "pkcs-12-secretBag",
-    "06032a864886f70d010c0a0106": "pkcs-12-safeContentsBag",
-    "06032a864886f70d010c02": "pkcs-12-ESPVKID",
-    "06032a864886f70d010c0201": "pkcs-12-PKCS8KeyShrouding",
-    "06032a864886f70d010c03": "pkcs-12-BagIds",
-    "06032a864886f70d010c0301": "pkcs-12-keyBagId",
-    "06032a864886f70d010c0302": "pkcs-12-certAndCRLBagId",
-    "06032a864886f70d010c0303": "pkcs-12-secretBagId",
-    "06032a864886f70d010c0304": "pkcs-12-safeContentsId",
-    "06032a864886f70d010c0305": "pkcs-12-pkcs-8ShroudedKeyBagId",
-    "06032a864886f70d010c04": "pkcs-12-CertBagID",
-    "06032a864886f70d010c0401": "pkcs-12-X509CertCRLBagID",
-    "06032a864886f70d010c0402": "pkcs-12-SDSICertBagID",
-    "06032a864886f70d010c05": "pkcs-12-OID",
-    "06032a864886f70d010c0501": "pkcs-12-PBEID",
-    "06032a864886f70d010c050101": "pkcs-12-PBEWithSha1And128BitRC4",
-    "06032a864886f70d010c050102": "pkcs-12-PBEWithSha1And40BitRC4",
-    "06032a864886f70d010c050103": "pkcs-12-PBEWithSha1AndTripleDESCBC",
-    "06032a864886f70d010c050104": "pkcs-12-PBEWithSha1And128BitRC2CBC",
-    "06032a864886f70d010c050105": "pkcs-12-PBEWithSha1And40BitRC2CBC",
-    "06032a864886f70d010c050106": "pkcs-12-PBEWithSha1AndRC4",
-    "06032a864886f70d010c050107": "pkcs-12-PBEWithSha1AndRC2CBC",
-    "06032a864886f70d010c0502": "pkcs-12-EnvelopingID",
-    "06032a864886f70d010c050201": "pkcs-12-RSAEncryptionWith128BitRC4",
-    "06032a864886f70d010c050202": "pkcs-12-RSAEncryptionWith40BitRC4",
-    "06032a864886f70d010c050203": "pkcs-12-RSAEncryptionWithTripleDES",
-    "06032a864886f70d010c0503": "pkcs-12-SignatureID",
-    "06032a864886f70d010c050301": "pkcs-12-RSASignatureWithSHA1Digest",
-    "06032a864886f70d0103": "pkcs-3",
-    "06032a864886f70d010301": "dhKeyAgreement",
-    "06032a864886f70d0105": "pkcs-5",
-    "06032a864886f70d010501": "pbeWithMD2AndDES-CBC",
-    "06032a864886f70d01050a": "pbeWithSHAAndDES-CBC",
-    "06032a864886f70d010503": "pbeWithMD5AndDES-CBC",
-    "06032a864886f70d010504": "pbeWithMD2AndRC2-CBC",
-    "06032a864886f70d010506": "pbeWithMD5AndRC2-CBC",
-    "06032a864886f70d010509": "pbeWithMD5AndXOR",
-    "06032a864886f70d0107": "pkcs-7",
-    "06032a864886f70d010701": "data",
-    "06032a864886f70d010702": "signedData",
-    "06032a864886f70d010703": "envelopedData",
-    "06032a864886f70d010704": "signedAndEnvelopedData",
-    "06032a864886f70d010705": "digestData",
-    "06032a864886f70d010706": "encryptedData",
-    "06032a864886f70d010707": "dataWithAttributes",
-    "06032a864886f70d010708": "encryptedPrivateKeyInfo",
-    "06032a864886f70d0109": "pkcs-9",
-    "06032a864886f70d010901": "emailAddress",
-    "06032a864886f70d01090a": "issuerAndSerialNumber",
-    "06032a864886f70d01090b": "passwordCheck",
-    "06032a864886f70d01090c": "publicKey",
-    "06032a864886f70d01090d": "signingDescription",
-    "06032a864886f70d01090e": "extensionReq",
-    "06032a864886f70d01090f": "sMIMECapabilities",
-    "06032a864886f70d01090f01": "preferSignedData",
-    "06032a864886f70d01090f02": "canNotDecryptAny",
-    "06032a864886f70d01090f03": "receiptRequest",
-    "06032a864886f70d01090f04": "receipt",
-    "06032a864886f70d01090f05": "contentHints",
-    "06032a864886f70d01090f06": "mlExpansionHistory",
-    "06032a864886f70d010910": "id-sMIME",
-    "06032a864886f70d01091000": "id-mod",
-    "06032a864886f70d0109100001": "id-mod-cms",
-    "06032a864886f70d0109100002": "id-mod-ess",
-    "06032a864886f70d01091001": "id-ct",
-    "06032a864886f70d0109100101": "id-ct-receipt",
-    "06032a864886f70d01091002": "id-aa",
-    "06032a864886f70d0109100201": "id-aa-receiptRequest",
-    "06032a864886f70d0109100202": "id-aa-securityLabel",
-    "06032a864886f70d0109100203": "id-aa-mlExpandHistory",
-    "06032a864886f70d0109100204": "id-aa-contentHint",
-    "06032a864886f70d010902": "unstructuredName",
-    "06032a864886f70d010914": "friendlyName",
-    "06032a864886f70d010915": "localKeyID",
-    "06032a864886f70d010916": "certTypes",
-    "06032a864886f70d01091601": "x509Certificate",
-    "06032a864886f70d01091602": "sdsiCertificate",
-    "06032a864886f70d010917": "crlTypes",
-    "06032a864886f70d01091701": "x509Crl",
-    "06032a864886f70d010903": "contentType",
-    "06032a864886f70d010904": "messageDigest",
-    "06032a864886f70d010905": "signingTime",
-    "06032a864886f70d010906": "countersignature",
-    "06032a864886f70d010907": "challengePassword",
-    "06032a864886f70d010908": "unstructuredAddress",
-    "06032a864886f70d010909": "extendedCertificateAttributes",
-    "06032a864886f70d02": "digestAlgorithm",
-    "06032a864886f70d0202": "md2",
-    "06032a864886f70d0204": "md4",
-    "06032a864886f70d0205": "md5",
-    "06032a864886f70d03": "encryptionAlgorithm",
-    "06032a864886f70d030a": "desCDMF",
-    "06032a864886f70d0302": "rc2CBC",
-    "06032a864886f70d0303": "rc2ECB",
-    "06032a864886f70d0304": "rc4",
-    "06032a864886f70d0305": "rc4WithMAC",
-    "06032a864886f70d0306": "DESX-CBC",
-    "06032a864886f70d0307": "DES-EDE3-CBC",
-    "06032a864886f70d0308": "RC5CBC",
-    "06032a864886f70d0309": "RC5-CBCPad",
-    "06032a864886f7140403": "microsoftExcel",
-    "06032a864886f7140404": "titledWithOID",
-    "06032a864886f7140405": "microsoftPowerPoint",
-    "06032b81051086480954": "x9-84",
-    "06032b8105108648095400": "x9-84-Module",
-    "06032b810510864809540001": "x9-84-Biometrics",
-    "06032b810510864809540002": "x9-84-CMS",
-    "06032b810510864809540003": "x9-84-Identifiers",
-    "06032b8105108648095401": "biometric",
-    "06032b810510864809540100": "id-unknown-Type",
-    "06032b810510864809540101": "id-body-Odor",
-    "06032b81051086480954010a": "id-palm",
-    "06032b81051086480954010b": "id-retina",
-    "06032b81051086480954010c": "id-signature",
-    "06032b81051086480954010d": "id-speech-Pattern",
-    "06032b81051086480954010e": "id-thermal-Image",
-    "06032b81051086480954010f": "id-vein-Pattern",
-    "06032b810510864809540110": "id-thermal-Face-Image",
-    "06032b810510864809540111": "id-thermal-Hand-Image",
-    "06032b810510864809540112": "id-lip-Movement",
-    "06032b810510864809540113": "id-gait",
-    "06032b810510864809540102": "id-dna",
-    "06032b810510864809540103": "id-ear-Shape",
-    "06032b810510864809540104": "id-facial-Features",
-    "06032b810510864809540105": "id-finger-Image",
-    "06032b810510864809540106": "id-finger-Geometry",
-    "06032b810510864809540107": "id-hand-Geometry",
-    "06032b810510864809540108": "id-iris-Features",
-    "06032b810510864809540109": "id-keystroke-Dynamics",
-    "06032b8105108648095402": "processing-algorithm",
-    "06032b8105108648095403": "matching-method",
-    "06032b8105108648095404": "format-Owner",
-    "06032b810510864809540400": "cbeff-Owner",
-    "06032b810510864809540401": "ibia-Owner",
-    "06032b81051086480954040101": "id-ibia-SAFLINK",
-    "06032b8105108648095404010a": "id-ibia-SecuGen",
-    "06032b8105108648095404010b": "id-ibia-PreciseBiometric",
-    "06032b8105108648095404010c": "id-ibia-Identix",
-    "06032b8105108648095404010d": "id-ibia-DERMALOG",
-    "06032b8105108648095404010e": "id-ibia-LOGICO",
-    "06032b8105108648095404010f": "id-ibia-NIST",
-    "06032b81051086480954040110": "id-ibia-A3Vision",
-    "06032b81051086480954040111": "id-ibia-NEC",
-    "06032b81051086480954040112": "id-ibia-STMicroelectronics",
-    "06032b81051086480954040102": "id-ibia-Bioscrypt",
-    "06032b81051086480954040103": "id-ibia-Visionics",
-    "06032b81051086480954040104": "id-ibia-InfineonTechnologiesAG",
-    "06032b81051086480954040105": "id-ibia-IridianTechnologies",
-    "06032b81051086480954040106": "id-ibia-Veridicom",
-    "06032b81051086480954040107": "id-ibia-CyberSIGN",
-    "06032b81051086480954040108": "id-ibia-eCryp.",
-    "06032b81051086480954040109": "id-ibia-FingerprintCardsAB",
-    "06032b810510864809540402": "x9-Owner",
-    "06032b0e021a05": "sha",
-    "06032b0e03020101": "rsa",
-    "06032b0e03020a": "desMAC",
-    "06032b0e03020b": "rsaSignature",
-    "06032b0e03020c": "dsa",
-    "06032b0e03020d": "dsaWithSHA",
-    "06032b0e03020e": "mdc2WithRSASignature",
-    "06032b0e03020f": "shaWithRSASignature",
-    "06032b0e030210": "dhWithCommonModulus",
-    "06032b0e030211": "desEDE",
-    "06032b0e030212": "sha",
-    "06032b0e030213": "mdc-2",
-    "06032b0e030202": "md4WitRSA",
-    "06032b0e03020201": "sqmod-N",
-    "06032b0e030214": "dsaCommon",
-    "06032b0e030215": "dsaCommonWithSHA",
-    "06032b0e030216": "rsaKeyTransport",
-    "06032b0e030217": "keyed-hash-seal",
-    "06032b0e030218": "md2WithRSASignature",
-    "06032b0e030219": "md5WithRSASignature",
-    "06032b0e03021a": "sha1",
-    "06032b0e03021b": "dsaWithSHA1",
-    "06032b0e03021c": "dsaWithCommonSHA1",
-    "06032b0e03021d": "sha-1WithRSAEncryption",
-    "06032b0e030203": "md5WithRSA",
-    "06032b0e03020301": "sqmod-NwithRSA",
-    "06032b0e030204": "md4WithRSAEncryption",
-    "06032b0e030206": "desECB",
-    "06032b0e030207": "desCBC",
-    "06032b0e030208": "desOFB",
-    "06032b0e030209": "desCFB",
-    "06032b0e030301": "simple-strong-auth-mechanism",
-    "06032b0e07020101": "ElGamal",
-    "06032b0e07020301": "md2WithRSA",
-    "06032b0e07020302": "md2WithElGamal",
-    "06032b2403": "algorithm",
-    "06032b240301": "encryptionAlgorithm",
-    "06032b24030101": "des",
-    "06032b240301010101": "desECBPad",
-    "06032b24030101010101": "desECBPadISO",
-    "06032b240301010201": "desCBCPad",
-    "06032b24030101020101": "desCBCPadISO",
-    "06032b24030102": "idea",
-    "06032b2403010201": "ideaECB",
-    "06032b240301020101": "ideaECBPad",
-    "06032b24030102010101": "ideaECBPadISO",
-    "06032b2403010202": "ideaCBC",
-    "06032b240301020201": "ideaCBCPad",
-    "06032b24030102020101": "ideaCBCPadISO",
-    "06032b2403010203": "ideaOFB",
-    "06032b2403010204": "ideaCFB",
-    "06032b24030103": "des-3",
-    "06032b240301030101": "des-3ECBPad",
-    "06032b24030103010101": "des-3ECBPadISO",
-    "06032b240301030201": "des-3CBCPad",
-    "06032b24030103020101": "des-3CBCPadISO",
-    "06032b240302": "hashAlgorithm",
-    "06032b24030201": "ripemd160",
-    "06032b24030202": "ripemd128",
-    "06032b24030203": "ripemd256",
-    "06032b24030204": "mdc2singleLength",
-    "06032b24030205": "mdc2doubleLength",
-    "06032b240303": "signatureAlgorithm",
-    "06032b24030301": "rsa",
-    "06032b2403030101": "rsaMitSHA-1",
-    "06032b2403030102": "rsaMitRIPEMD160",
-    "06032b24030302": "ellipticCurve",
-    "06032b240304": "signatureScheme",
-    "06032b24030401": "iso9796-1",
-    "06032b2403040201": "iso9796-2",
-    "06032b2403040202": "iso9796-2rsa",
-    "06032b2404": "attribute",
-    "06032b2405": "policy",
-    "06032b2406": "api",
-    "06032b240601": "manufacturerSpecific",
-    "06032b240602": "functionalitySpecific",
-    "06032b2407": "api",
-    "06032b240701": "keyAgreement",
-    "06032b240702": "keyTransport",
-    "06032b06010401927c0a0101": "UNINETT policyIdentifier",
-    "06032b0601040195180a": "ICE-TEL policyIdentifier",
-    "06032b0601040197552001": "cryptlibEnvelope",
-    "06032b0601040197552002": "cryptlibPrivateKey",
-    "060a2b060104018237": "Microsoft OID",
-    "060a2b0601040182370a": "Crypto 2.0",
-    "060a2b0601040182370a01": "certTrustList",
-    "060a2b0601040182370a0101": "szOID_SORTED_CTL",
-    "060a2b0601040182370a0a": "Microsoft CMC OIDs",
-    "060a2b0601040182370a0a01": "szOID_CMC_ADD_ATTRIBUTES",
-    "060a2b0601040182370a0b": "Microsoft certificate property OIDs",
-    "060a2b0601040182370a0b01": "szOID_CERT_PROP_ID_PREFIX",
-    "060a2b0601040182370a0c": "CryptUI",
-    "060a2b0601040182370a0c01": "szOID_ANY_APPLICATION_POLICY",
-    "060a2b0601040182370a02": "nextUpdateLocation",
-    "060a2b0601040182370a0301": "certTrustListSigning",
-    "060a2b0601040182370a030a": "szOID_KP_QUALIFIED_SUBORDINATION",
-    "060a2b0601040182370a030b": "szOID_KP_KEY_RECOVERY",
-    "060a2b0601040182370a030c": "szOID_KP_DOCUMENT_SIGNING",
-    "060a2b0601040182370a0302": "timeStampSigning",
-    "060a2b0601040182370a0303": "serverGatedCrypto",
-    "060a2b0601040182370a030301": "szOID_SERIALIZED",
-    "060a2b0601040182370a0304": "encryptedFileSystem",
-    "060a2b0601040182370a030401": "szOID_EFS_RECOVERY",
-    "060a2b0601040182370a0305": "szOID_WHQL_CRYPTO",
-    "060a2b0601040182370a0306": "szOID_NT5_CRYPTO",
-    "060a2b0601040182370a0307": "szOID_OEM_WHQL_CRYPTO",
-    "060a2b0601040182370a0308": "szOID_EMBEDDED_NT_CRYPTO",
-    "060a2b0601040182370a0309": "szOID_ROOT_LIST_SIGNER",
-    "060a2b0601040182370a0401": "yesnoTrustAttr",
-    "060a2b0601040182370a0501": "szOID_DRM",
-    "060a2b0601040182370a0502": "szOID_DRM_INDIVIDUALIZATION",
-    "060a2b0601040182370a0601": "szOID_LICENSES",
-    "060a2b0601040182370a0602": "szOID_LICENSE_SERVER",
-    "060a2b0601040182370a07": "szOID_MICROSOFT_RDN_PREFIX",
-    "060a2b0601040182370a0701": "szOID_KEYID_RDN",
-    "060a2b0601040182370a0801": "szOID_REMOVE_CERTIFICATE",
-    "060a2b0601040182370a0901": "szOID_CROSS_CERT_DIST_POINTS",
-    "060a2b0601040182370c": "Catalog",
-    "060a2b0601040182370c0101": "szOID_CATALOG_LIST",
-    "060a2b0601040182370c0102": "szOID_CATALOG_LIST_MEMBER",
-    "060a2b0601040182370c0201": "CAT_NAMEVALUE_OBJID",
-    "060a2b0601040182370c0202": "CAT_MEMBERINFO_OBJID",
-    "060a2b0601040182370d": "Microsoft PKCS10 OIDs",
-    "060a2b0601040182370d01": "szOID_RENEWAL_CERTIFICATE",
-    "060a2b0601040182370d0201": "szOID_ENROLLMENT_NAME_VALUE_PAIR",
-    "060a2b0601040182370d0202": "szOID_ENROLLMENT_CSP_PROVIDER",
-    "060a2b0601040182370d0203": "OS Version",
-    "060a2b0601040182370f": "Microsoft Java",
-    "060a2b06010401823710": "Microsoft Outlook/Exchange",
-    "060a2b0601040182371004": "Outlook Express",
-    "060a2b06010401823711": "Microsoft PKCS12 attributes",
-    "060a2b0601040182371101": "szOID_LOCAL_MACHINE_KEYSET",
-    "060a2b06010401823712": "Microsoft Hydra",
-    "060a2b06010401823713": "Microsoft ISPU Test",
-    "060a2b06010401823702": "Authenticode",
-    "060a2b06010401823702010a": "spcAgencyInfo",
-    "060a2b06010401823702010b": "spcStatementType",
-    "060a2b06010401823702010c": "spcSpOpusInfo",
-    "060a2b06010401823702010e": "certExtensions",
-    "060a2b06010401823702010f": "spcPelmageData",
-    "060a2b060104018237020112": "SPC_RAW_FILE_DATA_OBJID",
-    "060a2b060104018237020113": "SPC_STRUCTURED_STORAGE_DATA_OBJID",
-    "060a2b060104018237020114": "spcLink",
-    "060a2b060104018237020115": "individualCodeSigning",
-    "060a2b060104018237020116": "commercialCodeSigning",
-    "060a2b060104018237020119": "spcLink",
-    "060a2b06010401823702011a": "spcMinimalCriteriaInfo",
-    "060a2b06010401823702011b": "spcFinancialCriteriaInfo",
-    "060a2b06010401823702011c": "spcLink",
-    "060a2b06010401823702011d": "SPC_HASH_INFO_OBJID",
-    "060a2b06010401823702011e": "SPC_SIPINFO_OBJID",
-    "060a2b060104018237020104": "spcIndirectDataContext",
-    "060a2b0601040182370202": "CTL for Software Publishers Trusted CAs",
-    "060a2b060104018237020201": "szOID_TRUSTED_CODESIGNING_CA_LIST",
-    "060a2b060104018237020202": "szOID_TRUSTED_CLIENT_AUTH_CA_LIST",
-    "060a2b060104018237020203": "szOID_TRUSTED_SERVER_AUTH_CA_LIST",
-    "060a2b06010401823714": "Microsoft Enrollment Infrastructure",
-    "060a2b0601040182371401": "szOID_AUTO_ENROLL_CTL_USAGE",
-    "060a2b0601040182371402": "szOID_ENROLL_CERTTYPE_EXTENSION",
-    "060a2b060104018237140201": "szOID_ENROLLMENT_AGENT",
-    "060a2b060104018237140202": "szOID_KP_SMARTCARD_LOGON",
-    "060a2b060104018237140203": "szOID_NT_PRINCIPAL_NAME",
-    "060a2b0601040182371403": "szOID_CERT_MANIFOLD",
-    "06092b06010401823715": "Microsoft CertSrv Infrastructure",
-    "06092b0601040182371501": "szOID_CERTSRV_CA_VERSION",
-    "06092b0601040182371514": "Client Information",
-    "060a2b06010401823719": "Microsoft Directory Service",
-    "060a2b0601040182371901": "szOID_NTDS_REPLICATION",
-    "060a2b06010401823703": "Time Stamping",
-    "060a2b060104018237030201": "SPC_TIME_STAMP_REQUEST_OBJID",
-    "060a2b0601040182371e": "IIS",
-    "060a2b0601040182371f": "Windows updates and service packs",
-    "060a2b0601040182371f01": "szOID_PRODUCT_UPDATE",
-    "060a2b06010401823704": "Permissions",
-    "060a2b06010401823728": "Fonts",
-    "060a2b06010401823729": "Microsoft Licensing and Registration",
-    "060a2b0601040182372a": "Microsoft Corporate PKI (ITG)",
-    "060a2b06010401823758": "CAPICOM",
-    "060a2b0601040182375801": "szOID_CAPICOM_VERSION",
-    "060a2b0601040182375802": "szOID_CAPICOM_ATTRIBUTE",
-    "060a2b060104018237580201": "szOID_CAPICOM_DOCUMENT_NAME",
-    "060a2b060104018237580202": "szOID_CAPICOM_DOCUMENT_DESCRIPTION",
-    "060a2b0601040182375803": "szOID_CAPICOM_ENCRYPTED_DATA",
-    "060a2b060104018237580301": "szOID_CAPICOM_ENCRYPTED_CONTENT",
-    "06032b0601050507": "pkix",
-    "06032b060105050701": "privateExtension",
-    "06032b06010505070101": "authorityInfoAccess",
-    "06032b06010505070c02": "CMC Data",
-    "06032b060105050702": "policyQualifierIds",
-    // "06032b06010505070201" : "cps",
-    "06032b06010505070202": "unotice",
-    "06032b060105050703": "keyPurpose",
-    "06032b06010505070301": "serverAuth",
-    "06032b06010505070302": "clientAuth",
-    "06032b06010505070303": "codeSigning",
-    "06032b06010505070304": "emailProtection",
-    "06032b06010505070305": "ipsecEndSystem",
-    "06032b06010505070306": "ipsecTunnel",
-    "06032b06010505070307": "ipsecUser",
-    "06032b06010505070308": "timeStamping",
-    "06032b060105050704": "cmpInformationTypes",
-    "06032b06010505070401": "caProtEncCert",
-    "06032b06010505070402": "signKeyPairTypes",
-    "06032b06010505070403": "encKeyPairTypes",
-    "06032b06010505070404": "preferredSymmAlg",
-    "06032b06010505070405": "caKeyUpdateInfo",
-    "06032b06010505070406": "currentCRL",
-    "06032b06010505073001": "ocsp",
-    "06032b06010505073002": "caIssuers",
-    "06032b06010505080101": "HMAC-MD5",
-    "06032b06010505080102": "HMAC-SHA",
-    "060360864801650201010a": "mosaicKeyManagementAlgorithm",
-    "060360864801650201010b": "sdnsKMandSigAlgorithm",
-    "060360864801650201010c": "mosaicKMandSigAlgorithm",
-    "060360864801650201010d": "SuiteASignatureAlgorithm",
-    "060360864801650201010e": "SuiteAConfidentialityAlgorithm",
-    "060360864801650201010f": "SuiteAIntegrityAlgorithm",
-    "06036086480186f84201": "cert-extension",
-    // "06036086480186f8420101" : "netscape-cert-type",
-    "06036086480186f842010a": "EntityLogo",
-    "06036086480186f842010b": "UserPicture",
-    // "06036086480186f842010c" : "netscape-ssl-server-name",
-    // "06036086480186f842010d" : "netscape-comment",
-    // "06036086480186f8420102" : "netscape-base-url",
-    // "06036086480186f8420103" : "netscape-revocation-url",
-    // "06036086480186f8420104" : "netscape-ca-revocation-url",
-    // "06036086480186f8420107" : "netscape-cert-renewal-url",
-    // "06036086480186f8420108" : "netscape-ca-policy-url",
-    "06036086480186f8420109": "HomePage-url",
-    "06036086480186f84202": "data-type",
-    "06036086480186f8420201": "GIF",
-    "06036086480186f8420202": "JPEG",
-    "06036086480186f8420203": "URL",
-    "06036086480186f8420204": "HTML",
-    "06036086480186f8420205": "netscape-cert-sequence",
-    "06036086480186f8420206": "netscape-cert-url",
-    "06036086480186f84203": "directory",
-    "06036086480186f8420401": "serverGatedCrypto",
-    "06036086480186f845010603": "Unknown Verisign extension",
-    "06036086480186f845010606": "Unknown Verisign extension",
-    "06036086480186f84501070101": "Verisign certificatePolicy",
-    "06036086480186f8450107010101": "Unknown Verisign policy qualifier",
-    "06036086480186f8450107010102": "Unknown Verisign policy qualifier",
-    "0603678105": "TCPA",
-    "060367810501": "tcpaSpecVersion",
-    "060367810502": "tcpaAttribute",
-    "06036781050201": "tcpaAtTpmManufacturer",
-    "0603678105020a": "tcpaAtSecurityQualities",
-    "0603678105020b": "tcpaAtTpmProtectionProfile",
-    "0603678105020c": "tcpaAtTpmSecurityTarget",
-    "0603678105020d": "tcpaAtFoundationProtectionProfile",
-    "0603678105020e": "tcpaAtFoundationSecurityTarget",
-    "0603678105020f": "tcpaAtTpmIdLabel",
-    "06036781050202": "tcpaAtTpmModel",
-    "06036781050203": "tcpaAtTpmVersion",
-    "06036781050204": "tcpaAtPlatformManufacturer",
-    "06036781050205": "tcpaAtPlatformModel",
-    "06036781050206": "tcpaAtPlatformVersion",
-    "06036781050207": "tcpaAtComponentManufacturer",
-    "06036781050208": "tcpaAtComponentModel",
-    "06036781050209": "tcpaAtComponentVersion",
-    "060367810503": "tcpaProtocol",
-    "06036781050301": "tcpaPrttTpmIdProtocol",
-    "0603672a00": "contentType",
-    "0603672a0000": "PANData",
-    "0603672a0001": "PANToken",
-    "0603672a0002": "PANOnly",
-    "0603672a01": "msgExt",
-    "0603672a0a": "national",
-    "0603672a0a8140": "Japan",
-    "0603672a02": "field",
-    "0603672a0200": "fullName",
-    "0603672a0201": "givenName",
-    "0603672a020a": "amount",
-    "0603672a0202": "familyName",
-    "0603672a0203": "birthFamilyName",
-    "0603672a0204": "placeName",
-    "0603672a0205": "identificationNumber",
-    "0603672a0206": "month",
-    "0603672a0207": "date",
-    "0603672a02070b": "accountNumber",
-    "0603672a02070c": "passPhrase",
-    "0603672a0208": "address",
-    "0603672a0209": "telephone",
-    "0603672a03": "attribute",
-    "0603672a0300": "cert",
-    "0603672a030000": "rootKeyThumb",
-    "0603672a030001": "additionalPolicy",
-    "0603672a04": "algorithm",
-    "0603672a05": "policy",
-    "0603672a0500": "root",
-    "0603672a06": "module",
-    "0603672a07": "certExt",
-    "0603672a0700": "hashedRootKey",
-    "0603672a0701": "certificateType",
-    "0603672a0702": "merchantData",
-    "0603672a0703": "cardCertRequired",
-    "0603672a0704": "tunneling",
-    "0603672a0705": "setExtensions",
-    "0603672a0706": "setQualifier",
-    "0603672a08": "brand",
-    "0603672a0801": "IATA-ATA",
-    "0603672a081e": "Diners",
-    "0603672a0822": "AmericanExpress",
-    "0603672a0804": "VISA",
-    "0603672a0805": "MasterCard",
-    "0603672a08ae7b": "Novus",
-    "0603672a09": "vendor",
-    "0603672a0900": "GlobeSet",
-    "0603672a0901": "IBM",
-    "0603672a090a": "Griffin",
-    "0603672a090b": "Certicom",
-    "0603672a090c": "OSS",
-    "0603672a090d": "TenthMountain",
-    "0603672a090e": "Antares",
-    "0603672a090f": "ECC",
-    "0603672a0910": "Maithean",
-    "0603672a0911": "Netscape",
-    "0603672a0912": "Verisign",
-    "0603672a0913": "BlueMoney",
-    "0603672a0902": "CyberCash",
-    "0603672a0914": "Lacerte",
-    "0603672a0915": "Fujitsu",
-    "0603672a0916": "eLab",
-    "0603672a0917": "Entrust",
-    "0603672a0918": "VIAnet",
-    "0603672a0919": "III",
-    "0603672a091a": "OpenMarket",
-    "0603672a091b": "Lexem",
-    "0603672a091c": "Intertrader",
-    "0603672a091d": "Persimmon",
-    "0603672a0903": "Terisa",
-    "0603672a091e": "NABLE",
-    "0603672a091f": "espace-net",
-    "0603672a0920": "Hitachi",
-    "0603672a0921": "Microsoft",
-    "0603672a0922": "NEC",
-    "0603672a0923": "Mitsubishi",
-    "0603672a0924": "NCR",
-    "0603672a0925": "e-COMM",
-    "0603672a0926": "Gemplus",
-    "0603672a0904": "RSADSI",
-    "0603672a0905": "VeriFone",
-    "0603672a0906": "TrinTech",
-    "0603672a0907": "BankGate",
-    "0603672a0908": "GTE",
-    "0603672a0909": "CompuSource",
-    "0603551d01": "authorityKeyIdentifier",
-    "0603551d0a": "basicConstraints",
-    "0603551d0b": "nameConstraints",
-    "0603551d0c": "policyConstraints",
-    "0603551d0d": "basicConstraints",
-    "0603551d0e": "subjectKeyIdentifier",
-    "0603551d0f": "keyUsage",
-    "0603551d10": "privateKeyUsagePeriod",
-    "0603551d11": "subjectAltName",
-    "0603551d12": "issuerAltName",
-    "0603551d13": "basicConstraints",
-    "0603551d02": "keyAttributes",
-    "0603551d14": "cRLNumber",
-    "0603551d15": "cRLReason",
-    "0603551d16": "expirationDate",
-    "0603551d17": "instructionCode",
-    "0603551d18": "invalidityDate",
-    "0603551d1a": "issuingDistributionPoint",
-    "0603551d1b": "deltaCRLIndicator",
-    "0603551d1c": "issuingDistributionPoint",
-    "0603551d1d": "certificateIssuer",
-    "0603551d03": "certificatePolicies",
-    "0603551d1e": "nameConstraints",
-    "0603551d1f": "cRLDistributionPoints",
-    "0603551d20": "certificatePolicies",
-    "0603551d21": "policyMappings",
-    "0603551d22": "policyConstraints",
-    "0603551d23": "authorityKeyIdentifier",
-    "0603551d24": "policyConstraints",
-    "0603551d25": "extKeyUsage",
-    "0603551d04": "keyUsageRestriction",
-    "0603551d05": "policyMapping",
-    "0603551d06": "subtreesConstraint",
-    "0603551d07": "subjectAltName",
-    "0603551d08": "issuerAltName",
-    "0603551d09": "subjectDirectoryAttributes",
-    "0603550400": "objectClass",
-    "0603550401": "aliasObjectName",
-    // "060355040c" : "title",
-    "060355040d": "description",
-    "060355040e": "searchGuide",
-    "060355040f": "businessCategory",
-    "0603550410": "postalAddress",
-    "0603550411": "postalCode",
-    "0603550412": "postOfficeBox",
-    "0603550413": "physicalDeliveryOfficeName",
-    "0603550402": "knowledgeInformation",
-    // "0603550414" : "telephoneNumber",
-    "0603550415": "telexNumber",
-    "0603550416": "teletexTerminalIdentifier",
-    "0603550417": "facsimileTelephoneNumber",
-    "0603550418": "x121Address",
-    "0603550419": "internationalISDNNumber",
-    "060355041a": "registeredAddress",
-    "060355041b": "destinationIndicator",
-    "060355041c": "preferredDeliveryMehtod",
-    "060355041d": "presentationAddress",
-    "060355041e": "supportedApplicationContext",
-    "060355041f": "member",
-    "0603550420": "owner",
-    "0603550421": "roleOccupant",
-    "0603550422": "seeAlso",
-    "0603550423": "userPassword",
-    "0603550424": "userCertificate",
-    "0603550425": "caCertificate",
-    "0603550426": "authorityRevocationList",
-    "0603550427": "certificateRevocationList",
-    "0603550428": "crossCertificatePair",
-    "0603550429": "givenName",
-    // "060355042a" : "givenName",
-    "0603550405": "serialNumber",
-    "0603550434": "supportedAlgorithms",
-    "0603550435": "deltaRevocationList",
-    "060355043a": "crossCertificatePair",
-    // "0603550409" : "streetAddress",
-    "06035508": "X.500-Algorithms",
-    "0603550801": "X.500-Alg-Encryption",
-    "060355080101": "rsa",
-    "0603604c0101": "DPC"
-};

+ 0 - 78
src/core/operations/StrUtils.js

@@ -1,5 +1,4 @@
 import Utils from "../Utils.js";
-import * as JsDiff from "diff";
 
 
 /**
@@ -294,83 +293,6 @@ const StrUtils = {
     },
 
 
-    /**
-     * @constant
-     * @default
-     */
-    DIFF_SAMPLE_DELIMITER: "\\n\\n",
-    /**
-     * @constant
-     * @default
-     */
-    DIFF_BY: ["Character", "Word", "Line", "Sentence", "CSS", "JSON"],
-
-    /**
-     * Diff operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {html}
-     */
-    runDiff: function(input, args) {
-        let sampleDelim = args[0],
-            diffBy = args[1],
-            showAdded = args[2],
-            showRemoved = args[3],
-            ignoreWhitespace = args[4],
-            samples = input.split(sampleDelim),
-            output = "",
-            diff;
-
-        if (!samples || samples.length !== 2) {
-            return "Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?";
-        }
-
-        switch (diffBy) {
-            case "Character":
-                diff = JsDiff.diffChars(samples[0], samples[1]);
-                break;
-            case "Word":
-                if (ignoreWhitespace) {
-                    diff = JsDiff.diffWords(samples[0], samples[1]);
-                } else {
-                    diff = JsDiff.diffWordsWithSpace(samples[0], samples[1]);
-                }
-                break;
-            case "Line":
-                if (ignoreWhitespace) {
-                    diff = JsDiff.diffTrimmedLines(samples[0], samples[1]);
-                } else {
-                    diff = JsDiff.diffLines(samples[0], samples[1]);
-                }
-                break;
-            case "Sentence":
-                diff = JsDiff.diffSentences(samples[0], samples[1]);
-                break;
-            case "CSS":
-                diff = JsDiff.diffCss(samples[0], samples[1]);
-                break;
-            case "JSON":
-                diff = JsDiff.diffJson(samples[0], samples[1]);
-                break;
-            default:
-                return "Invalid 'Diff by' option.";
-        }
-
-        for (let i = 0; i < diff.length; i++) {
-            if (diff[i].added) {
-                if (showAdded) output += "<span class='hl5'>" + Utils.escapeHtml(diff[i].value) + "</span>";
-            } else if (diff[i].removed) {
-                if (showRemoved) output += "<span class='hl3'>" + Utils.escapeHtml(diff[i].value) + "</span>";
-            } else {
-                output += Utils.escapeHtml(diff[i].value);
-            }
-        }
-
-        return output;
-    },
-
-
     /**
      * @constant
      * @default

+ 67 - 86
src/web/App.js

@@ -1,5 +1,4 @@
 import Utils from "../core/Utils.js";
-import Chef from "../core/Chef.js";
 import Manager from "./Manager.js";
 import HTMLCategory from "./HTMLCategory.js";
 import HTMLOperation from "./HTMLOperation.js";
@@ -27,7 +26,6 @@ const App = function(categories, operations, defaultFavourites, defaultOptions)
     this.doptions      = defaultOptions;
     this.options       = Utils.extend({}, defaultOptions);
 
-    this.chef          = new Chef();
     this.manager       = new Manager(this);
 
     this.baking        = false;
@@ -35,8 +33,6 @@ const App = function(categories, operations, defaultFavourites, defaultOptions)
     this.autoBakePause = false;
     this.progress      = 0;
     this.ingId         = 0;
-
-    window.chef        = this.chef;
 };
 
 
@@ -54,6 +50,8 @@ App.prototype.setup = function() {
     this.resetLayout();
     this.setCompileMessage();
     this.loadURIParams();
+
+    this.appLoaded = true;
     this.loaded();
 };
 
@@ -64,6 +62,11 @@ App.prototype.setup = function() {
  * @fires Manager#apploaded
  */
 App.prototype.loaded = function() {
+    // Check that both the app and the worker have loaded successfully, and that
+    // we haven't already loaded before attempting to remove the loading screen.
+    if (!this.workerLoaded || !this.appLoaded ||
+        !document.getElementById("loader-wrapper")) return;
+
     // Trigger CSS animations to remove preloader
     document.body.classList.add("loaded");
 
@@ -94,76 +97,24 @@ App.prototype.handleError = function(err) {
 
 
 /**
- * Updates the UI to show if baking is in process or not.
- *
- * @param {bakingStatus}
- */
-App.prototype.setBakingStatus = function(bakingStatus) {
-    this.baking = bakingStatus;
-
-    let inputLoadingIcon = document.querySelector("#input .title .loading-icon"),
-        outputLoadingIcon = document.querySelector("#output .title .loading-icon"),
-        outputElement = document.querySelector("#output-text");
-
-    if (bakingStatus) {
-        inputLoadingIcon.style.display = "inline-block";
-        outputLoadingIcon.style.display = "inline-block";
-        outputElement.classList.add("disabled");
-        outputElement.disabled = true;
-    } else {
-        inputLoadingIcon.style.display = "none";
-        outputLoadingIcon.style.display = "none";
-        outputElement.classList.remove("disabled");
-        outputElement.disabled = false;
-    }
-};
-
-
-/**
- * Calls the Chef to bake the current input using the current recipe.
+ * Asks the ChefWorker to bake the current input using the current recipe.
  *
  * @param {boolean} [step] - Set to true if we should only execute one operation instead of the
  *   whole recipe.
  */
-App.prototype.bake = async function(step) {
-    let response;
-
+App.prototype.bake = function(step) {
     if (this.baking) return;
 
-    this.setBakingStatus(true);
-
-    try {
-        response = await this.chef.bake(
-            this.getInput(),          // The user's input
-            this.getRecipeConfig(),   // The configuration of the recipe
-            this.options,             // Options set by the user
-            this.progress,            // The current position in the recipe
-            step                      // Whether or not to take one step or execute the whole recipe
-        );
-    } catch (err) {
-        this.handleError(err);
-    }
-
-    this.setBakingStatus(false);
-
-    if (!response) return;
-
-    if (response.error) {
-        this.handleError(response.error);
-    }
+    // Reset attemptHighlight flag
+    this.options.attemptHighlight = true;
 
-    this.options  = response.options;
-    this.dishStr  = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result;
-    this.progress = response.progress;
-    this.manager.recipe.updateBreakpointIndicator(response.progress);
-    this.manager.output.set(response.result, response.type, response.duration);
-
-    // If baking took too long, disable auto-bake
-    if (response.duration > this.options.autoBakeThreshold && this.autoBake_) {
-        this.manager.controls.setAutoBake(false);
-        this.alert("Baking took longer than " + this.options.autoBakeThreshold +
-            "ms, Auto Bake has been disabled.", "warning", 5000);
-    }
+    this.manager.worker.bake(
+        this.getInput(),        // The user's input
+        this.getRecipeConfig(), // The configuration of the recipe
+        this.options,           // Options set by the user
+        this.progress,          // The current position in the recipe
+        step                    // Whether or not to take one step or execute the whole recipe
+    );
 };
 
 
@@ -171,30 +122,31 @@ App.prototype.bake = async function(step) {
  * Runs Auto Bake if it is set.
  */
 App.prototype.autoBake = function() {
-    if (this.autoBake_ && !this.autoBakePause) {
+    if (this.autoBake_ && !this.autoBakePause && !this.baking) {
         this.bake();
+    } else {
+        this.manager.controls.showStaleIndicator();
     }
 };
 
 
 /**
- * Runs a silent bake forcing the browser to load and cache all the relevant JavaScript code needed
+ * Runs a silent bake, forcing the browser to load and cache all the relevant JavaScript code needed
  * to do a real bake.
  *
- * The output will not be modified (hence "silent" bake). This will only actually execute the
- * recipe if auto-bake is enabled, otherwise it will just load the recipe, ingredients and dish.
- *
- * @returns {number} - The number of miliseconds it took to run the silent bake.
+ * The output will not be modified (hence "silent" bake). This will only actually execute the recipe
+ * if auto-bake is enabled, otherwise it will just wake up the ChefWorker with an empty recipe.
  */
 App.prototype.silentBake = function() {
-    let startTime = new Date().getTime(),
-        recipeConfig = this.getRecipeConfig();
+    let recipeConfig = [];
 
     if (this.autoBake_) {
-        this.chef.silentBake(recipeConfig);
+        // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled
+        // for a good reason.
+        recipeConfig = this.getRecipeConfig();
     }
 
-    return new Date().getTime() - startTime;
+    this.manager.worker.silentBake(recipeConfig);
 };
 
 
@@ -264,7 +216,7 @@ App.prototype.populateOperationsList = function() {
 App.prototype.initialiseSplitter = function() {
     this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
         sizes: [20, 30, 50],
-        minSize: [240, 325, 440],
+        minSize: [240, 325, 450],
         gutterSize: 4,
         onDrag: function() {
             this.manager.controls.adjustWidth();
@@ -288,7 +240,7 @@ App.prototype.initialiseSplitter = function() {
 App.prototype.loadLocalStorage = function() {
     // Load options
     let lOptions;
-    if (localStorage.options !== undefined) {
+    if (this.isLocalStorageAvailable() && localStorage.options !== undefined) {
         lOptions = JSON.parse(localStorage.options);
     }
     this.manager.options.load(lOptions);
@@ -304,13 +256,17 @@ App.prototype.loadLocalStorage = function() {
  * If the user currently has no saved favourites, the defaults from the view constructor are used.
  */
 App.prototype.loadFavourites = function() {
-    let favourites = localStorage.favourites &&
-        localStorage.favourites.length > 2 ?
-        JSON.parse(localStorage.favourites) :
-        this.dfavourites;
-
-    favourites = this.validFavourites(favourites);
-    this.saveFavourites(favourites);
+    let favourites;
+
+    if (this.isLocalStorageAvailable()) {
+        favourites = localStorage.favourites && localStorage.favourites.length > 2 ?
+            JSON.parse(localStorage.favourites) :
+            this.dfavourites;
+        favourites = this.validFavourites(favourites);
+        this.saveFavourites(favourites);
+    } else {
+        favourites = this.dfavourites;
+    }
 
     const favCat = this.categories.filter(function(c) {
         return c.name === "Favourites";
@@ -354,6 +310,15 @@ App.prototype.validFavourites = function(favourites) {
  * @param {string[]} favourites - A list of the user's favourite operations
  */
 App.prototype.saveFavourites = function(favourites) {
+    if (!this.isLocalStorageAvailable()) {
+        this.alert(
+            "Your security settings do not allow access to local storage so your favourites cannot be saved.",
+            "danger",
+            5000
+        );
+        return false;
+    }
+
     localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites)));
 };
 
@@ -551,6 +516,22 @@ App.prototype.setCompileMessage = function() {
 };
 
 
+/**
+ * Determines whether the browser supports Local Storage and if it is accessible.
+ * 
+ * @returns {boolean}
+ */
+App.prototype.isLocalStorageAvailable = function() {
+    try {
+        if (!localStorage) return false;
+        return true;
+    } catch (err) {
+        // Access to LocalStorage is denied
+        return false;
+    }
+};
+
+
 /**
  * Pops up a message to the user and writes it to the console log.
  *

+ 65 - 7
src/web/ControlsWaiter.js

@@ -78,10 +78,11 @@ ControlsWaiter.prototype.setAutoBake = function(value) {
  * Handler to trigger baking.
  */
 ControlsWaiter.prototype.bakeClick = function() {
-    this.app.bake();
-    const outputText = document.getElementById("output-text");
-    outputText.focus();
-    outputText.setSelectionRange(0, 0);
+    if (document.getElementById("bake").textContent.indexOf("Bake") > 0) {
+        this.app.bake();
+    } else {
+        this.manager.worker.cancelBake();
+    }
 };
 
 
@@ -90,9 +91,6 @@ ControlsWaiter.prototype.bakeClick = function() {
  */
 ControlsWaiter.prototype.stepClick = function() {
     this.app.bake(true);
-    const outputText = document.getElementById("output-text");
-    outputText.focus();
-    outputText.setSelectionRange(0, 0);
 };
 
 
@@ -256,6 +254,15 @@ ControlsWaiter.prototype.loadClick = function() {
  * Saves the recipe specified in the save textarea to local storage.
  */
 ControlsWaiter.prototype.saveButtonClick = function() {
+    if (!this.app.isLocalStorageAvailable()) {
+        this.app.alert(
+            "Your security settings do not allow access to local storage so your recipe cannot be saved.",
+            "danger",
+            5000
+        );
+        return false;
+    }
+
     const recipeName = Utils.escapeHtml(document.getElementById("save-name").value);
     const recipeStr  = document.querySelector("#save-texts .tab-pane.active textarea").value;
 
@@ -285,6 +292,8 @@ ControlsWaiter.prototype.saveButtonClick = function() {
  * Populates the list of saved recipes in the load dialog box from local storage.
  */
 ControlsWaiter.prototype.populateLoadRecipesList = function() {
+    if (!this.app.isLocalStorageAvailable()) return false;
+
     const loadNameEl = document.getElementById("load-name");
 
     // Remove current recipes from select
@@ -315,6 +324,8 @@ ControlsWaiter.prototype.populateLoadRecipesList = function() {
  * Removes the currently selected recipe from local storage.
  */
 ControlsWaiter.prototype.loadDeleteClick = function() {
+    if (!this.app.isLocalStorageAvailable()) return false;
+
     const id = parseInt(document.getElementById("load-name").value, 10);
     const rawSavedRecipes = localStorage.savedRecipes ?
         JSON.parse(localStorage.savedRecipes) : [];
@@ -330,6 +341,8 @@ ControlsWaiter.prototype.loadDeleteClick = function() {
  * Displays the selected recipe in the load text box.
  */
 ControlsWaiter.prototype.loadNameChange = function(e) {
+    if (!this.app.isLocalStorageAvailable()) return false;
+
     const el = e.target;
     const savedRecipes = localStorage.savedRecipes ?
         JSON.parse(localStorage.savedRecipes) : [];
@@ -375,4 +388,49 @@ ControlsWaiter.prototype.supportButtonClick = function(e) {
     }
 };
 
+
+/**
+ * Shows the stale indicator to show that the input or recipe has changed
+ * since the last bake.
+ */
+ControlsWaiter.prototype.showStaleIndicator = function() {
+    const staleIndicator = document.getElementById("stale-indicator");
+
+    staleIndicator.style.visibility = "visible";
+    staleIndicator.style.opacity = 1;
+};
+
+
+/**
+ * Hides the stale indicator to show that the input or recipe has not changed
+ * since the last bake.
+ */
+ControlsWaiter.prototype.hideStaleIndicator = function() {
+    const staleIndicator = document.getElementById("stale-indicator");
+
+    staleIndicator.style.opacity = 0;
+    staleIndicator.style.visibility = "hidden";
+};
+
+
+/**
+ * Switches the Bake button between 'Bake' and 'Cancel' functions.
+ *
+ * @param {boolean} cancel - Whether to change to cancel or not
+ */
+ControlsWaiter.prototype.toggleBakeButtonFunction = function(cancel) {
+    const bakeButton = document.getElementById("bake"),
+        btnText = bakeButton.querySelector("span");
+
+    if (cancel) {
+        btnText.innerText = "Cancel";
+        bakeButton.classList.remove("btn-success");
+        bakeButton.classList.add("btn-danger");
+    } else {
+        btnText.innerText = "Bake!";
+        bakeButton.classList.remove("btn-danger");
+        bakeButton.classList.add("btn-success");
+    }
+};
+
 export default ControlsWaiter;

+ 22 - 70
src/web/HighlighterWaiter.js

@@ -10,9 +10,11 @@ import Utils from "../core/Utils.js";
  *
  * @constructor
  * @param {App} app - The main view object for CyberChef.
+ * @param {Manager} manager - The CyberChef event manager.
  */
-const HighlighterWaiter = function(app) {
+const HighlighterWaiter = function(app, manager) {
     this.app = app;
+    this.manager = manager;
 
     this.mouseButtonDown = false;
     this.mouseTarget = null;
@@ -329,41 +331,6 @@ HighlighterWaiter.prototype.removeHighlights = function() {
 };
 
 
-/**
- * Generates a list of all the highlight functions assigned to operations in the recipe, if the
- * entire recipe supports highlighting.
- *
- * @returns {Object[]} highlights
- * @returns {function} highlights[].f
- * @returns {function} highlights[].b
- * @returns {Object[]} highlights[].args
- */
-HighlighterWaiter.prototype.generateHighlightList = function() {
-    const recipeConfig = this.app.getRecipeConfig();
-    const highlights = [];
-
-    for (let i = 0; i < recipeConfig.length; i++) {
-        if (recipeConfig[i].disabled) continue;
-
-        // If any breakpoints are set, do not attempt to highlight
-        if (recipeConfig[i].breakpoint) return false;
-
-        const op = this.app.operations[recipeConfig[i].op];
-
-        // If any of the operations do not support highlighting, fail immediately.
-        if (op.highlight === false || op.highlight === undefined) return false;
-
-        highlights.push({
-            f: op.highlight,
-            b: op.highlightReverse,
-            args: recipeConfig[i].args
-        });
-    }
-
-    return highlights;
-};
-
-
 /**
  * Highlights the given offsets in the output.
  * We will only highlight if:
@@ -376,26 +343,8 @@ HighlighterWaiter.prototype.generateHighlightList = function() {
  * @param {number} pos.end - The end offset.
  */
 HighlighterWaiter.prototype.highlightOutput = function(pos) {
-    const highlights = this.generateHighlightList();
-
-    if (!highlights || !this.app.autoBake_) {
-        return false;
-    }
-
-    for (let i = 0; i < highlights.length; i++) {
-        // Remove multiple highlights before processing again
-        pos = [pos[0]];
-
-        if (typeof highlights[i].f == "function") {
-            pos = highlights[i].f(pos, highlights[i].args);
-        }
-    }
-
-    document.getElementById("output-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end);
-    this.highlight(
-        document.getElementById("output-text"),
-        document.getElementById("output-highlighter"),
-        pos);
+    if (!this.app.autoBake_ || this.app.baking) return false;
+    this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos);
 };
 
 
@@ -411,25 +360,28 @@ HighlighterWaiter.prototype.highlightOutput = function(pos) {
  * @param {number} pos.end - The end offset.
  */
 HighlighterWaiter.prototype.highlightInput = function(pos) {
-    const highlights = this.generateHighlightList();
+    if (!this.app.autoBake_ || this.app.baking) return false;
+    this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos);
+};
 
-    if (!highlights || !this.app.autoBake_) {
-        return false;
-    }
 
-    for (let i = 0; i < highlights.length; i++) {
-        // Remove multiple highlights before processing again
-        pos = [pos[0]];
+/**
+ * Displays highlight offsets sent back from the Chef.
+ *
+ * @param {Object} pos - The position object for the highlight.
+ * @param {number} pos.start - The start offset.
+ * @param {number} pos.end - The end offset.
+ * @param {string} direction
+ */
+HighlighterWaiter.prototype.displayHighlights = function(pos, direction) {
+    if (!pos) return;
 
-        if (typeof highlights[i].b == "function") {
-            pos = highlights[i].b(pos, highlights[i].args);
-        }
-    }
+    const io = direction === "forward" ? "output" : "input";
 
-    document.getElementById("input-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end);
+    document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end);
     this.highlight(
-        document.getElementById("input-text"),
-        document.getElementById("input-highlighter"),
+        document.getElementById(io + "-text"),
+        document.getElementById(io + "-highlighter"),
         pos);
 };
 

+ 4 - 6
src/web/InputWaiter.js

@@ -158,17 +158,15 @@ InputWaiter.prototype.inputDrop = function(e) {
     const CHUNK_SIZE = 20480; // 20KB
 
     const setInput = function() {
-        if (inputCharcode.length > 100000 && this.app.autoBake_) {
-            this.manager.controls.setAutoBake(false);
-            this.app.alert("Turned off Auto Bake as the input is large", "warning", 5000);
-        }
-
-        this.set(inputCharcode);
+        this.app.autoBakePause = true;
         const recipeConfig = this.app.getRecipeConfig();
         if (!recipeConfig[0] || recipeConfig[0].op !== "From Hex") {
             recipeConfig.unshift({op: "From Hex", args: ["Space"]});
             this.app.setRecipeConfig(recipeConfig);
         }
+        this.app.autoBakePause = false;
+
+        this.set(inputCharcode);
 
         el.classList.remove("loadingFile");
     }.bind(this);

+ 4 - 1
src/web/Manager.js

@@ -1,3 +1,4 @@
+import WorkerWaiter from "./WorkerWaiter.js";
 import WindowWaiter from "./WindowWaiter.js";
 import ControlsWaiter from "./ControlsWaiter.js";
 import RecipeWaiter from "./RecipeWaiter.js";
@@ -49,6 +50,7 @@ const Manager = function(app) {
     this.statechange = new CustomEvent("statechange", {bubbles: true});
 
     // Define Waiter objects to handle various areas
+    this.worker      = new WorkerWaiter(this.app, this);
     this.window      = new WindowWaiter(this.app);
     this.controls    = new ControlsWaiter(this.app, this);
     this.recipe      = new RecipeWaiter(this.app, this);
@@ -56,7 +58,7 @@ const Manager = function(app) {
     this.input       = new InputWaiter(this.app, this);
     this.output      = new OutputWaiter(this.app, this);
     this.options     = new OptionsWaiter(this.app);
-    this.highlighter = new HighlighterWaiter(this.app);
+    this.highlighter = new HighlighterWaiter(this.app, this);
     this.seasonal    = new SeasonalWaiter(this.app, this);
 
     // Object to store dynamic handlers to fire on elements that may not exist yet
@@ -70,6 +72,7 @@ const Manager = function(app) {
  * Sets up the various components and listeners.
  */
 Manager.prototype.setup = function() {
+    this.worker.registerChefWorker();
     this.recipe.initialiseOperationDragNDrop();
     this.controls.autoBakeChange();
     this.seasonal.load();

+ 1 - 3
src/web/OperationsWaiter.js

@@ -38,7 +38,6 @@ OperationsWaiter.prototype.searchOperations = function(e) {
             selected = this.getSelectedOp(ops);
             if (selected > -1) {
                 this.manager.recipe.addOperation(ops[selected].innerHTML);
-                this.app.autoBake();
             }
         }
     }
@@ -197,7 +196,6 @@ OperationsWaiter.prototype.operationDblclick = function(e) {
     const li = e.target;
 
     this.manager.recipe.addOperation(li.textContent);
-    this.app.autoBake();
 };
 
 
@@ -231,7 +229,7 @@ OperationsWaiter.prototype.editFavouritesClick = function(e) {
         filter: ".remove-icon",
         onFilter: function (evt) {
             const el = editableList.closest(evt.item);
-            if (el) {
+            if (el && el.parentNode) {
                 $(el).popover("destroy");
                 el.parentNode.removeChild(el);
             }

+ 9 - 3
src/web/OptionsWaiter.js

@@ -87,7 +87,9 @@ OptionsWaiter.prototype.switchChange = function(e, state) {
     const option = el.getAttribute("option");
 
     this.app.options[option] = state;
-    localStorage.setItem("options", JSON.stringify(this.app.options));
+
+    if (this.app.isLocalStorageAvailable())
+        localStorage.setItem("options", JSON.stringify(this.app.options));
 };
 
 
@@ -102,7 +104,9 @@ OptionsWaiter.prototype.numberChange = function(e) {
     const option = el.getAttribute("option");
 
     this.app.options[option] = parseInt(el.value, 10);
-    localStorage.setItem("options", JSON.stringify(this.app.options));
+
+    if (this.app.isLocalStorageAvailable())
+        localStorage.setItem("options", JSON.stringify(this.app.options));
 };
 
 
@@ -117,7 +121,9 @@ OptionsWaiter.prototype.selectChange = function(e) {
     const option = el.getAttribute("option");
 
     this.app.options[option] = el.value;
-    localStorage.setItem("options", JSON.stringify(this.app.options));
+
+    if (this.app.isLocalStorageAvailable())
+        localStorage.setItem("options", JSON.stringify(this.app.options));
 };
 
 

+ 40 - 0
src/web/OutputWaiter.js

@@ -201,4 +201,44 @@ OutputWaiter.prototype.maximiseOutputClick = function(e) {
     }
 };
 
+
+/**
+ * Shows or hides the loading icon.
+ *
+ * @param {boolean} value
+ */
+OutputWaiter.prototype.toggleLoader = function(value) {
+    const outputLoader = document.getElementById("output-loader"),
+        outputElement = document.getElementById("output-text");
+
+    if (value) {
+        this.manager.controls.hideStaleIndicator();
+        this.bakingStatusTimeout = setTimeout(function() {
+            outputElement.disabled = true;
+            outputLoader.style.visibility = "visible";
+            outputLoader.style.opacity = 1;
+            this.manager.controls.toggleBakeButtonFunction(true);
+        }.bind(this), 200);
+    } else {
+        clearTimeout(this.bakingStatusTimeout);
+        outputElement.disabled = false;
+        outputLoader.style.opacity = 0;
+        outputLoader.style.visibility = "hidden";
+        this.manager.controls.toggleBakeButtonFunction(false);
+        this.setStatusMsg("");
+    }
+};
+
+
+/**
+ * Sets the baking status message value.
+ *
+ * @param {string} msg
+ */
+OutputWaiter.prototype.setStatusMsg = function(msg) {
+    const el = document.querySelector("#output-loader .loading-msg");
+
+    el.textContent = msg;
+};
+
 export default OutputWaiter;

+ 180 - 0
src/web/WorkerWaiter.js

@@ -0,0 +1,180 @@
+import Utils from "../core/Utils.js";
+import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.js";
+
+/**
+ * Waiter to handle conversations with the ChefWorker.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ * @constructor
+ * @param {App} app - The main view object for CyberChef.
+ * @param {Manager} manager - The CyberChef event manager.
+ */
+const WorkerWaiter = function(app, manager) {
+    this.app = app;
+    this.manager = manager;
+};
+
+
+/**
+ * Sets up the ChefWorker and associated listeners.
+ */
+WorkerWaiter.prototype.registerChefWorker = function() {
+    this.chefWorker = new ChefWorker();
+    this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this));
+
+    let docURL = document.location.href.split(/[#?]/)[0];
+    const index = docURL.lastIndexOf("/");
+    if (index > 0) {
+        docURL = docURL.substring(0, index);
+    }
+    this.chefWorker.postMessage({"action": "docURL", "data": docURL});
+};
+
+
+/**
+ * Handler for messages sent back by the ChefWorker.
+ *
+ * @param {MessageEvent} e
+ */
+WorkerWaiter.prototype.handleChefMessage = function(e) {
+    const r = e.data;
+    switch (r.action) {
+        case "bakeSuccess":
+            this.bakingComplete(r.data);
+            break;
+        case "bakeError":
+            this.app.handleError(r.data);
+            this.setBakingStatus(false);
+            break;
+        case "silentBakeComplete":
+            break;
+        case "workerLoaded":
+            this.app.workerLoaded = true;
+            this.app.loaded();
+            break;
+        case "statusMessage":
+            this.manager.output.setStatusMsg(r.data);
+            break;
+        case "optionUpdate":
+            this.app.options[r.data.option] = r.data.value;
+            break;
+        case "highlightsCalculated":
+            this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction);
+            break;
+        default:
+            console.error("Unrecognised message from ChefWorker", e);
+            break;
+    }
+};
+
+
+/**
+ * Updates the UI to show if baking is in process or not.
+ *
+ * @param {bakingStatus}
+ */
+WorkerWaiter.prototype.setBakingStatus = function(bakingStatus) {
+    this.app.baking = bakingStatus;
+
+    this.manager.output.toggleLoader(bakingStatus);
+};
+
+
+/**
+ * Cancels the current bake by terminating the ChefWorker and creating a new one.
+ */
+WorkerWaiter.prototype.cancelBake = function() {
+    this.chefWorker.terminate();
+    this.registerChefWorker();
+    this.setBakingStatus(false);
+    this.manager.controls.showStaleIndicator();
+};
+
+
+/**
+ * Handler for completed bakes.
+ *
+ * @param {Object} response
+ */
+WorkerWaiter.prototype.bakingComplete = function(response) {
+    this.setBakingStatus(false);
+
+    if (!response) return;
+
+    if (response.error) {
+        this.app.handleError(response.error);
+    }
+
+    this.app.dishStr  = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result;
+    this.app.progress = response.progress;
+    this.manager.recipe.updateBreakpointIndicator(response.progress);
+    this.manager.output.set(response.result, response.type, response.duration);
+};
+
+
+/**
+ * Asks the ChefWorker to bake the current input using the current recipe.
+ *
+ * @param {string} input
+ * @param {Object[]} recipeConfig
+ * @param {Object} options
+ * @param {number} progress
+ * @param {boolean} step
+ */
+WorkerWaiter.prototype.bake = function(input, recipeConfig, options, progress, step) {
+    this.setBakingStatus(true);
+
+    this.chefWorker.postMessage({
+        action: "bake",
+        data: {
+            input: input,
+            recipeConfig: recipeConfig,
+            options: options,
+            progress: progress,
+            step: step
+        }
+    });
+};
+
+
+/**
+ * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant
+ * JavaScript code needed to do a real bake.
+ *
+ * @param {Objectp[]} [recipeConfig]
+ */
+WorkerWaiter.prototype.silentBake = function(recipeConfig) {
+    this.chefWorker.postMessage({
+        action: "silentBake",
+        data: {
+            recipeConfig: recipeConfig
+        }
+    });
+};
+
+
+/**
+ * Asks the ChefWorker to calculate highlight offsets if possible.
+ *
+ * @param {Object[]} recipeConfig
+ * @param {string} direction
+ * @param {Object} pos - The position object for the highlight.
+ * @param {number} pos.start - The start offset.
+ * @param {number} pos.end - The end offset.
+ */
+WorkerWaiter.prototype.highlight = function(recipeConfig, direction, pos) {
+    this.chefWorker.postMessage({
+        action: "highlight",
+        data: {
+            recipeConfig: recipeConfig,
+            direction: direction,
+            pos: pos
+        }
+    });
+};
+
+
+export default WorkerWaiter;

+ 14 - 12
src/web/html/index.html

@@ -36,8 +36,10 @@
 
             // Load theme before the preloader is shown
             try {
-                document.querySelector(":root").className = JSON.parse(localStorage.getItem("options")).theme;
-            } catch (e) {}
+                document.querySelector(":root").className = (JSON.parse(localStorage.getItem("options")) || {}).theme;
+            } catch (err) {
+                // LocalStorage access is denied by security settings
+            }
 
             // Define loading messages
             const loadingMsgs = [
@@ -73,7 +75,8 @@
                 loadingMsgs.push(msg);
                 try {
                     const el = document.getElementById("preloader-msg");
-                    el.className = "loading"; // Causes CSS transition on first message
+                    if (!el.classList.contains("loading"))
+                        el.classList.add("loading"); // Causes CSS transition on first message
                     el.innerHTML = msg;
                 } catch (err) {} // Ignore errors if DOM not yet ready
             }
@@ -90,8 +93,8 @@
     <body>
         <!-- Preloader overlay -->
         <div id="loader-wrapper">
-            <div id="preloader"></div>
-            <div id="preloader-msg"></div>
+            <div id="preloader" class="loader"></div>
+            <div id="preloader-msg" class="loading-msg"></div>
         </div>
         <!-- End preloader overlay -->
         <span id="edit-favourites" class="btn btn-default btn-sm"><img aria-hidden="true" src="<%- require('../static/images/favourite-16x16.png') %>" alt="Star Icon"/> Edit</span>
@@ -142,7 +145,7 @@
                             <div id="bake-group">
                                 <button type="button" class="btn btn-success btn-lg" id="bake">
                                     <img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/>
-                                    Bake!
+                                    <span>Bake!</span>
                                 </button>
                                 <label class="btn btn-success btn-lg" id="auto-bake-label" for="auto-bake">
                                     <input type="checkbox" checked="checked" id="auto-bake">
@@ -168,7 +171,6 @@
                     <div id="input" class="split no-select">
                         <div class="title no-select">
                             <label for="input-text">Input</label>
-                            <div class="loading-icon" style="display: none"></div>
                             <div class="btn-group io-btn-group">
                                 <button type="button" class="btn btn-default btn-sm" id="clr-io"><img aria-hidden="true" src="<%- require('../static/images/recycle-16x16.png') %>" alt="Recycle Icon"/> Clear I/O</button>
                                 <button type="button" class="btn btn-default btn-sm" id="reset-layout"><img aria-hidden="true" src="<%- require('../static/images/layout-16x16.png') %>" alt="Grid Icon"/> Reset layout</button>
@@ -185,7 +187,6 @@
                     <div id="output" class="split">
                         <div class="title no-select">
                             <label for="output-text">Output</label>
-                            <div class="loading-icon" style="display: none"></div>
                             <div class="btn-group io-btn-group">
                                 <button type="button" class="btn btn-default btn-sm" id="save-to-file" title="Save to file"><img aria-hidden="true" src="<%- require('../static/images/save_as-16x16.png') %>" alt="Save Icon"/> Save to file</button>
                                 <button type="button" class="btn btn-default btn-sm" id="switch" title="Move output to input"><img aria-hidden="true" src="<%- require('../static/images/switch-16x16.png') %>" alt="Switch Icon"/> Move output to input</button>
@@ -194,11 +195,16 @@
                             </div>
                             <div class="io-info" id="output-info"></div>
                             <div class="io-info" id="output-selection-info"></div>
+                            <span id="stale-indicator" title="The output is stale.&#10;The input or recipe has changed since this output was generated. Bake again to get the new value.">&#x1F551;</span>
                         </div>
                         <div class="textarea-wrapper">
                             <div id="output-highlighter" class="no-select"></div>
                             <div id="output-html"></div>
                             <textarea id="output-text" readonly="readonly"></textarea>
+                            <div id="output-loader">
+                                <div class="loader"></div>
+                                <div class="loading-msg"></div>
+                            </div>
                         </div>
                     </div>
                 </div>
@@ -323,10 +329,6 @@
                             <input type="number" option="errorTimeout" id="errorTimeout" />
                             <label for="errorTimeout"> Operation error timeout in ms (0 for never) </label>
                         </div>
-                        <div class="option-item">
-                            <input type="number" option="autoBakeThreshold" id="autoBakeThreshold"/>
-                            <label for="autoBakeThreshold"> Auto Bake threshold in ms </label>
-                        </div>
                     </div>
                     <div class="modal-footer">
                         <button type="button" class="btn btn-default" id="reset-options">Reset options to default</button>

+ 1 - 2
src/web/index.js

@@ -17,7 +17,7 @@ import CanvasComponents from "../core/lib/canvascomponents.js";
 // CyberChef
 import App from "./App.js";
 import Categories from "../core/config/Categories.js";
-import OperationConfig from "../core/config/OperationConfig.js";
+import OperationConfig from "../core/config/MetaConfig.js";
 
 
 /**
@@ -44,7 +44,6 @@ function main() {
         wordWrap:          true,
         showErrors:        true,
         errorTimeout:      4000,
-        autoBakeThreshold: 200,
         attemptHighlight:  true,
         theme:             "classic",
     };

+ 1 - 1
src/web/stylesheets/layout/_controls.css

@@ -46,7 +46,7 @@
     width: 60px;
     border-top-left-radius: 0;
     border-bottom-left-radius: 0;
-    border-left: 1px solid var(--btn-success-bg-colour);
+    border-left: 1px solid transparent;
 }
 
 #auto-bake-label:hover {

+ 29 - 16
src/web/stylesheets/layout/_io.css

@@ -63,6 +63,20 @@
     border: none;
 }
 
+#output-loader {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    background-color: var(--primary-background-colour);
+    visibility: hidden;
+    opacity: 0;
+
+    transition: all 0.5s ease;
+}
+
 .io-btn-group {
     float: right;
     margin-top: -4px;
@@ -88,22 +102,21 @@
     border: 5px dashed var(--drop-file-border-colour) !important;
 }
 
-@keyframes spinner {
-    from {
-        transform:rotate(0deg);
-    }
-    to {
-        transform:rotate(359deg);
-    }
-}
-
-.loading-icon::before {
-    content: "\21bb";
+#stale-indicator {
+    visibility: hidden;
+    transition: all 0.3s;
+    margin-left: 5px;
+    font-size: larger;
+    font-weight: normal;
+    cursor: help;
 }
 
-.loading-icon {
-    animation-name: spinner;
-    animation-duration: 1000ms;
-    animation-iteration-count: infinite;
-    animation-timing-function: linear;
+#output-loader .loading-msg {
+    opacity: 1;
+    font-family: var(--primary-font-family);
+    line-height: var(--primary-line-height);
+    color: var(--primary-font-colour);
+    top: 50%;
+    
+    transition: all 0.5s ease;
 }

+ 8 - 10
src/web/stylesheets/preloader.css

@@ -16,7 +16,7 @@
     background-color: var(--secondary-border-colour);
 }
 
-#preloader {
+.loader {
     display: block;
     position: relative;
     left: 50%;
@@ -28,20 +28,19 @@
     border: 3px solid transparent;
     border-top-color: #3498db;
     border-radius: 50%;
-    z-index: 1500;
 
     animation: spin 2s linear infinite;
 }
 
-#preloader:before,
-#preloader:after {
+.loader:before,
+.loader:after {
     content: "";
     position: absolute;
     border: 3px solid transparent;
     border-radius: 50%;
 }
 
-#preloader:before {
+.loader:before {
     top: 5px;
     left: 5px;
     right: 5px;
@@ -50,7 +49,7 @@
     animation: spin 3s linear infinite;
 }
 
-#preloader:after {
+.loader:after {
     top: 13px;
     left: 13px;
     right: 13px;
@@ -59,7 +58,7 @@
     animation: spin 1.5s linear infinite;
 }
 
-#preloader-msg {
+.loading-msg {
     display: block;
     position: relative;
     width: 400px;
@@ -70,15 +69,14 @@
     opacity: 0;
 }
 
-#preloader-msg.loading {
+.loading-msg.loading {
     opacity: 1;
     transition: all 0.1s ease-in;
 }
 
 
 /* Loaded */
-.loaded #preloader,
-.loaded #preloader-msg {
+.loaded .loading-msg {
     opacity: 0;
     transition: all 0.3s ease-out;
 }

+ 116 - 0
webpack.config.js

@@ -0,0 +1,116 @@
+const webpack = require("webpack");
+const ExtractTextPlugin = require("extract-text-webpack-plugin");
+
+/**
+ * Webpack configuration details for use with Grunt.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+const banner = `/**
+ * CyberChef - The Cyber Swiss Army Knife
+ *
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ *   Copyright 2017 Crown Copyright
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */`;
+
+module.exports = {
+    plugins: [
+        new webpack.ProvidePlugin({
+            $: "jquery",
+            jQuery: "jquery",
+            moment: "moment-timezone"
+        }),
+        new webpack.BannerPlugin({
+            banner: banner,
+            raw: true,
+            entryOnly: true
+        }),
+        new ExtractTextPlugin("styles.css"),
+    ],
+    resolve: {
+        alias: {
+            jquery: "jquery/src/jquery"
+        }
+    },
+    module: {
+        rules: [
+            {
+                test: /\.js$/,
+                exclude: /node_modules/,
+                loader: "babel-loader?compact=false"
+            },
+            {
+                test: /MetaConfig\.js$/,
+                loader: "val-loader"
+            },
+            {
+                test: /\.css$/,
+                use: ExtractTextPlugin.extract({
+                    use: [
+                        { loader: "css-loader?minimize" },
+                        { loader: "postcss-loader" },
+                    ]
+                })
+            },
+            {
+                test: /\.less$/,
+                use: ExtractTextPlugin.extract({
+                    use: [
+                        { loader: "css-loader?minimize" },
+                        { loader: "postcss-loader" },
+                        { loader: "less-loader" }
+                    ]
+                })
+            },
+            {
+                test: /\.(ico|eot|ttf|woff|woff2)$/,
+                loader: "url-loader",
+                options: {
+                    limit: 10000
+                }
+            },
+            { // First party images are saved as files to be cached
+                test: /\.(png|jpg|gif|svg)$/,
+                exclude: /node_modules/,
+                loader: "file-loader",
+                options: {
+                    name: "images/[name].[ext]"
+                }
+            },
+            { // Third party images are inlined
+                test: /\.(png|jpg|gif|svg)$/,
+                exclude: /web\/static/,
+                loader: "url-loader",
+                options: {
+                    limit: 10000
+                }
+            },
+        ]
+    },
+    stats: {
+        children: false,
+        chunks: false,
+        modules: false,
+        warningsFilter: /source-map/,
+    },
+    node: {
+        fs: "empty"
+    }
+};

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