Browse Source

ESM: Added remaining Base64 ops and created a Base64 library. Added the prefer-const eslint rule.

n1474335 7 years ago
parent
commit
041cd9fb8e

+ 2 - 1
.eslintignore

@@ -1 +1,2 @@
-src/core/vendor/**
+src/core/vendor/**
+src/core/operations/legacy/**

+ 3 - 2
.eslintrc.json

@@ -1,6 +1,6 @@
 {
     "parserOptions": {
-        "ecmaVersion": 8,
+        "ecmaVersion": 9,
         "ecmaFeatures": {
             "impliedStrict": true
         },
@@ -84,7 +84,8 @@
         "no-whitespace-before-property": "error",
         "operator-linebreak": ["error", "after"],
         "space-in-parens": "error",
-        "no-var": "error"
+        "no-var": "error",
+        "prefer-const": "error"
     },
     "globals": {
         "$": false,

+ 1 - 1
Gruntfile.js

@@ -117,7 +117,7 @@ module.exports = function (grunt) {
      * Generates an entry list for all the modules.
      */
     function listEntryModules() {
-        let entryModules = {};
+        const entryModules = {};
 
         glob.sync("./src/core/config/modules/*.mjs").forEach(file => {
             const basename = path.basename(file);

+ 3 - 3
src/core/Chef.mjs

@@ -121,9 +121,9 @@ class Chef {
     silentBake(recipeConfig) {
         log.debug("Running silent bake");
 
-        let startTime = new Date().getTime(),
-            recipe    = new Recipe(recipeConfig),
-            dish      = new Dish("", Dish.STRING);
+        const startTime = new Date().getTime(),
+            recipe = new Recipe(recipeConfig),
+            dish = new Dish("", Dish.STRING);
 
         try {
             recipe.execute(dish);

+ 1 - 1
src/core/ChefWorker.js

@@ -132,7 +132,7 @@ function silentBake(data) {
  */
 function loadRequiredModules(recipeConfig) {
     recipeConfig.forEach(op => {
-        let module = self.OperationConfig[op.op].module;
+        const module = self.OperationConfig[op.op].module;
 
         if (!OpModules.hasOwnProperty(module)) {
             log.info("Loading module " + module);

+ 8 - 8
src/core/FlowControl.js

@@ -23,7 +23,7 @@ const FlowControl = {
      * @returns {Object} The updated state of the recipe.
      */
     runFork: async function(state) {
-        let opList       = state.opList,
+        const opList     = state.opList,
             inputType    = opList[state.progress].inputType,
             outputType   = opList[state.progress].outputType,
             input        = state.dish.get(inputType),
@@ -31,8 +31,8 @@ const FlowControl = {
             splitDelim   = ings[0],
             mergeDelim   = ings[1],
             ignoreErrors = ings[2],
-            subOpList    = [],
-            inputs       = [],
+            subOpList    = [];
+        let inputs       = [],
             i;
 
         if (input)
@@ -48,8 +48,8 @@ const FlowControl = {
             }
         }
 
-        let recipe = new Recipe(),
-            output = "",
+        const recipe = new Recipe();
+        let output = "",
             progress = 0;
 
         state.forkOffset += state.progress + 1;
@@ -223,7 +223,7 @@ const FlowControl = {
         }
 
         if (regexStr !== "") {
-            let strMatch = dish.get(Dish.STRING).search(regexStr) > -1;
+            const strMatch = dish.get(Dish.STRING).search(regexStr) > -1;
             if (!invert && strMatch || invert && !strMatch) {
                 state.progress = jmpIndex;
                 state.numJumps++;
@@ -274,9 +274,9 @@ const FlowControl = {
      */
     _getLabelIndex: function(name, state) {
         for (let o = 0; o < state.opList.length; o++) {
-            let operation = state.opList[o];
+            const operation = state.opList[o];
             if (operation.name === "Label"){
-                let ings = operation.ingValues;
+                const ings = operation.ingValues;
                 if (name === ings[0]) {
                     return o;
                 }

+ 1 - 1
src/core/Recipe.mjs

@@ -226,7 +226,7 @@ class Recipe  {
         const highlights = [];
 
         for (let i = 0; i < this.opList.length; i++) {
-            let op = this.opList[i];
+            const op = this.opList[i];
             if (op.disabled) continue;
 
             // If any breakpoints are set, do not attempt to highlight

+ 11 - 123
src/core/Utils.mjs

@@ -6,6 +6,7 @@
 
 import utf8 from "utf8";
 import moment from "moment-timezone";
+import {fromBase64} from "./lib/Base64";
 
 
 /**
@@ -270,7 +271,7 @@ class Utils {
             if (i < alphStr.length - 2 &&
                 alphStr[i+1] === "-" &&
                 alphStr[i] !== "\\") {
-                let start = Utils.ord(alphStr[i]),
+                const start = Utils.ord(alphStr[i]),
                     end = Utils.ord(alphStr[i+2]);
 
                 for (let j = start; j <= end; j++) {
@@ -313,7 +314,7 @@ class Utils {
             case "hex":
                 return Utils.fromHex(str);
             case "base64":
-                return Utils.fromBase64(str, null, "byteArray");
+                return fromBase64(str, null, "byteArray");
             case "utf8":
                 return Utils.strToUtf8ByteArray(str);
             case "latin1":
@@ -346,7 +347,7 @@ class Utils {
             case "hex":
                 return Utils.byteArrayToChars(Utils.fromHex(str));
             case "base64":
-                return Utils.byteArrayToChars(Utils.fromBase64(str, null, "byteArray"));
+                return Utils.byteArrayToChars(fromBase64(str, null, "byteArray"));
             case "utf8":
                 return utf8.encode(str);
             case "latin1":
@@ -518,118 +519,6 @@ class Utils {
     }
 
 
-    /**
-     * Base64's the input byte array using the given alphabet, returning a string.
-     *
-     * @param {byteArray|Uint8Array|string} data
-     * @param {string} [alphabet="A-Za-z0-9+/="]
-     * @returns {string}
-     *
-     * @example
-     * // returns "SGVsbG8="
-     * Utils.toBase64([72, 101, 108, 108, 111]);
-     *
-     * // returns "SGVsbG8="
-     * Utils.toBase64("Hello");
-     */
-    static toBase64(data, alphabet="A-Za-z0-9+/=") {
-        if (!data) return "";
-        if (typeof data == "string") {
-            data = Utils.strToByteArray(data);
-        }
-
-        alphabet = Utils.expandAlphRange(alphabet).join("");
-
-        let output = "",
-            chr1, chr2, chr3,
-            enc1, enc2, enc3, enc4,
-            i = 0;
-
-        while (i < data.length) {
-            chr1 = data[i++];
-            chr2 = data[i++];
-            chr3 = data[i++];
-
-            enc1 = chr1 >> 2;
-            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
-            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
-            enc4 = chr3 & 63;
-
-            if (isNaN(chr2)) {
-                enc3 = enc4 = 64;
-            } else if (isNaN(chr3)) {
-                enc4 = 64;
-            }
-
-            output += alphabet.charAt(enc1) + alphabet.charAt(enc2) +
-                alphabet.charAt(enc3) + alphabet.charAt(enc4);
-        }
-
-        return output;
-    }
-
-
-    /**
-     * UnBase64's the input string using the given alphabet, returning a byte array.
-     *
-     * @param {byteArray} data
-     * @param {string} [alphabet="A-Za-z0-9+/="]
-     * @param {string} [returnType="string"] - Either "string" or "byteArray"
-     * @param {boolean} [removeNonAlphChars=true]
-     * @returns {byteArray}
-     *
-     * @example
-     * // returns "Hello"
-     * Utils.fromBase64("SGVsbG8=");
-     *
-     * // returns [72, 101, 108, 108, 111]
-     * Utils.fromBase64("SGVsbG8=", null, "byteArray");
-     */
-    static fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) {
-        if (!data) {
-            return returnType === "string" ? "" : [];
-        }
-
-        alphabet = Utils.expandAlphRange(alphabet).join("");
-
-        let output = [],
-            chr1, chr2, chr3,
-            enc1, enc2, enc3, enc4,
-            i = 0;
-
-        if (removeNonAlphChars) {
-            const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
-            data = data.replace(re, "");
-        }
-
-        while (i < data.length) {
-            enc1 = alphabet.indexOf(data.charAt(i++));
-            enc2 = alphabet.indexOf(data.charAt(i++) || "=");
-            enc3 = alphabet.indexOf(data.charAt(i++) || "=");
-            enc4 = alphabet.indexOf(data.charAt(i++) || "=");
-
-            enc2 = enc2 === -1 ? 64 : enc2;
-            enc3 = enc3 === -1 ? 64 : enc3;
-            enc4 = enc4 === -1 ? 64 : enc4;
-
-            chr1 = (enc1 << 2) | (enc2 >> 4);
-            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
-            chr3 = ((enc3 & 3) << 6) | enc4;
-
-            output.push(chr1);
-
-            if (enc3 !== 64) {
-                output.push(chr2);
-            }
-            if (enc4 !== 64) {
-                output.push(chr3);
-            }
-        }
-
-        return returnType === "string" ? Utils.byteArrayToUtf8(output) : output;
-    }
-
-
     /**
      * Convert a byte array into a hex string.
      *
@@ -734,8 +623,8 @@ class Utils {
             ignoreNext = false,
             inString = false,
             cell = "",
-            line = [],
-            lines = [];
+            line = [];
+        const lines = [];
 
         for (let i = 0; i < data.length; i++) {
             b = data[i];
@@ -952,10 +841,9 @@ class Utils {
 
         // Parse bespoke recipe format
         recipe = recipe.replace(/\n/g, "");
-        let m,
-            recipeRegex = /([^(]+)\(((?:'[^'\\]*(?:\\.[^'\\]*)*'|[^)/'])*)(\/[^)]+)?\)/g,
-            recipeConfig = [],
-            args;
+        let m, args;
+        const recipeRegex = /([^(]+)\(((?:'[^'\\]*(?:\\.[^'\\]*)*'|[^)/'])*)(\/[^)]+)?\)/g,
+            recipeConfig = [];
 
         while ((m = recipeRegex.exec(recipe))) {
             // Translate strings in args back to double-quotes
@@ -966,7 +854,7 @@ class Utils {
                 .replace(/\\'/g, "'"); // Unescape single quotes
             args = "[" + args + "]";
 
-            let op = {
+            const op = {
                 op: m[1].replace(/_/g, " "),
                 args: JSON.parse(args)
             };
@@ -1252,7 +1140,7 @@ export default Utils;
  * ["One", "Two", "Three", "One"].unique();
  */
 Array.prototype.unique = function() {
-    let u = {}, a = [];
+    const u = {}, a = [];
     for (let i = 0, l = this.length; i < l; i++) {
         if (u.hasOwnProperty(this[i])) {
             continue;

+ 324 - 324
src/core/config/Categories.js

@@ -21,340 +21,340 @@ const Categories = [
     {
         name: "Favourites",
         ops: []
-    }/*,
+    },
     {
         name: "Data format",
         ops: [
-            "To Hexdump",
-            "From Hexdump",
-            "To Hex",
-            "From Hex",
-            "To Charcode",
-            "From Charcode",
-            "To Decimal",
-            "From Decimal",
-            "To Binary",
-            "From Binary",
-            "To Octal",
-            "From Octal",
+    //         "To Hexdump",
+    //         "From Hexdump",
+    //         "To Hex",
+    //         "From Hex",
+    //         "To Charcode",
+    //         "From Charcode",
+    //         "To Decimal",
+    //         "From Decimal",
+    //         "To Binary",
+    //         "From Binary",
+    //         "To Octal",
+    //         "From Octal",
             "To Base64",
             "From Base64",
             "Show Base64 offsets",
             "To Base32",
             "From Base32",
-            "To Base58",
-            "From Base58",
-            "To Base",
-            "From Base",
-            "To BCD",
-            "From BCD",
-            "To HTML Entity",
-            "From HTML Entity",
-            "URL Encode",
-            "URL Decode",
-            "Escape Unicode Characters",
-            "Unescape Unicode Characters",
-            "To Quoted Printable",
-            "From Quoted Printable",
-            "To Punycode",
-            "From Punycode",
-            "To Hex Content",
-            "From Hex Content",
-            "PEM to Hex",
-            "Hex to PEM",
-            "Parse ASN.1 hex string",
-            "Change IP format",
-            "Encode text",
-            "Decode text",
-            "Swap endianness",
-        ]
-    },
-    {
-        name: "Encryption / Encoding",
-        ops: [
-            "AES Encrypt",
-            "AES Decrypt",
-            "Blowfish Encrypt",
-            "Blowfish Decrypt",
-            "DES Encrypt",
-            "DES Decrypt",
-            "Triple DES Encrypt",
-            "Triple DES Decrypt",
-            "RC2 Encrypt",
-            "RC2 Decrypt",
-            "RC4",
-            "RC4 Drop",
-            "ROT13",
-            "ROT47",
-            "XOR",
-            "XOR Brute Force",
-            "Vigenère Encode",
-            "Vigenère Decode",
-            "To Morse Code",
-            "From Morse Code",
-            "Bifid Cipher Encode",
-            "Bifid Cipher Decode",
-            "Affine Cipher Encode",
-            "Affine Cipher Decode",
-            "Atbash Cipher",
-            "Substitute",
-            "Derive PBKDF2 key",
-            "Derive EVP key",
-            "Bcrypt",
-            "Scrypt",
-            "Pseudo-Random Number Generator",
-        ]
-    },
-    {
-        name: "Public Key",
-        ops: [
-            "Parse X.509 certificate",
-            "Parse ASN.1 hex string",
-            "PEM to Hex",
-            "Hex to PEM",
-            "Hex to Object Identifier",
-            "Object Identifier to Hex",
-        ]
-    },
-    {
-        name: "Arithmetic / Logic",
-        ops: [
-            "XOR",
-            "XOR Brute Force",
-            "OR",
-            "NOT",
-            "AND",
-            "ADD",
-            "SUB",
-            "Sum",
-            "Subtract",
-            "Multiply",
-            "Divide",
-            "Mean",
-            "Median",
-            "Standard Deviation",
-            "Bit shift left",
-            "Bit shift right",
-            "Rotate left",
-            "Rotate right",
-            "ROT13",
-        ]
-    },
-    {
-        name: "Networking",
-        ops: [
-            "HTTP request",
-            "Strip HTTP headers",
-            "Parse User Agent",
-            "Parse IP range",
-            "Parse IPv6 address",
-            "Parse IPv4 header",
-            "Parse URI",
-            "URL Encode",
-            "URL Decode",
-            "Format MAC addresses",
-            "Change IP format",
-            "Group IP addresses",
-            "Encode NetBIOS Name",
-            "Decode NetBIOS Name",
-        ]
-    },
-    {
-        name: "Language",
-        ops: [
-            "Encode text",
-            "Decode text",
-            "Unescape Unicode Characters",
-        ]
-    },
-    {
-        name: "Utils",
-        ops: [
-            "Diff",
-            "Remove whitespace",
-            "Remove null bytes",
-            "To Upper case",
-            "To Lower case",
-            "Add line numbers",
-            "Remove line numbers",
-            "Reverse",
-            "Sort",
-            "Unique",
-            "Split",
-            "Filter",
-            "Head",
-            "Tail",
-            "Count occurrences",
-            "Expand alphabet range",
-            "Drop bytes",
-            "Take bytes",
-            "Pad lines",
-            "Find / Replace",
-            "Regular expression",
-            "Offset checker",
-            "Hamming Distance",
-            "Convert distance",
-            "Convert area",
-            "Convert mass",
-            "Convert speed",
-            "Convert data units",
-            "Parse UNIX file permissions",
-            "Swap endianness",
-            "Parse colour code",
-            "Escape string",
-            "Unescape string",
-            "Pseudo-Random Number Generator",
-            "Sleep",
-        ]
-    },
-    {
-        name: "Date / Time",
-        ops: [
-            "Parse DateTime",
-            "Translate DateTime Format",
-            "From UNIX Timestamp",
-            "To UNIX Timestamp",
-            "Windows Filetime to UNIX Timestamp",
-            "UNIX Timestamp to Windows Filetime",
-            "Extract dates",
-            "Sleep",
-        ]
-    },
-    {
-        name: "Extractors",
-        ops: [
-            "Strings",
-            "Extract IP addresses",
-            "Extract email addresses",
-            "Extract MAC addresses",
-            "Extract URLs",
-            "Extract domains",
-            "Extract file paths",
-            "Extract dates",
-            "Regular expression",
-            "XPath expression",
-            "JPath expression",
-            "CSS selector",
-            "Extract EXIF",
-        ]
-    },
-    {
-        name: "Compression",
-        ops: [
-            "Raw Deflate",
-            "Raw Inflate",
-            "Zlib Deflate",
-            "Zlib Inflate",
-            "Gzip",
-            "Gunzip",
-            "Zip",
-            "Unzip",
-            "Bzip2 Decompress",
-            "Tar",
-            "Untar",
-        ]
-    },
-    {
-        name: "Hashing",
-        ops: [
-            "Analyse hash",
-            "Generate all hashes",
-            "MD2",
-            "MD4",
-            "MD5",
-            "MD6",
-            "SHA0",
-            "SHA1",
-            "SHA2",
-            "SHA3",
-            "Keccak",
-            "Shake",
-            "RIPEMD",
-            "HAS-160",
-            "Whirlpool",
-            "Snefru",
-            "SSDEEP",
-            "CTPH",
-            "Compare SSDEEP hashes",
-            "Compare CTPH hashes",
-            "HMAC",
-            "Bcrypt",
-            "Bcrypt compare",
-            "Bcrypt parse",
-            "Scrypt",
-            "Fletcher-8 Checksum",
-            "Fletcher-16 Checksum",
-            "Fletcher-32 Checksum",
-            "Fletcher-64 Checksum",
-            "Adler-32 Checksum",
-            "CRC-16 Checksum",
-            "CRC-32 Checksum",
-            "TCP/IP Checksum",
+    //         "To Base58",
+    //         "From Base58",
+    //         "To Base",
+    //         "From Base",
+    //         "To BCD",
+    //         "From BCD",
+    //         "To HTML Entity",
+    //         "From HTML Entity",
+    //         "URL Encode",
+    //         "URL Decode",
+    //         "Escape Unicode Characters",
+    //         "Unescape Unicode Characters",
+    //         "To Quoted Printable",
+    //         "From Quoted Printable",
+    //         "To Punycode",
+    //         "From Punycode",
+    //         "To Hex Content",
+    //         "From Hex Content",
+    //         "PEM to Hex",
+    //         "Hex to PEM",
+    //         "Parse ASN.1 hex string",
+    //         "Change IP format",
+    //         "Encode text",
+    //         "Decode text",
+    //         "Swap endianness",
+    //     ]
+    // },
+    // {
+    //     name: "Encryption / Encoding",
+    //     ops: [
+    //         "AES Encrypt",
+    //         "AES Decrypt",
+    //         "Blowfish Encrypt",
+    //         "Blowfish Decrypt",
+    //         "DES Encrypt",
+    //         "DES Decrypt",
+    //         "Triple DES Encrypt",
+    //         "Triple DES Decrypt",
+    //         "RC2 Encrypt",
+    //         "RC2 Decrypt",
+    //         "RC4",
+    //         "RC4 Drop",
+    //         "ROT13",
+    //         "ROT47",
+    //         "XOR",
+    //         "XOR Brute Force",
+    //         "Vigenère Encode",
+    //         "Vigenère Decode",
+    //         "To Morse Code",
+    //         "From Morse Code",
+    //         "Bifid Cipher Encode",
+    //         "Bifid Cipher Decode",
+    //         "Affine Cipher Encode",
+    //         "Affine Cipher Decode",
+    //         "Atbash Cipher",
+    //         "Substitute",
+    //         "Derive PBKDF2 key",
+    //         "Derive EVP key",
+    //         "Bcrypt",
+    //         "Scrypt",
+    //         "Pseudo-Random Number Generator",
         ]
     },
-    {
-        name: "Code tidy",
-        ops: [
-            "Syntax highlighter",
-            "Generic Code Beautify",
-            "JavaScript Parser",
-            "JavaScript Beautify",
-            "JavaScript Minify",
-            "JSON Beautify",
-            "JSON Minify",
-            "XML Beautify",
-            "XML Minify",
-            "SQL Beautify",
-            "SQL Minify",
-            "CSS Beautify",
-            "CSS Minify",
-            "XPath expression",
-            "JPath expression",
-            "CSS selector",
-            "PHP Deserialize",
-            "Microsoft Script Decoder",
-            "Strip HTML tags",
-            "Diff",
-            "To Snake case",
-            "To Camel case",
-            "To Kebab case",
-            "BSON serialise",
-            "BSON deserialise",
-        ]
-    },
-    {
-        name: "Other",
-        ops: [
-            "Entropy",
-            "Frequency distribution",
-            "Chi Square",
-            "Detect File Type",
-            "Scan for Embedded Files",
-            "Disassemble x86",
-            "Pseudo-Random Number Generator",
-            "Generate UUID",
-            "Generate TOTP",
-            "Generate HOTP",
-            "Render Image",
-            "Remove EXIF",
-            "Extract EXIF",
-            "Numberwang",
-            "XKCD Random Number",
-        ]
-    },
-    {
-        name: "Flow control",
-        ops: [
-            "Fork",
-            "Merge",
-            "Register",
-            "Label",
-            "Jump",
-            "Conditional Jump",
-            "Return",
-            "Comment"
-        ]
-    },*/
+    // {
+    //     name: "Public Key",
+    //     ops: [
+    //         "Parse X.509 certificate",
+    //         "Parse ASN.1 hex string",
+    //         "PEM to Hex",
+    //         "Hex to PEM",
+    //         "Hex to Object Identifier",
+    //         "Object Identifier to Hex",
+    //     ]
+    // },
+    // {
+    //     name: "Arithmetic / Logic",
+    //     ops: [
+    //         "XOR",
+    //         "XOR Brute Force",
+    //         "OR",
+    //         "NOT",
+    //         "AND",
+    //         "ADD",
+    //         "SUB",
+    //         "Sum",
+    //         "Subtract",
+    //         "Multiply",
+    //         "Divide",
+    //         "Mean",
+    //         "Median",
+    //         "Standard Deviation",
+    //         "Bit shift left",
+    //         "Bit shift right",
+    //         "Rotate left",
+    //         "Rotate right",
+    //         "ROT13",
+    //     ]
+    // },
+    // {
+    //     name: "Networking",
+    //     ops: [
+    //         "HTTP request",
+    //         "Strip HTTP headers",
+    //         "Parse User Agent",
+    //         "Parse IP range",
+    //         "Parse IPv6 address",
+    //         "Parse IPv4 header",
+    //         "Parse URI",
+    //         "URL Encode",
+    //         "URL Decode",
+    //         "Format MAC addresses",
+    //         "Change IP format",
+    //         "Group IP addresses",
+    //         "Encode NetBIOS Name",
+    //         "Decode NetBIOS Name",
+    //     ]
+    // },
+    // {
+    //     name: "Language",
+    //     ops: [
+    //         "Encode text",
+    //         "Decode text",
+    //         "Unescape Unicode Characters",
+    //     ]
+    // },
+    // {
+    //     name: "Utils",
+    //     ops: [
+    //         "Diff",
+    //         "Remove whitespace",
+    //         "Remove null bytes",
+    //         "To Upper case",
+    //         "To Lower case",
+    //         "Add line numbers",
+    //         "Remove line numbers",
+    //         "Reverse",
+    //         "Sort",
+    //         "Unique",
+    //         "Split",
+    //         "Filter",
+    //         "Head",
+    //         "Tail",
+    //         "Count occurrences",
+    //         "Expand alphabet range",
+    //         "Drop bytes",
+    //         "Take bytes",
+    //         "Pad lines",
+    //         "Find / Replace",
+    //         "Regular expression",
+    //         "Offset checker",
+    //         "Hamming Distance",
+    //         "Convert distance",
+    //         "Convert area",
+    //         "Convert mass",
+    //         "Convert speed",
+    //         "Convert data units",
+    //         "Parse UNIX file permissions",
+    //         "Swap endianness",
+    //         "Parse colour code",
+    //         "Escape string",
+    //         "Unescape string",
+    //         "Pseudo-Random Number Generator",
+    //         "Sleep",
+    //     ]
+    // },
+    // {
+    //     name: "Date / Time",
+    //     ops: [
+    //         "Parse DateTime",
+    //         "Translate DateTime Format",
+    //         "From UNIX Timestamp",
+    //         "To UNIX Timestamp",
+    //         "Windows Filetime to UNIX Timestamp",
+    //         "UNIX Timestamp to Windows Filetime",
+    //         "Extract dates",
+    //         "Sleep",
+    //     ]
+    // },
+    // {
+    //     name: "Extractors",
+    //     ops: [
+    //         "Strings",
+    //         "Extract IP addresses",
+    //         "Extract email addresses",
+    //         "Extract MAC addresses",
+    //         "Extract URLs",
+    //         "Extract domains",
+    //         "Extract file paths",
+    //         "Extract dates",
+    //         "Regular expression",
+    //         "XPath expression",
+    //         "JPath expression",
+    //         "CSS selector",
+    //         "Extract EXIF",
+    //     ]
+    // },
+    // {
+    //     name: "Compression",
+    //     ops: [
+    //         "Raw Deflate",
+    //         "Raw Inflate",
+    //         "Zlib Deflate",
+    //         "Zlib Inflate",
+    //         "Gzip",
+    //         "Gunzip",
+    //         "Zip",
+    //         "Unzip",
+    //         "Bzip2 Decompress",
+    //         "Tar",
+    //         "Untar",
+    //     ]
+    // },
+    // {
+    //     name: "Hashing",
+    //     ops: [
+    //         "Analyse hash",
+    //         "Generate all hashes",
+    //         "MD2",
+    //         "MD4",
+    //         "MD5",
+    //         "MD6",
+    //         "SHA0",
+    //         "SHA1",
+    //         "SHA2",
+    //         "SHA3",
+    //         "Keccak",
+    //         "Shake",
+    //         "RIPEMD",
+    //         "HAS-160",
+    //         "Whirlpool",
+    //         "Snefru",
+    //         "SSDEEP",
+    //         "CTPH",
+    //         "Compare SSDEEP hashes",
+    //         "Compare CTPH hashes",
+    //         "HMAC",
+    //         "Bcrypt",
+    //         "Bcrypt compare",
+    //         "Bcrypt parse",
+    //         "Scrypt",
+    //         "Fletcher-8 Checksum",
+    //         "Fletcher-16 Checksum",
+    //         "Fletcher-32 Checksum",
+    //         "Fletcher-64 Checksum",
+    //         "Adler-32 Checksum",
+    //         "CRC-16 Checksum",
+    //         "CRC-32 Checksum",
+    //         "TCP/IP Checksum",
+    //     ]
+    // },
+    // {
+    //     name: "Code tidy",
+    //     ops: [
+    //         "Syntax highlighter",
+    //         "Generic Code Beautify",
+    //         "JavaScript Parser",
+    //         "JavaScript Beautify",
+    //         "JavaScript Minify",
+    //         "JSON Beautify",
+    //         "JSON Minify",
+    //         "XML Beautify",
+    //         "XML Minify",
+    //         "SQL Beautify",
+    //         "SQL Minify",
+    //         "CSS Beautify",
+    //         "CSS Minify",
+    //         "XPath expression",
+    //         "JPath expression",
+    //         "CSS selector",
+    //         "PHP Deserialize",
+    //         "Microsoft Script Decoder",
+    //         "Strip HTML tags",
+    //         "Diff",
+    //         "To Snake case",
+    //         "To Camel case",
+    //         "To Kebab case",
+    //         "BSON serialise",
+    //         "BSON deserialise",
+    //     ]
+    // },
+    // {
+    //     name: "Other",
+    //     ops: [
+    //         "Entropy",
+    //         "Frequency distribution",
+    //         "Chi Square",
+    //         "Detect File Type",
+    //         "Scan for Embedded Files",
+    //         "Disassemble x86",
+    //         "Pseudo-Random Number Generator",
+    //         "Generate UUID",
+    //         "Generate TOTP",
+    //         "Generate HOTP",
+    //         "Render Image",
+    //         "Remove EXIF",
+    //         "Extract EXIF",
+    //         "Numberwang",
+    //         "XKCD Random Number",
+    //     ]
+    // },
+    // {
+    //     name: "Flow control",
+    //     ops: [
+    //         "Fork",
+    //         "Merge",
+    //         "Register",
+    //         "Label",
+    //         "Jump",
+    //         "Conditional Jump",
+    //         "Return",
+    //         "Comment"
+    //     ]
+    // },
 ];
 
 export default Categories;

+ 63 - 11
src/core/config/OperationConfig.json

@@ -1,9 +1,28 @@
 {
-    "To Base64": {
+    "From Base32": {
+        "module": "Default",
+        "description": "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.",
+        "inputType": "string",
+        "outputType": "byteArray",
+        "flowControl": false,
+        "args": [
+            {
+                "name": "Alphabet",
+                "type": "binaryString",
+                "value": "A-Z2-7="
+            },
+            {
+                "name": "Remove non-alphabet chars",
+                "type": "boolean",
+                "value": true
+            }
+        ]
+    },
+    "From Base64": {
         "module": "Default",
         "description": "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation decodes data from an ASCII Base64 string back into its raw format.<br><br>e.g. <code>aGVsbG8=</code> becomes <code>hello</code>",
-        "inputType": "ArrayBuffer",
-        "outputType": "string",
+        "inputType": "string",
+        "outputType": "byteArray",
         "flowControl": false,
         "args": [
             {
@@ -63,14 +82,52 @@
                         "value": "./0-9A-Za-z"
                     }
                 ]
+            },
+            {
+                "name": "Remove non-alphabet chars",
+                "type": "boolean",
+                "value": true
             }
         ]
     },
-    "From Base64": {
+    "Show Base64 offsets": {
+        "module": "Default",
+        "description": "When a string is within a block of data and the whole block is Base64'd, the string itself could be represented in Base64 in three distinct ways depending on its offset within the block.<br><br>This operation shows all possible offsets for a given string so that each possible encoding can be considered.",
+        "inputType": "byteArray",
+        "outputType": "html",
+        "flowControl": false,
+        "args": [
+            {
+                "name": "Alphabet",
+                "type": "binaryString",
+                "value": "A-Za-z0-9+/="
+            },
+            {
+                "name": "Show variable chars and padding",
+                "type": "boolean",
+                "value": true
+            }
+        ]
+    },
+    "To Base32": {
+        "module": "Default",
+        "description": "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.",
+        "inputType": "byteArray",
+        "outputType": "string",
+        "flowControl": false,
+        "args": [
+            {
+                "name": "Alphabet",
+                "type": "binaryString",
+                "value": "A-Z2-7="
+            }
+        ]
+    },
+    "To Base64": {
         "module": "Default",
         "description": "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation decodes data from an ASCII Base64 string back into its raw format.<br><br>e.g. <code>aGVsbG8=</code> becomes <code>hello</code>",
-        "inputType": "string",
-        "outputType": "byteArray",
+        "inputType": "ArrayBuffer",
+        "outputType": "string",
         "flowControl": false,
         "args": [
             {
@@ -130,11 +187,6 @@
                         "value": "./0-9A-Za-z"
                     }
                 ]
-            },
-            {
-                "name": "Remove non-alphabet chars",
-                "type": "boolean",
-                "value": true
             }
         ]
     }

+ 13 - 13
src/core/config/generateConfig.mjs

@@ -14,7 +14,7 @@
 import path from "path";
 import fs  from "fs";
 import process from "process";
-import OpIndex from "../operations/index";
+import * as Ops from "../operations/index";
 
 const dir = path.join(process.cwd() + "/src/core/config/");
 if (!fs.existsSync(dir)) {
@@ -25,14 +25,14 @@ if (!fs.existsSync(dir)) {
 }
 
 
-let operationConfig = {},
+const operationConfig = {},
     modules = {};
 
 /**
  * Generate operation config and module lists.
  */
-OpIndex.forEach(opObj => {
-    const op = new opObj();
+for (const opObj in Ops) {
+    const op = new Ops[opObj]();
 
     operationConfig[op.name] = {
         module: op.module,
@@ -45,8 +45,8 @@ OpIndex.forEach(opObj => {
 
     if (!modules.hasOwnProperty(op.module))
         modules[op.module] = {};
-    modules[op.module][op.name] = op.name.replace(/\s/g, "");
-});
+    modules[op.module][op.name] = opObj;
+}
 
 
 /**
@@ -67,7 +67,7 @@ fs.writeFile(
 /**
  * Write modules.
  */
-for (let module in modules) {
+for (const module in modules) {
     let code = `/**
 * THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/generateConfig.mjs
 *
@@ -77,17 +77,17 @@ for (let module in modules) {
 */
 `;
 
-    for (let opName in modules[module]) {
+    for (const opName in modules[module]) {
         const objName = modules[module][opName];
         code += `import ${objName} from "../../operations/${objName}";\n`;
     }
 
     code += `
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
+const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
 
 OpModules.${module} = {
 `;
-    for (let opName in modules[module]) {
+    for (const opName in modules[module]) {
         const objName = modules[module][opName];
         code += `    "${opName}": ${objName},\n`;
     }
@@ -123,18 +123,18 @@ let opModulesCode = `/**
 */
 `;
 
-for (let module in modules) {
+for (const module in modules) {
     opModulesCode += `import ${module}Module from "./${module}";\n`;
 }
 
 opModulesCode += `
-let OpModules = {};
+const OpModules = {};
 
 Object.assign(
     OpModules,
 `;
 
-for (let module in modules) {
+for (const module in modules) {
     opModulesCode += `    ${module}Module,\n`;
 }
 

+ 9 - 3
src/core/config/modules/Default.mjs

@@ -5,14 +5,20 @@
 * @copyright Crown Copyright 2018
 * @license Apache-2.0
 */
-import ToBase64 from "../../operations/ToBase64";
+import FromBase32 from "../../operations/FromBase32";
 import FromBase64 from "../../operations/FromBase64";
+import ShowBase64Offsets from "../../operations/ShowBase64Offsets";
+import ToBase32 from "../../operations/ToBase32";
+import ToBase64 from "../../operations/ToBase64";
 
-let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
+const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
 
 OpModules.Default = {
-    "To Base64": ToBase64,
+    "From Base32": FromBase32,
     "From Base64": FromBase64,
+    "Show Base64 offsets": ShowBase64Offsets,
+    "To Base32": ToBase32,
+    "To Base64": ToBase64,
 };
 
 export default OpModules;

+ 1 - 1
src/core/config/modules/OpModules.mjs

@@ -9,7 +9,7 @@
 */
 import DefaultModule from "./Default";
 
-let OpModules = {};
+const OpModules = {};
 
 Object.assign(
     OpModules,

+ 99 - 233
src/core/lib/Base64.mjs

@@ -1,263 +1,129 @@
-import Utils from "../Utils";
-
-
 /**
- * Base64 operations.
+ * Base64 functions.
  *
  * @author n1474335 [n1474335@gmail.com]
  * @copyright Crown Copyright 2016
  * @license Apache-2.0
- *
- * @namespace
  */
-const Base64 = {
-
-    /**
-     * @constant
-     * @default
-     */
-    BASE32_ALPHABET: "A-Z2-7=",
-
-    /**
-     * To Base32 operation.
-     *
-     * @param {byteArray} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runTo32: function(input, args) {
-        if (!input) return "";
-
-        let alphabet = args[0] ?
-                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
-            output = "",
-            chr1, chr2, chr3, chr4, chr5,
-            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
-            i = 0;
-
-        while (i < input.length) {
-            chr1 = input[i++];
-            chr2 = input[i++];
-            chr3 = input[i++];
-            chr4 = input[i++];
-            chr5 = input[i++];
-
-            enc1 = chr1 >> 3;
-            enc2 = ((chr1 & 7) << 2) | (chr2 >> 6);
-            enc3 = (chr2 >> 1) & 31;
-            enc4 = ((chr2 & 1) << 4) | (chr3 >> 4);
-            enc5 = ((chr3 & 15) << 1) | (chr4 >> 7);
-            enc6 = (chr4 >> 2) & 31;
-            enc7 = ((chr4 & 3) << 3) | (chr5 >> 5);
-            enc8 = chr5 & 31;
-
-            if (isNaN(chr2)) {
-                enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32;
-            } else if (isNaN(chr3)) {
-                enc5 = enc6 = enc7 = enc8 = 32;
-            } else if (isNaN(chr4)) {
-                enc6 = enc7 = enc8 = 32;
-            } else if (isNaN(chr5)) {
-                enc8 = 32;
-            }
 
-            output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) +
-                alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) +
-                alphabet.charAt(enc7) + alphabet.charAt(enc8);
-        }
-
-        return output;
-    },
-
-
-    /**
-     * From Base32 operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {byteArray}
-     */
-    runFrom32: function(input, args) {
-        if (!input) return [];
-
-        let alphabet = args[0] ?
-                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
-            removeNonAlphChars = args[0];
+import Utils from "../Utils";
 
-        let output = [],
-            chr1, chr2, chr3, chr4, chr5,
-            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
-            i = 0;
 
-        if (removeNonAlphChars) {
-            const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g");
-            input = input.replace(re, "");
+/**
+ * Base64's the input byte array using the given alphabet, returning a string.
+ *
+ * @param {byteArray|Uint8Array|string} data
+ * @param {string} [alphabet="A-Za-z0-9+/="]
+ * @returns {string}
+ *
+ * @example
+ * // returns "SGVsbG8="
+ * toBase64([72, 101, 108, 108, 111]);
+ *
+ * // returns "SGVsbG8="
+ * toBase64("Hello");
+ */
+export function toBase64(data, alphabet="A-Za-z0-9+/=") {
+    if (!data) return "";
+    if (typeof data == "string") {
+        data = Utils.strToByteArray(data);
+    }
+
+    alphabet = Utils.expandAlphRange(alphabet).join("");
+
+    let output = "",
+        chr1, chr2, chr3,
+        enc1, enc2, enc3, enc4,
+        i = 0;
+
+    while (i < data.length) {
+        chr1 = data[i++];
+        chr2 = data[i++];
+        chr3 = data[i++];
+
+        enc1 = chr1 >> 2;
+        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+        enc4 = chr3 & 63;
+
+        if (isNaN(chr2)) {
+            enc3 = enc4 = 64;
+        } else if (isNaN(chr3)) {
+            enc4 = 64;
         }
 
-        while (i < input.length) {
-            enc1 = alphabet.indexOf(input.charAt(i++));
-            enc2 = alphabet.indexOf(input.charAt(i++) || "=");
-            enc3 = alphabet.indexOf(input.charAt(i++) || "=");
-            enc4 = alphabet.indexOf(input.charAt(i++) || "=");
-            enc5 = alphabet.indexOf(input.charAt(i++) || "=");
-            enc6 = alphabet.indexOf(input.charAt(i++) || "=");
-            enc7 = alphabet.indexOf(input.charAt(i++) || "=");
-            enc8 = alphabet.indexOf(input.charAt(i++) || "=");
+        output += alphabet.charAt(enc1) + alphabet.charAt(enc2) +
+            alphabet.charAt(enc3) + alphabet.charAt(enc4);
+    }
 
-            chr1 = (enc1 << 3) | (enc2 >> 2);
-            chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4);
-            chr3 = ((enc4 & 15) << 4) | (enc5 >> 1);
-            chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3);
-            chr5 = ((enc7 & 7) << 5) | enc8;
+    return output;
+}
 
-            output.push(chr1);
-            if (enc2 & 3 !== 0 || enc3 !== 32) output.push(chr2);
-            if (enc4 & 15 !== 0 || enc5 !== 32) output.push(chr3);
-            if (enc5 & 1 !== 0 || enc6 !== 32) output.push(chr4);
-            if (enc7 & 7 !== 0 || enc8 !== 32) output.push(chr5);
-        }
 
-        return output;
-    },
+/**
+ * UnBase64's the input string using the given alphabet, returning a byte array.
+ *
+ * @param {byteArray} data
+ * @param {string} [alphabet="A-Za-z0-9+/="]
+ * @param {string} [returnType="string"] - Either "string" or "byteArray"
+ * @param {boolean} [removeNonAlphChars=true]
+ * @returns {byteArray}
+ *
+ * @example
+ * // returns "Hello"
+ * fromBase64("SGVsbG8=");
+ *
+ * // returns [72, 101, 108, 108, 111]
+ * fromBase64("SGVsbG8=", null, "byteArray");
+ */
+export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) {
+    if (!data) {
+        return returnType === "string" ? "" : [];
+    }
 
+    alphabet = Utils.expandAlphRange(alphabet).join("");
 
-    /**
-     * @constant
-     * @default
-     */
-    SHOW_IN_BINARY: false,
-    /**
-     * @constant
-     * @default
-     */
-    OFFSETS_SHOW_VARIABLE: true,
+    const output = [];
+    let chr1, chr2, chr3,
+        enc1, enc2, enc3, enc4,
+        i = 0;
 
-    /**
-     * Show Base64 offsets operation.
-     *
-     * @param {byteArray} input
-     * @param {Object[]} args
-     * @returns {html}
-     */
-    runOffsets: function(input, args) {
-        let alphabet = args[0] || Base64.ALPHABET,
-            showVariable = args[1],
-            offset0 = Utils.toBase64(input, alphabet),
-            offset1 = Utils.toBase64([0].concat(input), alphabet),
-            offset2 = Utils.toBase64([0, 0].concat(input), alphabet),
-            len0 = offset0.indexOf("="),
-            len1 = offset1.indexOf("="),
-            len2 = offset2.indexOf("="),
-            script = "<script type='application/javascript'>$('[data-toggle=\"tooltip\"]').tooltip()</script>",
-            staticSection = "",
-            padding = "";
+    if (removeNonAlphChars) {
+        const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
+        data = data.replace(re, "");
+    }
 
-        if (input.length < 1) {
-            return "Please enter a string.";
-        }
+    while (i < data.length) {
+        enc1 = alphabet.indexOf(data.charAt(i++));
+        enc2 = alphabet.indexOf(data.charAt(i++) || "=");
+        enc3 = alphabet.indexOf(data.charAt(i++) || "=");
+        enc4 = alphabet.indexOf(data.charAt(i++) || "=");
 
-        // Highlight offset 0
-        if (len0 % 4 === 2) {
-            staticSection = offset0.slice(0, -3);
-            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet).slice(0, -2)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset0.substr(offset0.length - 3, 1) + "</span>" +
-                "<span class='hl3'>" + offset0.substr(offset0.length - 2) + "</span>";
-        } else if (len0 % 4 === 3) {
-            staticSection = offset0.slice(0, -2);
-            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet).slice(0, -1)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset0.substr(offset0.length - 2, 1) + "</span>" +
-                "<span class='hl3'>" + offset0.substr(offset0.length - 1) + "</span>";
-        } else {
-            staticSection = offset0;
-            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet)) + "'>" +
-                staticSection + "</span>";
-        }
+        enc2 = enc2 === -1 ? 64 : enc2;
+        enc3 = enc3 === -1 ? 64 : enc3;
+        enc4 = enc4 === -1 ? 64 : enc4;
 
-        if (!showVariable) {
-            offset0 = staticSection;
-        }
+        chr1 = (enc1 << 2) | (enc2 >> 4);
+        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+        chr3 = ((enc3 & 3) << 6) | enc4;
 
+        output.push(chr1);
 
-        // Highlight offset 1
-        padding = "<span class='hl3'>" + offset1.substr(0, 1) + "</span>" +
-            "<span class='hl5'>" + offset1.substr(1, 1) + "</span>";
-        offset1 = offset1.substr(2);
-        if (len1 % 4 === 2) {
-            staticSection = offset1.slice(0, -3);
-            offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1, -2)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset1.substr(offset1.length - 3, 1) + "</span>" +
-                "<span class='hl3'>" + offset1.substr(offset1.length - 2) + "</span>";
-        } else if (len1 % 4 === 3) {
-            staticSection = offset1.slice(0, -2);
-            offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1, -1)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset1.substr(offset1.length - 2, 1) + "</span>" +
-                "<span class='hl3'>" + offset1.substr(offset1.length - 1) + "</span>";
-        } else {
-            staticSection = offset1;
-            offset1 = padding +  "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1)) + "'>" +
-                staticSection + "</span>";
+        if (enc3 !== 64) {
+            output.push(chr2);
         }
-
-        if (!showVariable) {
-            offset1 = staticSection;
-        }
-
-        // Highlight offset 2
-        padding = "<span class='hl3'>" + offset2.substr(0, 2) + "</span>" +
-            "<span class='hl5'>" + offset2.substr(2, 1) + "</span>";
-        offset2 = offset2.substr(3);
-        if (len2 % 4 === 2) {
-            staticSection = offset2.slice(0, -3);
-            offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset2.substr(offset2.length - 3, 1) + "</span>" +
-                "<span class='hl3'>" + offset2.substr(offset2.length - 2) + "</span>";
-        } else if (len2 % 4 === 3) {
-            staticSection = offset2.slice(0, -2);
-            offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
-                staticSection + "</span>" +
-                "<span class='hl5'>" + offset2.substr(offset2.length - 2, 1) + "</span>" +
-                "<span class='hl3'>" + offset2.substr(offset2.length - 1) + "</span>";
-        } else {
-            staticSection = offset2;
-            offset2 = padding +  "<span data-toggle='tooltip' data-placement='top' title='" +
-                Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2)) + "'>" +
-                staticSection + "</span>";
-        }
-
-        if (!showVariable) {
-            offset2 = staticSection;
+        if (enc4 !== 64) {
+            output.push(chr3);
         }
+    }
 
-        return (showVariable ? "Characters highlighted in <span class='hl5'>green</span> could change if the input is surrounded by more data." +
-            "\nCharacters highlighted in <span class='hl3'>red</span> are for padding purposes only." +
-            "\nUnhighlighted characters are <span data-toggle='tooltip' data-placement='top' title='Tooltip on left'>static</span>." +
-            "\nHover over the static sections to see what they decode to on their own.\n" +
-            "\nOffset 0: " + offset0 +
-            "\nOffset 1: " + offset1 +
-            "\nOffset 2: " + offset2 +
-            script :
-            offset0 + "\n" + offset1 + "\n" + offset2);
-    },
+    return returnType === "string" ? Utils.byteArrayToUtf8(output) : output;
+}
 
-};
-
-export default Base64;
-
-export const ALPHABET = "A-Za-z0-9+/=";
 
+/**
+ * Base64 alphabets.
+ */
 export const ALPHABET_OPTIONS = [
     {name: "Standard: A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
     {name: "URL safe: A-Za-z0-9-_", value: "A-Za-z0-9-_"},

+ 90 - 0
src/core/operations/FromBase32.mjs

@@ -0,0 +1,90 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+/**
+ * From Base32 operation
+ */
+class FromBase32 extends Operation {
+
+    /**
+     * FromBase32 constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "From Base32";
+        this.module = "Default";
+        this.description = "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.";
+        this.inputType = "string";
+        this.outputType = "byteArray";
+        this.args = [
+            {
+                name: "Alphabet",
+                type: "binaryString",
+                value: "A-Z2-7="
+            },
+            {
+                name: "Remove non-alphabet chars",
+                type: "boolean",
+                value: true
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {byteArray}
+     */
+    run(input, args) {
+        if (!input) return [];
+
+        const alphabet = args[0] ?
+                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
+            removeNonAlphChars = args[1],
+            output = [];
+
+        let chr1, chr2, chr3, chr4, chr5,
+            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
+            i = 0;
+
+        if (removeNonAlphChars) {
+            const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g");
+            input = input.replace(re, "");
+        }
+
+        while (i < input.length) {
+            enc1 = alphabet.indexOf(input.charAt(i++));
+            enc2 = alphabet.indexOf(input.charAt(i++) || "=");
+            enc3 = alphabet.indexOf(input.charAt(i++) || "=");
+            enc4 = alphabet.indexOf(input.charAt(i++) || "=");
+            enc5 = alphabet.indexOf(input.charAt(i++) || "=");
+            enc6 = alphabet.indexOf(input.charAt(i++) || "=");
+            enc7 = alphabet.indexOf(input.charAt(i++) || "=");
+            enc8 = alphabet.indexOf(input.charAt(i++) || "=");
+
+            chr1 = (enc1 << 3) | (enc2 >> 2);
+            chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4);
+            chr3 = ((enc4 & 15) << 4) | (enc5 >> 1);
+            chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3);
+            chr5 = ((enc7 & 7) << 5) | enc8;
+
+            output.push(chr1);
+            if (enc2 & 3 !== 0 || enc3 !== 32) output.push(chr2);
+            if (enc4 & 15 !== 0 || enc5 !== 32) output.push(chr3);
+            if (enc5 & 1 !== 0 || enc6 !== 32) output.push(chr4);
+            if (enc7 & 7 !== 0 || enc8 !== 32) output.push(chr5);
+        }
+
+        return output;
+    }
+
+}
+
+export default FromBase32;

+ 9 - 11
src/core/operations/FromBase64.mjs

@@ -5,8 +5,7 @@
  */
 
 import Operation from "../Operation";
-import Utils from "../Utils";
-import {ALPHABET, ALPHABET_OPTIONS} from "../lib/Base64";
+import {fromBase64, ALPHABET_OPTIONS} from "../lib/Base64";
 
 /**
  * From Base64 operation
@@ -14,17 +13,17 @@ import {ALPHABET, ALPHABET_OPTIONS} from "../lib/Base64";
 class FromBase64 extends Operation {
 
     /**
-     * ToBase64 constructor
+     * FromBase64 constructor
      */
     constructor() {
         super();
 
-        this.name        = "From Base64";
-        this.module      = "Default";
+        this.name = "From Base64";
+        this.module = "Default";
         this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation decodes data from an ASCII Base64 string back into its raw format.<br><br>e.g. <code>aGVsbG8=</code> becomes <code>hello</code>";
-        this.inputType   = "string";
-        this.outputType  = "byteArray";
-        this.args        = [
+        this.inputType = "string";
+        this.outputType = "byteArray";
+        this.args = [
             {
                 name: "Alphabet",
                 type: "editableOption",
@@ -44,10 +43,9 @@ class FromBase64 extends Operation {
      * @returns {string}
      */
     run(input, args) {
-        let alphabet = args[0] || ALPHABET,
-            removeNonAlphChars = args[1];
+        const [alphabet, removeNonAlphChars] = args;
 
-        return Utils.fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
+        return fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
     }
 
     /**

+ 162 - 0
src/core/operations/ShowBase64Offsets.mjs

@@ -0,0 +1,162 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+import {fromBase64, toBase64} from "../lib/Base64";
+
+/**
+ * Show Base64 offsets operation
+ */
+class ShowBase64Offsets extends Operation {
+
+    /**
+     * ShowBase64Offsets constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Show Base64 offsets";
+        this.module = "Default";
+        this.description = "When a string is within a block of data and the whole block is Base64'd, the string itself could be represented in Base64 in three distinct ways depending on its offset within the block.<br><br>This operation shows all possible offsets for a given string so that each possible encoding can be considered.";
+        this.inputType = "byteArray";
+        this.outputType = "html";
+        this.args = [
+            {
+                name: "Alphabet",
+                type: "binaryString",
+                value: "A-Za-z0-9+/="
+            },
+            {
+                name: "Show variable chars and padding",
+                type: "boolean",
+                value: true
+            }
+        ];
+    }
+
+    /**
+     * @param {byteArray} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    run(input, args) {
+        const [alphabet, showVariable] = args;
+
+        let offset0 = toBase64(input, alphabet),
+            offset1 = toBase64([0].concat(input), alphabet),
+            offset2 = toBase64([0, 0].concat(input), alphabet),
+            staticSection = "",
+            padding = "";
+
+        const len0 = offset0.indexOf("="),
+            len1 = offset1.indexOf("="),
+            len2 = offset2.indexOf("="),
+            script = "<script type='application/javascript'>$('[data-toggle=\"tooltip\"]').tooltip()</script>";
+
+        if (input.length < 1) {
+            return "Please enter a string.";
+        }
+
+        // Highlight offset 0
+        if (len0 % 4 === 2) {
+            staticSection = offset0.slice(0, -3);
+            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64(staticSection, alphabet).slice(0, -2)) + "'>" +
+                staticSection + "</span>" +
+                "<span class='hl5'>" + offset0.substr(offset0.length - 3, 1) + "</span>" +
+                "<span class='hl3'>" + offset0.substr(offset0.length - 2) + "</span>";
+        } else if (len0 % 4 === 3) {
+            staticSection = offset0.slice(0, -2);
+            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64(staticSection, alphabet).slice(0, -1)) + "'>" +
+                staticSection + "</span>" +
+                "<span class='hl5'>" + offset0.substr(offset0.length - 2, 1) + "</span>" +
+                "<span class='hl3'>" + offset0.substr(offset0.length - 1) + "</span>";
+        } else {
+            staticSection = offset0;
+            offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64(staticSection, alphabet)) + "'>" +
+                staticSection + "</span>";
+        }
+
+        if (!showVariable) {
+            offset0 = staticSection;
+        }
+
+
+        // Highlight offset 1
+        padding = "<span class='hl3'>" + offset1.substr(0, 1) + "</span>" +
+            "<span class='hl5'>" + offset1.substr(1, 1) + "</span>";
+        offset1 = offset1.substr(2);
+        if (len1 % 4 === 2) {
+            staticSection = offset1.slice(0, -3);
+            offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64("AA" + staticSection, alphabet).slice(1, -2)) + "'>" +
+                staticSection + "</span>" +
+                "<span class='hl5'>" + offset1.substr(offset1.length - 3, 1) + "</span>" +
+                "<span class='hl3'>" + offset1.substr(offset1.length - 2) + "</span>";
+        } else if (len1 % 4 === 3) {
+            staticSection = offset1.slice(0, -2);
+            offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64("AA" + staticSection, alphabet).slice(1, -1)) + "'>" +
+                staticSection + "</span>" +
+                "<span class='hl5'>" + offset1.substr(offset1.length - 2, 1) + "</span>" +
+                "<span class='hl3'>" + offset1.substr(offset1.length - 1) + "</span>";
+        } else {
+            staticSection = offset1;
+            offset1 = padding +  "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64("AA" + staticSection, alphabet).slice(1)) + "'>" +
+                staticSection + "</span>";
+        }
+
+        if (!showVariable) {
+            offset1 = staticSection;
+        }
+
+        // Highlight offset 2
+        padding = "<span class='hl3'>" + offset2.substr(0, 2) + "</span>" +
+            "<span class='hl5'>" + offset2.substr(2, 1) + "</span>";
+        offset2 = offset2.substr(3);
+        if (len2 % 4 === 2) {
+            staticSection = offset2.slice(0, -3);
+            offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
+                staticSection + "</span>" +
+                "<span class='hl5'>" + offset2.substr(offset2.length - 3, 1) + "</span>" +
+                "<span class='hl3'>" + offset2.substr(offset2.length - 2) + "</span>";
+        } else if (len2 % 4 === 3) {
+            staticSection = offset2.slice(0, -2);
+            offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
+                staticSection + "</span>" +
+                "<span class='hl5'>" + offset2.substr(offset2.length - 2, 1) + "</span>" +
+                "<span class='hl3'>" + offset2.substr(offset2.length - 1) + "</span>";
+        } else {
+            staticSection = offset2;
+            offset2 = padding +  "<span data-toggle='tooltip' data-placement='top' title='" +
+                Utils.escapeHtml(fromBase64("AAA" + staticSection, alphabet).slice(2)) + "'>" +
+                staticSection + "</span>";
+        }
+
+        if (!showVariable) {
+            offset2 = staticSection;
+        }
+
+        return (showVariable ? "Characters highlighted in <span class='hl5'>green</span> could change if the input is surrounded by more data." +
+            "\nCharacters highlighted in <span class='hl3'>red</span> are for padding purposes only." +
+            "\nUnhighlighted characters are <span data-toggle='tooltip' data-placement='top' title='Tooltip on left'>static</span>." +
+            "\nHover over the static sections to see what they decode to on their own.\n" +
+            "\nOffset 0: " + offset0 +
+            "\nOffset 1: " + offset1 +
+            "\nOffset 2: " + offset2 +
+            script :
+            offset0 + "\n" + offset1 + "\n" + offset2);
+    }
+
+}
+
+export default ShowBase64Offsets;

+ 85 - 0
src/core/operations/ToBase32.mjs

@@ -0,0 +1,85 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+/**
+ * To Base32 operation
+ */
+class ToBase32 extends Operation {
+
+    /**
+     * ToBase32 constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "To Base32";
+        this.module = "Default";
+        this.description = "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.";
+        this.inputType = "byteArray";
+        this.outputType = "string";
+        this.args = [
+            {
+                name: "Alphabet",
+                type: "binaryString",
+                value: "A-Z2-7="
+            }
+        ];
+    }
+
+    /**
+     * @param {byteArray} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        if (!input) return "";
+
+        const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";
+        let output = "",
+            chr1, chr2, chr3, chr4, chr5,
+            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
+            i = 0;
+
+        while (i < input.length) {
+            chr1 = input[i++];
+            chr2 = input[i++];
+            chr3 = input[i++];
+            chr4 = input[i++];
+            chr5 = input[i++];
+
+            enc1 = chr1 >> 3;
+            enc2 = ((chr1 & 7) << 2) | (chr2 >> 6);
+            enc3 = (chr2 >> 1) & 31;
+            enc4 = ((chr2 & 1) << 4) | (chr3 >> 4);
+            enc5 = ((chr3 & 15) << 1) | (chr4 >> 7);
+            enc6 = (chr4 >> 2) & 31;
+            enc7 = ((chr4 & 3) << 3) | (chr5 >> 5);
+            enc8 = chr5 & 31;
+
+            if (isNaN(chr2)) {
+                enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32;
+            } else if (isNaN(chr3)) {
+                enc5 = enc6 = enc7 = enc8 = 32;
+            } else if (isNaN(chr4)) {
+                enc6 = enc7 = enc8 = 32;
+            } else if (isNaN(chr5)) {
+                enc8 = 32;
+            }
+
+            output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) +
+                alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) +
+                alphabet.charAt(enc7) + alphabet.charAt(enc8);
+        }
+
+        return output;
+    }
+
+}
+
+export default ToBase32;

+ 8 - 9
src/core/operations/ToBase64.mjs

@@ -5,8 +5,7 @@
  */
 
 import Operation from "../Operation";
-import Utils from "../Utils";
-import {ALPHABET, ALPHABET_OPTIONS} from "../lib/Base64";
+import {toBase64, ALPHABET_OPTIONS} from "../lib/Base64";
 
 /**
  * To Base64 operation
@@ -19,12 +18,12 @@ class ToBase64 extends Operation {
     constructor() {
         super();
 
-        this.name        = "To Base64";
-        this.module      = "Default";
+        this.name = "To Base64";
+        this.module = "Default";
         this.description = "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation decodes data from an ASCII Base64 string back into its raw format.<br><br>e.g. <code>aGVsbG8=</code> becomes <code>hello</code>";
-        this.inputType   = "ArrayBuffer";
-        this.outputType  = "string";
-        this.args        = [
+        this.inputType = "ArrayBuffer";
+        this.outputType = "string";
+        this.args = [
             {
                 name: "Alphabet",
                 type: "editableOption",
@@ -39,8 +38,8 @@ class ToBase64 extends Operation {
      * @returns {string}
      */
     run(input, args) {
-        const alphabet = args[0] || ALPHABET;
-        return Utils.toBase64(new Uint8Array(input), alphabet);
+        const alphabet = args[0];
+        return toBase64(new Uint8Array(input), alphabet);
     }
 
     /**

+ 9 - 3
src/core/operations/index.mjs

@@ -1,7 +1,13 @@
 import ToBase64 from "./ToBase64";
 import FromBase64 from "./FromBase64";
+import ToBase32 from "./ToBase32";
+import FromBase32 from "./FromBase32";
+import ShowBase64Offsets from "./ShowBase64Offsets";
 
-export default [
+export {
     ToBase64,
-    FromBase64
-];
+    FromBase64,
+    ToBase32,
+    FromBase32,
+    ShowBase64Offsets
+};

+ 3 - 2
src/core/operations/legacy/Cipher.js

@@ -1,4 +1,5 @@
 import Utils from "../Utils.js";
+import {toBase64} from "../lib/Base64";
 import CryptoJS from "crypto-js";
 import forge from "imports-loader?jQuery=>null!node-forge/dist/forge.min.js";
 import {blowfish as Blowfish} from "sladex-blowfish";
@@ -366,7 +367,7 @@ DES uses a key length of 8 bytes (64 bits).`;
 
         input = Utils.convertToByteString(input, inputType);
 
-        Blowfish.setIV(Utils.toBase64(iv), 0);
+        Blowfish.setIV(toBase64(iv), 0);
 
         const enc = Blowfish.encrypt(input, key, {
             outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType],
@@ -395,7 +396,7 @@ DES uses a key length of 8 bytes (64 bits).`;
 
         input = inputType === "Raw" ? Utils.strToByteArray(input) : input;
 
-        Blowfish.setIV(Utils.toBase64(iv), 0);
+        Blowfish.setIV(toBase64(iv), 0);
 
         const result = Blowfish.decrypt(input, key, {
             outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[inputType], // This actually means inputType. The library is weird.

+ 3 - 2
src/core/operations/legacy/Image.js

@@ -2,6 +2,7 @@ import * as ExifParser from "exif-parser";
 import removeEXIF from "../vendor/remove-exif.js";
 import Utils from "../Utils.js";
 import FileType from "./FileType.js";
+import {fromBase64, toBase64} from "../lib/Base64";
 
 
 /**
@@ -96,7 +97,7 @@ const Image = {
             case "Base64":
                 // Don't trust the Base64 entered by the user.
                 // Unwrap it first, then re-encode later.
-                input = Utils.fromBase64(input, null, "byteArray");
+                input = fromBase64(input, null, "byteArray");
                 break;
             case "Raw":
             default:
@@ -113,7 +114,7 @@ const Image = {
         }
 
         // Add image data to URI
-        dataURI += "base64," + Utils.toBase64(input);
+        dataURI += "base64," + toBase64(input);
 
         return "<img src='" + dataURI + "'>";
     },

+ 2 - 1
src/core/operations/legacy/PublicKey.js

@@ -1,4 +1,5 @@
 import Utils from "../Utils.js";
+import {fromBase64} from "../lib/Base64";
 import * as r from "jsrsasign";
 
 
@@ -43,7 +44,7 @@ const PublicKey = {
                 cert.readCertPEM(input);
                 break;
             case "Base64":
-                cert.readCertHex(Utils.toHex(Utils.fromBase64(input, null, "byteArray"), ""));
+                cert.readCertHex(Utils.toHex(fromBase64(input, null, "byteArray"), ""));
                 break;
             case "Raw":
                 cert.readCertHex(Utils.toHex(Utils.strToByteArray(input), ""));

+ 7 - 6
src/web/App.js

@@ -1,4 +1,5 @@
 import Utils from "../core/Utils";
+import {fromBase64} from "../core/lib/Base64";
 import Manager from "./Manager.js";
 import HTMLCategory from "./HTMLCategory.js";
 import HTMLOperation from "./HTMLOperation.js";
@@ -193,12 +194,12 @@ App.prototype.populateOperationsList = function() {
     let i;
 
     for (i = 0; i < this.categories.length; i++) {
-        let catConf = this.categories[i],
+        const catConf = this.categories[i],
             selected = i === 0,
             cat = new HTMLCategory(catConf.name, selected);
 
         for (let j = 0; j < catConf.ops.length; j++) {
-            let opName = catConf.ops[j],
+            const opName = catConf.ops[j],
                 op = new HTMLOperation(opName, this.operations[opName], this, this.manager);
             cat.addOperation(op);
         }
@@ -405,7 +406,7 @@ App.prototype.loadURIParams = function() {
     // Read in input data from URI params
     if (this.uriParams.input) {
         try {
-            const inputData = Utils.fromBase64(this.uriParams.input);
+            const inputData = fromBase64(this.uriParams.input);
             this.setInput(inputData);
         } catch (err) {}
     }
@@ -503,11 +504,11 @@ App.prototype.resetLayout = function() {
  */
 App.prototype.setCompileMessage = function() {
     // Display time since last build and compile message
-    let now = new Date(),
+    const now = new Date(),
         timeSinceCompile = Utils.fuzzyTime(now.getTime() - window.compileTime);
 
     // Calculate previous version to compare to
-    let prev = PKG_VERSION.split(".").map(n => {
+    const prev = PKG_VERSION.split(".").map(n => {
         return parseInt(n, 10);
     });
     if (prev[2] > 0) prev[2]--;
@@ -574,7 +575,7 @@ App.prototype.alert = function(str, style, timeout, silent) {
     style = style || "danger";
     timeout = timeout || 0;
 
-    let alertEl = document.getElementById("alert"),
+    const alertEl = document.getElementById("alert"),
         alertContent = document.getElementById("alert-content");
 
     alertEl.classList.remove("alert-danger");

+ 5 - 4
src/web/ControlsWaiter.js

@@ -1,4 +1,5 @@
 import Utils from "../core/Utils";
+import {toBase64} from "../core/lib/Base64";
 
 
 /**
@@ -169,7 +170,7 @@ ControlsWaiter.prototype.generateStateUrl = function(includeRecipe, includeInput
         window.location.host +
         window.location.pathname;
     const recipeStr = Utils.generatePrettyRecipe(recipeConfig);
-    const inputStr = Utils.toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding
+    const inputStr = toBase64(this.app.getInput(), "A-Za-z0-9+/"); // B64 alphabet with no padding
 
     includeRecipe = includeRecipe && (recipeConfig.length > 0);
     // Only inlcude input if it is less than 50KB (51200 * 4/3 as it is Base64 encoded)
@@ -271,9 +272,9 @@ ControlsWaiter.prototype.saveButtonClick = function() {
         return;
     }
 
-    let savedRecipes = localStorage.savedRecipes ?
-            JSON.parse(localStorage.savedRecipes) : [],
-        recipeId = localStorage.recipeId || 0;
+    const savedRecipes = localStorage.savedRecipes ?
+        JSON.parse(localStorage.savedRecipes) : [];
+    let recipeId = localStorage.recipeId || 0;
 
     savedRecipes.push({
         id: ++recipeId,

+ 9 - 7
src/web/HTMLIngredient.js

@@ -32,12 +32,14 @@ const HTMLIngredient = function(config, app, manager) {
  * @returns {string}
  */
 HTMLIngredient.prototype.toHtml = function() {
-    let inline = (this.type === "boolean" ||
-                  this.type === "number" ||
-                  this.type === "option" ||
-                  this.type === "shortString" ||
-                  this.type === "binaryShortString"),
-        html = inline ? "" : "<div class='clearfix'>&nbsp;</div>",
+    const inline = (
+        this.type === "boolean" ||
+        this.type === "number" ||
+        this.type === "option" ||
+        this.type === "shortString" ||
+        this.type === "binaryShortString"
+    );
+    let html = inline ? "" : "<div class='clearfix'>&nbsp;</div>",
         i, m;
 
     html += "<div class='arg-group" + (inline ? " inline-args" : "") +
@@ -202,7 +204,7 @@ HTMLIngredient.prototype.populateOptionChange = function(e) {
  * @param {event} e
  */
 HTMLIngredient.prototype.editableOptionChange = function(e) {
-    let select = e.target,
+    const select = e.target,
         input = select.nextSibling;
 
     input.value = select.childNodes[select.selectedIndex].value;

+ 2 - 2
src/web/HighlighterWaiter.js

@@ -40,8 +40,8 @@ HighlighterWaiter.OUTPUT = 1;
  * @returns {boolean}
  */
 HighlighterWaiter.prototype._isSelectionBackwards = function() {
-    let backwards = false,
-        sel = window.getSelection();
+    let backwards = false;
+    const sel = window.getSelection();
 
     if (!sel.isCollapsed) {
         const range = document.createRange();

+ 1 - 1
src/web/LoaderWorker.js

@@ -25,7 +25,7 @@ self.addEventListener("message", function(e) {
  */
 self.loadFile = function(file) {
     const reader = new FileReader();
-    let data = new Uint8Array(file.size);
+    const data = new Uint8Array(file.size);
     let offset = 0;
     const CHUNK_SIZE = 10485760; // 10MiB
 

+ 1 - 1
src/web/RecipeWaiter.js

@@ -453,7 +453,7 @@ RecipeWaiter.prototype.setRegisters = function(opIndex, numPrevRegisters, regist
     // Remove previous div
     if (prevRegList) prevRegList.remove();
 
-    let registerList = [];
+    const registerList = [];
     for (let i = 0; i < registers.length; i++) {
         registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`);
     }

+ 1 - 1
src/web/static/sitemap.js

@@ -20,7 +20,7 @@ sitemap.add({
     priority: 1.0
 });
 
-for (let op in OperationConfig) {
+for (const op in OperationConfig) {
     sitemap.add({
         url: `/?op=${encodeURIComponent(op)}`,
         changeFreq: "yearly",