n1474335 vor 8 Jahren
Ursprung
Commit
0dc72d8301

+ 1 - 1
.eslintrc.json

@@ -1,6 +1,6 @@
 {
     "parserOptions": {
-        "ecmaVersion": 6,
+        "ecmaVersion": 8,
         "ecmaFeatures": {
             "impliedStrict": true
         },

+ 2 - 1
Gruntfile.js

@@ -220,7 +220,8 @@ module.exports = function (grunt) {
                     ]
                 },
                 stats: {
-                    children: false
+                    children: false,
+                    warningsFilter: /source-map/
                 }
             },
             webDev: {

+ 4 - 1
docs/jsdoc.conf.json

@@ -2,7 +2,10 @@
     "tags": {
         "allowUnknownTags": true
     },
-    "plugins": ["plugins/markdown"],
+    "plugins": [
+        "plugins/markdown",
+        "node_modules/jsdoc-babel"
+    ],
     "templates": {
         "systemName": "CyberChef",
         "footer": "",

+ 3 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "cyberchef",
-  "version": "5.3.0",
+  "version": "5.3.5",
   "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
   "author": "n1474335 <n1474335@gmail.com>",
   "homepage": "https://gchq.github.io/CyberChef",
@@ -53,6 +53,7 @@
     "ink-docstrap": "^1.1.4",
     "node-sass": "^4.5.2",
     "sass-loader": "^6.0.3",
+    "jsdoc-babel": "^0.3.0",
     "style-loader": "^0.15.0",
     "url-loader": "^0.5.8",
     "web-resource-inliner": "^4.1.0",
@@ -71,7 +72,7 @@
     "google-code-prettify": "^1.0.5",
     "jquery": "^3.1.1",
     "jsbn": "^1.1.0",
-    "jsrsasign": "^7.1.0",
+    "jsrsasign": "7.1.3",
     "lodash": "^4.17.4",
     "moment": "^2.17.1",
     "moment-timezone": "^0.5.11",

+ 2 - 2
src/core/Chef.js

@@ -34,7 +34,7 @@ const Chef = function() {
  * @returns {number} response.duration - The number of ms it took to execute the recipe
  * @returns {number} response.error - The error object thrown by a failed operation (false if no error)
 */
-Chef.prototype.bake = function(inputText, recipeConfig, options, progress, step) {
+Chef.prototype.bake = async function(inputText, recipeConfig, options, progress, step) {
     let startTime  = new Date().getTime(),
         recipe     = new Recipe(recipeConfig),
         containsFc = recipe.containsFlowControl(),
@@ -72,7 +72,7 @@ Chef.prototype.bake = function(inputText, recipeConfig, options, progress, step)
     }
 
     try {
-        progress = recipe.execute(this.dish, progress);
+        progress = await recipe.execute(this.dish, progress);
     } catch (err) {
         // Return the error in the result so that everything else gets correctly updated
         // rather than throwing it here and losing state info.

+ 2 - 2
src/core/FlowControl.js

@@ -38,7 +38,7 @@ const FlowControl = {
      * @param {Operation[]} state.opList - The list of operations in the recipe.
      * @returns {Object} The updated state of the recipe.
      */
-    runFork: function(state) {
+    runFork: async function(state) {
         let opList       = state.opList,
             inputType    = opList[state.progress].inputType,
             outputType   = opList[state.progress].outputType,
@@ -74,7 +74,7 @@ const FlowControl = {
         for (i = 0; i < inputs.length; i++) {
             const dish = new Dish(inputs[i], inputType);
             try {
-                progress = recipe.execute(dish, 0);
+                progress = await recipe.execute(dish, 0);
             } catch (err) {
                 if (!ignoreErrors) {
                     throw err;

+ 3 - 3
src/core/Recipe.js

@@ -145,7 +145,7 @@ Recipe.prototype.lastOpIndex = function(startIndex) {
  * @param {number} [startFrom=0] - The index of the Operation to start executing from
  * @returns {number} - The final progress through the recipe
  */
-Recipe.prototype.execute = function(dish, startFrom) {
+Recipe.prototype.execute = async function(dish, startFrom) {
     startFrom = startFrom || 0;
     let op, input, output, numJumps = 0;
 
@@ -170,11 +170,11 @@ Recipe.prototype.execute = function(dish, startFrom) {
                     "numJumps" : numJumps
                 };
 
-                state = op.run(state);
+                state = await op.run(state);
                 i = state.progress;
                 numJumps = state.numJumps;
             } else {
-                output = op.run(input, op.getIngValues());
+                output = await op.run(input, op.getIngValues());
                 dish.set(output, op.outputType);
             }
         } catch (err) {

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

@@ -1401,6 +1401,11 @@ const OperationConfig = {
                 type: "number",
                 value: Cipher.KDF_ITERATIONS
             },
+            {
+                name: "Hashing function",
+                type: "option",
+                value: Cipher.HASHERS
+            },
             {
                 name: "Salt (hex)",
                 type: "string",
@@ -1434,6 +1439,11 @@ const OperationConfig = {
                 type: "number",
                 value: Cipher.KDF_ITERATIONS
             },
+            {
+                name: "Hashing function",
+                type: "option",
+                value: Cipher.HASHERS
+            },
             {
                 name: "Salt (hex)",
                 type: "string",

+ 23 - 8
src/core/operations/Cipher.js

@@ -309,6 +309,11 @@ const Cipher = {
      * @default
      */
     KDF_ITERATIONS: 1,
+    /**
+     * @constant
+     * @default
+     */
+    HASHERS: ["MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "RIPEMD160"],
 
     /**
      * Derive PBKDF2 key operation.
@@ -320,11 +325,16 @@ const Cipher = {
     runPbkdf2: function (input, args) {
         let keySize = args[0] / 32,
             iterations = args[1],
-            salt = CryptoJS.enc.Hex.parse(args[2] || ""),
-            inputFormat = args[3],
-            outputFormat = args[4],
+            hasher = args[2],
+            salt = CryptoJS.enc.Hex.parse(args[3] || ""),
+            inputFormat = args[4],
+            outputFormat = args[5],
             passphrase = Utils.format[inputFormat].parse(input),
-            key = CryptoJS.PBKDF2(passphrase, salt, { keySize: keySize, iterations: iterations });
+            key = CryptoJS.PBKDF2(passphrase, salt, {
+                keySize: keySize,
+                hasher: CryptoJS.algo[hasher],
+                iterations: iterations,
+            });
 
         return key.toString(Utils.format[outputFormat]);
     },
@@ -340,11 +350,16 @@ const Cipher = {
     runEvpkdf: function (input, args) {
         let keySize = args[0] / 32,
             iterations = args[1],
-            salt = CryptoJS.enc.Hex.parse(args[2] || ""),
-            inputFormat = args[3],
-            outputFormat = args[4],
+            hasher = args[2],
+            salt = CryptoJS.enc.Hex.parse(args[3] || ""),
+            inputFormat = args[4],
+            outputFormat = args[5],
             passphrase = Utils.format[inputFormat].parse(input),
-            key = CryptoJS.EvpKDF(passphrase, salt, { keySize: keySize, iterations: iterations });
+            key = CryptoJS.EvpKDF(passphrase, salt, {
+                keySize: keySize,
+                hasher: CryptoJS.algo[hasher],
+                iterations: iterations,
+            });
 
         return key.toString(Utils.format[outputFormat]);
     },

+ 4 - 4
src/core/operations/Code.js

@@ -426,7 +426,7 @@ const Code = {
      * @returns {string}
      */
     _replaceVariableNames(input, replacer) {
-        let tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
+        const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
 
         return input.replace(tokenRegex, (...args) => {
             let match = args[0],
@@ -450,7 +450,7 @@ const Code = {
      *
      */
     runToSnakeCase(input, args) {
-        let smart = args[0];
+        const smart = args[0];
 
         if (smart) {
             return Code._replaceVariableNames(input, snakeCase);
@@ -469,7 +469,7 @@ const Code = {
      *
      */
     runToCamelCase(input, args) {
-        let smart = args[0];
+        const smart = args[0];
 
         if (smart) {
             return Code._replaceVariableNames(input, camelCase);
@@ -488,7 +488,7 @@ const Code = {
      *
      */
     runToKebabCase(input, args) {
-        let smart = args[0];
+        const smart = args[0];
 
         if (smart) {
             return Code._replaceVariableNames(input, kebabCase);

+ 1 - 6
src/core/operations/Entropy.js

@@ -88,17 +88,12 @@ const Entropy = {
     runFreqDistrib: function (input, args) {
         if (!input.length) return "No data";
 
-        let distrib = new Array(256),
+        let distrib = new Array(256).fill(0),
             percentages = new Array(256),
             len = input.length,
             showZeroes = args[0],
             i;
 
-        // Initialise distrib to 0
-        for (i = 0; i < 256; i++) {
-            distrib[i] = 0;
-        }
-
         // Count bytes
         for (i = 0; i < len; i++) {
             distrib[input[i]]++;

+ 1 - 5
src/core/operations/IP.js

@@ -713,13 +713,9 @@ const IP = {
             ip2 = IP._strToIpv6(range[14]);
 
         let t = "",
-            total = new Array(128),
+            total = new Array(128).fill(),
             i;
 
-        // Initialise total array to "0"
-        for (i = 0; i < 128; i++)
-            total[i] = "0";
-
         for (i = 0; i < 8; i++) {
             t = (ip2[i] - ip1[i]).toString(2);
             if (t !== "0") {

+ 9 - 2
src/core/operations/PublicKey.js

@@ -124,10 +124,17 @@ const PublicKey = {
         }
 
         // Signature fields
-        if (r.ASN1HEX.dump(certSig).indexOf("SEQUENCE") === 0) { // DSA or ECDSA
+        let breakoutSig = false;
+        try {
+            breakoutSig = r.ASN1HEX.dump(certSig).indexOf("SEQUENCE") === 0;
+        } catch (err) {
+            // Error processing signature, output without further breakout
+        }
+
+        if (breakoutSig) { // DSA or ECDSA
             sigStr = "  r:              " + PublicKey._formatByteStr(r.ASN1HEX.getDecendantHexVByNthList(certSig, 0, [0]), 16, 18) + "\n" +
                 "  s:              " + PublicKey._formatByteStr(r.ASN1HEX.getDecendantHexVByNthList(certSig, 0, [1]), 16, 18) + "\n";
-        } else { // RSA
+        } else { // RSA or unknown
             sigStr = "  Signature:      " + PublicKey._formatByteStr(certSig, 16, 18) + "\n";
         }
 

+ 4 - 6
src/core/operations/StrUtils.js

@@ -385,7 +385,7 @@ const StrUtils = {
     runOffsetChecker: function(input, args) {
         let sampleDelim = args[0],
             samples = input.split(sampleDelim),
-            outputs = [],
+            outputs = new Array(samples.length),
             i = 0,
             s = 0,
             match = false,
@@ -397,9 +397,7 @@ const StrUtils = {
         }
 
         // Initialise output strings
-        for (s = 0; s < samples.length; s++) {
-            outputs[s] = "";
-        }
+        outputs.fill("", 0, samples.length);
 
         // Loop through each character in the first sample
         for (i = 0; i < samples[0].length; i++) {
@@ -473,7 +471,7 @@ const StrUtils = {
             number = args[1];
 
         delimiter = Utils.charRep[delimiter];
-        let splitInput = input.split(delimiter);
+        const splitInput = input.split(delimiter);
 
         return splitInput
         .filter((line, lineIndex) => {
@@ -501,7 +499,7 @@ const StrUtils = {
             number = args[1];
 
         delimiter = Utils.charRep[delimiter];
-        let splitInput = input.split(delimiter);
+        const splitInput = input.split(delimiter);
 
         return splitInput
         .filter((line, lineIndex) => {

+ 37 - 4
src/web/App.js

@@ -30,6 +30,7 @@ const App = function(categories, operations, defaultFavourites, defaultOptions)
     this.chef        = new Chef();
     this.manager     = new Manager(this);
 
+    this.baking      = false;
     this.autoBake_   = false;
     this.progress    = 0;
     this.ingId       = 0;
@@ -84,19 +85,49 @@ 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.
  *
  * @param {boolean} [step] - Set to true if we should only execute one operation instead of the
  *   whole recipe.
  */
-App.prototype.bake = function(step) {
+App.prototype.bake = async function(step) {
     let response;
 
+    if (this.baking) return;
+
+    this.setBakingStatus(true);
+
     try {
-        response = this.chef.bake(
-            this.getInput(),         // The user's input
-            this.getRecipeConfig(), // The configuration of the recipe
+        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
@@ -105,6 +136,8 @@ App.prototype.bake = function(step) {
         this.handleError(err);
     }
 
+    this.setBakingStatus(false);
+
     if (!response) return;
 
     if (response.error) {

+ 44 - 44
src/web/ControlsWaiter.js

@@ -23,14 +23,14 @@ const ControlsWaiter = function(app, manager) {
  * without wrapping or overflowing.
  */
 ControlsWaiter.prototype.adjustWidth = function() {
-    let controls     = document.getElementById("controls"),
-        step         = document.getElementById("step"),
-        clrBreaks    = document.getElementById("clr-breaks"),
-        saveImg      = document.querySelector("#save img"),
-        loadImg      = document.querySelector("#load img"),
-        stepImg      = document.querySelector("#step img"),
-        clrRecipImg  = document.querySelector("#clr-recipe img"),
-        clrBreaksImg = document.querySelector("#clr-breaks img");
+    const controls     = document.getElementById("controls");
+    const step         = document.getElementById("step");
+    const clrBreaks    = document.getElementById("clr-breaks");
+    const saveImg      = document.querySelector("#save img");
+    const loadImg      = document.querySelector("#load img");
+    const stepImg      = document.querySelector("#step img");
+    const clrRecipImg  = document.querySelector("#clr-recipe img");
+    const clrBreaksImg = document.querySelector("#clr-breaks img");
 
     if (controls.clientWidth < 470) {
         step.childNodes[1].nodeValue = " Step";
@@ -100,8 +100,8 @@ ControlsWaiter.prototype.stepClick = function() {
  * Handler for changes made to the Auto Bake checkbox.
  */
 ControlsWaiter.prototype.autoBakeChange = function() {
-    let autoBakeLabel    = document.getElementById("auto-bake-label"),
-        autoBakeCheckbox = document.getElementById("auto-bake");
+    const autoBakeLabel    = document.getElementById("auto-bake-label");
+    const autoBakeCheckbox = document.getElementById("auto-bake");
 
     this.app.autoBake_ = autoBakeCheckbox.checked;
 
@@ -145,10 +145,10 @@ ControlsWaiter.prototype.clearBreaksClick = function() {
 ControlsWaiter.prototype.initialiseSaveLink = function(recipeConfig) {
     recipeConfig = recipeConfig || this.app.getRecipeConfig();
 
-    let includeRecipe = document.getElementById("save-link-recipe-checkbox").checked,
-        includeInput = document.getElementById("save-link-input-checkbox").checked,
-        saveLinkEl = document.getElementById("save-link"),
-        saveLink = this.generateStateUrl(includeRecipe, includeInput, recipeConfig);
+    const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked;
+    const includeInput = document.getElementById("save-link-input-checkbox").checked;
+    const saveLinkEl = document.getElementById("save-link");
+    const saveLink = this.generateStateUrl(includeRecipe, includeInput, recipeConfig);
 
     saveLinkEl.innerHTML = Utils.truncate(saveLink, 120);
     saveLinkEl.setAttribute("href", saveLink);
@@ -167,23 +167,27 @@ ControlsWaiter.prototype.initialiseSaveLink = function(recipeConfig) {
 ControlsWaiter.prototype.generateStateUrl = function(includeRecipe, includeInput, recipeConfig, baseURL) {
     recipeConfig = recipeConfig || this.app.getRecipeConfig();
 
-    let link = baseURL || window.location.protocol + "//" +
-                window.location.host +
-                window.location.pathname,
-        recipeStr = JSON.stringify(recipeConfig),
-        inputStr = Utils.toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding
+    const link = baseURL || window.location.protocol + "//" +
+        window.location.host +
+        window.location.pathname;
+    const recipeStr = JSON.stringify(recipeConfig);
+    const inputStr = Utils.toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding
 
     includeRecipe = includeRecipe && (recipeConfig.length > 0);
     includeInput = includeInput && (inputStr.length > 0) && (inputStr.length < 8000);
 
-    if (includeRecipe) {
-        link += "?recipe=" + encodeURIComponent(recipeStr);
-    }
+    const params = [
+        includeRecipe ? ["recipe", recipeStr] : undefined,
+        includeInput ? ["input", inputStr] : undefined,
+    ];
 
-    if (includeRecipe && includeInput) {
-        link += "&input=" + encodeURIComponent(inputStr);
-    } else if (includeInput) {
-        link += "?input=" + encodeURIComponent(inputStr);
+    const query = params
+       .filter(v => v)
+       .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
+       .join("&");
+
+    if (query) {
+        return `${link}?${query}`;
     }
 
     return link;
@@ -205,8 +209,8 @@ ControlsWaiter.prototype.saveTextChange = function() {
  * Handler for the 'Save' command. Pops up the save dialog box.
  */
 ControlsWaiter.prototype.saveClick = function() {
-    let recipeConfig = this.app.getRecipeConfig(),
-        recipeStr = JSON.stringify(recipeConfig).replace(/},{/g, "},\n{");
+    const recipeConfig = this.app.getRecipeConfig();
+    const recipeStr = JSON.stringify(recipeConfig).replace(/},{/g, "},\n{");
 
     document.getElementById("save-text").value = recipeStr;
 
@@ -244,8 +248,8 @@ ControlsWaiter.prototype.loadClick = function() {
  * Saves the recipe specified in the save textarea to local storage.
  */
 ControlsWaiter.prototype.saveButtonClick = function() {
-    let recipeName = Utils.escapeHtml(document.getElementById("save-name").value);
-    let recipeStr  = document.getElementById("save-text").value;
+    const recipeName = Utils.escapeHtml(document.getElementById("save-name").value);
+    const recipeStr  = document.getElementById("save-text").value;
 
     if (!recipeName) {
         this.app.alert("Please enter a recipe name", "danger", 2000);
@@ -303,13 +307,11 @@ ControlsWaiter.prototype.populateLoadRecipesList = function() {
  * Removes the currently selected recipe from local storage.
  */
 ControlsWaiter.prototype.loadDeleteClick = function() {
-    let id = parseInt(document.getElementById("load-name").value, 10),
-        savedRecipes = localStorage.savedRecipes ?
+    const id = parseInt(document.getElementById("load-name").value, 10);
+    const rawSavedRecipes = localStorage.savedRecipes ?
             JSON.parse(localStorage.savedRecipes) : [];
 
-    savedRecipes = savedRecipes.filter(function(r) {
-        return r.id !== id;
-    });
+    const savedRecipes = rawSavedRecipes.filter(r => r.id !== id);
 
     localStorage.savedRecipes = JSON.stringify(savedRecipes);
     this.populateLoadRecipesList();
@@ -320,14 +322,12 @@ ControlsWaiter.prototype.loadDeleteClick = function() {
  * Displays the selected recipe in the load text box.
  */
 ControlsWaiter.prototype.loadNameChange = function(e) {
-    let el = e.target,
-        savedRecipes = localStorage.savedRecipes ?
-            JSON.parse(localStorage.savedRecipes) : [],
-        id = parseInt(el.value, 10);
+    const el = e.target;
+    const savedRecipes = localStorage.savedRecipes ?
+            JSON.parse(localStorage.savedRecipes) : [];
+    const id = parseInt(el.value, 10);
 
-    const recipe = savedRecipes.filter(function(r) {
-        return r.id === id;
-    })[0];
+    const recipe = savedRecipes.find(r => r.id === id);
 
     document.getElementById("load-text").value = recipe.recipe;
 };
@@ -352,8 +352,8 @@ ControlsWaiter.prototype.loadButtonClick = function() {
  * Populates the bug report information box with useful technical info.
  */
 ControlsWaiter.prototype.supportButtonClick = function() {
-    let reportBugInfo = document.getElementById("report-bug-info"),
-        saveLink = this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/");
+    const reportBugInfo = document.getElementById("report-bug-info");
+    const saveLink = this.generateStateUrl(true, true, null, "https://gchq.github.io/CyberChef/");
 
     reportBugInfo.innerHTML = "* CyberChef compile time: " + COMPILE_TIME + "\n" +
         "* User-Agent: \n" + navigator.userAgent + "\n" +

+ 7 - 8
src/web/HTMLIngredient.js

@@ -158,13 +158,12 @@ HTMLIngredient.prototype.toHtml = function() {
  * @param {event} e
  */
 HTMLIngredient.prototype.toggleDisableArgs = function(e) {
-    let el = e.target,
-        op = el.parentNode.parentNode,
-        args = op.querySelectorAll(".arg-group"),
-        els;
+    const el = e.target;
+    const op = el.parentNode.parentNode;
+    const args = op.querySelectorAll(".arg-group");
 
     for (let i = 0; i < this.disableArgs.length; i++) {
-        els = args[this.disableArgs[i]].querySelectorAll("input, select, button");
+        const els = args[this.disableArgs[i]].querySelectorAll("input, select, button");
 
         for (let j = 0; j < els.length; j++) {
             if (els[j].getAttribute("disabled")) {
@@ -186,9 +185,9 @@ HTMLIngredient.prototype.toggleDisableArgs = function(e) {
  * @param {event} e
  */
 HTMLIngredient.prototype.populateOptionChange = function(e) {
-    let el = e.target,
-        op = el.parentNode.parentNode,
-        target = op.querySelectorAll(".arg-group")[this.target].querySelector("input, select, textarea");
+    const el = e.target;
+    const op = el.parentNode.parentNode;
+    const target = op.querySelectorAll(".arg-group")[this.target].querySelector("input, select, textarea");
 
     target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value");
 

+ 28 - 28
src/web/HighlighterWaiter.js

@@ -64,8 +64,8 @@ HighlighterWaiter.prototype._isSelectionBackwards = function() {
  * @returns {number}
  */
 HighlighterWaiter.prototype._getOutputHtmlOffset = function(node, offset) {
-    let sel = window.getSelection(),
-        range = document.createRange();
+    const sel = window.getSelection();
+    const range = document.createRange();
 
     range.selectNodeContents(document.getElementById("output-html"));
     range.setEnd(node, offset);
@@ -85,8 +85,8 @@ HighlighterWaiter.prototype._getOutputHtmlOffset = function(node, offset) {
  * @returns {number} pos.end
  */
 HighlighterWaiter.prototype._getOutputHtmlSelectionOffsets = function() {
-    let sel = window.getSelection(),
-        range,
+    const sel = window.getSelection();
+    let range,
         start = 0,
         end = 0,
         backwards = false;
@@ -151,9 +151,9 @@ HighlighterWaiter.prototype.inputMousedown = function(e) {
     this.mouseTarget = HighlighterWaiter.INPUT;
     this.removeHighlights();
 
-    let el = e.target,
-        start = el.selectionStart,
-        end = el.selectionEnd;
+    const el = e.target;
+    const start = el.selectionStart;
+    const end = el.selectionEnd;
 
     if (start !== 0 || end !== 0) {
         document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end);
@@ -173,9 +173,9 @@ HighlighterWaiter.prototype.outputMousedown = function(e) {
     this.mouseTarget = HighlighterWaiter.OUTPUT;
     this.removeHighlights();
 
-    let el = e.target,
-        start = el.selectionStart,
-        end = el.selectionEnd;
+    const el = e.target;
+    const start = el.selectionStart;
+    const end = el.selectionEnd;
 
     if (start !== 0 || end !== 0) {
         document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end);
@@ -244,9 +244,9 @@ HighlighterWaiter.prototype.inputMousemove = function(e) {
         this.mouseTarget !== HighlighterWaiter.INPUT)
         return;
 
-    let el = e.target,
-        start = el.selectionStart,
-        end = el.selectionEnd;
+    const el = e.target;
+    const start = el.selectionStart;
+    const end = el.selectionEnd;
 
     if (start !== 0 || end !== 0) {
         document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end);
@@ -268,9 +268,9 @@ HighlighterWaiter.prototype.outputMousemove = function(e) {
         this.mouseTarget !== HighlighterWaiter.OUTPUT)
         return;
 
-    let el = e.target,
-        start = el.selectionStart,
-        end = el.selectionEnd;
+    const el = e.target;
+    const start = el.selectionStart;
+    const end = el.selectionEnd;
 
     if (start !== 0 || end !== 0) {
         document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end);
@@ -308,11 +308,11 @@ HighlighterWaiter.prototype.outputHtmlMousemove = function(e) {
  * @returns {string}
  */
 HighlighterWaiter.prototype.selectionInfo = function(start, end) {
-    let width = end.toString().length;
-    width = width < 2 ? 2 : width;
-    let startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, "&nbsp;"),
-        endStr   = Utils.pad(end.toString(), width, " ").replace(/ /g, "&nbsp;"),
-        lenStr   = Utils.pad((end-start).toString(), width, " ").replace(/ /g, "&nbsp;");
+    const len = end.toString().length;
+    const width = len < 2 ? 2 : len;
+    const startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, "&nbsp;");
+    const endStr   = Utils.pad(end.toString(), width, " ").replace(/ /g, "&nbsp;");
+    const lenStr   = Utils.pad((end-start).toString(), width, " ").replace(/ /g, "&nbsp;");
 
     return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr;
 };
@@ -339,8 +339,8 @@ HighlighterWaiter.prototype.removeHighlights = function() {
  * @returns {Object[]} highlights[].args
  */
 HighlighterWaiter.prototype.generateHighlightList = function() {
-    let recipeConfig = this.app.getRecipeConfig(),
-        highlights = [];
+    const recipeConfig = this.app.getRecipeConfig();
+    const highlights = [];
 
     for (let i = 0; i < recipeConfig.length; i++) {
         if (recipeConfig[i].disabled) continue;
@@ -452,11 +452,11 @@ HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) {
     // be displayed by the HTML textarea and will mess up highlighting offsets.
     if (!this.app.dishStr || this.app.dishStr.indexOf("\r") >= 0) return false;
 
-    let startPlaceholder = "[startHighlight]",
-        startPlaceholderRegex = /\[startHighlight\]/g,
-        endPlaceholder = "[endHighlight]",
-        endPlaceholderRegex = /\[endHighlight\]/g,
-        text = textarea.value;
+    const startPlaceholder = "[startHighlight]";
+    const startPlaceholderRegex = /\[startHighlight\]/g;
+    const endPlaceholder = "[endHighlight]";
+    const endPlaceholderRegex = /\[endHighlight\]/g;
+    let text = textarea.value;
 
     // Put placeholders in position
     // If there's only one value, select that

+ 9 - 9
src/web/InputWaiter.js

@@ -92,8 +92,8 @@ InputWaiter.prototype.inputChange = function(e) {
     this.app.progress = 0;
 
     // Update the input metadata info
-    let inputText = this.get(),
-        lines = inputText.count("\n") + 1;
+    const inputText = this.get();
+    const lines = inputText.count("\n") + 1;
 
     this.setInputInfo(inputText.length, lines);
 
@@ -149,13 +149,13 @@ InputWaiter.prototype.inputDrop = function(e) {
     e.stopPropagation();
     e.preventDefault();
 
-    let el = e.target,
-        file = e.dataTransfer.files[0],
-        text = e.dataTransfer.getData("Text"),
-        reader = new FileReader(),
-        inputCharcode = "",
-        offset = 0,
-        CHUNK_SIZE = 20480; // 20KB
+    const el = e.target;
+    const file = e.dataTransfer.files[0];
+    const text = e.dataTransfer.getData("Text");
+    const reader = new FileReader();
+    let inputCharcode = "";
+    let offset = 0;
+    const CHUNK_SIZE = 20480; // 20KB
 
     const setInput = function() {
         if (inputCharcode.length > 100000 && this.app.autoBake_) {

+ 8 - 7
src/web/Manager.js

@@ -262,15 +262,16 @@ Manager.prototype.addDynamicListener = function(selector, eventType, callback, s
  * @param {Event} e - The event to be handled
  */
 Manager.prototype.dynamicListenerHandler = function(e) {
-    let handlers = this.dynamicHandlers[e.type],
-        matches = e.target.matches ||
-            e.target.webkitMatchesSelector ||
-            e.target.mozMatchesSelector ||
-            e.target.msMatchesSelector ||
-            e.target.oMatchesSelector;
+    const { type, target } = e;
+    const handlers = this.dynamicHandlers[type];
+    const matches = target.matches ||
+            target.webkitMatchesSelector ||
+            target.mozMatchesSelector ||
+            target.msMatchesSelector ||
+            target.oMatchesSelector;
 
     for (let i = 0; i < handlers.length; i++) {
-        if (matches && e.target[matches.name](handlers[i].selector)) {
+        if (matches && matches.call(target, handlers[i].selector)) {
             handlers[i].callback(e);
         }
     }

+ 18 - 24
src/web/OperationsWaiter.js

@@ -68,9 +68,9 @@ OperationsWaiter.prototype.searchOperations = function(e) {
             ops[selected-1].classList.add("selected-op");
         }
     } else {
-        let searchResultsEl = document.getElementById("search-results"),
-            el = e.target,
-            str = el.value;
+        const searchResultsEl = document.getElementById("search-results");
+        const el = e.target;
+        const str = el.value;
 
         while (searchResultsEl.firstChild) {
             try {
@@ -81,12 +81,10 @@ OperationsWaiter.prototype.searchOperations = function(e) {
 
         $("#categories .in").collapse("hide");
         if (str) {
-            let matchedOps = this.filterOperations(str, true),
-                matchedOpsHtml = "";
-
-            for (let i = 0; i < matchedOps.length; i++) {
-                matchedOpsHtml += matchedOps[i].toStubHtml();
-            }
+            const matchedOps = this.filterOperations(str, true);
+            const matchedOpsHtml = matchedOps
+                .map(v => v.toStubHtml())
+                .join("");
 
             searchResultsEl.innerHTML = matchedOpsHtml;
             searchResultsEl.dispatchEvent(this.manager.oplistcreate);
@@ -103,16 +101,16 @@ OperationsWaiter.prototype.searchOperations = function(e) {
  *   name and description
  * @returns {string[]}
  */
-OperationsWaiter.prototype.filterOperations = function(searchStr, highlight) {
-    let matchedOps = [],
-        matchedDescs = [];
+OperationsWaiter.prototype.filterOperations = function(inStr, highlight) {
+    const matchedOps = [];
+    const matchedDescs = [];
 
-    searchStr = searchStr.toLowerCase();
+    const searchStr = inStr.toLowerCase();
 
     for (const opName in this.app.operations) {
-        let op = this.app.operations[opName],
-            namePos = opName.toLowerCase().indexOf(searchStr),
-            descPos = op.description.toLowerCase().indexOf(searchStr);
+        const op = this.app.operations[opName];
+        const namePos = opName.toLowerCase().indexOf(searchStr);
+        const descPos = op.description.toLowerCase().indexOf(searchStr);
 
         if (namePos >= 0 || descPos >= 0) {
             const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
@@ -236,12 +234,8 @@ OperationsWaiter.prototype.editFavouritesClick = function(e) {
  * Saves the selected favourites and reloads them.
  */
 OperationsWaiter.prototype.saveFavouritesClick = function() {
-    let favouritesList = [],
-        favs = document.querySelectorAll("#edit-favourites-list li");
-
-    for (let i = 0; i < favs.length; i++) {
-        favouritesList.push(favs[i].textContent);
-    }
+    const favs = document.querySelectorAll("#edit-favourites-list li");
+    const favouritesList = Array.from(favs, e => e.textContent);
 
     this.app.saveFavourites(favouritesList);
     this.app.loadFavourites();
@@ -281,8 +275,8 @@ OperationsWaiter.prototype.opIconMouseover = function(e) {
  * @param {event} e
  */
 OperationsWaiter.prototype.opIconMouseleave = function(e) {
-    let opEl = e.target.parentNode,
-        toEl = e.toElement || e.relatedElement;
+    const opEl = e.target.parentNode;
+    const toEl = e.toElement || e.relatedElement;
 
     if (e.target.getAttribute("data-toggle") === "popover" && toEl === opEl) {
         $(opEl).popover("show");

+ 6 - 6
src/web/OptionsWaiter.js

@@ -75,8 +75,8 @@ OptionsWaiter.prototype.resetOptionsClick = function() {
  * @param {boolean} state
  */
 OptionsWaiter.prototype.switchChange = function(e, state) {
-    let el = e.target,
-        option = el.getAttribute("option");
+    const el = e.target;
+    const option = el.getAttribute("option");
 
     this.app.options[option] = state;
     localStorage.setItem("options", JSON.stringify(this.app.options));
@@ -90,8 +90,8 @@ OptionsWaiter.prototype.switchChange = function(e, state) {
  * @param {event} e
  */
 OptionsWaiter.prototype.numberChange = function(e) {
-    let el = e.target,
-        option = el.getAttribute("option");
+    const el = e.target;
+    const option = el.getAttribute("option");
 
     this.app.options[option] = parseInt(el.value, 10);
     localStorage.setItem("options", JSON.stringify(this.app.options));
@@ -105,8 +105,8 @@ OptionsWaiter.prototype.numberChange = function(e) {
  * @param {event} e
  */
 OptionsWaiter.prototype.selectChange = function(e) {
-    let el = e.target,
-        option = el.getAttribute("option");
+    const el = e.target;
+    const option = el.getAttribute("option");
 
     this.app.options[option] = el.value;
     localStorage.setItem("options", JSON.stringify(this.app.options));

+ 11 - 11
src/web/OutputWaiter.js

@@ -36,10 +36,10 @@ OutputWaiter.prototype.get = function() {
  * @param {number} duration - The length of time (ms) it took to generate the output
  */
 OutputWaiter.prototype.set = function(dataStr, type, duration) {
-    let outputText = document.getElementById("output-text"),
-        outputHtml = document.getElementById("output-html"),
-        outputHighlighter = document.getElementById("output-highlighter"),
-        inputHighlighter = document.getElementById("input-highlighter");
+    const outputText = document.getElementById("output-text");
+    const outputHtml = document.getElementById("output-html");
+    const outputHighlighter = document.getElementById("output-highlighter");
+    const inputHighlighter = document.getElementById("input-highlighter");
 
     if (type === "html") {
         outputText.style.display = "none";
@@ -103,11 +103,11 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) {
  * without wrapping or overflowing.
  */
 OutputWaiter.prototype.adjustWidth = function() {
-    let output         = document.getElementById("output"),
-        saveToFile     = document.getElementById("save-to-file"),
-        switchIO       = document.getElementById("switch"),
-        undoSwitch     = document.getElementById("undo-switch"),
-        maximiseOutput = document.getElementById("maximise-output");
+    const output         = document.getElementById("output");
+    const saveToFile     = document.getElementById("save-to-file");
+    const switchIO       = document.getElementById("switch");
+    const undoSwitch     = document.getElementById("undo-switch");
+    const maximiseOutput = document.getElementById("maximise-output");
 
     if (output.clientWidth < 680) {
         saveToFile.childNodes[1].nodeValue = "";
@@ -129,8 +129,8 @@ OutputWaiter.prototype.adjustWidth = function() {
  * Saves the current output to a file, downloaded as a URL octet stream.
  */
 OutputWaiter.prototype.saveClick = function() {
-    let data = Utils.toBase64(this.app.dishStr),
-        filename = window.prompt("Please enter a filename:", "download.dat");
+    const data = Utils.toBase64(this.app.dishStr);
+    const filename = window.prompt("Please enter a filename:", "download.dat");
 
     if (filename) {
         const el = document.createElement("a");

+ 7 - 6
src/web/RecipeWaiter.js

@@ -60,8 +60,8 @@ RecipeWaiter.prototype.initialiseOperationDragNDrop = function() {
     }.bind(this));
 
     Sortable.utils.on(recList, "touchend", function(e) {
-        let loc = e.changedTouches[0],
-            target = document.elementFromPoint(loc.clientX, loc.clientY);
+        const loc = e.changedTouches[0];
+        const target = document.elementFromPoint(loc.clientX, loc.clientY);
 
         this.removeIntent = !recList.contains(target);
     }.bind(this));
@@ -276,8 +276,9 @@ RecipeWaiter.prototype.operationChildDblclick = function(e) {
  * @returns {recipeConfig}
  */
 RecipeWaiter.prototype.getConfig = function() {
-    let config = [], ingredients, ingList, disabled, bp, item,
-        operations = document.querySelectorAll("#rec-list li.operation");
+    const config = [];
+    let ingredients, ingList, disabled, bp, item;
+    const operations = document.querySelectorAll("#rec-list li.operation");
 
     for (let i = 0; i < operations.length; i++) {
         ingredients = [];
@@ -402,8 +403,8 @@ RecipeWaiter.prototype.clearRecipe = function() {
  * @param {event} e
  */
 RecipeWaiter.prototype.dropdownToggleClick = function(e) {
-    let el = e.target,
-        button = el.parentNode.parentNode.previousSibling;
+    const el = e.target;
+    const button = el.parentNode.parentNode.previousSibling;
 
     button.innerHTML = el.textContent + " <span class='caret'></span>";
     this.ingChange();

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 15
src/web/SeasonalWaiter.js


+ 2 - 0
src/web/html/index.html

@@ -105,6 +105,7 @@
                     <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>
@@ -121,6 +122,7 @@
                     <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>

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
src/web/stylesheets/layout/_io.css


+ 4 - 4
test/TestRegister.js

@@ -38,17 +38,17 @@ import Chef from "../src/core/Chef.js";
     TestRegister.prototype.runTests = function() {
         return Promise.all(
             this.tests.map(function(test, i) {
-                let chef = new Chef();
+                const chef = new Chef();
 
-                return Promise.resolve(chef.bake(
+                return chef.bake(
                     test.input,
                     test.recipeConfig,
                     {},
                     0,
                     false
-                ))
+                )
                 .then(function(result) {
-                    let ret = {
+                    const ret = {
                         test: test,
                         status: null,
                         output: null,

+ 8 - 8
test/index.js

@@ -19,10 +19,10 @@ import "./tests/operations/FlowControl.js";
 import "./tests/operations/MorseCode.js";
 import "./tests/operations/StrUtils.js";
 
-let allTestsPassing = true,
-    testStatusCounts = {
-        total: 0,
-    };
+let allTestsPassing = true;
+const testStatusCounts = {
+    total: 0,
+};
 
 
 /**
@@ -32,7 +32,7 @@ let allTestsPassing = true,
  * @returns {string}
  */
 function statusToIcon(status) {
-    let icons = {
+    const icons = {
         erroring: "🔥",
         failing: "❌",
         passing: "✔️️",
@@ -48,7 +48,7 @@ function statusToIcon(status) {
  */
 function handleTestResult(testResult) {
     allTestsPassing = allTestsPassing && testResult.status === "passing";
-    let newCount = (testStatusCounts[testResult.status] || 0) + 1;
+    const newCount = (testStatusCounts[testResult.status] || 0) + 1;
     testStatusCounts[testResult.status] = newCount;
     testStatusCounts.total += 1;
 
@@ -83,8 +83,8 @@ TestRegister.runTests()
 
         console.log("\n");
 
-        for (let testStatus in testStatusCounts) {
-            let count = testStatusCounts[testStatus];
+        for (const testStatus in testStatusCounts) {
+            const count = testStatusCounts[testStatus];
             if (count > 0) {
                 console.log(testStatus.toUpperCase(), count);
             }

+ 133 - 0
test/tests/operations/FlowControl.js

@@ -66,6 +66,62 @@ TestRegister.addTests([
             {"op":"To Base64", "args":["A-Za-z0-9+/="]}
         ]
     },
+    {
+        name: "Jump: skips 0",
+        input: [
+            "should be changed",
+        ].join("\n"),
+        expectedOutput: [
+            "should be changed was changed",
+        ].join("\n"),
+        recipeConfig: [
+            {
+                op: "Jump",
+                args: [0, 10],
+            },
+            {
+                op: "Find / Replace",
+                args: [
+                    {
+                        "option": "Regex",
+                        "string": "should be changed"
+                    },
+                    "should be changed was changed",
+                    true,
+                    true,
+                    true,
+                ],
+            },
+        ],
+    },
+    {
+        name: "Jump: skips 1",
+        input: [
+            "shouldnt be changed",
+        ].join("\n"),
+        expectedOutput: [
+            "shouldnt be changed",
+        ].join("\n"),
+        recipeConfig: [
+            {
+                op: "Jump",
+                args: [1, 10],
+            },
+            {
+                op: "Find / Replace",
+                args: [
+                    {
+                        "option": "Regex",
+                        "string": "shouldnt be changed"
+                    },
+                    "shouldnt be changed was changed",
+                    true,
+                    true,
+                    true,
+                ],
+            },
+        ],
+    },
     {
         name: "Conditional Jump: Skips 0",
         input: [
@@ -141,4 +197,81 @@ TestRegister.addTests([
             }
         ]
     },
+    {
+        name: "Conditional Jump: Skips 1",
+        input: [
+            "match",
+            "should not be changed",
+            "should be changed",
+        ].join("\n"),
+        expectedOutput: [
+            "match",
+            "should not be changed",
+            "should be changed was changed"
+        ].join("\n"),
+        recipeConfig: [
+            {
+                op: "Conditional Jump",
+                args: ["match", 1, 10],
+            },
+            {
+                op: "Find / Replace",
+                args: [
+                    {
+                        "option": "Regex",
+                        "string": "should not be changed"
+                    },
+                    "should not be changed was changed",
+                    true,
+                    true,
+                    true,
+                ],
+            },
+            {
+                op: "Find / Replace",
+                args: [
+                    {
+                        "option": "Regex",
+                        "string": "should be changed"
+                    },
+                    "should be changed was changed",
+                    true,
+                    true,
+                    true,
+                ],
+            },
+        ],
+    },
+    {
+        name: "Conditional Jump: Skips negatively",
+        input: [
+            "match",
+        ].join("\n"),
+        expectedOutput: [
+            "replaced",
+        ].join("\n"),
+        recipeConfig: [
+            {
+                op: "Jump",
+                args: [1],
+            },
+            {
+                op: "Find / Replace",
+                args: [
+                    {
+                        "option": "Regex",
+                        "string": "match"
+                    },
+                    "replaced",
+                    true,
+                    true,
+                    true,
+                ],
+            },
+            {
+                op: "Conditional Jump",
+                args: ["match", -2, 10],
+            },
+        ],
+    },
 ]);

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.