瀏覽代碼

Merge branch 'esmconversion' of https://github.com/artemisbot/CyberChef into esm

n1474335 7 年之前
父節點
當前提交
acb8a342a7
共有 43 個文件被更改,包括 1554 次插入849 次删除
  1. 41 0
      src/core/lib/Extract.mjs
  2. 116 0
      src/core/lib/PGP.mjs
  3. 6 3
      src/core/operations/AESDecrypt.mjs
  4. 5 2
      src/core/operations/AESEncrypt.mjs
  5. 2 0
      src/core/operations/AffineCipherDecode.mjs
  6. 2 0
      src/core/operations/BifidCipherDecode.mjs
  7. 2 0
      src/core/operations/BifidCipherEncode.mjs
  8. 1 1
      src/core/operations/CartesianProduct.mjs
  9. 131 0
      src/core/operations/DisassembleX86.mjs
  10. 4 1
      src/core/operations/DropBytes.mjs
  11. 52 0
      src/core/operations/ExtractDates.mjs
  12. 49 0
      src/core/operations/ExtractDomains.mjs
  13. 49 0
      src/core/operations/ExtractEmailAddresses.mjs
  14. 79 0
      src/core/operations/ExtractFilePaths.mjs
  15. 94 0
      src/core/operations/ExtractIPAddresses.mjs
  16. 49 0
      src/core/operations/ExtractMACAddresses.mjs
  17. 55 0
      src/core/operations/ExtractURLs.mjs
  18. 2 1
      src/core/operations/Filter.mjs
  19. 4 1
      src/core/operations/FromCharcode.mjs
  20. 4 1
      src/core/operations/FromUNIXTimestamp.mjs
  21. 115 0
      src/core/operations/GeneratePGPKeyPair.mjs
  22. 3 2
      src/core/operations/HammingDistance.mjs
  23. 2 1
      src/core/operations/OffsetChecker.mjs
  24. 79 0
      src/core/operations/PGPDecrypt.mjs
  25. 113 0
      src/core/operations/PGPDecryptAndVerify.mjs
  26. 77 0
      src/core/operations/PGPEncrypt.mjs
  27. 84 0
      src/core/operations/PGPEncryptAndSign.mjs
  28. 2 1
      src/core/operations/ParseDateTime.mjs
  29. 2 1
      src/core/operations/ParseUNIXFilePermissions.mjs
  30. 69 0
      src/core/operations/ParseURI.mjs
  31. 2 1
      src/core/operations/RawInflate.mjs
  32. 2 1
      src/core/operations/ShowBase64Offsets.mjs
  33. 118 0
      src/core/operations/Strings.mjs
  34. 4 1
      src/core/operations/TakeBytes.mjs
  35. 4 1
      src/core/operations/ToCharcode.mjs
  36. 4 1
      src/core/operations/ToUNIXTimestamp.mjs
  37. 2 1
      src/core/operations/TranslateDateTimeFormat.mjs
  38. 44 0
      src/core/operations/URLDecode.mjs
  39. 68 0
      src/core/operations/URLEncode.mjs
  40. 0 333
      src/core/operations/legacy/Extract.js
  41. 0 364
      src/core/operations/legacy/PGP.js
  42. 0 118
      src/core/operations/legacy/URL.js
  43. 13 13
      src/core/vendor/DisassembleX86-64.mjs

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

@@ -0,0 +1,41 @@
+/**
+ * Identifier extraction functions
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ *
+ */
+
+/**
+ * Runs search operations across the input data using regular expressions.
+ *
+ * @param {string} input
+ * @param {RegExp} searchRegex
+ * @param {RegExp} removeRegex - A regular expression defining results to remove from the
+ *      final list
+ * @param {boolean} includeTotal - Whether or not to include the total number of results
+ * @returns {string}
+ */
+export function search (input, searchRegex, removeRegex, includeTotal) {
+    let output = "",
+        total = 0,
+        match;
+
+    while ((match = searchRegex.exec(input))) {
+        // Moves pointer when an empty string is matched (prevents infinite loop)
+        if (match.index === searchRegex.lastIndex) {
+            searchRegex.lastIndex++;
+        }
+
+        if (removeRegex && removeRegex.test(match[0]))
+            continue;
+        total++;
+        output += match[0] + "\n";
+    }
+
+    if (includeTotal)
+        output = "Total found: " + total + "\n\n" + output;
+
+    return output;
+}

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

@@ -0,0 +1,116 @@
+/**
+ * PGP functions.
+ *
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [matt@artemisbot.uk]
+ * @author n1474335 [n1474335@gmail.com]
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ *
+ */
+
+import kbpgp from "kbpgp";
+import promisifyDefault from "es6-promisify";
+const promisify = promisifyDefault.promisify;
+/**
+ * Progress callback
+ *
+ */
+export const ASP = kbpgp.ASP({
+    "progress_hook": info => {
+        let msg = "";
+
+        switch (info.what) {
+            case "guess":
+                msg = "Guessing a prime";
+                break;
+            case "fermat":
+                msg = "Factoring prime using Fermat's factorization method";
+                break;
+            case "mr":
+                msg = "Performing Miller-Rabin primality test";
+                break;
+            case "passed_mr":
+                msg = "Passed Miller-Rabin primality test";
+                break;
+            case "failed_mr":
+                msg = "Failed Miller-Rabin primality test";
+                break;
+            case "found":
+                msg = "Prime found";
+                break;
+            default:
+                msg = `Stage: ${info.what}`;
+        }
+
+        if (ENVIRONMENT_IS_WORKER())
+            self.sendStatusMessage(msg);
+    }
+});
+
+/**
+ * Get size of subkey
+ *
+ * @param {number} keySize
+ * @returns {number}
+ */
+export function getSubkeySize(keySize) {
+    return {
+        1024: 1024,
+        2048: 1024,
+        4096: 2048,
+        256:   256,
+        384:   256,
+    }[keySize];
+}
+
+/**
+* Import private key and unlock if necessary
+*
+* @param {string} privateKey
+* @param {string} [passphrase]
+* @returns {Object}
+*/
+export async function importPrivateKey(privateKey, passphrase) {
+    try {
+        const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
+            armored: privateKey,
+            opts: {
+                "no_check_keys": true
+            }
+        });
+        if (key.is_pgp_locked()) {
+            if (passphrase) {
+                await promisify(key.unlock_pgp.bind(key))({
+                    passphrase
+                });
+            } else {
+                throw "Did not provide passphrase with locked private key.";
+            }
+        }
+        return key;
+    } catch (err) {
+        throw `Could not import private key: ${err}`;
+    }
+}
+
+/**
+ * Import public key
+ *
+ * @param {string} publicKey
+ * @returns {Object}
+ */
+export async function importPublicKey (publicKey) {
+    try {
+        const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
+            armored: publicKey,
+            opts: {
+                "no_check_keys": true
+            }
+        });
+        return key;
+    } catch (err) {
+        throw `Could not import public key: ${err}`;
+    }
+}

+ 6 - 3
src/core/operations/AESDecrypt.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import Utils from "../Utils";
 import forge from "node-forge/dist/forge.min.js";
+import OperationError from "../errors/OperationError";
 
 /**
  * AES Decrypt operation
@@ -65,6 +66,8 @@ class AESDecrypt extends Operation {
      * @param {string} input
      * @param {Object[]} args
      * @returns {string}
+     *
+     * @throws {OperationError} if cannot decrypt input or invalid key length
      */
     run(input, args) {
         const key = Utils.convertToByteArray(args[0].string, args[0].option),
@@ -75,12 +78,12 @@ class AESDecrypt extends Operation {
             gcmTag = Utils.convertToByteString(args[5].string, args[5].option);
 
         if ([16, 24, 32].indexOf(key.length) < 0) {
-            return `Invalid key length: ${key.length} bytes
+            throw new OperationError(`Invalid key length: ${key.length} bytes
 
 The following algorithms will be used based on the size of the key:
   16 bytes = AES-128
   24 bytes = AES-192
-  32 bytes = AES-256`;
+  32 bytes = AES-256`);
         }
 
         input = Utils.convertToByteString(input, inputType);
@@ -96,7 +99,7 @@ The following algorithms will be used based on the size of the key:
         if (result) {
             return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
         } else {
-            return "Unable to decrypt input with these parameters.";
+            throw new OperationError("Unable to decrypt input with these parameters.");
         }
     }
 

+ 5 - 2
src/core/operations/AESEncrypt.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import Utils from "../Utils";
 import forge from "node-forge/dist/forge.min.js";
+import OperationError from "../errors/OperationError";
 
 /**
  * AES Encrypt operation
@@ -59,6 +60,8 @@ class AESEncrypt extends Operation {
      * @param {string} input
      * @param {Object[]} args
      * @returns {string}
+     *
+     * @throws {OperationError} if invalid key length
      */
     run(input, args) {
         const key = Utils.convertToByteArray(args[0].string, args[0].option),
@@ -68,12 +71,12 @@ class AESEncrypt extends Operation {
             outputType = args[4];
 
         if ([16, 24, 32].indexOf(key.length) < 0) {
-            return `Invalid key length: ${key.length} bytes
+            throw new OperationError(`Invalid key length: ${key.length} bytes
 
 The following algorithms will be used based on the size of the key:
   16 bytes = AES-128
   24 bytes = AES-192
-  32 bytes = AES-256`;
+  32 bytes = AES-256`);
         }
 
         input = Utils.convertToByteString(input, inputType);

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

@@ -42,6 +42,8 @@ class AffineCipherDecode extends Operation {
      * @param {string} input
      * @param {Object[]} args
      * @returns {string}
+     *
+     * @throws {OperationError} if a or b values are invalid
      */
     run(input, args) {
         const alphabet = "abcdefghijklmnopqrstuvwxyz",

+ 2 - 0
src/core/operations/BifidCipherDecode.mjs

@@ -37,6 +37,8 @@ class BifidCipherDecode extends Operation {
      * @param {string} input
      * @param {Object[]} args
      * @returns {string}
+     *
+     * @throws {OperationError} if invalid key
      */
     run(input, args) {
         const keywordStr = args[0].toUpperCase().replace("J", "I"),

+ 2 - 0
src/core/operations/BifidCipherEncode.mjs

@@ -37,6 +37,8 @@ class BifidCipherEncode extends Operation {
      * @param {string} input
      * @param {Object[]} args
      * @returns {string}
+     *
+     * @throws {OperationError} if key is invalid
      */
     run(input, args) {
         const keywordStr = args[0].toUpperCase().replace("J", "I"),

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

@@ -41,7 +41,7 @@ class CartesianProduct extends Operation {
      * Validate input length
      *
      * @param {Object[]} sets
-     * @throws {Error} if fewer than 2 sets
+     * @throws {OperationError} if fewer than 2 sets
      */
     validateSampleNumbers(sets) {
         if (!sets || sets.length < 2) {

+ 131 - 0
src/core/operations/DisassembleX86.mjs

@@ -0,0 +1,131 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import * as disassemble from "../vendor/DisassembleX86-64";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Disassemble x86 operation
+ */
+class DisassembleX86 extends Operation {
+
+    /**
+     * DisassembleX86 constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Disassemble x86";
+        this.module = "Shellcode";
+        this.description = "Disassembly is the process of translating machine language into assembly language.<br><br>This operation supports 64-bit, 32-bit and 16-bit code written for Intel or AMD x86 processors. It is particularly useful for reverse engineering shellcode.<br><br>Input should be in hexadecimal.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Bit mode",
+                "type": "option",
+                "value": ["64", "32", "16"]
+            },
+            {
+                "name": "Compatibility",
+                "type": "option",
+                "value": [
+                    "Full x86 architecture",
+                    "Knights Corner",
+                    "Larrabee",
+                    "Cyrix",
+                    "Geode",
+                    "Centaur",
+                    "X86/486"
+                ]
+            },
+            {
+                "name": "Code Segment (CS)",
+                "type": "number",
+                "value": 16
+            },
+            {
+                "name": "Offset (IP)",
+                "type": "number",
+                "value": 0
+            },
+            {
+                "name": "Show instruction hex",
+                "type": "boolean",
+                "value": true
+            },
+            {
+                "name": "Show instruction position",
+                "type": "boolean",
+                "value": true
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     *
+     * @throws {OperationError} if invalid mode value
+     */
+    run(input, args) {
+        const mode = args[0],
+            compatibility = args[1],
+            codeSegment = args[2],
+            offset = args[3],
+            showInstructionHex = args[4],
+            showInstructionPos = args[5];
+
+        switch (mode) {
+            case "64":
+                disassemble.setBitMode(2);
+                break;
+            case "32":
+                disassemble.setBitMode(1);
+                break;
+            case "16":
+                disassemble.setBitMode(0);
+                break;
+            default:
+                throw new OperationError("Invalid mode value");
+        }
+
+        switch (compatibility) {
+            case "Full x86 architecture":
+                disassemble.CompatibilityMode(0);
+                break;
+            case "Knights Corner":
+                disassemble.CompatibilityMode(1);
+                break;
+            case "Larrabee":
+                disassemble.CompatibilityMode(2);
+                break;
+            case "Cyrix":
+                disassemble.CompatibilityMode(3);
+                break;
+            case "Geode":
+                disassemble.CompatibilityMode(4);
+                break;
+            case "Centaur":
+                disassemble.CompatibilityMode(5);
+                break;
+            case "X86/486":
+                disassemble.CompatibilityMode(6);
+                break;
+        }
+
+        disassemble.SetBasePosition(codeSegment + ":" + offset);
+        disassemble.setShowInstructionHex(showInstructionHex);
+        disassemble.setShowInstructionPos(showInstructionPos);
+        disassemble.LoadBinCode(input.replace(/\s/g, ""));
+        return disassemble.LDisassemble();
+    }
+
+}
+
+export default DisassembleX86;

+ 4 - 1
src/core/operations/DropBytes.mjs

@@ -5,6 +5,7 @@
  */
 
 import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
 
 /**
  * Drop bytes operation
@@ -45,6 +46,8 @@ class DropBytes extends Operation {
      * @param {ArrayBuffer} input
      * @param {Object[]} args
      * @returns {ArrayBuffer}
+     *
+     * @throws {OperationError} if invalid input
      */
     run(input, args) {
         const start = args[0],
@@ -52,7 +55,7 @@ class DropBytes extends Operation {
             applyToEachLine = args[2];
 
         if (start < 0 || length < 0)
-            throw "Error: Invalid value";
+            throw new OperationError("Error: Invalid value");
 
         if (!applyToEachLine) {
             const left = input.slice(0, start),

+ 52 - 0
src/core/operations/ExtractDates.mjs

@@ -0,0 +1,52 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { search } from "../lib/Extract";
+
+/**
+ * Extract dates operation
+ */
+class ExtractDates extends Operation {
+
+    /**
+     * ExtractDates constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Extract dates";
+        this.module = "Regex";
+        this.description = "Extracts dates in the following formats<ul><li><code>yyyy-mm-dd</code></li><li><code>dd/mm/yyyy</code></li><li><code>mm/dd/yyyy</code></li></ul>Dividers can be any of /, -, . or space";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Display total",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const displayTotal = args[0],
+            date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd
+            date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy
+            date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy
+            regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig");
+
+        return search(input, regex, null, displayTotal);
+    }
+
+}
+
+export default ExtractDates;

+ 49 - 0
src/core/operations/ExtractDomains.mjs

@@ -0,0 +1,49 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { search } from "../lib/Extract";
+
+/**
+ * Extract domains operation
+ */
+class ExtractDomains extends Operation {
+
+    /**
+     * ExtractDomains constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Extract domains";
+        this.module = "Regex";
+        this.description = "Extracts domain names.<br>Note that this will not include paths. Use <strong>Extract URLs</strong> to find entire URLs.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Display total",
+                "type": "boolean",
+                "value": "Extract.DISPLAY_TOTAL"
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const displayTotal = args[0],
+            regex = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig;
+
+        return search(input, regex, null, displayTotal);
+    }
+
+}
+
+export default ExtractDomains;

+ 49 - 0
src/core/operations/ExtractEmailAddresses.mjs

@@ -0,0 +1,49 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { search } from "../lib/Extract";
+
+/**
+ * Extract email addresses operation
+ */
+class ExtractEmailAddresses extends Operation {
+
+    /**
+     * ExtractEmailAddresses constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Extract email addresses";
+        this.module = "Regex";
+        this.description = "Extracts all email addresses from the input.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Display total",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const displayTotal = args[0],
+            regex = /\b\w[-.\w]*@[-\w]+(?:\.[-\w]+)*\.[A-Z]{2,4}\b/ig;
+
+        return search(input, regex, null, displayTotal);
+    }
+
+}
+
+export default ExtractEmailAddresses;

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

@@ -0,0 +1,79 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { search } from "../lib/Extract";
+/**
+ * Extract file paths operation
+ */
+class ExtractFilePaths extends Operation {
+
+    /**
+     * ExtractFilePaths constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Extract file paths";
+        this.module = "Regex";
+        this.description = "Extracts anything that looks like a Windows or UNIX file path.<br><br>Note that if UNIX is selected, there will likely be a lot of false positives.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Windows",
+                "type": "boolean",
+                "value": true
+            },
+            {
+                "name": "UNIX",
+                "type": "boolean",
+                "value": true
+            },
+            {
+                "name": "Display total",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const includeWinPath = args[0],
+            includeUnixPath = args[1],
+            displayTotal = args[2],
+            winDrive = "[A-Z]:\\\\",
+            winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}",
+            winExt = "[A-Z\\d]{1,6}",
+            winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName +
+                "(?:\\." + winExt + ")?",
+            unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+";
+        let filePaths = "";
+
+        if (includeWinPath && includeUnixPath) {
+            filePaths = winPath + "|" + unixPath;
+        } else if (includeWinPath) {
+            filePaths = winPath;
+        } else if (includeUnixPath) {
+            filePaths = unixPath;
+        }
+
+        if (filePaths) {
+            const regex = new RegExp(filePaths, "ig");
+            return search(input, regex, null, displayTotal);
+        } else {
+            return "";
+        }
+    }
+
+}
+
+export default ExtractFilePaths;

+ 94 - 0
src/core/operations/ExtractIPAddresses.mjs

@@ -0,0 +1,94 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { search } from "../lib/Extract";
+
+/**
+ * Extract IP addresses operation
+ */
+class ExtractIPAddresses extends Operation {
+
+    /**
+     * ExtractIPAddresses constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Extract IP addresses";
+        this.module = "Regex";
+        this.description = "Extracts all IPv4 and IPv6 addresses.<br><br>Warning: Given a string <code>710.65.0.456</code>, this will match <code>10.65.0.45</code> so always check the original input!";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "IPv4",
+                "type": "boolean",
+                "value": true
+            },
+            {
+                "name": "IPv6",
+                "type": "boolean",
+                "value": false
+            },
+            {
+                "name": "Remove local IPv4 addresses",
+                "type": "boolean",
+                "value": false
+            },
+            {
+                "name": "Display total",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const includeIpv4  = args[0],
+            includeIpv6  = args[1],
+            removeLocal  = args[2],
+            displayTotal = args[3],
+            ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?",
+            ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})";
+        let ips  = "";
+
+        if (includeIpv4 && includeIpv6) {
+            ips = ipv4 + "|" + ipv6;
+        } else if (includeIpv4) {
+            ips = ipv4;
+        } else if (includeIpv6) {
+            ips = ipv6;
+        }
+
+        if (ips) {
+            const regex = new RegExp(ips, "ig");
+
+            if (removeLocal) {
+                const ten = "10\\..+",
+                    oneninetwo = "192\\.168\\..+",
+                    oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
+                    onetwoseven = "127\\..+",
+                    removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
+                        "|" + oneseventwo + "|" + onetwoseven + ")");
+
+                return search(input, regex, removeRegex, displayTotal);
+            } else {
+                return search(input, regex, null, displayTotal);
+            }
+        } else {
+            return "";
+        }
+    }
+
+}
+
+export default ExtractIPAddresses;

+ 49 - 0
src/core/operations/ExtractMACAddresses.mjs

@@ -0,0 +1,49 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { search } from "../lib/Extract";
+
+/**
+ * Extract MAC addresses operation
+ */
+class ExtractMACAddresses extends Operation {
+
+    /**
+     * ExtractMACAddresses constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Extract MAC addresses";
+        this.module = "Regex";
+        this.description = "Extracts all Media Access Control (MAC) addresses from the input.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Display total",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const displayTotal = args[0],
+            regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig;
+
+        return search(input, regex, null, displayTotal);
+    }
+
+}
+
+export default ExtractMACAddresses;

+ 55 - 0
src/core/operations/ExtractURLs.mjs

@@ -0,0 +1,55 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import { search } from "../lib/Extract";
+
+/**
+ * Extract URLs operation
+ */
+class ExtractURLs extends Operation {
+
+    /**
+     * ExtractURLs constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Extract URLs";
+        this.module = "Regex";
+        this.description = "Extracts Uniform Resource Locators (URLs) from the input. The protocol (http, ftp etc.) is required otherwise there will be far too many false positives.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Display total",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const displayTotal = args[0],
+            protocol = "[A-Z]+://",
+            hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+",
+            port = ":\\d+";
+        let path = "/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*";
+
+        path += "(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*";
+        const regex = new RegExp(protocol + hostname + "(?:" + port +
+            ")?(?:" + path + ")?", "ig");
+        return search(input, regex, null, displayTotal);
+    }
+
+}
+
+export default ExtractURLs;

+ 2 - 1
src/core/operations/Filter.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import Utils from "../Utils";
 import {INPUT_DELIM_OPTIONS} from "../lib/Delim";
+import OperationError from "../errors/OperationError";
 
 /**
  * Filter operation
@@ -56,7 +57,7 @@ class Filter extends Operation {
         try {
             regex = new RegExp(args[1]);
         } catch (err) {
-            return "Invalid regex. Details: " + err.message;
+            throw new OperationError(`Invalid regex. Details: ${err.message}`);
         }
 
         const regexFilter = function(value) {

+ 4 - 1
src/core/operations/FromCharcode.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import Utils from "../Utils";
 import {DELIM_OPTIONS} from "../lib/Delim";
+import OperationError from "../errors/OperationError";
 
 /**
  * From Charcode operation
@@ -42,6 +43,8 @@ class FromCharcode extends Operation {
      * @param {string} input
      * @param {Object[]} args
      * @returns {byteArray}
+     *
+     * @throws {OperationError} if base out of range
      */
     run(input, args) {
         const delim = Utils.charRep(args[0] || "Space"),
@@ -50,7 +53,7 @@ class FromCharcode extends Operation {
             i = 0;
 
         if (base < 2 || base > 36) {
-            throw "Error: Base argument must be between 2 and 36";
+            throw new OperationError("Error: Base argument must be between 2 and 36");
         }
 
         if (input.length === 0) {

+ 4 - 1
src/core/operations/FromUNIXTimestamp.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import moment from "moment-timezone";
 import {UNITS} from "../lib/DateTime";
+import OperationError from "../errors/OperationError";
 
 /**
  * From UNIX Timestamp operation
@@ -37,6 +38,8 @@ class FromUNIXTimestamp extends Operation {
      * @param {number} input
      * @param {Object[]} args
      * @returns {string}
+     *
+     * @throws {OperationError} if invalid unit
      */
     run(input, args) {
         const units = args[0];
@@ -57,7 +60,7 @@ class FromUNIXTimestamp extends Operation {
             d = moment(input / 1000000);
             return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC";
         } else {
-            throw "Unrecognised unit";
+            throw new OperationError("Unrecognised unit");
         }
     }
 

+ 115 - 0
src/core/operations/GeneratePGPKeyPair.mjs

@@ -0,0 +1,115 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [matt@artemisbot.uk]
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import kbpgp from "kbpgp";
+import { getSubkeySize, ASP } from "../lib/PGP";
+import promisifyDefault from "es6-promisify";
+const promisify = promisifyDefault.promisify;
+/**
+ * Generate PGP Key Pair operation
+ */
+class GeneratePGPKeyPair extends Operation {
+
+    /**
+     * GeneratePGPKeyPair constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Generate PGP Key Pair";
+        this.module = "PGP";
+        this.description = "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Key type",
+                "type": "option",
+                "value": ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384"]
+            },
+            {
+                "name": "Password (optional)",
+                "type": "string",
+                "value": ""
+            },
+            {
+                "name": "Name (optional)",
+                "type": "string",
+                "value": ""
+            },
+            {
+                "name": "Email (optional)",
+                "type": "string",
+                "value": ""
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const [keyType, keySize] = args[0].split("-"),
+            password = args[1],
+            name = args[2],
+            email = args[3];
+        let userIdentifier = "";
+
+        if (name) userIdentifier += name;
+        if (email) userIdentifier += ` <${email}>`;
+
+        let flags = kbpgp.const.openpgp.certify_keys;
+        flags |= kbpgp.const.openpgp.sign_data;
+        flags |= kbpgp.const.openpgp.auth;
+        flags |= kbpgp.const.openpgp.encrypt_comm;
+        flags |= kbpgp.const.openpgp.encrypt_storage;
+
+        const keyGenerationOptions = {
+            userid: userIdentifier,
+            ecc: keyType === "ecc",
+            primary: {
+                "nbits": keySize,
+                "flags": flags,
+                "expire_in": 0
+            },
+            subkeys: [{
+                "nbits": getSubkeySize(keySize),
+                "flags": kbpgp.const.openpgp.sign_data,
+                "expire_in": 86400 * 365 * 8
+            }, {
+                "nbits": getSubkeySize(keySize),
+                "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage,
+                "expire_in": 86400 * 365 * 2
+            }],
+            asp: ASP
+        };
+
+        return new Promise(async (resolve, reject) => {
+            try {
+                const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions);
+                await promisify(unsignedKey.sign.bind(unsignedKey))({});
+
+                const signedKey = unsignedKey,
+                    privateKeyExportOptions = {};
+
+                if (password) privateKeyExportOptions.passphrase = password;
+                const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions);
+                const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({});
+                resolve(privateKey + "\n" + publicKey.trim());
+            } catch (err) {
+                reject(`Error whilst generating key pair: ${err}`);
+            }
+        });
+    }
+
+}
+
+export default GeneratePGPKeyPair;

+ 3 - 2
src/core/operations/HammingDistance.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import Utils from "../Utils";
 import {fromHex} from "../lib/Hex";
+import OperationError from "../errors/OperationError";
 
 /**
  * Hamming Distance operation
@@ -55,11 +56,11 @@ class HammingDistance extends Operation {
             samples = input.split(delim);
 
         if (samples.length !== 2) {
-            return "Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter.";
+            throw new OperationError("Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter.");
         }
 
         if (samples[0].length !== samples[1].length) {
-            return "Error: Both inputs must be of the same length.";
+            throw new OperationError("Error: Both inputs must be of the same length.");
         }
 
         if (inputType === "Hex") {

+ 2 - 1
src/core/operations/OffsetChecker.mjs

@@ -6,6 +6,7 @@
 
 import Operation from "../Operation";
 import Utils from "../Utils";
+import OperationError from "../errors/OperationError";
 
 /**
  * Offset checker operation
@@ -48,7 +49,7 @@ class OffsetChecker extends Operation {
             chr;
 
         if (!samples || samples.length < 2) {
-            return "Not enough samples, perhaps you need to modify the sample delimiter or add more data?";
+            throw new OperationError("Not enough samples, perhaps you need to modify the sample delimiter or add more data?");
         }
 
         // Initialise output strings

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

@@ -0,0 +1,79 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import kbpgp from "kbpgp";
+import { ASP, importPrivateKey } from "../lib/PGP";
+import OperationError from "../errors/OperationError";
+import promisifyDefault from "es6-promisify";
+const promisify = promisifyDefault.promisify;
+
+
+/**
+ * PGP Decrypt operation
+ */
+class PGPDecrypt extends Operation {
+
+    /**
+     * PGPDecrypt constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "PGP Decrypt";
+        this.module = "PGP";
+        this.description = "Input: the ASCII-armoured PGP message you want to decrypt.\n<br><br>\nArguments: the ASCII-armoured PGP private key of the recipient, \n(and the private key password if necessary).\n<br><br>\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n<br><br>\nThis function uses the Keybase implementation of PGP.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Private key of recipient",
+                "type": "text",
+                "value": ""
+            },
+            {
+                "name": "Private key passphrase",
+                "type": "string",
+                "value": ""
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     *
+     * @throws {OperationError} if invalid private key
+     */
+    async run(input, args) {
+        const encryptedMessage = input,
+            privateKey = args[0],
+            passphrase = args[1],
+            keyring = new kbpgp.keyring.KeyRing();
+        let plaintextMessage;
+
+        if (!privateKey) throw new OperationError("Enter the private key of the recipient.");
+
+        const key = await importPrivateKey(privateKey, passphrase);
+        keyring.add_key_manager(key);
+
+        try {
+            plaintextMessage = await promisify(kbpgp.unbox)({
+                armored: encryptedMessage,
+                keyfetch: keyring,
+                asp: ASP
+            });
+        } catch (err) {
+            throw new OperationError(`Couldn't decrypt message with provided private key: ${err}`);
+        }
+
+        return plaintextMessage.toString();
+    }
+
+}
+
+export default PGPDecrypt;

+ 113 - 0
src/core/operations/PGPDecryptAndVerify.mjs

@@ -0,0 +1,113 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import kbpgp from "kbpgp";
+import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP";
+import OperationError from "../errors/OperationError";
+import promisifyDefault from "es6-promisify";
+const promisify = promisifyDefault.promisify;
+
+/**
+ * PGP Decrypt and Verify operation
+ */
+class PGPDecryptAndVerify extends Operation {
+
+    /**
+     * PGPDecryptAndVerify constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "PGP Decrypt and Verify";
+        this.module = "PGP";
+        this.description = "Input: the ASCII-armoured encrypted PGP message you want to verify.\n<br><br>\nArguments: the ASCII-armoured PGP public key of the signer, \nthe ASCII-armoured private key of the recipient (and the private key password if necessary).\n<br><br>\nThis operation uses PGP to decrypt and verify an encrypted digital signature.\n<br><br>\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n<br><br>\nThis function uses the Keybase implementation of PGP.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Public key of signer",
+                "type": "text",
+                "value": ""
+            },
+            {
+                "name": "Private key of recipient",
+                "type": "text",
+                "value": ""
+            },
+            {
+                "name": "Private key password",
+                "type": "string",
+                "value": ""
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    async run(input, args) {
+        const signedMessage = input,
+            publicKey = args[0],
+            privateKey = args[1],
+            passphrase = args[2],
+            keyring = new kbpgp.keyring.KeyRing();
+        let unboxedLiterals;
+
+        if (!publicKey) throw new OperationError("Enter the public key of the signer.");
+        if (!privateKey) throw new OperationError("Enter the private key of the recipient.");
+        const privKey = await importPrivateKey(privateKey, passphrase);
+        const pubKey = await importPublicKey(publicKey);
+        keyring.add_key_manager(privKey);
+        keyring.add_key_manager(pubKey);
+
+        try {
+            unboxedLiterals = await promisify(kbpgp.unbox)({
+                armored: signedMessage,
+                keyfetch: keyring,
+                asp: ASP
+            });
+            const ds = unboxedLiterals[0].get_data_signer();
+            if (ds) {
+                const km = ds.get_key_manager();
+                if (km) {
+                    const signer = km.get_userids_mark_primary()[0].components;
+                    let text = "Signed by ";
+                    if (signer.email || signer.username || signer.comment) {
+                        if (signer.username) {
+                            text += `${signer.username} `;
+                        }
+                        if (signer.comment) {
+                            text += `${signer.comment} `;
+                        }
+                        if (signer.email) {
+                            text += `<${signer.email}>`;
+                        }
+                        text += "\n";
+                    }
+                    text += [
+                        `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`,
+                        `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`,
+                        "----------------------------------\n"
+                    ].join("\n");
+                    text += unboxedLiterals.toString();
+                    return text.trim();
+                } else {
+                    throw new OperationError("Could not identify a key manager.");
+                }
+            } else {
+                throw new OperationError("The data does not appear to be signed.");
+            }
+        } catch (err) {
+            throw new OperationError(`Couldn't verify message: ${err}`);
+        }
+    }
+
+}
+
+export default PGPDecryptAndVerify;

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

@@ -0,0 +1,77 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import kbpgp from "kbpgp";
+import { ASP } from "../lib/PGP";
+import OperationError from "../errors/OperationError";
+import promisifyDefault from "es6-promisify";
+const promisify = promisifyDefault.promisify;
+
+/**
+ * PGP Encrypt operation
+ */
+class PGPEncrypt extends Operation {
+
+    /**
+     * PGPEncrypt constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "PGP Encrypt";
+        this.module = "PGP";
+        this.description = "Input: the message you want to encrypt.\n<br><br>\nArguments: the ASCII-armoured PGP public key of the recipient.\n<br><br>\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n<br><br>\nThis function uses the Keybase implementation of PGP.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Public key of recipient",
+                "type": "text",
+                "value": ""
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     *
+     * @throws {OperationError} if failed private key import or failed encryption
+     */
+    async run(input, args) {
+        const plaintextMessage = input,
+            plainPubKey = args[0];
+        let key,
+            encryptedMessage;
+
+        if (!plainPubKey) throw new OperationError("Enter the public key of the recipient.");
+
+        try {
+            key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
+                armored: plainPubKey,
+            });
+        } catch (err) {
+            throw new OperationError(`Could not import public key: ${err}`);
+        }
+
+        try {
+            encryptedMessage = await promisify(kbpgp.box)({
+                "msg": plaintextMessage,
+                "encrypt_for": key,
+                "asp": ASP
+            });
+        } catch (err) {
+            throw new OperationError(`Couldn't encrypt message with provided public key: ${err}`);
+        }
+
+        return encryptedMessage.toString();
+    }
+
+}
+
+export default PGPEncrypt;

+ 84 - 0
src/core/operations/PGPEncryptAndSign.mjs

@@ -0,0 +1,84 @@
+/**
+ * @author tlwr [toby@toby.codes]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import kbpgp from "kbpgp";
+import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP";
+import OperationError from "../errors/OperationError";
+import promisifyDefault from "es6-promisify";
+const promisify = promisifyDefault.promisify;
+
+/**
+ * PGP Encrypt and Sign operation
+ */
+class PGPEncryptAndSign extends Operation {
+
+    /**
+     * PGPEncryptAndSign constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "PGP Encrypt and Sign";
+        this.module = "PGP";
+        this.description = "Input: the cleartext you want to sign.\n<br><br>\nArguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)\nand the ASCII-armoured PGP public key of the recipient.\n<br><br>\nThis operation uses PGP to produce an encrypted digital signature.\n<br><br>\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n<br><br>\nThis function uses the Keybase implementation of PGP.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Private key of signer",
+                "type": "text",
+                "value": ""
+            },
+            {
+                "name": "Private key passphrase",
+                "type": "string",
+                "value": ""
+            },
+            {
+                "name": "Public key of recipient",
+                "type": "text",
+                "value": ""
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     *
+     * @throws {OperationError} if failure to sign message
+     */
+    async run(input, args) {
+        const message = input,
+            privateKey = args[0],
+            passphrase = args[1],
+            publicKey = args[2];
+        let signedMessage;
+
+        if (!privateKey) throw new OperationError("Enter the private key of the signer.");
+        if (!publicKey) throw new OperationError("Enter the public key of the recipient.");
+        const privKey = await importPrivateKey(privateKey, passphrase);
+        const pubKey = await importPublicKey(publicKey);
+
+        try {
+            signedMessage = await promisify(kbpgp.box)({
+                "msg": message,
+                "encrypt_for": pubKey,
+                "sign_with": privKey,
+                "asp": ASP
+            });
+        } catch (err) {
+            throw new OperationError(`Couldn't sign message: ${err}`);
+        }
+
+        return signedMessage;
+    }
+
+}
+
+export default PGPEncryptAndSign;

+ 2 - 1
src/core/operations/ParseDateTime.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import moment from "moment-timezone";
 import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime";
+import OperationError from "../errors/OperationError";
 
 /**
  * Parse DateTime operation
@@ -59,7 +60,7 @@ class ParseDateTime extends Operation {
             date = moment.tz(input, inputFormat, inputTimezone);
             if (!date || date.format() === "Invalid date") throw Error;
         } catch (err) {
-            return "Invalid format.\n\n" + FORMAT_EXAMPLES;
+            throw new OperationError(`Invalid format.\n\n${FORMAT_EXAMPLES}`);
         }
 
         output += "Date: " + date.format("dddd Do MMMM YYYY") +

+ 2 - 1
src/core/operations/ParseUNIXFilePermissions.mjs

@@ -5,6 +5,7 @@
  */
 
 import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
 
 /**
  * Parse UNIX file permissions operation
@@ -169,7 +170,7 @@ class ParseUNIXFilePermissions extends Operation {
                 }
             }
         } else {
-            return "Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format.";
+            throw new OperationError("Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format.");
         }
 
         output += "Textual representation: " + permsToStr(perms);

+ 69 - 0
src/core/operations/ParseURI.mjs

@@ -0,0 +1,69 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import url from "url";
+
+/**
+ * Parse URI operation
+ */
+class ParseURI extends Operation {
+
+    /**
+     * ParseURI constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Parse URI";
+        this.module = "URL";
+        this.description = "Pretty prints complicated Uniform Resource Identifier (URI) strings for ease of reading. Particularly useful for Uniform Resource Locators (URLs) with a lot of arguments.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const uri = url.parse(input, true);
+
+        let output = "";
+
+        if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n";
+        if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n";
+        if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n";
+        if (uri.port) output += "Port:\t\t" + uri.port + "\n";
+        if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n";
+        if (uri.query) {
+            const keys = Object.keys(uri.query);
+            let padding = 0;
+
+            keys.forEach(k => {
+                padding = (k.length > padding) ? k.length : padding;
+            });
+
+            output += "Arguments:\n";
+            for (const key in uri.query) {
+                output += "\t" + key.padEnd(padding, " ");
+                if (uri.query[key].length) {
+                    output += " = " + uri.query[key] + "\n";
+                } else {
+                    output += "\n";
+                }
+            }
+        }
+        if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n";
+
+        return output;
+    }
+
+}
+
+export default ParseURI;

+ 2 - 1
src/core/operations/RawInflate.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import {INFLATE_BUFFER_TYPE} from "../lib/Zlib";
 import rawinflate from "zlibjs/bin/rawinflate.min";
+import OperationError from "../errors/OperationError";
 
 const Zlib = rawinflate.Zlib;
 
@@ -90,7 +91,7 @@ class RawInflate extends Operation {
             }
 
             if (!valid) {
-                throw "Error: Unable to inflate data";
+                throw new OperationError("Error: Unable to inflate data");
             }
         }
         // This seems to be the easiest way...

+ 2 - 1
src/core/operations/ShowBase64Offsets.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import Utils from "../Utils";
 import {fromBase64, toBase64} from "../lib/Base64";
+import OperationError from "../errors/OperationError";
 
 /**
  * Show Base64 offsets operation
@@ -58,7 +59,7 @@ class ShowBase64Offsets extends Operation {
             script = "<script type='application/javascript'>$('[data-toggle=\"tooltip\"]').tooltip()</script>";
 
         if (input.length < 1) {
-            return "Please enter a string.";
+            throw new OperationError("Please enter a string.");
         }
 
         // Highlight offset 0

+ 118 - 0
src/core/operations/Strings.mjs

@@ -0,0 +1,118 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import XRegExp from "xregexp";
+import { search } from "../lib/Extract";
+/**
+ * Strings operation
+ */
+class Strings extends Operation {
+
+    /**
+     * Strings constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Strings";
+        this.module = "Regex";
+        this.description = "Extracts all strings from the input.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Encoding",
+                "type": "option",
+                "value": ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"]
+            },
+            {
+                "name": "Minimum length",
+                "type": "number",
+                "value": 4
+            },
+            {
+                "name": "Match",
+                "type": "option",
+                "value": [
+                    "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)",
+                    "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)"
+                ]
+            },
+            {
+                "name": "Display total",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const encoding = args[0],
+            minLen = args[1],
+            matchType = args[2],
+            displayTotal = args[3],
+            alphanumeric = "A-Z\\d",
+            punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@",
+            printable = "\x20-\x7e",
+            uniAlphanumeric = "\\pL\\pN",
+            uniPunctuation = "\\pP\\pZ",
+            uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP";
+
+        let strings = "";
+
+        switch (matchType) {
+            case "Alphanumeric + punctuation (A)":
+                strings = `[${alphanumeric + punctuation}]`;
+                break;
+            case "All printable chars (A)":
+            case "Null-terminated strings (A)":
+                strings = `[${printable}]`;
+                break;
+            case "Alphanumeric + punctuation (U)":
+                strings = `[${uniAlphanumeric + uniPunctuation}]`;
+                break;
+            case "All printable chars (U)":
+            case "Null-terminated strings (U)":
+                strings = `[${uniPrintable}]`;
+                break;
+        }
+
+        // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars
+        switch (encoding) {
+            case "All":
+                strings = `(\x00?${strings}\x00?)`;
+                break;
+            case "16-bit littleendian":
+                strings = `(${strings}\x00)`;
+                break;
+            case "16-bit bigendian":
+                strings = `(\x00${strings})`;
+                break;
+            case "Single byte":
+            default:
+                break;
+        }
+
+        strings = `${strings}{${minLen},}`;
+
+        if (matchType.includes("Null-terminated")) {
+            strings += "\x00";
+        }
+
+        const regex = new XRegExp(strings, "ig");
+
+        return search(input, regex, null, displayTotal);
+    }
+
+}
+
+export default Strings;

+ 4 - 1
src/core/operations/TakeBytes.mjs

@@ -5,6 +5,7 @@
  */
 
 import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
 
 /**
  * Take bytes operation
@@ -45,6 +46,8 @@ class TakeBytes extends Operation {
      * @param {ArrayBuffer} input
      * @param {Object[]} args
      * @returns {ArrayBuffer}
+     *
+     * @throws {OperationError} if invalid value
      */
     run(input, args) {
         const start = args[0],
@@ -52,7 +55,7 @@ class TakeBytes extends Operation {
             applyToEachLine = args[2];
 
         if (start < 0 || length < 0)
-            throw "Error: Invalid value";
+            throw new OperationError("Error: Invalid value");
 
         if (!applyToEachLine)
             return input.slice(start, start+length);

+ 4 - 1
src/core/operations/ToCharcode.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import Utils from "../Utils";
 import {DELIM_OPTIONS} from "../lib/Delim";
+import OperationError from "../errors/OperationError";
 
 /**
  * To Charcode operation
@@ -42,6 +43,8 @@ class ToCharcode extends Operation {
      * @param {string} input
      * @param {Object[]} args
      * @returns {string}
+     *
+     * @throws {OperationError} if base argument out of range
      */
     run(input, args) {
         const delim = Utils.charRep(args[0] || "Space"),
@@ -51,7 +54,7 @@ class ToCharcode extends Operation {
             ordinal;
 
         if (base < 2 || base > 36) {
-            throw "Error: Base argument must be between 2 and 36";
+            throw new OperationError("Error: Base argument must be between 2 and 36");
         }
 
         const charcode = Utils.strToCharcode(input);

+ 4 - 1
src/core/operations/ToUNIXTimestamp.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import moment from "moment-timezone";
 import {UNITS} from "../lib/DateTime";
+import OperationError from "../errors/OperationError";
 
 /**
  * To UNIX Timestamp operation
@@ -47,6 +48,8 @@ class ToUNIXTimestamp extends Operation {
      * @param {string} input
      * @param {Object[]} args
      * @returns {string}
+     *
+     * @throws {OperationError} if unit unrecognised
      */
     run(input, args) {
         const [units, treatAsUTC, showDateTime] = args,
@@ -63,7 +66,7 @@ class ToUNIXTimestamp extends Operation {
         } else if (units === "Nanoseconds (ns)") {
             result = d.valueOf() * 1000000;
         } else {
-            throw "Unrecognised unit";
+            throw new OperationError("Unrecognised unit");
         }
 
         return showDateTime ? `${result} (${d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss")} UTC)` : result.toString();

+ 2 - 1
src/core/operations/TranslateDateTimeFormat.mjs

@@ -7,6 +7,7 @@
 import Operation from "../Operation";
 import moment from "moment-timezone";
 import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime";
+import OperationError from "../errors/OperationError";
 
 /**
  * Translate DateTime Format operation
@@ -67,7 +68,7 @@ class TranslateDateTimeFormat extends Operation {
             date = moment.tz(input, inputFormat, inputTimezone);
             if (!date || date.format() === "Invalid date") throw Error;
         } catch (err) {
-            return "Invalid format.\n\n" + FORMAT_EXAMPLES;
+            throw new OperationError(`Invalid format.\n\n${FORMAT_EXAMPLES}`);
         }
 
         return date.tz(outputTimezone).format(outputFormat);

+ 44 - 0
src/core/operations/URLDecode.mjs

@@ -0,0 +1,44 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * URL Decode operation
+ */
+class URLDecode extends Operation {
+
+    /**
+     * URLDecode constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "URL Decode";
+        this.module = "URL";
+        this.description = "Converts URI/URL percent-encoded characters back to their raw values.<br><br>e.g. <code>%3d</code> becomes <code>=</code>";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const data = input.replace(/\+/g, "%20");
+        try {
+            return decodeURIComponent(data);
+        } catch (err) {
+            return unescape(data);
+        }
+    }
+
+}
+
+export default URLDecode;

+ 68 - 0
src/core/operations/URLEncode.mjs

@@ -0,0 +1,68 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * URL Encode operation
+ */
+class URLEncode extends Operation {
+
+    /**
+     * URLEncode constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "URL Encode";
+        this.module = "URL";
+        this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.<br><br>e.g. <code>=</code> becomes <code>%3d</code>";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Encode all special chars",
+                "type": "boolean",
+                "value": false
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const encodeAll = args[0];
+        return encodeAll ? this.encodeAllChars(input) : encodeURI(input);
+    }
+
+    /**
+     * Encode characters in URL outside of encodeURI() function spec
+     *
+     * @param {string} str
+     * @returns {string}
+     */
+    encodeAllChars (str) {
+        //TODO Do this programatically
+        return encodeURIComponent(str)
+            .replace(/!/g, "%21")
+            .replace(/#/g, "%23")
+            .replace(/'/g, "%27")
+            .replace(/\(/g, "%28")
+            .replace(/\)/g, "%29")
+            .replace(/\*/g, "%2A")
+            .replace(/-/g, "%2D")
+            .replace(/\./g, "%2E")
+            .replace(/_/g, "%5F")
+            .replace(/~/g, "%7E");
+    }
+
+}
+
+
+export default URLEncode;

+ 0 - 333
src/core/operations/legacy/Extract.js

@@ -1,333 +0,0 @@
-import XRegExp from "xregexp";
-
-
-/**
- * Identifier extraction operations.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @namespace
- */
-const Extract = {
-
-    /**
-     * Runs search operations across the input data using regular expressions.
-     *
-     * @private
-     * @param {string} input
-     * @param {RegExp} searchRegex
-     * @param {RegExp} removeRegex - A regular expression defining results to remove from the
-     *      final list
-     * @param {boolean} includeTotal - Whether or not to include the total number of results
-     * @returns {string}
-     */
-    _search: function(input, searchRegex, removeRegex, includeTotal) {
-        let output = "",
-            total = 0,
-            match;
-
-        while ((match = searchRegex.exec(input))) {
-            // Moves pointer when an empty string is matched (prevents infinite loop)
-            if (match.index === searchRegex.lastIndex) {
-                searchRegex.lastIndex++;
-            }
-
-            if (removeRegex && removeRegex.test(match[0]))
-                continue;
-            total++;
-            output += match[0] + "\n";
-        }
-
-        if (includeTotal)
-            output = "Total found: " + total + "\n\n" + output;
-
-        return output;
-    },
-
-
-    /**
-     * @constant
-     * @default
-     */
-    MIN_STRING_LEN: 4,
-    /**
-     * @constant
-     * @default
-     */
-    STRING_MATCH_TYPE: [
-        "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)",
-        "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)"
-    ],
-    /**
-     * @constant
-     * @default
-     */
-    ENCODING_LIST: ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"],
-    /**
-     * @constant
-     * @default
-     */
-    DISPLAY_TOTAL: false,
-
-    /**
-     * Strings operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runStrings: function(input, args) {
-        const encoding = args[0],
-            minLen = args[1],
-            matchType = args[2],
-            displayTotal = args[3],
-            alphanumeric = "A-Z\\d",
-            punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@",
-            printable = "\x20-\x7e",
-            uniAlphanumeric = "\\pL\\pN",
-            uniPunctuation = "\\pP\\pZ",
-            uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP";
-
-        let strings = "";
-
-        switch (matchType) {
-            case "Alphanumeric + punctuation (A)":
-                strings = `[${alphanumeric + punctuation}]`;
-                break;
-            case "All printable chars (A)":
-            case "Null-terminated strings (A)":
-                strings = `[${printable}]`;
-                break;
-            case "Alphanumeric + punctuation (U)":
-                strings = `[${uniAlphanumeric + uniPunctuation}]`;
-                break;
-            case "All printable chars (U)":
-            case "Null-terminated strings (U)":
-                strings = `[${uniPrintable}]`;
-                break;
-        }
-
-        // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars
-        switch (encoding) {
-            case "All":
-                strings = `(\x00?${strings}\x00?)`;
-                break;
-            case "16-bit littleendian":
-                strings = `(${strings}\x00)`;
-                break;
-            case "16-bit bigendian":
-                strings = `(\x00${strings})`;
-                break;
-            case "Single byte":
-            default:
-                break;
-        }
-
-        strings = `${strings}{${minLen},}`;
-
-        if (matchType.includes("Null-terminated")) {
-            strings += "\x00";
-        }
-
-        const regex = new XRegExp(strings, "ig");
-
-        return Extract._search(input, regex, null, displayTotal);
-    },
-
-
-    /**
-     * @constant
-     * @default
-     */
-    INCLUDE_IPV4: true,
-    /**
-     * @constant
-     * @default
-     */
-    INCLUDE_IPV6: false,
-    /**
-     * @constant
-     * @default
-     */
-    REMOVE_LOCAL: false,
-
-    /**
-     * Extract IP addresses operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runIp: function(input, args) {
-        let includeIpv4  = args[0],
-            includeIpv6  = args[1],
-            removeLocal  = args[2],
-            displayTotal = args[3],
-            ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?",
-            ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})",
-            ips  = "";
-
-        if (includeIpv4 && includeIpv6) {
-            ips = ipv4 + "|" + ipv6;
-        } else if (includeIpv4) {
-            ips = ipv4;
-        } else if (includeIpv6) {
-            ips = ipv6;
-        }
-
-        if (ips) {
-            const regex = new RegExp(ips, "ig");
-
-            if (removeLocal) {
-                let ten = "10\\..+",
-                    oneninetwo = "192\\.168\\..+",
-                    oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
-                    onetwoseven = "127\\..+",
-                    removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
-                        "|" + oneseventwo + "|" + onetwoseven + ")");
-
-                return Extract._search(input, regex, removeRegex, displayTotal);
-            } else {
-                return Extract._search(input, regex, null, displayTotal);
-            }
-        } else {
-            return "";
-        }
-    },
-
-
-    /**
-     * Extract email addresses operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runEmail: function(input, args) {
-        let displayTotal = args[0],
-            regex = /\b\w[-.\w]*@[-\w]+(?:\.[-\w]+)*\.[A-Z]{2,4}\b/ig;
-
-        return Extract._search(input, regex, null, displayTotal);
-    },
-
-
-    /**
-     * Extract MAC addresses operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runMac: function(input, args) {
-        let displayTotal = args[0],
-            regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig;
-
-        return Extract._search(input, regex, null, displayTotal);
-    },
-
-
-    /**
-     * Extract URLs operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runUrls: function(input, args) {
-        let displayTotal = args[0],
-            protocol = "[A-Z]+://",
-            hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+",
-            port = ":\\d+",
-            path = "/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*";
-
-        path += "(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*";
-        const regex = new RegExp(protocol + hostname + "(?:" + port +
-            ")?(?:" + path + ")?", "ig");
-        return Extract._search(input, regex, null, displayTotal);
-    },
-
-
-    /**
-     * Extract domains operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runDomains: function(input, args) {
-        const displayTotal = args[0],
-            regex = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig;
-
-        return Extract._search(input, regex, null, displayTotal);
-    },
-
-
-    /**
-     * @constant
-     * @default
-     */
-    INCLUDE_WIN_PATH: true,
-    /**
-     * @constant
-     * @default
-     */
-    INCLUDE_UNIX_PATH: true,
-
-    /**
-     * Extract file paths operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runFilePaths: function(input, args) {
-        let includeWinPath = args[0],
-            includeUnixPath = args[1],
-            displayTotal = args[2],
-            winDrive = "[A-Z]:\\\\",
-            winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}",
-            winExt = "[A-Z\\d]{1,6}",
-            winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName +
-                "(?:\\." + winExt + ")?",
-            unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+",
-            filePaths = "";
-
-        if (includeWinPath && includeUnixPath) {
-            filePaths = winPath + "|" + unixPath;
-        } else if (includeWinPath) {
-            filePaths = winPath;
-        } else if (includeUnixPath) {
-            filePaths = unixPath;
-        }
-
-        if (filePaths) {
-            const regex = new RegExp(filePaths, "ig");
-            return Extract._search(input, regex, null, displayTotal);
-        } else {
-            return "";
-        }
-    },
-
-
-    /**
-     * Extract dates operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runDates: function(input, args) {
-        let displayTotal = args[0],
-            date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd
-            date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy
-            date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy
-            regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig");
-
-        return Extract._search(input, regex, null, displayTotal);
-    },
-
-};
-
-export default Extract;

+ 0 - 364
src/core/operations/legacy/PGP.js

@@ -1,364 +0,0 @@
-import * as kbpgp from "kbpgp";
-import {promisify} from "es6-promisify";
-
-
-/**
- * PGP operations.
- *
- * @author tlwr [toby@toby.codes]
- * @author Matt C [matt@artemisbot.uk]
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2017
- * @license Apache-2.0
- *
- * @namespace
- */
-const PGP = {
-
-    /**
-     * @constant
-     * @default
-     */
-    KEY_TYPES: ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384"],
-
-
-    /**
-     * Get size of subkey
-     *
-     * @private
-     * @param {number} keySize
-     * @returns {number}
-     */
-    _getSubkeySize(keySize) {
-        return {
-            1024: 1024,
-            2048: 1024,
-            4096: 2048,
-            256:   256,
-            384:   256,
-        }[keySize];
-    },
-
-
-    /**
-     * Progress callback
-     *
-     * @private
-     */
-    _ASP: new kbpgp.ASP({
-        "progress_hook": info => {
-            let msg = "";
-
-            switch (info.what) {
-                case "guess":
-                    msg = "Guessing a prime";
-                    break;
-                case "fermat":
-                    msg = "Factoring prime using Fermat's factorization method";
-                    break;
-                case "mr":
-                    msg = "Performing Miller-Rabin primality test";
-                    break;
-                case "passed_mr":
-                    msg = "Passed Miller-Rabin primality test";
-                    break;
-                case "failed_mr":
-                    msg = "Failed Miller-Rabin primality test";
-                    break;
-                case "found":
-                    msg = "Prime found";
-                    break;
-                default:
-                    msg = `Stage: ${info.what}`;
-            }
-
-            if (ENVIRONMENT_IS_WORKER())
-                self.sendStatusMessage(msg);
-        }
-    }),
-
-
-    /**
-     * Import private key and unlock if necessary
-     *
-     * @private
-     * @param {string} privateKey
-     * @param {string} [passphrase]
-     * @returns {Object}
-     */
-    async _importPrivateKey(privateKey, passphrase) {
-        try {
-            const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
-                armored: privateKey,
-                opts: {
-                    "no_check_keys": true
-                }
-            });
-            if (key.is_pgp_locked()) {
-                if (passphrase) {
-                    await promisify(key.unlock_pgp.bind(key))({
-                        passphrase
-                    });
-                } else {
-                    throw "Did not provide passphrase with locked private key.";
-                }
-            }
-            return key;
-        } catch (err) {
-            throw `Could not import private key: ${err}`;
-        }
-    },
-
-
-    /**
-     * Import public key
-     *
-     * @private
-     * @param {string} publicKey
-     * @returns {Object}
-     */
-    async _importPublicKey (publicKey) {
-        try {
-            const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
-                armored: publicKey,
-                opts: {
-                    "no_check_keys": true
-                }
-            });
-            return key;
-        } catch (err) {
-            throw `Could not import public key: ${err}`;
-        }
-    },
-
-
-    /**
-     * Generate PGP Key Pair operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runGenerateKeyPair(input, args) {
-        let [keyType, keySize] = args[0].split("-"),
-            password = args[1],
-            name = args[2],
-            email = args[3],
-            userIdentifier = "";
-
-        if (name) userIdentifier += name;
-        if (email) userIdentifier += ` <${email}>`;
-
-        let flags = kbpgp.const.openpgp.certify_keys;
-        flags |= kbpgp.const.openpgp.sign_data;
-        flags |= kbpgp.const.openpgp.auth;
-        flags |= kbpgp.const.openpgp.encrypt_comm;
-        flags |= kbpgp.const.openpgp.encrypt_storage;
-
-        let keyGenerationOptions = {
-            userid: userIdentifier,
-            ecc: keyType === "ecc",
-            primary: {
-                "nbits": keySize,
-                "flags": flags,
-                "expire_in": 0
-            },
-            subkeys: [{
-                "nbits": PGP._getSubkeySize(keySize),
-                "flags": kbpgp.const.openpgp.sign_data,
-                "expire_in": 86400 * 365 * 8
-            }, {
-                "nbits": PGP._getSubkeySize(keySize),
-                "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage,
-                "expire_in": 86400 * 365 * 2
-            }],
-            asp: PGP._ASP
-        };
-
-        return new Promise(async (resolve, reject) => {
-            try {
-                const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions);
-                await promisify(unsignedKey.sign.bind(unsignedKey))({});
-                let signedKey = unsignedKey;
-                let privateKeyExportOptions = {};
-                if (password) privateKeyExportOptions.passphrase = password;
-                const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions);
-                const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({});
-                resolve(privateKey + "\n" + publicKey.trim());
-            } catch (err) {
-                reject(`Error whilst generating key pair: ${err}`);
-            }
-        });
-    },
-
-
-    /**
-     * PGP Encrypt operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    async runEncrypt(input, args) {
-        let plaintextMessage = input,
-            plainPubKey = args[0],
-            key,
-            encryptedMessage;
-
-        if (!plainPubKey) return "Enter the public key of the recipient.";
-
-        try {
-            key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
-                armored: plainPubKey,
-            });
-        } catch (err) {
-            throw `Could not import public key: ${err}`;
-        }
-
-        try {
-            encryptedMessage = await promisify(kbpgp.box)({
-                "msg": plaintextMessage,
-                "encrypt_for": key,
-                "asp": PGP._ASP
-            });
-        } catch (err) {
-            throw `Couldn't encrypt message with provided public key: ${err}`;
-        }
-
-        return encryptedMessage.toString();
-    },
-
-
-    /**
-     * PGP Decrypt operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    async runDecrypt(input, args) {
-        let encryptedMessage = input,
-            privateKey = args[0],
-            passphrase = args[1],
-            keyring = new kbpgp.keyring.KeyRing(),
-            plaintextMessage;
-
-        if (!privateKey) return "Enter the private key of the recipient.";
-
-        const key = await PGP._importPrivateKey(privateKey, passphrase);
-        keyring.add_key_manager(key);
-
-        try {
-            plaintextMessage = await promisify(kbpgp.unbox)({
-                armored: encryptedMessage,
-                keyfetch: keyring,
-                asp: PGP._ASP
-            });
-        } catch (err) {
-            throw `Couldn't decrypt message with provided private key: ${err}`;
-        }
-
-        return plaintextMessage.toString();
-    },
-
-
-    /**
-     * PGP Sign Message operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    async runSign(input, args) {
-        let message = input,
-            privateKey = args[0],
-            passphrase = args[1],
-            publicKey = args[2],
-            signedMessage;
-
-        if (!privateKey) return "Enter the private key of the signer.";
-        if (!publicKey) return "Enter the public key of the recipient.";
-        const privKey = await PGP._importPrivateKey(privateKey, passphrase);
-        const pubKey = await PGP._importPublicKey(publicKey);
-
-        try {
-            signedMessage = await promisify(kbpgp.box)({
-                "msg": message,
-                "encrypt_for": pubKey,
-                "sign_with": privKey,
-                "asp": PGP._ASP
-            });
-        } catch (err) {
-            throw `Couldn't sign message: ${err}`;
-        }
-
-        return signedMessage;
-    },
-
-
-    /**
-     * PGP Verify Message operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    async runVerify(input, args) {
-        let signedMessage = input,
-            publicKey = args[0],
-            privateKey = args[1],
-            passphrase = args[2],
-            keyring = new kbpgp.keyring.KeyRing(),
-            unboxedLiterals;
-
-        if (!publicKey) return "Enter the public key of the signer.";
-        if (!privateKey) return "Enter the private key of the recipient.";
-        const privKey = await PGP._importPrivateKey(privateKey, passphrase);
-        const pubKey = await PGP._importPublicKey(publicKey);
-        keyring.add_key_manager(privKey);
-        keyring.add_key_manager(pubKey);
-
-        try {
-            unboxedLiterals = await promisify(kbpgp.unbox)({
-                armored: signedMessage,
-                keyfetch: keyring,
-                asp: PGP._ASP
-            });
-            const ds = unboxedLiterals[0].get_data_signer();
-            if (ds) {
-                const km = ds.get_key_manager();
-                if (km) {
-                    const signer = km.get_userids_mark_primary()[0].components;
-                    let text = "Signed by ";
-                    if (signer.email || signer.username || signer.comment) {
-                        if (signer.username) {
-                            text += `${signer.username} `;
-                        }
-                        if (signer.comment) {
-                            text += `${signer.comment} `;
-                        }
-                        if (signer.email) {
-                            text += `<${signer.email}>`;
-                        }
-                        text += "\n";
-                    }
-                    text += [
-                        `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`,
-                        `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`,
-                        "----------------------------------\n"
-                    ].join("\n");
-                    text += unboxedLiterals.toString();
-                    return text.trim();
-                } else {
-                    return "Could not identify a key manager.";
-                }
-            } else {
-                return "The data does not appear to be signed.";
-            }
-        } catch (err) {
-            return `Couldn't verify message: ${err}`;
-        }
-    },
-};
-
-export default PGP;

+ 0 - 118
src/core/operations/legacy/URL.js

@@ -1,118 +0,0 @@
-/* globals unescape */
-import url from "url";
-
-
-/**
- * URL operations.
- * Namespace is appended with an underscore to prevent overwriting the global URL object.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @copyright Crown Copyright 2016
- * @license Apache-2.0
- *
- * @namespace
- */
-const URL_ = {
-
-    /**
-     * @constant
-     * @default
-     */
-    ENCODE_ALL: false,
-
-    /**
-     * URL Encode operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runTo: function(input, args) {
-        const encodeAll = args[0];
-        return encodeAll ? URL_._encodeAllChars(input) : encodeURI(input);
-    },
-
-
-    /**
-     * URL Decode operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runFrom: function(input, args) {
-        const data = input.replace(/\+/g, "%20");
-        try {
-            return decodeURIComponent(data);
-        } catch (err) {
-            return unescape(data);
-        }
-    },
-
-
-    /**
-     * Parse URI operation.
-     *
-     * @param {string} input
-     * @param {Object[]} args
-     * @returns {string}
-     */
-    runParse: function(input, args) {
-        const uri = url.parse(input, true);
-
-        let output = "";
-
-        if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n";
-        if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n";
-        if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n";
-        if (uri.port) output += "Port:\t\t" + uri.port + "\n";
-        if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n";
-        if (uri.query) {
-            let keys = Object.keys(uri.query),
-                padding = 0;
-
-            keys.forEach(k => {
-                padding = (k.length > padding) ? k.length : padding;
-            });
-
-            output += "Arguments:\n";
-            for (let key in uri.query) {
-                output += "\t" + key.padEnd(padding, " ");
-                if (uri.query[key].length) {
-                    output += " = " + uri.query[key] + "\n";
-                } else {
-                    output += "\n";
-                }
-            }
-        }
-        if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n";
-
-        return output;
-    },
-
-
-    /**
-     * URL encodes additional special characters beyond the standard set.
-     *
-     * @private
-     * @param {string} str
-     * @returns {string}
-     */
-    _encodeAllChars: function(str) {
-        //TODO Do this programatically
-        return encodeURIComponent(str)
-            .replace(/!/g, "%21")
-            .replace(/#/g, "%23")
-            .replace(/'/g, "%27")
-            .replace(/\(/g, "%28")
-            .replace(/\)/g, "%29")
-            .replace(/\*/g, "%2A")
-            .replace(/-/g, "%2D")
-            .replace(/\./g, "%2E")
-            .replace(/_/g, "%5F")
-            .replace(/~/g, "%7E");
-    },
-
-};
-
-export default URL_;

+ 13 - 13
src/core/vendor/DisassembleX86-64.js → src/core/vendor/DisassembleX86-64.mjs

@@ -3316,7 +3316,7 @@ If input "type" is set 5 it will adjust the mnemonic array to decode Centaur ins
 If input "type" is set 6 it will adjust the mnemonic array to decode instruction for the X86/486 CPU which conflict with the vector unit instructions with UMOV.
 -------------------------------------------------------------------------------------------------------------------------*/
 
-function CompatibilityMode( type )
+export function CompatibilityMode( type )
 {
   //Reset the changeable sections of the Mnemonics array, and operand encoding array.
   
@@ -3515,7 +3515,7 @@ The function "GetPosition()" Gives back the current base address in it's proper
 If the hex input is invalid returns false.
 -------------------------------------------------------------------------------------------------------------------------*/
 
-function LoadBinCode( HexStr )
+export function LoadBinCode( HexStr )
 {
   //Clear BinCode, and Reset Code Position in Bin Code array.
 
@@ -3605,7 +3605,7 @@ segment, and offset address. Note that the Code Segment is used in 16 bit code.
 if set 36, or higher. Effects instruction location in memory when decoding a program.
 -------------------------------------------------------------------------------------------------------------------------*/
 
-function SetBasePosition( Address )
+export function SetBasePosition( Address )
 {
   //Split the Segment:offset.
 
@@ -5652,7 +5652,7 @@ function Reset()
 do an linear disassemble.
 -------------------------------------------------------------------------------------------------------------------------*/
 
-function LDisassemble()
+export function LDisassemble()
 {
   var Instruction = ""; //Stores the Decoded instruction.
   var Out = "";  //The Disassemble output
@@ -5709,13 +5709,13 @@ function LDisassemble()
  * The following code has been added to expose public methods for use in CyberChef
  */
 
-export default {
-  LoadBinCode: LoadBinCode,
-  LDisassemble: LDisassemble,
-  SetBasePosition: SetBasePosition,
-  CompatibilityMode: CompatibilityMode,
-
-  setBitMode: val => { BitMode = val; },
-  setShowInstructionHex: val => { ShowInstructionHex = val; },
-  setShowInstructionPos: val => { ShowInstructionPos = val; },
+export function setBitMode (val) {
+  BitMode = val; 
+};
+export function setShowInstructionHex (val) {
+  ShowInstructionHex = val;
 };
+export function setShowInstructionPos (val) {
+  ShowInstructionPos = val; 
+};
+