From 12c226f874f75ea27a0fc98da300c788e52836dc Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 23 Mar 2018 20:01:56 +0000 Subject: [PATCH 01/11] Updated DisassembleX86-64 library to fix issue with call instrution. Closes #246. --- src/core/lib/DisassembleX86-64.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/lib/DisassembleX86-64.js b/src/core/lib/DisassembleX86-64.js index e350bd16..e320c6c2 100644 --- a/src/core/lib/DisassembleX86-64.js +++ b/src/core/lib/DisassembleX86-64.js @@ -1443,8 +1443,8 @@ const Operands = [ ------------------------------------------------------------------------------------------------------------------------*/ "10000004","10000004","10000004","10000004", "16000C00","170E0C00","0C001600","0C00170E", - "10020008", - "10020008", + "110E0008", + "110E0008", "0D060C01", //JMP Ap (w:z). "100000040004", "16001A01","170E1A01", @@ -5703,7 +5703,6 @@ function LDisassemble() } - //////////////////////////////////////////////////////////////////////////////////////////////// /* From 5ece79c74dda8fa4a1e77a8a8c6c1661e3e7717d Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 23 Mar 2018 20:02:03 +0000 Subject: [PATCH 02/11] 7.8.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e80565d..70628a32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "7.8.0", + "version": "7.8.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 68c19352..b9ef681a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "7.8.0", + "version": "7.8.1", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 2f5b0533d8ab37e2d71304ea106020713d261c5a Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 23 Mar 2018 20:08:53 +0000 Subject: [PATCH 03/11] Added note to 'From UNIX Timestamp' op regarding date formats. --- src/core/config/OperationConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 6a8f5d3b..7eed4876 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -2417,7 +2417,7 @@ const OperationConfig = { }, "From UNIX Timestamp": { module: "Default", - description: "Converts a UNIX timestamp to a datetime string.

e.g. 978346800 becomes Mon 1 January 2001 11:00:00 UTC

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).", + description: "Converts a UNIX timestamp to a datetime string.

e.g. 978346800 becomes Mon 1 January 2001 11:00:00 UTC

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).

Note that this operation supports various date formats including the US 'MM/DD/YYYY' format, but not the international 'DD/MM/YYYY' format. For dates in this format, use the 'Translate DateTime format' operation instead.", inputType: "number", outputType: "string", args: [ From 715ca1c2922c1d48fb4d53a7258585106f63d0db Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 26 Mar 2018 22:25:36 +0100 Subject: [PATCH 04/11] Added Bcrypt, Scrypt, BSON and string operations along with many new tests. --- Gruntfile.js | 2 +- package-lock.json | 30 +++- package.json | 4 + src/core/Utils.js | 42 +++-- src/core/config/Categories.js | 9 + src/core/config/OperationConfig.js | 152 ++++++++++++++++- src/core/config/modules/BSON.js | 22 +++ src/core/config/modules/Default.js | 2 + src/core/config/modules/Hashing.js | 4 + src/core/config/modules/OpModules.js | 2 + src/core/operations/BSON.js | 59 +++++++ src/core/operations/ByteRepr.js | 4 + src/core/operations/Code.js | 10 +- src/core/operations/DateTime.js | 20 ++- src/core/operations/Hash.js | 123 ++++++++++++++ src/core/operations/Hexdump.js | 2 +- src/core/operations/StrUtils.js | 65 +++----- src/core/operations/Unicode.js | 34 ++++ src/web/index.js | 1 + test/index.js | 3 + test/tests/operations/BSON.js | 56 +++++++ test/tests/operations/Base64.js | 119 ++++++++++++++ test/tests/operations/ByteRepr.js | 151 +++++++++++++++++ test/tests/operations/FlowControl.js | 100 +++++++++++- test/tests/operations/Hash.js | 79 +++++++++ test/tests/operations/Hexdump.js | 235 +++++++++++++++++++++++++++ test/tests/operations/StrUtils.js | 43 ++++- webpack.config.js | 1 - 28 files changed, 1290 insertions(+), 84 deletions(-) create mode 100644 src/core/config/modules/BSON.js create mode 100644 src/core/operations/BSON.js create mode 100755 test/tests/operations/BSON.js create mode 100644 test/tests/operations/Base64.js mode change 100644 => 100755 test/tests/operations/ByteRepr.js mode change 100644 => 100755 test/tests/operations/FlowControl.js mode change 100644 => 100755 test/tests/operations/Hash.js create mode 100755 test/tests/operations/Hexdump.js mode change 100644 => 100755 test/tests/operations/StrUtils.js diff --git a/Gruntfile.js b/Gruntfile.js index 4595a48d..7a9890f1 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -131,7 +131,7 @@ module.exports = function (grunt) { grunt.initConfig({ clean: { - dev: ["build/dev/*", "src/core/config/MetaConfig.js"], + dev: ["build/dev/*"], prod: ["build/prod/*", "src/core/config/MetaConfig.js"], test: ["build/test/*", "src/core/config/MetaConfig.js"], node: ["build/node/*", "src/core/config/MetaConfig.js"], diff --git a/package-lock.json b/package-lock.json index 70628a32..d6371b41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -519,6 +519,14 @@ "lodash": "4.17.5", "source-map": "0.5.7", "trim-right": "1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + } } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -1161,6 +1169,11 @@ "tweetnacl": "0.14.5" } }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, "big.js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", @@ -1440,6 +1453,11 @@ } } }, + "bson": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-2.0.4.tgz", + "integrity": "sha512-e/GPy6CE0xL7MOYYRMIEwPGKF21WNaQdPIpV0YvaQDoR7oc47KUZ8c2P/TlRJVQP8RZ4CEsArGBC1NbkCRvl1w==" + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -6877,10 +6895,9 @@ } }, "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" }, "jshint": { "version": "2.9.5", @@ -10473,6 +10490,11 @@ "ajv": "5.2.3" } }, + "scryptsy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.0.0.tgz", + "integrity": "sha1-Jiw28CMc+nZU4jY/o5TNLexm83g=" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/package.json b/package.json index b9ef681a..b896d2aa 100644 --- a/package.json +++ b/package.json @@ -70,10 +70,12 @@ }, "dependencies": { "babel-polyfill": "^6.26.0", + "bcryptjs": "^2.4.3", "bignumber.js": "^6.0.0", "bootstrap": "^3.3.7", "bootstrap-colorpicker": "^2.5.2", "bootstrap-switch": "^3.3.4", + "bson": "^2.0.4", "crypto-api": "^0.8.0", "crypto-js": "^3.1.9-1", "ctph.js": "0.0.5", @@ -88,6 +90,7 @@ "js-crc": "^0.2.0", "js-sha3": "^0.7.0", "jsbn": "^1.1.0", + "jsesc": "^2.5.1", "jsonpath": "^1.0.0", "jsrsasign": "8.0.6", "lodash": "^4.17.5", @@ -99,6 +102,7 @@ "node-md6": "^0.1.0", "nwmatcher": "^1.4.3", "otp": "^0.1.3", + "scryptsy": "^2.0.0", "sladex-blowfish": "^0.8.1", "sortablejs": "^1.7.0", "split.js": "^1.3.5", diff --git a/src/core/Utils.js b/src/core/Utils.js index 3a34af9e..6d80c21b 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -1,4 +1,5 @@ import utf8 from "utf8"; +import moment from "moment-timezone"; /** @@ -201,21 +202,34 @@ const Utils = { * Utils.parseEscapedChars("\\n"); */ parseEscapedChars: function(str) { - return str.replace(/(\\)?\\([nrtbf]|x[\da-fA-F]{2})/g, function(m, a, b) { + return str.replace(/(\\)?\\([bfnrtv0'"]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) { if (a === "\\") return "\\"+b; switch (b[0]) { - case "n": - return "\n"; - case "r": - return "\r"; - case "t": - return "\t"; + case "0": + return "\0"; case "b": return "\b"; + case "t": + return "\t"; + case "n": + return "\n"; + case "v": + return "\v"; case "f": return "\f"; + case "r": + return "\r"; + case '"': + return '"'; + case "'": + return "'"; case "x": - return Utils.chr(parseInt(b.substr(1), 16)); + return String.fromCharCode(parseInt(b.substr(1), 16)); + case "u": + if (b[1] === "{") + return String.fromCodePoint(parseInt(b.slice(2, -1), 16)); + else + return String.fromCharCode(parseInt(b.substr(1), 16)); } }); }, @@ -322,14 +336,14 @@ const Utils = { * @returns {string} * * @example - * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] - * Utils.convertToByteArray("Привет", "utf8"); + * // returns "Привет" + * Utils.convertToByteString("Привет", "utf8"); * - * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] - * Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex"); + * // returns "Здравствуйте" + * Utils.convertToByteString("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex"); * - * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] - * Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); + * // returns "Здравствуйте" + * Utils.convertToByteString("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); */ convertToByteString: function(str, type) { switch (type.toLowerCase()) { diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js index 6f04267f..61026d32 100755 --- a/src/core/config/Categories.js +++ b/src/core/config/Categories.js @@ -52,6 +52,7 @@ const Categories = [ "From HTML Entity", "URL Encode", "URL Decode", + "Escape Unicode Characters", "Unescape Unicode Characters", "To Quoted Printable", "From Quoted Printable", @@ -99,6 +100,8 @@ const Categories = [ "Substitute", "Derive PBKDF2 key", "Derive EVP key", + "Bcrypt", + "Scrypt", "Pseudo-Random Number Generator", ] }, @@ -275,6 +278,10 @@ const Categories = [ "Compare SSDEEP hashes", "Compare CTPH hashes", "HMAC", + "Bcrypt", + "Bcrypt compare", + "Bcrypt parse", + "Scrypt", "Fletcher-8 Checksum", "Fletcher-16 Checksum", "Fletcher-32 Checksum", @@ -311,6 +318,8 @@ const Categories = [ "To Snake case", "To Camel case", "To Kebab case", + "BSON serialise", + "BSON deserialise", ] }, { diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 7eed4876..71d3c46d 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -912,6 +912,34 @@ const OperationConfig = { } ] }, + "Escape Unicode Characters": { + module: "Default", + description: "Converts characters to their unicode-escaped notations.

Supports the prefixes:
  • \\u
  • %u
  • U+
e.g. σου becomes \\u03C3\\u03BF\\u03C5", + inputType: "string", + outputType: "string", + args: [ + { + name: "Prefix", + type: "option", + value: Unicode.PREFIXES + }, + { + name: "Encode all chars", + type: "boolean", + value: false + }, + { + name: "Padding", + type: "number", + value: 4 + }, + { + name: "Uppercase hex", + type: "boolean", + value: true + } + ] + }, "From Quoted Printable": { module: "Default", description: "Converts QP-encoded text back to standard text.", @@ -2417,7 +2445,7 @@ const OperationConfig = { }, "From UNIX Timestamp": { module: "Default", - description: "Converts a UNIX timestamp to a datetime string.

e.g. 978346800 becomes Mon 1 January 2001 11:00:00 UTC

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).

Note that this operation supports various date formats including the US 'MM/DD/YYYY' format, but not the international 'DD/MM/YYYY' format. For dates in this format, use the 'Translate DateTime format' operation instead.", + description: "Converts a UNIX timestamp to a datetime string.

e.g. 978346800 becomes Mon 1 January 2001 11:00:00 UTC

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).", inputType: "number", outputType: "string", args: [ @@ -2432,7 +2460,7 @@ const OperationConfig = { module: "Default", description: "Parses a datetime string in UTC and returns the corresponding UNIX timestamp.

e.g. Mon 1 January 2001 11:00:00 becomes 978346800

A UNIX timestamp is a 32-bit value representing the number of seconds since January 1, 1970 UTC (the UNIX epoch).", inputType: "string", - outputType: "number", + outputType: "string", args: [ { name: "Units", @@ -2443,6 +2471,11 @@ const OperationConfig = { name: "Treat as UTC", type: "boolean", value: DateTime.TREAT_AS_UTC + }, + { + name: "Show parsed datetime", + type: "boolean", + value: true } ] }, @@ -3554,14 +3587,40 @@ const OperationConfig = { }, "Escape string": { module: "Default", - description: "Escapes special characters in a string so that they do not cause conflicts. For example, Don't stop me now becomes Don\\'t stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
", + description: "Escapes special characters in a string so that they do not cause conflicts. For example, Don't stop me now becomes Don\\'t stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
  • \\unnnn (Unicode character)
  • \\u{nnnnnn} (Unicode code point)
", inputType: "string", outputType: "string", - args: [] + args: [ + { + name: "Escape level", + type: "option", + value: StrUtils.ESCAPE_LEVEL + }, + { + name: "Escape quote", + type: "option", + value: StrUtils.QUOTE_TYPES + }, + { + name: "JSON compatible", + type: "boolean", + value: false + }, + { + name: "ES6 compatible", + type: "boolean", + value: true + }, + { + name: "Uppercase hex", + type: "boolean", + value: false + } + ] }, "Unescape string": { module: "Default", - description: "Unescapes characters in a string that have been escaped. For example, Don\\'t stop me now becomes Don't stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
", + description: "Unescapes characters in a string that have been escaped. For example, Don\\'t stop me now becomes Don't stop me now.

Supports the following escape sequences:
  • \\n (Line feed/newline)
  • \\r (Carriage return)
  • \\t (Horizontal tab)
  • \\b (Backspace)
  • \\f (Form feed)
  • \\xnn (Hex, where n is 0-f)
  • \\\\ (Backslash)
  • \\' (Single quote)
  • \\" (Double quote)
  • \\unnnn (Unicode character)
  • \\u{nnnnnn} (Unicode code point)
", inputType: "string", outputType: "string", args: [] @@ -4018,7 +4077,88 @@ const OperationConfig = { inputType: "string", outputType: "number", args: [] - } + }, + "Bcrypt": { + module: "Hashing", + description: "bcrypt is a password hashing function designed by Niels Provos and David Mazières, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count (rounds) can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.

Enter the password in the input to generate its hash.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Rounds", + type: "number", + value: Hash.BCRYPT_ROUNDS + } + ] + }, + "Bcrypt compare": { + module: "Hashing", + description: "Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Hash", + type: "string", + value: "" + } + ] + }, + "Bcrypt parse": { + module: "Hashing", + description: "Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash.", + inputType: "string", + outputType: "string", + args: [] + }, + "Scrypt": { + module: "Hashing", + description: "scrypt is a password-based key derivation function (PBKDF) created by Colin Percival. The algorithm was specifically designed to make it costly to perform large-scale custom hardware attacks by requiring large amounts of memory. In 2016, the scrypt algorithm was published by IETF as RFC 7914.

Enter the password in the input to generate its hash.", + inputType: "string", + outputType: "string", + args: [ + { + name: "Salt", + type: "toggleString", + value: "", + toggleValues: Hash.KEY_FORMAT + }, + { + name: "Iterations (N)", + type: "number", + value: Hash.SCRYPT_ITERATIONS + }, + { + name: "Memory factor (r)", + type: "number", + value: Hash.SCRYPT_MEM_FACTOR + }, + { + name: "Parallelization factor (p)", + type: "number", + value: Hash.SCRYPT_PARALLEL_FACTOR + }, + { + name: "Key length", + type: "number", + value: Hash.SCRYPT_KEY_LENGTH + }, + ] + }, + "BSON serialise": { + module: "BSON", + description: "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.

Input data should be valid JSON.", + inputType: "string", + outputType: "ArrayBuffer", + args: [] + }, + "BSON deserialise": { + module: "BSON", + description: "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.

Input data should be in a raw bytes format.", + inputType: "ArrayBuffer", + outputType: "string", + args: [] + }, }; diff --git a/src/core/config/modules/BSON.js b/src/core/config/modules/BSON.js new file mode 100644 index 00000000..69a693f4 --- /dev/null +++ b/src/core/config/modules/BSON.js @@ -0,0 +1,22 @@ +import BSON from "../../operations/BSON.js"; + + +/** + * BSON module. + * + * Libraries: + * - bson + * - buffer + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +let OpModules = typeof self === "undefined" ? {} : self.OpModules || {}; + +OpModules.BSON = { + "BSON serialise": BSON.runBSONSerialise, + "BSON deserialise": BSON.runBSONDeserialise, +}; + +export default OpModules; diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js index d59b7b21..e5f070cf 100644 --- a/src/core/config/modules/Default.js +++ b/src/core/config/modules/Default.js @@ -43,6 +43,7 @@ import XKCD from "../../operations/XKCD.js"; * - otp * - crypto * - bignumber.js + * - jsesc * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2017 @@ -81,6 +82,7 @@ OpModules.Default = { "Strip HTML tags": HTML.runStripTags, "Parse colour code": HTML.runParseColourCode, "Unescape Unicode Characters": Unicode.runUnescape, + "Escape Unicode Characters": Unicode.runEscape, "To Quoted Printable": QuotedPrintable.runTo, "From Quoted Printable": QuotedPrintable.runFrom, "Swap endianness": Endian.runSwapEndianness, diff --git a/src/core/config/modules/Hashing.js b/src/core/config/modules/Hashing.js index 2b9ffea8..1598ff99 100644 --- a/src/core/config/modules/Hashing.js +++ b/src/core/config/modules/Hashing.js @@ -39,6 +39,10 @@ OpModules.Hashing = { "Compare CTPH hashes": Hash.runCompareCTPH, "Compare SSDEEP hashes": Hash.runCompareSSDEEP, "HMAC": Hash.runHMAC, + "Bcrypt": Hash.runBcrypt, + "Bcrypt compare": Hash.runBcryptCompare, + "Bcrypt parse": Hash.runBcryptParse, + "Scrypt": Hash.runScrypt, "Fletcher-8 Checksum": Checksum.runFletcher8, "Fletcher-16 Checksum": Checksum.runFletcher16, "Fletcher-32 Checksum": Checksum.runFletcher32, diff --git a/src/core/config/modules/OpModules.js b/src/core/config/modules/OpModules.js index 9a5e3ff5..cb6d2ffc 100644 --- a/src/core/config/modules/OpModules.js +++ b/src/core/config/modules/OpModules.js @@ -7,6 +7,7 @@ */ import OpModules from "./Default.js"; +import BSONModule from "./BSON.js"; import CharEncModule from "./CharEnc.js"; import CipherModule from "./Ciphers.js"; import CodeModule from "./Code.js"; @@ -24,6 +25,7 @@ import URLModule from "./URL.js"; Object.assign( OpModules, + BSONModule, CharEncModule, CipherModule, CodeModule, diff --git a/src/core/operations/BSON.js b/src/core/operations/BSON.js new file mode 100644 index 00000000..52ee98bc --- /dev/null +++ b/src/core/operations/BSON.js @@ -0,0 +1,59 @@ +import bsonjs from "bson"; +import {Buffer} from "buffer"; + + +/** + * BSON operations. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + * @namespace + */ +const BSON = { + + /** + * BSON serialise operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + runBSONSerialise(input, args) { + if (!input) return new ArrayBuffer(); + + const bson = new bsonjs(); + + try { + const data = JSON.parse(input); + return bson.serialize(data).buffer; + } catch (err) { + return err.toString(); + } + }, + + + /** + * BSON deserialise operation. + * + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + * + */ + runBSONDeserialise(input, args) { + if (!input.byteLength) return ""; + + const bson = new bsonjs(); + + try { + const data = bson.deserialize(new Buffer(input)); + return JSON.stringify(data, null, 2); + } catch (err) { + return err.toString(); + } + }, +}; + +export default BSON; diff --git a/src/core/operations/ByteRepr.js b/src/core/operations/ByteRepr.js index 986926ca..5546a22c 100755 --- a/src/core/operations/ByteRepr.js +++ b/src/core/operations/ByteRepr.js @@ -148,6 +148,10 @@ const ByteRepr = { throw "Error: Base argument must be between 2 and 36"; } + if (input.length === 0) { + return []; + } + if (base !== 16 && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); // Split into groups of 2 if the whole string is concatenated and diff --git a/src/core/operations/Code.js b/src/core/operations/Code.js index 42a9bbb4..ad777d01 100755 --- a/src/core/operations/Code.js +++ b/src/core/operations/Code.js @@ -483,12 +483,11 @@ const Code = { /** - * Converts to snake_case. + * To Snake Case operation. * * @param {string} input * @param {Object[]} args * @returns {string} - * */ runToSnakeCase(input, args) { const smart = args[0]; @@ -502,12 +501,11 @@ const Code = { /** - * Converts to camelCase. + * To Camel Case operation. * * @param {string} input * @param {Object[]} args * @returns {string} - * */ runToCamelCase(input, args) { const smart = args[0]; @@ -521,12 +519,11 @@ const Code = { /** - * Converts to kebab-case. + * To Kebab Case operation. * * @param {string} input * @param {Object[]} args * @returns {string} - * */ runToKebabCase(input, args) { const smart = args[0]; @@ -537,6 +534,7 @@ const Code = { return kebabCase(input); } }, + }; export default Code; diff --git a/src/core/operations/DateTime.js b/src/core/operations/DateTime.js index 9f87939d..b117a2ca 100755 --- a/src/core/operations/DateTime.js +++ b/src/core/operations/DateTime.js @@ -1,3 +1,6 @@ +import moment from "moment-timezone"; + + /** * Date and time operations. * @@ -57,24 +60,29 @@ const DateTime = { * * @param {string} input * @param {Object[]} args - * @returns {number} + * @returns {string} */ runToUnixTimestamp: function(input, args) { - let units = args[0], + const units = args[0], treatAsUTC = args[1], + showDateTime = args[2], d = treatAsUTC ? moment.utc(input) : moment(input); + let result = ""; + if (units === "Seconds (s)") { - return d.unix(); + result = d.unix(); } else if (units === "Milliseconds (ms)") { - return d.valueOf(); + result = d.valueOf(); } else if (units === "Microseconds (μs)") { - return d.valueOf() * 1000; + result = d.valueOf() * 1000; } else if (units === "Nanoseconds (ns)") { - return d.valueOf() * 1000000; + result = d.valueOf() * 1000000; } else { throw "Unrecognised unit"; } + + return showDateTime ? `${result} (${d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss")} UTC)` : result.toString(); }, diff --git a/src/core/operations/Hash.js b/src/core/operations/Hash.js index 1d125aa6..89142adb 100755 --- a/src/core/operations/Hash.js +++ b/src/core/operations/Hash.js @@ -5,6 +5,8 @@ import * as SHA3 from "js-sha3"; import Checksum from "./Checksum.js"; import ctph from "ctph.js"; import ssdeep from "ssdeep.js"; +import bcrypt from "bcryptjs"; +import scrypt from "scryptsy"; /** @@ -449,6 +451,127 @@ const Hash = { }, + /** + * @constant + * @default + */ + BCRYPT_ROUNDS: 10, + + /** + * Bcrypt operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runBcrypt: async function (input, args) { + const rounds = args[0]; + const salt = await bcrypt.genSalt(rounds); + + return await bcrypt.hash(input, salt, null, p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`); + }); + }, + + + /** + * Bcrypt compare operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runBcryptCompare: async function (input, args) { + const hash = args[0]; + + const match = await bcrypt.compare(input, hash, null, p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`); + }); + + return match ? "Match: " + input : "No match"; + }, + + + /** + * Bcrypt parse operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runBcryptParse: async function (input, args) { + try { + return `Rounds: ${bcrypt.getRounds(input)} +Salt: ${bcrypt.getSalt(input)} +Password hash: ${input.split(bcrypt.getSalt(input))[1]} +Full hash: ${input}`; + } catch (err) { + return "Error: " + err.toString(); + } + }, + + + /** + * @constant + * @default + */ + KEY_FORMAT: ["Hex", "Base64", "UTF8", "Latin1"], + /** + * @constant + * @default + */ + SCRYPT_ITERATIONS: 16384, + /** + * @constant + * @default + */ + SCRYPT_MEM_FACTOR: 8, + /** + * @constant + * @default + */ + SCRYPT_PARALLEL_FACTOR: 1, + /** + * @constant + * @default + */ + SCRYPT_KEY_LENGTH: 64, + + /** + * Scrypt operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runScrypt: function (input, args) { + const salt = Utils.convertToByteString(args[0].string || "", args[0].option), + iterations = args[1], + memFactor = args[2], + parallelFactor = args[3], + keyLength = args[4]; + + try { + const data = scrypt( + input, salt, iterations, memFactor, parallelFactor, keyLength, + p => { + // Progress callback + if (ENVIRONMENT_IS_WORKER()) + self.sendStatusMessage(`Progress: ${p.percent.toFixed(0)}%`); + } + ); + + return data.toString("hex"); + } catch (err) { + return "Error: " + err.toString(); + } + }, + + /** * Generate all hashes operation. * diff --git a/src/core/operations/Hexdump.js b/src/core/operations/Hexdump.js index fc907d9e..a10a7060 100755 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/Hexdump.js @@ -78,7 +78,7 @@ const Hexdump = { */ runFrom: function(input, args) { let output = [], - regex = /^\s*(?:[\dA-F]{4,16}:?)?\s*((?:[\dA-F]{2}\s){1,8}(?:\s|[\dA-F]{2}-)(?:[\dA-F]{2}\s){1,8}|(?:[\dA-F]{2}\s|[\dA-F]{4}\s)+)/igm, + regex = /^\s*(?:[\dA-F]{4,16}h?:?)?\s*((?:[\dA-F]{2}\s){1,8}(?:\s|[\dA-F]{2}-)(?:[\dA-F]{2}\s){1,8}|(?:[\dA-F]{2}\s|[\dA-F]{4}\s)+)/igm, block, line; while ((block = regex.exec(input))) { diff --git a/src/core/operations/StrUtils.js b/src/core/operations/StrUtils.js index 7e7f8b33..69781b1b 100755 --- a/src/core/operations/StrUtils.js +++ b/src/core/operations/StrUtils.js @@ -1,4 +1,5 @@ import Utils from "../Utils.js"; +import jsesc from "jsesc"; /** @@ -219,35 +220,45 @@ const StrUtils = { * @constant * @default */ - ESCAPE_REPLACEMENTS: [ - {"escaped": "\\\\", "unescaped": "\\"}, // Must be first - {"escaped": "\\'", "unescaped": "'"}, - {"escaped": "\\\"", "unescaped": "\""}, - {"escaped": "\\n", "unescaped": "\n"}, - {"escaped": "\\r", "unescaped": "\r"}, - {"escaped": "\\t", "unescaped": "\t"}, - {"escaped": "\\b", "unescaped": "\b"}, - {"escaped": "\\f", "unescaped": "\f"}, - ], + QUOTE_TYPES: ["Single", "Double", "Backtick"], + /** + * @constant + * @default + */ + ESCAPE_LEVEL: ["Special chars", "Everything", "Minimal"], /** * Escape string operation. * * @author Vel0x [dalemy@microsoft.com] + * @author n1474335 [n1474335@gmail.com] * * @param {string} input * @param {Object[]} args * @returns {string} * * @example - * StrUtils.runUnescape("Don't do that", []) + * StrUtils.runEscape("Don't do that", []) * > "Don\'t do that" - * StrUtils.runUnescape(`Hello + * StrUtils.runEscape(`Hello * World`, []) * > "Hello\nWorld" */ runEscape: function(input, args) { - return StrUtils._replaceByKeys(input, "unescaped", "escaped"); + const level = args[0], + quotes = args[1], + jsonCompat = args[2], + es6Compat = args[3], + lowercaseHex = !args[4]; + + return jsesc(input, { + quotes: quotes.toLowerCase(), + es6: es6Compat, + escapeEverything: level === "Everything", + minimal: level === "Minimal", + json: jsonCompat, + lowercaseHex: lowercaseHex, + }); }, @@ -255,6 +266,7 @@ const StrUtils = { * Unescape string operation. * * @author Vel0x [dalemy@microsoft.com] + * @author n1474335 [n1474335@gmail.com] * * @param {string} input * @param {Object[]} args @@ -268,32 +280,7 @@ const StrUtils = { * World` */ runUnescape: function(input, args) { - return StrUtils._replaceByKeys(input, "escaped", "unescaped"); - }, - - - /** - * Replaces all matching tokens in ESCAPE_REPLACEMENTS with the correction. The - * ordering is determined by the patternKey and the replacementKey. - * - * @author Vel0x [dalemy@microsoft.com] - * @author Matt C [matt@artemisbot.uk] - * - * @param {string} input - * @param {string} pattern_key - * @param {string} replacement_key - * @returns {string} - */ - _replaceByKeys: function(input, patternKey, replacementKey) { - let output = input; - - // Catch the \\x encoded characters - if (patternKey === "escaped") output = Utils.parseEscapedChars(input); - - StrUtils.ESCAPE_REPLACEMENTS.forEach(replacement => { - output = output.split(replacement[patternKey]).join(replacement[replacementKey]); - }); - return output; + return Utils.parseEscapedChars(input); }, diff --git a/src/core/operations/Unicode.js b/src/core/operations/Unicode.js index 16e9357f..104ef07e 100755 --- a/src/core/operations/Unicode.js +++ b/src/core/operations/Unicode.js @@ -50,6 +50,40 @@ const Unicode = { }, + /** + * Escape Unicode Characters operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runEscape: function(input, args) { + const regexWhitelist = /[ -~]/i, + prefix = args[0], + encodeAll = args[1], + padding = args[2], + uppercaseHex = args[3]; + + let output = "", + character = ""; + + for (let i = 0; i < input.length; i++) { + character = input[i]; + if (!encodeAll && regexWhitelist.test(character)) { + // It’s a printable ASCII character so don’t escape it. + output += character; + continue; + } + + let cp = character.codePointAt(0).toString(16); + if (uppercaseHex) cp = cp.toUpperCase(); + output += prefix + cp.padStart(padding, "0"); + } + + return output; + }, + + /** * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex. * diff --git a/src/web/index.js b/src/web/index.js index 965f2efc..e956335c 100755 --- a/src/web/index.js +++ b/src/web/index.js @@ -12,6 +12,7 @@ import "babel-polyfill"; import "bootstrap"; import "bootstrap-switch"; import "bootstrap-colorpicker"; +import moment from "moment-timezone"; import CanvasComponents from "../core/lib/canvascomponents.js"; // CyberChef diff --git a/test/index.js b/test/index.js index e58d7e20..3afabba0 100644 --- a/test/index.js +++ b/test/index.js @@ -14,8 +14,10 @@ import "babel-polyfill"; import TestRegister from "./TestRegister.js"; import "./tests/operations/Base58.js"; +import "./tests/operations/Base64.js"; import "./tests/operations/BCD.js"; import "./tests/operations/BitwiseOp.js"; +import "./tests/operations/BSON.js"; import "./tests/operations/ByteRepr.js"; import "./tests/operations/CharEnc.js"; import "./tests/operations/Cipher.js"; @@ -24,6 +26,7 @@ import "./tests/operations/Compress.js"; import "./tests/operations/DateTime.js"; import "./tests/operations/FlowControl.js"; import "./tests/operations/Hash.js"; +import "./tests/operations/Hexdump.js"; import "./tests/operations/Image.js"; import "./tests/operations/MorseCode.js"; import "./tests/operations/MS.js"; diff --git a/test/tests/operations/BSON.js b/test/tests/operations/BSON.js new file mode 100755 index 00000000..61ee10c4 --- /dev/null +++ b/test/tests/operations/BSON.js @@ -0,0 +1,56 @@ +/** + * BSON tests. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../TestRegister.js"; + +TestRegister.addTests([ + { + name: "BSON serialise: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "BSON serialise", + args: [], + }, + ], + }, + { + name: "BSON serialise: basic", + input: "{\"hello\":\"world\"}", + expectedOutput: "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00", + recipeConfig: [ + { + op: "BSON serialise", + args: [], + }, + ], + }, + { + name: "BSON deserialise: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "BSON deserialise", + args: [], + }, + ], + }, + { + name: "BSON deserialise: basic", + input: "\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00", + expectedOutput: "{\n \"hello\": \"world\"\n}", + recipeConfig: [ + { + op: "BSON deserialise", + args: [], + }, + ], + }, +]); diff --git a/test/tests/operations/Base64.js b/test/tests/operations/Base64.js new file mode 100644 index 00000000..e8b17166 --- /dev/null +++ b/test/tests/operations/Base64.js @@ -0,0 +1,119 @@ +/** + * Base64 tests. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../TestRegister.js"; + +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + +TestRegister.addTests([ + { + name: "To Base64: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "To Base64", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "To Base64: Hello, World!", + input: "Hello, World!", + expectedOutput: "SGVsbG8sIFdvcmxkIQ==", + recipeConfig: [ + { + op: "To Base64", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "To Base64: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "4YOc4YOjIOGDnuGDkOGDnOGDmOGDmeGDkOGDoQ==", + recipeConfig: [ + { + op: "To Base64", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "To Base64: All bytes", + input: ALL_BYTES, + expectedOutput: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==", + recipeConfig: [ + { + op: "To Base64", + args: ["A-Za-z0-9+/="], + }, + ], + }, + { + name: "From Base64: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "From Base64", + args: ["A-Za-z0-9+/=", true], + }, + ], + }, + { + name: "From Base64: Hello, World!", + input: "SGVsbG8sIFdvcmxkIQ==", + expectedOutput: "Hello, World!", + recipeConfig: [ + { + op: "From Base64", + args: ["A-Za-z0-9+/=", true], + }, + ], + }, + { + name: "From Base64: UTF-8", + input: "4YOc4YOjIOGDnuGDkOGDnOGDmOGDmeGDkOGDoQ==", + expectedOutput: "ნუ პანიკას", + recipeConfig: [ + { + op: "From Base64", + args: ["A-Za-z0-9+/=", true], + }, + ], + }, + { + name: "From Base64: All bytes", + input: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==", + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "From Base64", + args: ["A-Za-z0-9+/=", true], + }, + ], + }, +]); diff --git a/test/tests/operations/ByteRepr.js b/test/tests/operations/ByteRepr.js old mode 100644 new mode 100755 index 0c57d1fc..13fbd4cc --- a/test/tests/operations/ByteRepr.js +++ b/test/tests/operations/ByteRepr.js @@ -7,6 +7,25 @@ */ import TestRegister from "../../TestRegister.js"; +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + TestRegister.addTests([ { name: "To Octal: nothing", @@ -74,4 +93,136 @@ TestRegister.addTests([ } ] }, + { + name: "To Hex: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "To Hex", + args: ["Space"] + }, + ] + }, + { + name: "To Hex: All bytes", + input: ALL_BYTES, + expectedOutput: "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff", + recipeConfig: [ + { + op: "To Hex", + args: ["Space"] + }, + ] + }, + { + name: "To Hex: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "e1839ce183a320e1839ee18390e1839ce18398e18399e18390e183a1", + recipeConfig: [ + { + op: "To Hex", + args: ["None"] + }, + ] + }, + { + name: "From Hex: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "From Hex", + args: ["Space"] + } + ] + }, + { + name: "From Hex: All bytes", + input: "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff", + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "From Hex", + args: ["Space"] + } + ] + }, + { + name: "From Hex: UTF-8", + input: "e1839ce183a320e1839ee18390e1839ce18398e18399e18390e183a1", + expectedOutput: "ნუ პანიკას", + recipeConfig: [ + { + op: "From Hex", + args: ["None"] + } + ] + }, + { + name: "To Charcode: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "To Charcode", + args: ["Space", 16] + }, + ] + }, + { + name: "To Charcode: All bytes", + input: ALL_BYTES, + expectedOutput: "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff", + recipeConfig: [ + { + op: "To Charcode", + args: ["Space", 16] + }, + ] + }, + { + name: "To Charcode: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "10dc 10e3 20 10de 10d0 10dc 10d8 10d9 10d0 10e1", + recipeConfig: [ + { + op: "To Charcode", + args: ["Space", 16] + }, + ] + }, + { + name: "From Charcode: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "From Charcode", + args: ["Space", 16] + } + ] + }, + { + name: "From Charcode: All bytes", + input: "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff", + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "From Charcode", + args: ["Space", 16] + } + ] + }, + { + name: "From Charcode: UTF-8", + input: "10dc 10e3 20 10de 10d0 10dc 10d8 10d9 10d0 10e1", + expectedOutput: "ნუ პანიკას", + recipeConfig: [ + { + op: "From Charcode", + args: ["Space", 16] + } + ] + }, ]); diff --git a/test/tests/operations/FlowControl.js b/test/tests/operations/FlowControl.js old mode 100644 new mode 100755 index 51b21f34..49b13026 --- a/test/tests/operations/FlowControl.js +++ b/test/tests/operations/FlowControl.js @@ -8,6 +8,25 @@ */ import TestRegister from "../../TestRegister.js"; +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + TestRegister.addTests([ { name: "Fork: nothing", @@ -251,7 +270,7 @@ TestRegister.addTests([ ], }, { - name: "Conditional Jump: Skips negatively", + name: "Conditional Jump: Skips backwards", input: [ "match", ].join("\n"), @@ -290,4 +309,83 @@ TestRegister.addTests([ }, ], }, + { + name: "Register: RC4 key", + input: "http://malwarez.biz/beacon.php?key=0e932a5c&data=8db7d5ebe38663a54ecbb334e3db11", + expectedOutput: "All the secrets", + recipeConfig: [ + { + op: "Register", + args: ["key=([\\da-f]*)", true, false] + }, + { + op: "Find / Replace", + args: [ + { + "option": "Regex", + "string": ".*data=(.*)" + }, "$1", true, false, true + ] + }, + { + op: "RC4", + args: [ + { + "option": "Hex", + "string": "$R0" + }, "Hex", "Latin1" + ] + } + ] + }, + { + name: "Register: AES key", + input: "51e201d463698ef5f717f71f5b4712af20be674b3bff53d38546396ee61daac4908e319ca3fcf7089bfb6b38ea99e781d26e577ba9dd6f311a39420b8978e93014b042d44726caedf5436eaf652429c0df94b521676c7c2ce812097c277273c7c72cd89aec8d9fb4a27586ccf6aa0aee224c34ba3bfdf7aeb1ddd477622b91e72c9e709ab60f8daf731ec0cc85ce0f746ff1554a5a3ec291ca40f9e629a872592d988fdd834534aba79c1ad1676769a7c010bf04739ecdb65d95302371d629d9e37e7b4a361da468f1ed5358922d2ea752dd11c366f3017b14aa011d2af03c44f95579098a15e3cf9b4486f8ffe9c239f34de7151f6ca6500fe4b850c3f1c02e801caf3a24464614e42801615b8ffaa07ac8251493ffda7de5ddf3368880c2b95b030f41f8f15066add071a66cf60e5f46f3a230d397b652963a21a53f", + expectedOutput: `"You know," said Arthur, "it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young." +"Why, what did she tell you?" +"I don't know, I didn't listen."`, + recipeConfig: [ + { + op: "Register", + args: ["(.{32})", true, false] + }, + { + op: "Drop bytes", + args: [0, 32, false] + }, + { + op: "AES Decrypt", + args: [ + { + "option": "Hex", + "string": "1748e7179bd56570d51fa4ba287cc3e5" + }, + { + "option": "Hex", + "string": "$R0" + }, + "CTR", "Hex", "Raw", + { + "option": "Hex", + "string": "" + } + ] + } + ] + }, + { + name: "Label, Comment: Complex content", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "Label", + args: [""] + }, + { + op: "Comment", + args: [""] + } + ] + }, ]); diff --git a/test/tests/operations/Hash.js b/test/tests/operations/Hash.js old mode 100644 new mode 100755 index e8804091..b1f27479 --- a/test/tests/operations/Hash.js +++ b/test/tests/operations/Hash.js @@ -675,4 +675,83 @@ TestRegister.addTests([ } ] }, + { + name: "Bcrypt compare: dolphin", + input: "dolphin", + expectedOutput: "Match: dolphin", + recipeConfig: [ + { + op: "Bcrypt compare", + args: ["$2a$10$qyon0LQCmMxpFFjwWH6Qh.dDdhqntQh./IN0RXCc3XIMILuOYZKgK"] + } + ] + }, + { + name: "Scrypt: RFC test vector 1", + input: "", + expectedOutput: "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", + recipeConfig: [ + { + op: "Scrypt", + args: [ + { + "option": "Latin1", + "string": "" + }, + 16, 1, 1, 64 + ] + } + ] + }, + { + name: "Scrypt: RFC test vector 2", + input: "password", + expectedOutput: "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", + recipeConfig: [ + { + op: "Scrypt", + args: [ + { + "option": "Latin1", + "string": "NaCl" + }, + 1024, 8, 16, 64 + ] + } + ] + }, + { + name: "Scrypt: RFC test vector 3", + input: "pleaseletmein", + expectedOutput: "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", + recipeConfig: [ + { + op: "Scrypt", + args: [ + { + "option": "Latin1", + "string": "SodiumChloride" + }, + 16384, 8, 1, 64 + ] + } + ] + }, + /*{ // This takes a LONG time to run (over a minute usually). + name: "Scrypt: RFC test vector 4", + input: "pleaseletmein", + expectedOutput: "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4", + recipeConfig: [ + { + op: "Scrypt", + args: [ + { + "option": "Latin1", + "string": "SodiumChloride" + }, + 1048576, 8, 1, 64 + ] + } + ] + },*/ ]); diff --git a/test/tests/operations/Hexdump.js b/test/tests/operations/Hexdump.js new file mode 100755 index 00000000..842e1fd9 --- /dev/null +++ b/test/tests/operations/Hexdump.js @@ -0,0 +1,235 @@ +/** + * Hexdump tests. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../TestRegister.js"; + +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + +TestRegister.addTests([ + { + name: "Hexdump: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "To Hexdump", + args: [16, false, false] + }, + { + op: "From Hexdump", + args: [] + } + ], + }, + { + name: "Hexdump: Hello, World!", + input: "Hello, World!", + expectedOutput: "Hello, World!", + recipeConfig: [ + { + op: "To Hexdump", + args: [16, false, false] + }, + { + op: "From Hexdump", + args: [] + } + ], + }, + { + name: "Hexdump: UTF-8", + input: "ნუ პანიკას", + expectedOutput: "ნუ პანიკას", + recipeConfig: [ + { + op: "To Hexdump", + args: [16, false, false] + }, + { + op: "From Hexdump", + args: [] + } + ], + }, + { + name: "Hexdump: All bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "To Hexdump", + args: [16, false, false] + }, + { + op: "From Hexdump", + args: [] + } + ], + }, + { + name: "To Hexdump: UTF-8", + input: "ნუ პანიკას", + expectedOutput: `00000000 e1 83 9c e1 83 a3 20 e1 83 9e e1 83 90 e1 83 9c |á..á.£ á..á..á..| +00000010 e1 83 98 e1 83 99 e1 83 90 e1 83 a1 |á..á..á..á.¡|`, + recipeConfig: [ + { + op: "To Hexdump", + args: [16, false, false] + } + ], + }, + { + name: "To Hexdump: All bytes", + input: ALL_BYTES, + expectedOutput: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................| +00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................| +00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./| +00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?| +00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO| +00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\\]^_| +00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |\`abcdefghijklmno| +00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.| +00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f |................| +00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f |................| +000000a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af |\xa0¡¢£¤¥¦§¨©ª«¬.®¯| +000000b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf |°±²³´µ¶·¸¹º»¼½¾¿| +000000c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ| +000000d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df |ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß| +000000e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef |àáâãäåæçèéêëìíîï| +000000f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff |ðñòóôõö÷øùúûüýþÿ|`, + recipeConfig: [ + { + op: "To Hexdump", + args: [16, false, false] + } + ], + }, + { + name: "From Hexdump: xxd", + input: `00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f ................ +00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f ................ +00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f !"#$%&'()*+,-./ +00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f 0123456789:;<=>? +00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f @ABCDEFGHIJKLMNO +00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f PQRSTUVWXYZ[\\]^_ +00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f \`abcdefghijklmno +00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f pqrstuvwxyz{|}~. +00000080: 8081 8283 8485 8687 8889 8a8b 8c8d 8e8f ................ +00000090: 9091 9293 9495 9697 9899 9a9b 9c9d 9e9f ................ +000000a0: a0a1 a2a3 a4a5 a6a7 a8a9 aaab acad aeaf ................ +000000b0: b0b1 b2b3 b4b5 b6b7 b8b9 babb bcbd bebf ................ +000000c0: c0c1 c2c3 c4c5 c6c7 c8c9 cacb cccd cecf ................ +000000d0: d0d1 d2d3 d4d5 d6d7 d8d9 dadb dcdd dedf ................ +000000e0: e0e1 e2e3 e4e5 e6e7 e8e9 eaeb eced eeef ................ +000000f0: f0f1 f2f3 f4f5 f6f7 f8f9 fafb fcfd feff ................`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "From Hexdump", + args: [] + } + ], + }, + { + name: "From Hexdump: Wireshark", + input: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ........ ........ +00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ........ ........ +00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&' ()*+,-./ +00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 01234567 89:;<=>? +00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFG HIJKLMNO +00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVW XYZ[\\]^_ +00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f \`abcdefg hijklmno +00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvw xyz{|}~. +00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f ........ ........ +00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f ........ ........ +000000A0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af ........ ........ +000000B0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf ........ ........ +000000C0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf ........ ........ +000000D0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df ........ ........ +000000E0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef ........ ........ +000000F0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff ........ ........ +`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "From Hexdump", + args: [] + } + ], + }, + { + name: "From Hexdump: 010", + input: `0000h: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ................ +0010h: 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ................ +0020h: 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F !"#$%&'()*+,-./ +0030h: 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 0123456789:;<=>? +0040h: 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F @ABCDEFGHIJKLMNO +0050h: 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F PQRSTUVWXYZ[\\]^_ +0060h: 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F \`abcdefghijklmno +0070h: 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F pqrstuvwxyz{|}~ +0080h: 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F €.‚ƒ„…†‡ˆ‰Š‹Œ... +0090h: 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F .‘’“”•–—˜™š›œ.žŸ +00A0h: A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF \xa0¡¢£¤¥¦§¨©ª«¬­®¯ +00B0h: B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF °±²³´µ¶·¸¹º»¼½¾¿ +00C0h: C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ +00D0h: D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß +00E0h: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF àáâãäåæçèéêëìíîï +00F0h: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ðñòóôõö÷øùúûüýþÿ`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "From Hexdump", + args: [] + } + ], + }, + { + name: "From Hexdump: Linux hexdump", + input: `00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................| +00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................| +00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./| +00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?| +00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO| +00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\\]^_| +00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |\`abcdefghijklmno| +00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.| +00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f |................| +00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f |................| +000000a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af |................| +000000b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf |................| +000000c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |................| +000000d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df |................| +000000e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef |................| +000000f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff |................| +00000100`, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + op: "From Hexdump", + args: [] + } + ], + }, +]); diff --git a/test/tests/operations/StrUtils.js b/test/tests/operations/StrUtils.js old mode 100644 new mode 100755 index 8110d067..4777fd10 --- a/test/tests/operations/StrUtils.js +++ b/test/tests/operations/StrUtils.js @@ -218,13 +218,24 @@ TestRegister.addTests([ ], }, { - name: "Escape String: quotes", - input: "Hello \"World\"! Escape 'these' quotes.", - expectedOutput: "Hello \\\"World\\\"! Escape \\'these\\' quotes.", + name: "Escape String: single quotes", + input: "Escape 'these' quotes.", + expectedOutput: "Escape \\'these\\' quotes.", recipeConfig: [ { "op": "Escape string", - "args": [] + "args": ["Special chars", "Single", false, true, false] + } + ], + }, + { + name: "Escape String: double quotes", + input: "Hello \"World\"!", + expectedOutput: "Hello \\\"World\\\"!", + recipeConfig: [ + { + "op": "Escape string", + "args": ["Special chars", "Double", false, true, false] } ], }, @@ -235,7 +246,7 @@ TestRegister.addTests([ recipeConfig: [ { "op": "Escape string", - "args": [] + "args": ["Special chars", "Double", false, true, false] } ], }, @@ -261,4 +272,26 @@ TestRegister.addTests([ } ], }, + { + name: "Escape String: complex", + input: "null\0backspace\btab\tnewline\nverticaltab\vformfeed\fcarriagereturn\rdoublequote\"singlequote'hex\xa9unicode\u2665codepoint\u{1D306}", + expectedOutput: "null\\0backspace\\btab\\tnewline\\nverticaltab\\x0bformfeed\\fcarriagereturn\\rdoublequote\"singlequote\\'hex\\xa9unicode\\u2665codepoint\\u{1d306}", + recipeConfig: [ + { + "op": "Escape string", + "args": ["Special chars", "Single", false, true, false] + } + ], + }, + { + name: "Unescape String: complex", + input: "null\\0backspace\\btab\\tnewline\\nverticaltab\\vformfeed\\fcarriagereturn\\rdoublequote\\\"singlequote\\'hex\\xa9unicode\\u2665codepoint\\u{1D306}", + expectedOutput: "null\0backspace\btab\tnewline\nverticaltab\vformfeed\fcarriagereturn\rdoublequote\"singlequote'hex\xa9unicode\u2665codepoint\u{1D306}", + recipeConfig: [ + { + "op": "Unescape string", + "args": [] + } + ], + }, ]); diff --git a/webpack.config.js b/webpack.config.js index 6f8b3e2f..362bea7c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -35,7 +35,6 @@ module.exports = { new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", - moment: "moment-timezone", log: "loglevel" }), new webpack.BannerPlugin({ From c1bb93eec13879a4dec214462e8b0d5b8ee4dce7 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 26 Mar 2018 22:43:58 +0100 Subject: [PATCH 05/11] 7.9.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6371b41..8a220bc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "7.8.1", + "version": "7.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b896d2aa..6d9e75b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "7.8.1", + "version": "7.9.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 9b4fc3d3aa2f428af10f69294f1a4604b0b03139 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 26 Mar 2018 23:14:23 +0100 Subject: [PATCH 06/11] Converted the core to ES modules --- .babelrc | 2 +- .eslintignore | 3 +- .eslintrc.json | 1 - .gitignore | 3 +- .travis.yml | 2 +- Gruntfile.js | 92 ++---- package-lock.json | 63 +--- package.json | 4 +- src/core/Chef.js | 165 ---------- src/core/Chef.mjs | 172 ++++++++++ src/core/ChefWorker.js | 6 +- src/core/Dish.js | 274 ---------------- src/core/Dish.mjs | 293 ++++++++++++++++++ src/core/FlowControl.js | 24 +- src/core/Ingredient.js | 92 ------ src/core/Ingredient.mjs | 109 +++++++ src/core/Operation.js | 174 ----------- src/core/Operation.mjs | 239 ++++++++++++++ src/core/Recipe.js | 264 ---------------- src/core/Recipe.mjs | 250 +++++++++++++++ src/core/{Utils.js => Utils.mjs} | 275 ++++++++-------- src/core/config/Categories.js | 4 +- src/core/config/OperationConfig.json | 141 +++++++++ ...{OperationConfig.js => generateConfig.mjs} | 266 ++++++++++------ src/core/config/modules/BSON.js | 22 -- src/core/config/modules/CharEnc.js | 21 -- src/core/config/modules/Ciphers.js | 44 --- src/core/config/modules/Code.js | 44 --- src/core/config/modules/Compression.js | 32 -- src/core/config/modules/Default.js | 199 ------------ src/core/config/modules/Default.mjs | 18 ++ src/core/config/modules/Diff.js | 20 -- src/core/config/modules/Encodings.js | 21 -- src/core/config/modules/HTTP.js | 22 -- src/core/config/modules/Hashing.js | 56 ---- src/core/config/modules/Image.js | 25 -- src/core/config/modules/JSBN.js | 25 -- src/core/config/modules/OpModules.js | 45 --- src/core/config/modules/OpModules.mjs | 19 ++ src/core/config/modules/PublicKey.js | 25 -- src/core/config/modules/Regex.js | 30 -- src/core/config/modules/Shellcode.js | 20 -- src/core/config/modules/URL.js | 23 -- .../{operations/Base64.js => lib/Base64.mjs} | 110 ++----- src/core/operations/FromBase64.mjs | 84 +++++ src/core/operations/ToBase64.mjs | 77 +++++ src/core/operations/index.mjs | 7 + .../operations/{ => legacy}/Arithmetic.js | 2 +- src/core/operations/{ => legacy}/BCD.js | 0 src/core/operations/{ => legacy}/BSON.js | 0 src/core/operations/{ => legacy}/Base.js | 0 src/core/operations/{ => legacy}/Base58.js | 0 src/core/operations/{ => legacy}/BitwiseOp.js | 0 src/core/operations/{ => legacy}/ByteRepr.js | 26 +- src/core/operations/{ => legacy}/CharEnc.js | 2 +- src/core/operations/{ => legacy}/Checksum.js | 0 src/core/operations/{ => legacy}/Cipher.js | 0 src/core/operations/{ => legacy}/Code.js | 0 src/core/operations/{ => legacy}/Compress.js | 2 +- src/core/operations/{ => legacy}/Convert.js | 0 src/core/operations/{ => legacy}/DateTime.js | 0 src/core/operations/{ => legacy}/Diff.js | 0 src/core/operations/{ => legacy}/Endian.js | 0 src/core/operations/{ => legacy}/Entropy.js | 0 src/core/operations/{ => legacy}/Extract.js | 0 src/core/operations/{ => legacy}/FileType.js | 0 src/core/operations/{ => legacy}/Filetime.js | 0 src/core/operations/{ => legacy}/HTML.js | 0 src/core/operations/{ => legacy}/HTTP.js | 0 src/core/operations/{ => legacy}/Hash.js | 4 +- src/core/operations/{ => legacy}/Hexdump.js | 0 src/core/operations/{ => legacy}/IP.js | 2 +- src/core/operations/{ => legacy}/Image.js | 2 +- src/core/operations/{ => legacy}/JS.js | 0 src/core/operations/{ => legacy}/MAC.js | 0 src/core/operations/{ => legacy}/MS.js | 0 src/core/operations/{ => legacy}/MorseCode.js | 8 +- src/core/operations/{ => legacy}/NetBIOS.js | 0 .../operations/{ => legacy}/Numberwang.js | 0 src/core/operations/{ => legacy}/OS.js | 0 src/core/operations/{ => legacy}/OTP.js | 0 src/core/operations/{ => legacy}/PHP.js | 0 src/core/operations/{ => legacy}/PublicKey.js | 0 src/core/operations/{ => legacy}/Punycode.js | 0 .../{ => legacy}/QuotedPrintable.js | 0 src/core/operations/{ => legacy}/Regex.js | 0 src/core/operations/{ => legacy}/Rotate.js | 0 src/core/operations/{ => legacy}/SeqUtils.js | 4 +- src/core/operations/{ => legacy}/Shellcode.js | 2 +- src/core/operations/{ => legacy}/StrUtils.js | 6 +- src/core/operations/{ => legacy}/Tidy.js | 0 src/core/operations/{ => legacy}/URL.js | 0 src/core/operations/{ => legacy}/UUID.js | 0 src/core/operations/{ => legacy}/Unicode.js | 0 src/core/operations/{ => legacy}/XKCD.js | 0 src/core/{lib => vendor}/DisassembleX86-64.js | 0 src/core/{lib => vendor}/bzip2.js | 0 src/core/{lib => vendor}/canvascomponents.js | 0 .../{lib => vendor}/js-codepage/cptable.js | 0 .../{lib => vendor}/js-codepage/cputils.js | 0 src/core/{lib => vendor}/remove-exif.js | 0 src/node/index.js | 27 -- src/node/index.mjs | 39 +++ src/web/App.js | 4 +- src/web/BindingsWaiter.js | 0 src/web/ControlsWaiter.js | 2 +- src/web/InputWaiter.js | 2 +- src/web/LoaderWorker.js | 0 src/web/OutputWaiter.js | 2 +- src/web/RecipeWaiter.js | 2 +- src/web/WorkerWaiter.js | 0 src/web/index.js | 4 +- src/web/static/ga.html | 0 src/web/static/images/file-128x128.png | Bin src/web/static/images/file-32x32.png | Bin src/web/static/sitemap.js | 2 +- src/web/static/structuredData.json | 0 src/web/stylesheets/components/_alert.css | 0 src/web/stylesheets/components/_button.css | 0 src/web/stylesheets/components/_list.css | 0 src/web/stylesheets/components/_operation.css | 0 src/web/stylesheets/components/_pane.css | 0 src/web/stylesheets/index.css | 0 src/web/stylesheets/index.js | 0 src/web/stylesheets/layout/_banner.css | 0 src/web/stylesheets/layout/_controls.css | 0 src/web/stylesheets/layout/_io.css | 0 src/web/stylesheets/layout/_modals.css | 0 src/web/stylesheets/layout/_operations.css | 0 src/web/stylesheets/layout/_recipe.css | 0 src/web/stylesheets/layout/_structure.css | 0 src/web/stylesheets/preloader.css | 0 src/web/stylesheets/themes/_dark.css | 0 src/web/stylesheets/vendors/bootstrap.less | 0 test/{TestRegister.js => TestRegister.mjs} | 2 +- test/{index.js => index.mjs} | 59 ++-- test/tests/operations/BCD.js | 0 test/tests/operations/Base58.js | 0 .../operations/{Base64.js => Base64.mjs} | 2 +- test/tests/operations/BitwiseOp.js | 0 test/tests/operations/CharEnc.js | 0 test/tests/operations/Cipher.js | 0 test/tests/operations/Code.js | 0 test/tests/operations/Compress.js | 0 test/tests/operations/DateTime.js | 0 test/tests/operations/Image.js | 0 test/tests/operations/MS.js | 0 test/tests/operations/MorseCode.js | 0 test/tests/operations/NetBIOS.js | 0 test/tests/operations/OTP.js | 0 test/tests/operations/PHP.js | 0 test/tests/operations/Regex.js | 0 test/tests/operations/SeqUtils.js | 0 webpack.config.js | 10 +- 154 files changed, 1901 insertions(+), 2223 deletions(-) delete mode 100755 src/core/Chef.js create mode 100755 src/core/Chef.mjs delete mode 100755 src/core/Dish.js create mode 100755 src/core/Dish.mjs delete mode 100755 src/core/Ingredient.js create mode 100755 src/core/Ingredient.mjs delete mode 100755 src/core/Operation.js create mode 100755 src/core/Operation.mjs delete mode 100755 src/core/Recipe.js create mode 100755 src/core/Recipe.mjs rename src/core/{Utils.js => Utils.mjs} (91%) create mode 100644 src/core/config/OperationConfig.json rename src/core/config/{OperationConfig.js => generateConfig.mjs} (97%) mode change 100755 => 100644 delete mode 100644 src/core/config/modules/BSON.js delete mode 100644 src/core/config/modules/CharEnc.js delete mode 100644 src/core/config/modules/Ciphers.js delete mode 100644 src/core/config/modules/Code.js delete mode 100644 src/core/config/modules/Compression.js delete mode 100644 src/core/config/modules/Default.js create mode 100644 src/core/config/modules/Default.mjs delete mode 100644 src/core/config/modules/Diff.js delete mode 100644 src/core/config/modules/Encodings.js delete mode 100644 src/core/config/modules/HTTP.js delete mode 100644 src/core/config/modules/Hashing.js delete mode 100644 src/core/config/modules/Image.js delete mode 100644 src/core/config/modules/JSBN.js delete mode 100644 src/core/config/modules/OpModules.js create mode 100644 src/core/config/modules/OpModules.mjs delete mode 100644 src/core/config/modules/PublicKey.js delete mode 100644 src/core/config/modules/Regex.js delete mode 100644 src/core/config/modules/Shellcode.js delete mode 100644 src/core/config/modules/URL.js rename src/core/{operations/Base64.js => lib/Base64.mjs} (79%) create mode 100644 src/core/operations/FromBase64.mjs create mode 100644 src/core/operations/ToBase64.mjs create mode 100644 src/core/operations/index.mjs rename src/core/operations/{ => legacy}/Arithmetic.js (99%) rename src/core/operations/{ => legacy}/BCD.js (100%) rename src/core/operations/{ => legacy}/BSON.js (100%) rename src/core/operations/{ => legacy}/Base.js (100%) rename src/core/operations/{ => legacy}/Base58.js (100%) rename src/core/operations/{ => legacy}/BitwiseOp.js (100%) rename src/core/operations/{ => legacy}/ByteRepr.js (93%) rename src/core/operations/{ => legacy}/CharEnc.js (98%) rename src/core/operations/{ => legacy}/Checksum.js (100%) rename src/core/operations/{ => legacy}/Cipher.js (100%) rename src/core/operations/{ => legacy}/Code.js (100%) rename src/core/operations/{ => legacy}/Compress.js (99%) rename src/core/operations/{ => legacy}/Convert.js (100%) rename src/core/operations/{ => legacy}/DateTime.js (100%) rename src/core/operations/{ => legacy}/Diff.js (100%) rename src/core/operations/{ => legacy}/Endian.js (100%) rename src/core/operations/{ => legacy}/Entropy.js (100%) rename src/core/operations/{ => legacy}/Extract.js (100%) rename src/core/operations/{ => legacy}/FileType.js (100%) rename src/core/operations/{ => legacy}/Filetime.js (100%) rename src/core/operations/{ => legacy}/HTML.js (100%) rename src/core/operations/{ => legacy}/HTTP.js (100%) rename src/core/operations/{ => legacy}/Hash.js (99%) rename src/core/operations/{ => legacy}/Hexdump.js (100%) rename src/core/operations/{ => legacy}/IP.js (99%) rename src/core/operations/{ => legacy}/Image.js (98%) rename src/core/operations/{ => legacy}/JS.js (100%) rename src/core/operations/{ => legacy}/MAC.js (100%) rename src/core/operations/{ => legacy}/MS.js (100%) rename src/core/operations/{ => legacy}/MorseCode.js (96%) rename src/core/operations/{ => legacy}/NetBIOS.js (100%) rename src/core/operations/{ => legacy}/Numberwang.js (100%) rename src/core/operations/{ => legacy}/OS.js (100%) rename src/core/operations/{ => legacy}/OTP.js (100%) rename src/core/operations/{ => legacy}/PHP.js (100%) rename src/core/operations/{ => legacy}/PublicKey.js (100%) rename src/core/operations/{ => legacy}/Punycode.js (100%) rename src/core/operations/{ => legacy}/QuotedPrintable.js (100%) rename src/core/operations/{ => legacy}/Regex.js (100%) rename src/core/operations/{ => legacy}/Rotate.js (100%) rename src/core/operations/{ => legacy}/SeqUtils.js (98%) rename src/core/operations/{ => legacy}/Shellcode.js (97%) rename src/core/operations/{ => legacy}/StrUtils.js (98%) rename src/core/operations/{ => legacy}/Tidy.js (100%) rename src/core/operations/{ => legacy}/URL.js (100%) rename src/core/operations/{ => legacy}/UUID.js (100%) rename src/core/operations/{ => legacy}/Unicode.js (100%) rename src/core/operations/{ => legacy}/XKCD.js (100%) rename src/core/{lib => vendor}/DisassembleX86-64.js (100%) rename src/core/{lib => vendor}/bzip2.js (100%) rename src/core/{lib => vendor}/canvascomponents.js (100%) rename src/core/{lib => vendor}/js-codepage/cptable.js (100%) rename src/core/{lib => vendor}/js-codepage/cputils.js (100%) rename src/core/{lib => vendor}/remove-exif.js (100%) delete mode 100644 src/node/index.js create mode 100644 src/node/index.mjs mode change 100644 => 100755 src/web/BindingsWaiter.js mode change 100644 => 100755 src/web/LoaderWorker.js mode change 100644 => 100755 src/web/WorkerWaiter.js mode change 100644 => 100755 src/web/static/ga.html mode change 100644 => 100755 src/web/static/images/file-128x128.png mode change 100644 => 100755 src/web/static/images/file-32x32.png mode change 100644 => 100755 src/web/static/structuredData.json mode change 100644 => 100755 src/web/stylesheets/components/_alert.css mode change 100644 => 100755 src/web/stylesheets/components/_button.css mode change 100644 => 100755 src/web/stylesheets/components/_list.css mode change 100644 => 100755 src/web/stylesheets/components/_operation.css mode change 100644 => 100755 src/web/stylesheets/components/_pane.css mode change 100644 => 100755 src/web/stylesheets/index.css mode change 100644 => 100755 src/web/stylesheets/index.js mode change 100644 => 100755 src/web/stylesheets/layout/_banner.css mode change 100644 => 100755 src/web/stylesheets/layout/_controls.css mode change 100644 => 100755 src/web/stylesheets/layout/_io.css mode change 100644 => 100755 src/web/stylesheets/layout/_modals.css mode change 100644 => 100755 src/web/stylesheets/layout/_operations.css mode change 100644 => 100755 src/web/stylesheets/layout/_recipe.css mode change 100644 => 100755 src/web/stylesheets/layout/_structure.css mode change 100644 => 100755 src/web/stylesheets/preloader.css mode change 100644 => 100755 src/web/stylesheets/themes/_dark.css mode change 100644 => 100755 src/web/stylesheets/vendors/bootstrap.less rename test/{TestRegister.js => TestRegister.mjs} (98%) rename test/{index.js => index.mjs} (59%) mode change 100644 => 100755 test/tests/operations/BCD.js mode change 100644 => 100755 test/tests/operations/Base58.js rename test/tests/operations/{Base64.js => Base64.mjs} (98%) mode change 100644 => 100755 test/tests/operations/BitwiseOp.js mode change 100644 => 100755 test/tests/operations/CharEnc.js mode change 100644 => 100755 test/tests/operations/Cipher.js mode change 100644 => 100755 test/tests/operations/Code.js mode change 100644 => 100755 test/tests/operations/Compress.js mode change 100644 => 100755 test/tests/operations/DateTime.js mode change 100644 => 100755 test/tests/operations/Image.js mode change 100644 => 100755 test/tests/operations/MS.js mode change 100644 => 100755 test/tests/operations/MorseCode.js mode change 100644 => 100755 test/tests/operations/NetBIOS.js mode change 100644 => 100755 test/tests/operations/OTP.js mode change 100644 => 100755 test/tests/operations/PHP.js mode change 100644 => 100755 test/tests/operations/Regex.js mode change 100644 => 100755 test/tests/operations/SeqUtils.js diff --git a/.babelrc b/.babelrc index 92a857cf..094c0592 100644 --- a/.babelrc +++ b/.babelrc @@ -5,7 +5,7 @@ "chrome": 40, "firefox": 35, "edge": 14, - "node": "6.5", + "node": "6.5" }, "modules": false, "useBuiltIns": true diff --git a/.eslintignore b/.eslintignore index 1034aa8a..83279ae8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1 @@ -src/core/lib/** -src/core/config/MetaConfig.js \ No newline at end of file +src/core/vendor/** \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index d63e35e8..5d0a6331 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -89,7 +89,6 @@ "globals": { "$": false, "jQuery": false, - "moment": false, "log": false, "COMPILE_TIME": false, diff --git a/.gitignore b/.gitignore index 79c28325..31b15bd9 100755 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ build docs/* !docs/*.conf.json !docs/*.ico -.vscode -src/core/config/MetaConfig.js \ No newline at end of file +.vscode \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 0cb60d89..805f6b45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "8.4" + - node install: npm install before_script: - npm install -g grunt diff --git a/Gruntfile.js b/Gruntfile.js index 7a9890f1..4b0eef0e 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,7 +4,8 @@ const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const NodeExternals = require("webpack-node-externals"); const Inliner = require("web-resource-inliner"); -const fs = require("fs"); +const glob = require("glob"); +const path = require("path"); /** * Grunt configuration for building the app in various formats. @@ -21,15 +22,15 @@ module.exports = function (grunt) { // Tasks grunt.registerTask("dev", "A persistent task which creates a development build whenever source files are modified.", - ["clean:dev", "concurrent:dev"]); + ["clean:dev", "clean:config", "webpack-dev-server:start"]); grunt.registerTask("node", "Compiles CyberChef into a single NodeJS module.", - ["clean:node", "webpack:metaConf", "webpack:node", "chmod:build"]); + ["clean:node", "clean:config", "webpack:node", "chmod:build"]); grunt.registerTask("test", "A task which runs all the tests in test/tests.", - ["clean:test", "webpack:metaConf", "webpack:tests", "execute:test"]); + ["exec:tests"]); grunt.registerTask("docs", "Compiles documentation in the /docs directory.", @@ -37,7 +38,7 @@ module.exports = function (grunt) { grunt.registerTask("prod", "Creates a production-ready build. Use the --msg flag to add a compile message.", - ["eslint", "clean:prod", "webpack:metaConf", "webpack:web", "inline", "chmod"]); + ["eslint", "clean:prod", "clean:config", "webpack:web", "inline", "chmod"]); grunt.registerTask("default", "Lints the code base", @@ -62,9 +63,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks("grunt-contrib-copy"); grunt.loadNpmTasks("grunt-chmod"); grunt.loadNpmTasks("grunt-exec"); - grunt.loadNpmTasks("grunt-execute"); grunt.loadNpmTasks("grunt-accessibility"); - grunt.loadNpmTasks("grunt-concurrent"); // Project configuration @@ -118,12 +117,12 @@ module.exports = function (grunt) { * Generates an entry list for all the modules. */ function listEntryModules() { - const path = "./src/core/config/modules/"; let entryModules = {}; - fs.readdirSync(path).forEach(file => { - if (file !== "Default.js" && file !== "OpModules.js") - entryModules[file.split(".js")[0]] = path + file; + glob.sync("./src/core/config/modules/*.mjs").forEach(file => { + const basename = path.basename(file); + if (basename !== "Default.mjs" && basename !== "OpModules.mjs") + entryModules[basename.split(".mjs")[0]] = path.resolve(file); }); return entryModules; @@ -132,9 +131,9 @@ module.exports = function (grunt) { grunt.initConfig({ clean: { dev: ["build/dev/*"], - prod: ["build/prod/*", "src/core/config/MetaConfig.js"], - test: ["build/test/*", "src/core/config/MetaConfig.js"], - node: ["build/node/*", "src/core/config/MetaConfig.js"], + prod: ["build/prod/*"], + node: ["build/node/*"], + config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*"], docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"], inlineScripts: ["build/prod/scripts.js"], }, @@ -143,10 +142,10 @@ module.exports = function (grunt) { configFile: "./.eslintrc.json" }, configs: ["Gruntfile.js"], - core: ["src/core/**/*.js", "!src/core/lib/**/*", "!src/core/config/MetaConfig.js"], - web: ["src/web/**/*.js"], - node: ["src/node/**/*.js"], - tests: ["test/**/*.js"], + core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*"], + web: ["src/web/**/*.{js,mjs}"], + node: ["src/node/**/*.{js,mjs}"], + tests: ["test/**/*.{js,mjs}"], }, jsdoc: { options: { @@ -159,17 +158,11 @@ module.exports = function (grunt) { all: { src: [ "src/**/*.js", - "!src/core/lib/**/*", - "!src/core/config/MetaConfig.js" + "src/**/*.mjs", + "!src/core/vendor/**/*" ], } }, - concurrent: { - options: { - logConcurrentOutput: true - }, - dev: ["webpack:metaConfDev", "webpack-dev-server:start"] - }, accessibility: { options: { accessibilityLevel: "WCAG2A", @@ -184,39 +177,6 @@ module.exports = function (grunt) { }, webpack: { options: webpackConfig, - metaConf: { - mode: "production", - target: "node", - entry: [ - "babel-polyfill", - "./src/core/config/OperationConfig.js" - ], - output: { - filename: "MetaConfig.js", - path: __dirname + "/src/core/config/", - library: "MetaConfig", - libraryTarget: "commonjs2", - libraryExport: "default" - }, - externals: [NodeExternals()], - }, - metaConfDev: { - mode: "development", - target: "node", - entry: [ - "babel-polyfill", - "./src/core/config/OperationConfig.js" - ], - output: { - filename: "MetaConfig.js", - path: __dirname + "/src/core/config/", - library: "MetaConfig", - libraryTarget: "commonjs2", - libraryExport: "default" - }, - externals: [NodeExternals()], - watch: true - }, web: { mode: "production", target: "web", @@ -229,7 +189,7 @@ module.exports = function (grunt) { }, resolve: { alias: { - "./config/modules/OpModules.js": "./config/modules/Default.js" + "./config/modules/OpModules": "./config/modules/Default" } }, plugins: [ @@ -279,7 +239,7 @@ module.exports = function (grunt) { tests: { mode: "development", target: "node", - entry: "./test/index.js", + entry: "./test/index.mjs", externals: [NodeExternals()], output: { filename: "index.js", @@ -292,7 +252,7 @@ module.exports = function (grunt) { node: { mode: "production", target: "node", - entry: "./src/node/index.js", + entry: "./src/node/index.mjs", externals: [NodeExternals()], output: { filename: "CyberChef.js", @@ -330,7 +290,7 @@ module.exports = function (grunt) { }, moduleEntryPoints), resolve: { alias: { - "./config/modules/OpModules.js": "./config/modules/Default.js" + "./config/modules/OpModules": "./config/modules/Default" } }, plugins: [ @@ -401,10 +361,10 @@ module.exports = function (grunt) { }, sitemap: { command: "node build/prod/sitemap.js > build/prod/sitemap.xml" + }, + tests: { + command: "node --experimental-modules test/index.mjs" } }, - execute: { - test: "build/test/index.js" - }, }); }; diff --git a/package-lock.json b/package-lock.json index 8a220bc7..0354d97d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5300,26 +5300,6 @@ "shelljs": "0.5.3" } }, - "grunt-concurrent": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-2.3.1.tgz", - "integrity": "sha1-Hj2zjM71o9oRleYdYx/n4yE0TSM=", - "dev": true, - "requires": { - "arrify": "1.0.1", - "async": "1.5.2", - "indent-string": "2.1.0", - "pad-stream": "1.2.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - } - } - }, "grunt-contrib-clean": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.1.0.tgz", @@ -5460,12 +5440,6 @@ "integrity": "sha512-cgAlreXf3muSYS5LzW0Cc4xHK03BjFOYk0MqCQ/MZ3k1Xz2GU7D+IAJg4UKicxpO+XdONJdx/NJ6kpy2wI+uHg==", "dev": true }, - "grunt-execute": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/grunt-execute/-/grunt-execute-0.2.2.tgz", - "integrity": "sha1-TpRf5XlZzA3neZCDtrQq7ZYWNQo=", - "dev": true - }, "grunt-jsdoc": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/grunt-jsdoc/-/grunt-jsdoc-2.2.1.tgz", @@ -8419,19 +8393,6 @@ "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", "dev": true }, - "pad-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-1.2.0.tgz", - "integrity": "sha1-Yx3Mn3mBC3BZZeid7eps/w/B38k=", - "dev": true, - "requires": { - "meow": "3.7.0", - "pumpify": "1.3.5", - "repeating": "2.0.1", - "split2": "1.1.1", - "through2": "2.0.3" - } - }, "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -11046,15 +11007,6 @@ "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.3.5.tgz", "integrity": "sha1-YuLOZtLPkcx3SqXwdJ/yUTgDn1A=" }, - "split2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.1.1.tgz", - "integrity": "sha1-Fi2bGIZfAqsvKtlYVSLbm1TEgfk=", - "dev": true, - "requires": { - "through2": "2.0.3" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12198,15 +12150,6 @@ "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", "dev": true }, - "val-loader": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/val-loader/-/val-loader-1.1.0.tgz", - "integrity": "sha512-8m62XF42FcfrBBl02rtDY9hQhDcDczrEcr60/aSMxlzJiXAcbAimRPvsDoDa5QcGAusOgOmVTpFtK5EbfZdDwA==", - "dev": true, - "requires": { - "loader-utils": "1.1.0" - } - }, "valid-data-url": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.4.tgz", @@ -12709,6 +12652,12 @@ "integrity": "sha1-Iyxi7GCSsQBjWj0p2DwXRxKN+b0=", "dev": true }, + "webpack-shell-plugin": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/webpack-shell-plugin/-/webpack-shell-plugin-0.5.0.tgz", + "integrity": "sha1-Kbih2A3erg3bEOcpZn9yhlPCx0I=", + "dev": true + }, "webpack-sources": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz", diff --git a/package.json b/package.json index 6d9e75b2..119d915b 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,10 @@ "grunt": ">=1.0.2", "grunt-accessibility": "~6.0.0", "grunt-chmod": "~1.1.1", - "grunt-concurrent": "^2.3.1", "grunt-contrib-clean": "~1.1.0", "grunt-contrib-copy": "~1.0.0", "grunt-eslint": "^20.1.0", "grunt-exec": "~3.0.0", - "grunt-execute": "^0.2.2", "grunt-jsdoc": "^2.2.1", "grunt-webpack": "^3.0.2", "html-webpack-plugin": "^3.0.4", @@ -61,11 +59,11 @@ "sitemap": "^1.13.0", "style-loader": "^0.20.2", "url-loader": "^0.6.2", - "val-loader": "^1.1.0", "web-resource-inliner": "^4.2.1", "webpack": "^4.0.1", "webpack-dev-server": "^3.1.0", "webpack-node-externals": "^1.6.0", + "webpack-shell-plugin": "^0.5.0", "worker-loader": "^1.1.1" }, "dependencies": { diff --git a/src/core/Chef.js b/src/core/Chef.js deleted file mode 100755 index aba3f4f7..00000000 --- a/src/core/Chef.js +++ /dev/null @@ -1,165 +0,0 @@ -import Dish from "./Dish.js"; -import Recipe from "./Recipe.js"; - - -/** - * The main controller for CyberChef. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - */ -const Chef = function() { - this.dish = new Dish(); -}; - - -/** - * Runs the recipe over the input. - * - * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer - * @param {Object[]} recipeConfig - The recipe configuration object - * @param {Object} options - The options object storing various user choices - * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting - * @param {number} progress - The position in the recipe to start from - * @param {number} [step] - Whether to only execute one operation in the recipe - * - * @returns {Object} response - * @returns {string} response.result - The output of the recipe - * @returns {string} response.type - The data type of the result - * @returns {number} response.progress - The position that we have got to in the recipe - * @returns {number} response.duration - The number of ms it took to execute the recipe - * @returns {number} response.error - The error object thrown by a failed operation (false if no error) -*/ -Chef.prototype.bake = async function(input, recipeConfig, options, progress, step) { - log.debug("Chef baking"); - const startTime = new Date().getTime(), - recipe = new Recipe(recipeConfig), - containsFc = recipe.containsFlowControl(), - notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8; - let error = false; - - if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); - - // Clean up progress - if (progress >= recipeConfig.length) { - progress = 0; - } - - if (step) { - // Unset breakpoint on this step - recipe.setBreakpoint(progress, false); - // Set breakpoint on next step - recipe.setBreakpoint(progress + 1, true); - } - - // If stepping with flow control, we have to start from the beginning - // but still want to skip all previous breakpoints - if (progress > 0 && containsFc) { - recipe.removeBreaksUpTo(progress); - progress = 0; - } - - // If starting from scratch, load data - if (progress === 0) { - const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING; - this.dish.set(input, type); - } - - try { - progress = await recipe.execute(this.dish, progress); - } catch (err) { - log.error(err); - error = { - displayStr: err.displayStr, - }; - progress = err.progress; - } - - // Depending on the size of the output, we may send it back as a string or an ArrayBuffer. - // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file. - // The threshold is specified in KiB. - const threshold = (options.ioDisplayThreshold || 1024) * 1024; - const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; - - return { - result: this.dish.type === Dish.HTML ? - this.dish.get(Dish.HTML, notUTF8) : - this.dish.get(returnType, notUTF8), - type: Dish.enumLookup(this.dish.type), - progress: progress, - duration: new Date().getTime() - startTime, - error: error - }; -}; - - -/** - * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs, - * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a - * minute, we run a silent bake which will force the browser to load and cache all the relevant - * JavaScript code needed to do a real bake. - * - * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a - * long time and the browser has swapped out all its memory. - * - * The output will not be modified (hence "silent" bake). - * - * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load - * the recipe, ingredients and dish. - * - * @param {Object[]} recipeConfig - The recipe configuration object - * @returns {number} The time it took to run the silent bake in milliseconds. -*/ -Chef.prototype.silentBake = function(recipeConfig) { - log.debug("Running silent bake"); - - let startTime = new Date().getTime(), - recipe = new Recipe(recipeConfig), - dish = new Dish("", Dish.STRING); - - try { - recipe.execute(dish); - } catch (err) { - // Suppress all errors - } - return new Date().getTime() - startTime; -}; - - -/** - * Calculates highlight offsets if possible. - * - * @param {Object[]} recipeConfig - * @param {string} direction - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. - * @returns {Object} - */ -Chef.prototype.calculateHighlights = function(recipeConfig, direction, pos) { - const recipe = new Recipe(recipeConfig); - const highlights = recipe.generateHighlightList(); - - if (!highlights) return false; - - for (let i = 0; i < highlights.length; i++) { - // Remove multiple highlights before processing again - pos = [pos[0]]; - - const func = direction === "forward" ? highlights[i].f : highlights[i].b; - - if (typeof func == "function") { - pos = func(pos, highlights[i].args); - } - } - - return { - pos: pos, - direction: direction - }; -}; - -export default Chef; diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs new file mode 100755 index 00000000..c7338184 --- /dev/null +++ b/src/core/Chef.mjs @@ -0,0 +1,172 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Dish from "./Dish"; +import Recipe from "./Recipe"; +import log from "loglevel"; + +/** + * The main controller for CyberChef. + */ +class Chef { + + /** + * Chef constructor + */ + constructor() { + this.dish = new Dish(); + } + + + /** + * Runs the recipe over the input. + * + * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer + * @param {Object[]} recipeConfig - The recipe configuration object + * @param {Object} options - The options object storing various user choices + * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting + * @param {number} progress - The position in the recipe to start from + * @param {number} [step] - Whether to only execute one operation in the recipe + * + * @returns {Object} response + * @returns {string} response.result - The output of the recipe + * @returns {string} response.type - The data type of the result + * @returns {number} response.progress - The position that we have got to in the recipe + * @returns {number} response.duration - The number of ms it took to execute the recipe + * @returns {number} response.error - The error object thrown by a failed operation (false if no error) + */ + async bake(input, recipeConfig, options, progress, step) { + log.debug("Chef baking"); + const startTime = new Date().getTime(), + recipe = new Recipe(recipeConfig), + containsFc = recipe.containsFlowControl(), + notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8; + let error = false; + + if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false); + + // Clean up progress + if (progress >= recipeConfig.length) { + progress = 0; + } + + if (step) { + // Unset breakpoint on this step + recipe.setBreakpoint(progress, false); + // Set breakpoint on next step + recipe.setBreakpoint(progress + 1, true); + } + + // If stepping with flow control, we have to start from the beginning + // but still want to skip all previous breakpoints + if (progress > 0 && containsFc) { + recipe.removeBreaksUpTo(progress); + progress = 0; + } + + // If starting from scratch, load data + if (progress === 0) { + const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING; + this.dish.set(input, type); + } + + try { + progress = await recipe.execute(this.dish, progress); + } catch (err) { + log.error(err); + error = { + displayStr: err.displayStr, + }; + progress = err.progress; + } + + // Depending on the size of the output, we may send it back as a string or an ArrayBuffer. + // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file. + // The threshold is specified in KiB. + const threshold = (options.ioDisplayThreshold || 1024) * 1024; + const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; + + return { + result: this.dish.type === Dish.HTML ? + this.dish.get(Dish.HTML, notUTF8) : + this.dish.get(returnType, notUTF8), + type: Dish.enumLookup(this.dish.type), + progress: progress, + duration: new Date().getTime() - startTime, + error: error + }; + } + + + /** + * When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs, + * it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a + * minute, we run a silent bake which will force the browser to load and cache all the relevant + * JavaScript code needed to do a real bake. + * + * This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a + * long time and the browser has swapped out all its memory. + * + * The output will not be modified (hence "silent" bake). + * + * This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load + * the recipe, ingredients and dish. + * + * @param {Object[]} recipeConfig - The recipe configuration object + * @returns {number} The time it took to run the silent bake in milliseconds. + */ + silentBake(recipeConfig) { + log.debug("Running silent bake"); + + let startTime = new Date().getTime(), + recipe = new Recipe(recipeConfig), + dish = new Dish("", Dish.STRING); + + try { + recipe.execute(dish); + } catch (err) { + // Suppress all errors + } + return new Date().getTime() - startTime; + } + + + /** + * Calculates highlight offsets if possible. + * + * @param {Object[]} recipeConfig + * @param {string} direction + * @param {Object} pos - The position object for the highlight. + * @param {number} pos.start - The start offset. + * @param {number} pos.end - The end offset. + * @returns {Object} + */ + calculateHighlights(recipeConfig, direction, pos) { + const recipe = new Recipe(recipeConfig); + const highlights = recipe.generateHighlightList(); + + if (!highlights) return false; + + for (let i = 0; i < highlights.length; i++) { + // Remove multiple highlights before processing again + pos = [pos[0]]; + + const func = direction === "forward" ? highlights[i].f : highlights[i].b; + + if (typeof func == "function") { + pos = func(pos, highlights[i].args); + } + } + + return { + pos: pos, + direction: direction + }; + } + +} + +export default Chef; diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index a091e09c..3ef65a95 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -7,9 +7,9 @@ */ import "babel-polyfill"; -import Chef from "./Chef.js"; -import OperationConfig from "./config/MetaConfig.js"; -import OpModules from "./config/modules/Default.js"; +import Chef from "./Chef"; +import OperationConfig from "./config/OperationConfig.json"; +import OpModules from "./config/modules/Default"; // Add ">" to the start of all log messages in the Chef Worker import loglevelMessagePrefix from "loglevel-message-prefix"; diff --git a/src/core/Dish.js b/src/core/Dish.js deleted file mode 100755 index f0093e81..00000000 --- a/src/core/Dish.js +++ /dev/null @@ -1,274 +0,0 @@ -import Utils from "./Utils.js"; -import BigNumber from "bignumber.js"; - -/** - * The data being operated on by each operation. - * - * @author n1474335 [n1474335@gmail.com] - * @author Matt C [matt@artemisbot.uk] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data. - * @param {number} type - The data type of value, see Dish enums. - */ -const Dish = function(value, type) { - this.value = value || typeof value === "string" ? value : null; - this.type = type || Dish.BYTE_ARRAY; -}; - - -/** - * Dish data type enum for byte arrays. - * @readonly - * @enum - */ -Dish.BYTE_ARRAY = 0; -/** - * Dish data type enum for strings. - * @readonly - * @enum - */ -Dish.STRING = 1; -/** - * Dish data type enum for numbers. - * @readonly - * @enum - */ -Dish.NUMBER = 2; -/** - * Dish data type enum for HTML. - * @readonly - * @enum - */ -Dish.HTML = 3; -/** - * Dish data type enum for ArrayBuffers. - * @readonly - * @enum - */ -Dish.ARRAY_BUFFER = 4; -/** - * Dish data type enum for BigNumbers. - * @readonly - * @enum - */ -Dish.BIG_NUMBER = 5; - - -/** - * Returns the data type enum for the given type string. - * - * @static - * @param {string} typeStr - The name of the data type. - * @returns {number} The data type enum value. - */ -Dish.typeEnum = function(typeStr) { - switch (typeStr.toLowerCase()) { - case "bytearray": - case "byte array": - return Dish.BYTE_ARRAY; - case "string": - return Dish.STRING; - case "number": - return Dish.NUMBER; - case "html": - return Dish.HTML; - case "arraybuffer": - case "array buffer": - return Dish.ARRAY_BUFFER; - case "bignumber": - case "big number": - return Dish.BIG_NUMBER; - default: - throw "Invalid data type string. No matching enum."; - } -}; - - -/** - * Returns the data type string for the given type enum. - * - * @static - * @param {number} typeEnum - The enum value of the data type. - * @returns {string} The data type as a string. - */ -Dish.enumLookup = function(typeEnum) { - switch (typeEnum) { - case Dish.BYTE_ARRAY: - return "byteArray"; - case Dish.STRING: - return "string"; - case Dish.NUMBER: - return "number"; - case Dish.HTML: - return "html"; - case Dish.ARRAY_BUFFER: - return "ArrayBuffer"; - case Dish.BIG_NUMBER: - return "BigNumber"; - default: - throw "Invalid data type enum. No matching type."; - } -}; - - -/** - * Sets the data value and type and then validates them. - * - * @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data. - * @param {number} type - The data type of value, see Dish enums. - */ -Dish.prototype.set = function(value, type) { - log.debug("Dish type: " + Dish.enumLookup(type)); - this.value = value; - this.type = type; - - if (!this.valid()) { - const sample = Utils.truncate(JSON.stringify(this.value), 13); - throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample; - } -}; - - -/** - * Returns the value of the data in the type format specified. - * - * @param {number} type - The data type of value, see Dish enums. - * @param {boolean} [notUTF8] - Do not treat strings as UTF8. - * @returns {byteArray|string|number|ArrayBuffer|BigNumber} The value of the output data. - */ -Dish.prototype.get = function(type, notUTF8) { - if (this.type !== type) { - this.translate(type, notUTF8); - } - return this.value; -}; - - -/** - * Translates the data to the given type format. - * - * @param {number} toType - The data type of value, see Dish enums. - * @param {boolean} [notUTF8] - Do not treat strings as UTF8. - */ -Dish.prototype.translate = function(toType, notUTF8) { - log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); - const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8; - - // Convert data to intermediate byteArray type - switch (this.type) { - case Dish.STRING: - this.value = this.value ? Utils.strToByteArray(this.value) : []; - break; - case Dish.NUMBER: - this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : []; - break; - case Dish.HTML: - this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; - break; - case Dish.ARRAY_BUFFER: - // Array.from() would be nicer here, but it's slightly slower - this.value = Array.prototype.slice.call(new Uint8Array(this.value)); - break; - case Dish.BIG_NUMBER: - this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : []; - break; - default: - break; - } - - this.type = Dish.BYTE_ARRAY; - - // Convert from byteArray to toType - switch (toType) { - case Dish.STRING: - case Dish.HTML: - this.value = this.value ? byteArrayToStr(this.value) : ""; - this.type = Dish.STRING; - break; - case Dish.NUMBER: - this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0; - this.type = Dish.NUMBER; - break; - case Dish.ARRAY_BUFFER: - this.value = new Uint8Array(this.value).buffer; - this.type = Dish.ARRAY_BUFFER; - break; - case Dish.BIG_NUMBER: - try { - this.value = new BigNumber(byteArrayToStr(this.value)); - } catch (err) { - this.value = new BigNumber(NaN); - } - this.type = Dish.BIG_NUMBER; - break; - default: - break; - } -}; - - -/** - * Validates that the value is the type that has been specified. - * May have to disable parts of BYTE_ARRAY validation if it effects performance. - * - * @returns {boolean} Whether the data is valid or not. -*/ -Dish.prototype.valid = function() { - switch (this.type) { - case Dish.BYTE_ARRAY: - if (!(this.value instanceof Array)) { - return false; - } - - // Check that every value is a number between 0 - 255 - for (let i = 0; i < this.value.length; i++) { - if (typeof this.value[i] !== "number" || - this.value[i] < 0 || - this.value[i] > 255) { - return false; - } - } - return true; - case Dish.STRING: - case Dish.HTML: - return typeof this.value === "string"; - case Dish.NUMBER: - return typeof this.value === "number"; - case Dish.ARRAY_BUFFER: - return this.value instanceof ArrayBuffer; - case Dish.BIG_NUMBER: - return this.value instanceof BigNumber; - default: - return false; - } -}; - - -/** - * Determines how much space the Dish takes up. - * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish, - * we measure how many bytes are taken up when the number is written as a string. - * - * @returns {number} -*/ -Dish.prototype.size = function() { - switch (this.type) { - case Dish.BYTE_ARRAY: - case Dish.STRING: - case Dish.HTML: - return this.value.length; - case Dish.NUMBER: - case Dish.BIG_NUMBER: - return this.value.toString().length; - case Dish.ARRAY_BUFFER: - return this.value.byteLength; - default: - return -1; - } -}; - - -export default Dish; diff --git a/src/core/Dish.mjs b/src/core/Dish.mjs new file mode 100755 index 00000000..792395c1 --- /dev/null +++ b/src/core/Dish.mjs @@ -0,0 +1,293 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "./Utils"; +import BigNumber from "bignumber.js"; +import log from "loglevel"; + +/** + * The data being operated on by each operation. + */ +class Dish { + + /** + * Dish constructor + * + * @param {byteArray|string|number|ArrayBuffer|BigNumber} [value=null] + * - The value of the input data. + * @param {number} [type=Dish.BYTE_ARRAY] + * - The data type of value, see Dish enums. + */ + constructor(value=null, type=Dish.BYTE_ARRAY) { + this.value = value; + this.type = type; + } + + + /** + * Returns the data type enum for the given type string. + * + * @param {string} typeStr - The name of the data type. + * @returns {number} The data type enum value. + */ + static typeEnum(typeStr) { + switch (typeStr.toLowerCase()) { + case "bytearray": + case "byte array": + return Dish.BYTE_ARRAY; + case "string": + return Dish.STRING; + case "number": + return Dish.NUMBER; + case "html": + return Dish.HTML; + case "arraybuffer": + case "array buffer": + return Dish.ARRAY_BUFFER; + case "bignumber": + case "big number": + return Dish.BIG_NUMBER; + default: + throw "Invalid data type string. No matching enum."; + } + } + + + /** + * Returns the data type string for the given type enum. + * + * @param {number} typeEnum - The enum value of the data type. + * @returns {string} The data type as a string. + */ + static enumLookup(typeEnum) { + switch (typeEnum) { + case Dish.BYTE_ARRAY: + return "byteArray"; + case Dish.STRING: + return "string"; + case Dish.NUMBER: + return "number"; + case Dish.HTML: + return "html"; + case Dish.ARRAY_BUFFER: + return "ArrayBuffer"; + case Dish.BIG_NUMBER: + return "BigNumber"; + default: + throw "Invalid data type enum. No matching type."; + } + } + + + /** + * Sets the data value and type and then validates them. + * + * @param {byteArray|string|number|ArrayBuffer|BigNumber} value + * - The value of the input data. + * @param {number} type + * - The data type of value, see Dish enums. + */ + set(value, type) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + + log.debug("Dish type: " + Dish.enumLookup(type)); + this.value = value; + this.type = type; + + if (!this.valid()) { + const sample = Utils.truncate(JSON.stringify(this.value), 13); + throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample; + } + } + + + /** + * Returns the value of the data in the type format specified. + * + * @param {number} type - The data type of value, see Dish enums. + * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. + * @returns {byteArray|string|number|ArrayBuffer|BigNumber} + * The value of the output data. + */ + get(type, notUTF8=false) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + if (this.type !== type) { + this.translate(type, notUTF8); + } + return this.value; + } + + + /** + * Translates the data to the given type format. + * + * @param {number} toType - The data type of value, see Dish enums. + * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. + */ + translate(toType, notUTF8=false) { + log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); + const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8; + + // Convert data to intermediate byteArray type + switch (this.type) { + case Dish.STRING: + this.value = this.value ? Utils.strToByteArray(this.value) : []; + break; + case Dish.NUMBER: + this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : []; + break; + case Dish.HTML: + this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; + break; + case Dish.ARRAY_BUFFER: + // Array.from() would be nicer here, but it's slightly slower + this.value = Array.prototype.slice.call(new Uint8Array(this.value)); + break; + case Dish.BIG_NUMBER: + this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : []; + break; + default: + break; + } + + this.type = Dish.BYTE_ARRAY; + + // Convert from byteArray to toType + switch (toType) { + case Dish.STRING: + case Dish.HTML: + this.value = this.value ? byteArrayToStr(this.value) : ""; + this.type = Dish.STRING; + break; + case Dish.NUMBER: + this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0; + this.type = Dish.NUMBER; + break; + case Dish.ARRAY_BUFFER: + this.value = new Uint8Array(this.value).buffer; + this.type = Dish.ARRAY_BUFFER; + break; + case Dish.BIG_NUMBER: + try { + this.value = new BigNumber(byteArrayToStr(this.value)); + } catch (err) { + this.value = new BigNumber(NaN); + } + this.type = Dish.BIG_NUMBER; + break; + default: + break; + } + } + + + /** + * Validates that the value is the type that has been specified. + * May have to disable parts of BYTE_ARRAY validation if it effects performance. + * + * @returns {boolean} Whether the data is valid or not. + */ + valid() { + switch (this.type) { + case Dish.BYTE_ARRAY: + if (!(this.value instanceof Array)) { + return false; + } + + // Check that every value is a number between 0 - 255 + for (let i = 0; i < this.value.length; i++) { + if (typeof this.value[i] !== "number" || + this.value[i] < 0 || + this.value[i] > 255) { + return false; + } + } + return true; + case Dish.STRING: + case Dish.HTML: + return typeof this.value === "string"; + case Dish.NUMBER: + return typeof this.value === "number"; + case Dish.ARRAY_BUFFER: + return this.value instanceof ArrayBuffer; + case Dish.BIG_NUMBER: + return this.value instanceof BigNumber; + default: + return false; + } + } + + + /** + * Determines how much space the Dish takes up. + * Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish, + * we measure how many bytes are taken up when the number is written as a string. + * + * @returns {number} + */ + get size() { + switch (this.type) { + case Dish.BYTE_ARRAY: + case Dish.STRING: + case Dish.HTML: + return this.value.length; + case Dish.NUMBER: + case Dish.BIG_NUMBER: + return this.value.toString().length; + case Dish.ARRAY_BUFFER: + return this.value.byteLength; + default: + return -1; + } + } + +} + + +/** + * Dish data type enum for byte arrays. + * @readonly + * @enum + */ +Dish.BYTE_ARRAY = 0; +/** + * Dish data type enum for strings. + * @readonly + * @enum + */ +Dish.STRING = 1; +/** + * Dish data type enum for numbers. + * @readonly + * @enum + */ +Dish.NUMBER = 2; +/** + * Dish data type enum for HTML. + * @readonly + * @enum + */ +Dish.HTML = 3; +/** + * Dish data type enum for ArrayBuffers. + * @readonly + * @enum + */ +Dish.ARRAY_BUFFER = 4; +/** + * Dish data type enum for BigNumbers. + * @readonly + * @enum + */ +Dish.BIG_NUMBER = 5; + + +export default Dish; diff --git a/src/core/FlowControl.js b/src/core/FlowControl.js index fd2c0785..daa1c343 100755 --- a/src/core/FlowControl.js +++ b/src/core/FlowControl.js @@ -1,5 +1,5 @@ -import Recipe from "./Recipe.js"; -import Dish from "./Dish.js"; +import Recipe from "./Recipe"; +import Dish from "./Dish"; /** @@ -27,7 +27,7 @@ const FlowControl = { inputType = opList[state.progress].inputType, outputType = opList[state.progress].outputType, input = state.dish.get(inputType), - ings = opList[state.progress].getIngValues(), + ings = opList[state.progress].ingValues, splitDelim = ings[0], mergeDelim = ings[1], ignoreErrors = ings[2], @@ -41,7 +41,7 @@ const FlowControl = { // Create subOpList for each tranche to operate on // (all remaining operations unless we encounter a Merge) for (i = state.progress + 1; i < opList.length; i++) { - if (opList[i].name === "Merge" && !opList[i].isDisabled()) { + if (opList[i].name === "Merge" && !opList[i].disabled) { break; } else { subOpList.push(opList[i]); @@ -57,7 +57,7 @@ const FlowControl = { recipe.addOperations(subOpList); // Take a deep(ish) copy of the ingredient values - const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.getIngValues()))); + const ingValues = subOpList.map(op => JSON.parse(JSON.stringify(op.ingValues))); // Run recipe over each tranche for (i = 0; i < inputs.length; i++) { @@ -65,7 +65,7 @@ const FlowControl = { // Baseline ing values for each tranche so that registers are reset subOpList.forEach((op, i) => { - op.setIngValues(JSON.parse(JSON.stringify(ingValues[i]))); + op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); }); const dish = new Dish(inputs[i], inputType); @@ -112,7 +112,7 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runRegister: function(state) { - const ings = state.opList[state.progress].getIngValues(), + const ings = state.opList[state.progress].ingValues, extractorStr = ings[0], i = ings[1], m = ings[2]; @@ -150,9 +150,9 @@ const FlowControl = { // Step through all subsequent ops and replace registers in args with extracted content for (let i = state.progress + 1; i < state.opList.length; i++) { - if (state.opList[i].isDisabled()) continue; + if (state.opList[i].disabled) continue; - let args = state.opList[i].getIngValues(); + let args = state.opList[i].ingValues; args = args.map(arg => { if (typeof arg !== "string" && typeof arg !== "object") return arg; @@ -181,7 +181,7 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runJump: function(state) { - const ings = state.opList[state.progress].getIngValues(), + const ings = state.opList[state.progress].ingValues, label = ings[0], maxJumps = ings[1], jmpIndex = FlowControl._getLabelIndex(label, state); @@ -209,7 +209,7 @@ const FlowControl = { * @returns {Object} The updated state of the recipe. */ runCondJump: function(state) { - const ings = state.opList[state.progress].getIngValues(), + const ings = state.opList[state.progress].ingValues, dish = state.dish, regexStr = ings[0], invert = ings[1], @@ -276,7 +276,7 @@ const FlowControl = { for (let o = 0; o < state.opList.length; o++) { let operation = state.opList[o]; if (operation.name === "Label"){ - let ings = operation.getIngValues(); + let ings = operation.ingValues; if (name === ings[0]) { return o; } diff --git a/src/core/Ingredient.js b/src/core/Ingredient.js deleted file mode 100755 index e8d8a8cc..00000000 --- a/src/core/Ingredient.js +++ /dev/null @@ -1,92 +0,0 @@ -import Utils from "./Utils.js"; - - -/** - * The arguments to operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {Object} ingredientConfig - */ -const Ingredient = function(ingredientConfig) { - this.name = ""; - this.type = ""; - this.value = null; - - if (ingredientConfig) { - this._parseConfig(ingredientConfig); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} ingredientConfig - */ -Ingredient.prototype._parseConfig = function(ingredientConfig) { - this.name = ingredientConfig.name; - this.type = ingredientConfig.type; -}; - - -/** - * Returns the value of the Ingredient as it should be displayed in a recipe config. - * - * @returns {*} - */ -Ingredient.prototype.getConfig = function() { - return this.value; -}; - - -/** - * Sets the value of the Ingredient. - * - * @param {*} value - */ -Ingredient.prototype.setValue = function(value) { - this.value = Ingredient.prepare(value, this.type); -}; - - -/** - * Most values will be strings when they are entered. This function converts them to the correct - * type. - * - * @static - * @param {*} data - * @param {string} type - The name of the data type. -*/ -Ingredient.prepare = function(data, type) { - let number; - - switch (type) { - case "binaryString": - case "binaryShortString": - case "editableOption": - return Utils.parseEscapedChars(data); - case "byteArray": - if (typeof data == "string") { - data = data.replace(/\s+/g, ""); - return Utils.fromHex(data); - } else { - return data; - } - case "number": - number = parseFloat(data); - if (isNaN(number)) { - const sample = Utils.truncate(data.toString(), 10); - throw "Invalid ingredient value. Not a number: " + sample; - } - return number; - default: - return data; - } -}; - -export default Ingredient; diff --git a/src/core/Ingredient.mjs b/src/core/Ingredient.mjs new file mode 100755 index 00000000..af96beaf --- /dev/null +++ b/src/core/Ingredient.mjs @@ -0,0 +1,109 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Utils from "./Utils"; + +/** + * The arguments to operations. + */ +class Ingredient { + + /** + * Ingredient constructor + * + * @param {Object} ingredientConfig + */ + constructor(ingredientConfig) { + this.name = ""; + this.type = ""; + this._value = null; + + if (ingredientConfig) { + this._parseConfig(ingredientConfig); + } + } + + + /** + * Reads and parses the given config. + * + * @private + * @param {Object} ingredientConfig + */ + _parseConfig(ingredientConfig) { + this.name = ingredientConfig.name; + this.type = ingredientConfig.type; + this.defaultValue = ingredientConfig.value; + } + + + /** + * Returns the value of the Ingredient as it should be displayed in a recipe config. + * + * @returns {*} + */ + get config() { + return this._value; + } + + + /** + * Sets the value of the Ingredient. + * + * @param {*} value + */ + set value(value) { + this._value = Ingredient.prepare(value, this.type); + } + + + /** + * Gets the value of the Ingredient. + * + * @returns {*} + */ + get value() { + return this._value; + } + + + /** + * Most values will be strings when they are entered. This function converts them to the correct + * type. + * + * @param {*} data + * @param {string} type - The name of the data type. + */ + static prepare(data, type) { + let number; + + switch (type) { + case "binaryString": + case "binaryShortString": + case "editableOption": + return Utils.parseEscapedChars(data); + case "byteArray": + if (typeof data == "string") { + data = data.replace(/\s+/g, ""); + return Utils.fromHex(data); + } else { + return data; + } + case "number": + number = parseFloat(data); + if (isNaN(number)) { + const sample = Utils.truncate(data.toString(), 10); + throw "Invalid ingredient value. Not a number: " + sample; + } + return number; + default: + return data; + } + } + +} + +export default Ingredient; diff --git a/src/core/Operation.js b/src/core/Operation.js deleted file mode 100755 index d4ee21d3..00000000 --- a/src/core/Operation.js +++ /dev/null @@ -1,174 +0,0 @@ -import Dish from "./Dish.js"; -import Ingredient from "./Ingredient.js"; -import OperationConfig from "./config/MetaConfig.js"; -import OpModules from "./config/modules/OpModules.js"; - - -/** - * The Operation specified by the user to be run. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {string} operationName - */ -const Operation = function(operationName) { - this.name = operationName; - this.module = ""; - this.description = ""; - this.inputType = -1; - this.outputType = -1; - this.run = null; - this.highlight = null; - this.highlightReverse = null; - this.breakpoint = false; - this.disabled = false; - this.ingList = []; - - if (OperationConfig.hasOwnProperty(this.name)) { - this._parseConfig(OperationConfig[this.name]); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} operationConfig - */ -Operation.prototype._parseConfig = function(operationConfig) { - this.module = operationConfig.module; - this.description = operationConfig.description; - this.inputType = Dish.typeEnum(operationConfig.inputType); - this.outputType = Dish.typeEnum(operationConfig.outputType); - this.highlight = operationConfig.highlight; - this.highlightReverse = operationConfig.highlightReverse; - this.flowControl = operationConfig.flowControl; - this.run = OpModules[this.module][this.name]; - - for (let a = 0; a < operationConfig.args.length; a++) { - const ingredientConfig = operationConfig.args[a]; - const ingredient = new Ingredient(ingredientConfig); - this.addIngredient(ingredient); - } - - if (this.highlight === "func") { - this.highlight = OpModules[this.module][`${this.name}-highlight`]; - } - - if (this.highlightReverse === "func") { - this.highlightReverse = OpModules[this.module][`${this.name}-highlightReverse`]; - } -}; - - -/** - * Returns the value of the Operation as it should be displayed in a recipe config. - * - * @returns {Object} - */ -Operation.prototype.getConfig = function() { - const ingredientConfig = []; - - for (let o = 0; o < this.ingList.length; o++) { - ingredientConfig.push(this.ingList[o].getConfig()); - } - - const operationConfig = { - "op": this.name, - "args": ingredientConfig - }; - - return operationConfig; -}; - - -/** - * Adds a new Ingredient to this Operation. - * - * @param {Ingredient} ingredient - */ -Operation.prototype.addIngredient = function(ingredient) { - this.ingList.push(ingredient); -}; - - -/** - * Set the Ingredient values for this Operation. - * - * @param {Object[]} ingValues - */ -Operation.prototype.setIngValues = function(ingValues) { - for (let i = 0; i < ingValues.length; i++) { - this.ingList[i].setValue(ingValues[i]); - } -}; - - -/** - * Get the Ingredient values for this Operation. - * - * @returns {Object[]} - */ -Operation.prototype.getIngValues = function() { - const ingValues = []; - for (let i = 0; i < this.ingList.length; i++) { - ingValues.push(this.ingList[i].value); - } - return ingValues; -}; - - -/** - * Set whether this Operation has a breakpoint. - * - * @param {boolean} value - */ -Operation.prototype.setBreakpoint = function(value) { - this.breakpoint = !!value; -}; - - -/** - * Returns true if this Operation has a breakpoint set. - * - * @returns {boolean} - */ -Operation.prototype.isBreakpoint = function() { - return this.breakpoint; -}; - - -/** - * Set whether this Operation is disabled. - * - * @param {boolean} value - */ -Operation.prototype.setDisabled = function(value) { - this.disabled = !!value; -}; - - -/** - * Returns true if this Operation is disabled. - * - * @returns {boolean} - */ -Operation.prototype.isDisabled = function() { - return this.disabled; -}; - - -/** - * Returns true if this Operation is a flow control. - * - * @returns {boolean} - */ -Operation.prototype.isFlowControl = function() { - return this.flowControl; -}; - -export default Operation; diff --git a/src/core/Operation.mjs b/src/core/Operation.mjs new file mode 100755 index 00000000..30ad71e4 --- /dev/null +++ b/src/core/Operation.mjs @@ -0,0 +1,239 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Dish from "./Dish"; +import Ingredient from "./Ingredient"; + +/** + * The Operation specified by the user to be run. + */ +class Operation { + + /** + * Operation constructor + */ + constructor() { + // Private fields + this._inputType = -1; + this._outputType = -1; + this._breakpoint = false; + this._disabled = false; + this._flowControl = false; + this._ingList = []; + + // Public fields + this.name = ""; + this.module = ""; + this.description = ""; + } + + + /** + * Interface for operation runner + * + * @param {*} input + * @param {Object[]} args + * @returns {*} + */ + run(input, args) { + return input; + } + + + /** + * Interface for forward highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return false; + } + + + /** + * Interface for reverse highlighter + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return false; + } + + + /** + * Sets the input type as a Dish enum. + * + * @param {string} typeStr + */ + set inputType(typeStr) { + this._inputType = Dish.typeEnum(typeStr); + } + + + /** + * Gets the input type as a readable string. + * + * @returns {string} + */ + get inputType() { + return Dish.enumLookup(this._inputType); + } + + + /** + * Sets the output type as a Dish enum. + * + * @param {string} typeStr + */ + set outputType(typeStr) { + this._outputType = Dish.typeEnum(typeStr); + } + + + /** + * Gets the output type as a readable string. + * + * @returns {string} + */ + get outputType() { + return Dish.enumLookup(this._outputType); + } + + + /** + * Sets the args for the current operation. + * + * @param {Object[]} conf + */ + set args(conf) { + conf.forEach(arg => { + const ingredient = new Ingredient(arg); + this.addIngredient(ingredient); + }); + } + + + /** + * Gets the args for the current operation. + * + * @param {Object[]} conf + */ + get args() { + return this._ingList.map(ing => { + return { + name: ing.name, + type: ing.type, + value: ing.defaultValue + }; + }); + } + + + /** + * Returns the value of the Operation as it should be displayed in a recipe config. + * + * @returns {Object} + */ + get config() { + return { + "op": this.name, + "args": this._ingList.map(ing => ing.conf) + }; + } + + + /** + * Adds a new Ingredient to this Operation. + * + * @param {Ingredient} ingredient + */ + addIngredient(ingredient) { + this._ingList.push(ingredient); + } + + + /** + * Set the Ingredient values for this Operation. + * + * @param {Object[]} ingValues + */ + set ingValues(ingValues) { + ingValues.forEach((val, i) => { + this._ingList[i].value = val; + }); + } + + + /** + * Get the Ingredient values for this Operation. + * + * @returns {Object[]} + */ + get ingValues() { + return this._ingList.map(ing => ing.value); + } + + + /** + * Set whether this Operation has a breakpoint. + * + * @param {boolean} value + */ + set breakpoint(value) { + this._breakpoint = !!value; + } + + + /** + * Returns true if this Operation has a breakpoint set. + * + * @returns {boolean} + */ + get breakpoint() { + return this._breakpoint; + } + + + /** + * Set whether this Operation is disabled. + * + * @param {boolean} value + */ + set disabled(value) { + this._disabled = !!value; + } + + + /** + * Returns true if this Operation is disabled. + * + * @returns {boolean} + */ + get disabled() { + return this._disabled; + } + + + /** + * Returns true if this Operation is a flow control. + * + * @returns {boolean} + */ + get flowControl() { + return this._flowControl; + } + +} + +export default Operation; diff --git a/src/core/Recipe.js b/src/core/Recipe.js deleted file mode 100755 index 9305c32d..00000000 --- a/src/core/Recipe.js +++ /dev/null @@ -1,264 +0,0 @@ -import Operation from "./Operation.js"; - - -/** - * The Recipe controls a list of Operations and the Dish they operate on. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @class - * @param {Object} recipeConfig - */ -const Recipe = function(recipeConfig) { - this.opList = []; - - if (recipeConfig) { - this._parseConfig(recipeConfig); - } -}; - - -/** - * Reads and parses the given config. - * - * @private - * @param {Object} recipeConfig - */ -Recipe.prototype._parseConfig = function(recipeConfig) { - for (let c = 0; c < recipeConfig.length; c++) { - const operationName = recipeConfig[c].op; - const operation = new Operation(operationName); - operation.setIngValues(recipeConfig[c].args); - operation.setBreakpoint(recipeConfig[c].breakpoint); - operation.setDisabled(recipeConfig[c].disabled); - this.addOperation(operation); - } -}; - - -/** - * Returns the value of the Recipe as it should be displayed in a recipe config. - * - * @returns {*} - */ -Recipe.prototype.getConfig = function() { - const recipeConfig = []; - - for (let o = 0; o < this.opList.length; o++) { - recipeConfig.push(this.opList[o].getConfig()); - } - - return recipeConfig; -}; - - -/** - * Adds a new Operation to this Recipe. - * - * @param {Operation} operation - */ -Recipe.prototype.addOperation = function(operation) { - this.opList.push(operation); -}; - - -/** - * Adds a list of Operations to this Recipe. - * - * @param {Operation[]} operations - */ -Recipe.prototype.addOperations = function(operations) { - this.opList = this.opList.concat(operations); -}; - - -/** - * Set a breakpoint on a specified Operation. - * - * @param {number} position - The index of the Operation - * @param {boolean} value - */ -Recipe.prototype.setBreakpoint = function(position, value) { - try { - this.opList[position].setBreakpoint(value); - } catch (err) { - // Ignore index error - } -}; - - -/** - * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow - * Control Fork operation. - * - * @param {number} pos - */ -Recipe.prototype.removeBreaksUpTo = function(pos) { - for (let i = 0; i < pos; i++) { - this.opList[i].setBreakpoint(false); - } -}; - - -/** - * Returns true if there is an Flow Control Operation in this Recipe. - * - * @returns {boolean} - */ -Recipe.prototype.containsFlowControl = function() { - for (let i = 0; i < this.opList.length; i++) { - if (this.opList[i].isFlowControl()) return true; - } - return false; -}; - - -/** - * Returns the index of the last Operation index that will be executed, taking into account disabled - * Operations and breakpoints. - * - * @param {number} [startIndex=0] - The index to start searching from - * @returns (number} - */ -Recipe.prototype.lastOpIndex = function(startIndex) { - let i = startIndex + 1 || 0, - op; - - for (; i < this.opList.length; i++) { - op = this.opList[i]; - if (op.isDisabled()) return i-1; - if (op.isBreakpoint()) return i-1; - } - - return i-1; -}; - - -/** - * Executes each operation in the recipe over the given Dish. - * - * @param {Dish} dish - * @param {number} [startFrom=0] - The index of the Operation to start executing from - * @param {number} [forkState={}] - If this is a forked recipe, the state of the recipe up to this point - * @returns {number} - The final progress through the recipe - */ -Recipe.prototype.execute = async function(dish, startFrom = 0, forkState = {}) { - let op, input, output, - numJumps = 0, - numRegisters = forkState.numRegisters || 0; - - log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`); - - for (let i = startFrom; i < this.opList.length; i++) { - op = this.opList[i]; - log.debug(`[${i}] ${op.name} ${JSON.stringify(op.getIngValues())}`); - if (op.isDisabled()) { - log.debug("Operation is disabled, skipping"); - continue; - } - if (op.isBreakpoint()) { - log.debug("Pausing at breakpoint"); - return i; - } - - try { - input = dish.get(op.inputType); - log.debug("Executing operation"); - - if (op.isFlowControl()) { - // Package up the current state - let state = { - "progress": i, - "dish": dish, - "opList": this.opList, - "numJumps": numJumps, - "numRegisters": numRegisters, - "forkOffset": forkState.forkOffset || 0 - }; - - state = await op.run(state); - i = state.progress; - numJumps = state.numJumps; - numRegisters = state.numRegisters; - } else { - output = await op.run(input, op.getIngValues()); - dish.set(output, op.outputType); - } - } catch (err) { - const e = typeof err == "string" ? { message: err } : err; - - e.progress = i; - if (e.fileName) { - e.displayStr = op.name + " - " + e.name + " in " + - e.fileName + " on line " + e.lineNumber + - ".

Message: " + (e.displayStr || e.message); - } else { - e.displayStr = op.name + " - " + (e.displayStr || e.message); - } - - throw e; - } - } - - log.debug("Recipe complete"); - return this.opList.length; -}; - - -/** - * Returns the recipe configuration in string format. - * - * @returns {string} - */ -Recipe.prototype.toString = function() { - return JSON.stringify(this.getConfig()); -}; - - -/** - * Creates a Recipe from a given configuration string. - * - * @param {string} recipeStr - */ -Recipe.prototype.fromString = function(recipeStr) { - const recipeConfig = JSON.parse(recipeStr); - this._parseConfig(recipeConfig); -}; - - -/** - * Generates a list of all the highlight functions assigned to operations in the recipe, if the - * entire recipe supports highlighting. - * - * @returns {Object[]} highlights - * @returns {function} highlights[].f - * @returns {function} highlights[].b - * @returns {Object[]} highlights[].args - */ -Recipe.prototype.generateHighlightList = function() { - const highlights = []; - - for (let i = 0; i < this.opList.length; i++) { - let op = this.opList[i]; - if (op.isDisabled()) continue; - - // If any breakpoints are set, do not attempt to highlight - if (op.isBreakpoint()) return false; - - // If any of the operations do not support highlighting, fail immediately. - if (op.highlight === false || op.highlight === undefined) return false; - - highlights.push({ - f: op.highlight, - b: op.highlightReverse, - args: op.getIngValues() - }); - } - - return highlights; -}; - - -export default Recipe; diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs new file mode 100755 index 00000000..ce03f8bf --- /dev/null +++ b/src/core/Recipe.mjs @@ -0,0 +1,250 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +// import Operation from "./Operation.js"; +import OpModules from "./config/modules/OpModules"; +import OperationConfig from "./config/OperationConfig.json"; +import log from "loglevel"; + +/** + * The Recipe controls a list of Operations and the Dish they operate on. + */ +class Recipe { + + /** + * Recipe constructor + * + * @param {Object} recipeConfig + */ + constructor(recipeConfig) { + this.opList = []; + + if (recipeConfig) { + this._parseConfig(recipeConfig); + } + } + + + /** + * Reads and parses the given config. + * + * @private + * @param {Object} recipeConfig + */ + _parseConfig(recipeConfig) { + for (let c = 0; c < recipeConfig.length; c++) { + const operationName = recipeConfig[c].op; + const opConf = OperationConfig[operationName]; + const opObj = OpModules[opConf.module][operationName]; + const operation = new opObj(); + operation.ingValues = recipeConfig[c].args; + operation.breakpoint = recipeConfig[c].breakpoint; + operation.disabled = recipeConfig[c].disabled; + this.addOperation(operation); + } + } + + + /** + * Returns the value of the Recipe as it should be displayed in a recipe config. + * + * @returns {Object[]} + */ + get config() { + return this.opList.map(op => op.config); + } + + + /** + * Adds a new Operation to this Recipe. + * + * @param {Operation} operation + */ + addOperation(operation) { + this.opList.push(operation); + } + + + /** + * Adds a list of Operations to this Recipe. + * + * @param {Operation[]} operations + */ + addOperations(operations) { + this.opList = this.opList.concat(operations); + } + + + /** + * Set a breakpoint on a specified Operation. + * + * @param {number} position - The index of the Operation + * @param {boolean} value + */ + setBreakpoint(position, value) { + try { + this.opList[position].breakpoint = value; + } catch (err) { + // Ignore index error + } + } + + + /** + * Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow + * Control Fork operation. + * + * @param {number} pos + */ + removeBreaksUpTo(pos) { + for (let i = 0; i < pos; i++) { + this.opList[i].breakpoint = false; + } + } + + + /** + * Returns true if there is an Flow Control Operation in this Recipe. + * + * @returns {boolean} + */ + containsFlowControl() { + return this.opList.reduce((acc, curr) => { + return acc || curr.flowControl; + }, false); + } + + + /** + * Executes each operation in the recipe over the given Dish. + * + * @param {Dish} dish + * @param {number} [startFrom=0] + * - The index of the Operation to start executing from + * @param {number} [forkState={}] + * - If this is a forked recipe, the state of the recipe up to this point + * @returns {number} + * - The final progress through the recipe + */ + async execute(dish, startFrom=0, forkState={}) { + let op, input, output, + numJumps = 0, + numRegisters = forkState.numRegisters || 0; + + log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`); + + for (let i = startFrom; i < this.opList.length; i++) { + op = this.opList[i]; + log.debug(`[${i}] ${op.name} ${JSON.stringify(op.ingValues)}`); + if (op.disabled) { + log.debug("Operation is disabled, skipping"); + continue; + } + if (op.breakpoint) { + log.debug("Pausing at breakpoint"); + return i; + } + + try { + input = dish.get(op.inputType); + log.debug("Executing operation"); + + if (op.flowControl) { + // Package up the current state + let state = { + "progress": i, + "dish": dish, + "opList": this.opList, + "numJumps": numJumps, + "numRegisters": numRegisters, + "forkOffset": forkState.forkOffset || 0 + }; + + state = await op.run(state); + i = state.progress; + numJumps = state.numJumps; + numRegisters = state.numRegisters; + } else { + output = await op.run(input, op.ingValues); + dish.set(output, op.outputType); + } + } catch (err) { + const e = typeof err == "string" ? { message: err } : err; + + e.progress = i; + if (e.fileName) { + e.displayStr = op.name + " - " + e.name + " in " + + e.fileName + " on line " + e.lineNumber + + ".

Message: " + (e.displayStr || e.message); + } else { + e.displayStr = op.name + " - " + (e.displayStr || e.message); + } + + throw e; + } + } + + log.debug("Recipe complete"); + return this.opList.length; + } + + + /** + * Returns the recipe configuration in string format. + * + * @returns {string} + */ + toString() { + return JSON.stringify(this.config); + } + + + /** + * Creates a Recipe from a given configuration string. + * + * @param {string} recipeStr + */ + fromString(recipeStr) { + const recipeConfig = JSON.parse(recipeStr); + this._parseConfig(recipeConfig); + } + + + /** + * Generates a list of all the highlight functions assigned to operations in the recipe, if the + * entire recipe supports highlighting. + * + * @returns {Object[]} highlights + * @returns {function} highlights[].f + * @returns {function} highlights[].b + * @returns {Object[]} highlights[].args + */ + generateHighlightList() { + const highlights = []; + + for (let i = 0; i < this.opList.length; i++) { + let op = this.opList[i]; + if (op.disabled) continue; + + // If any breakpoints are set, do not attempt to highlight + if (op.breakpoint) return false; + + // If any of the operations do not support highlighting, fail immediately. + if (op.highlight === false || op.highlight === undefined) return false; + + highlights.push({ + f: op.highlight, + b: op.highlightReverse, + args: op.ingValues + }); + } + + return highlights; + } + +} + +export default Recipe; diff --git a/src/core/Utils.js b/src/core/Utils.mjs similarity index 91% rename from src/core/Utils.js rename to src/core/Utils.mjs index 6d80c21b..116d856c 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.mjs @@ -1,17 +1,17 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + import utf8 from "utf8"; import moment from "moment-timezone"; /** * Utility functions for use in operations, the core framework and the stage. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace */ -const Utils = { +class Utils { /** * Translates an ordinal into a character. @@ -23,7 +23,7 @@ const Utils = { * // returns 'a' * Utils.chr(97); */ - chr: function(o) { + static chr(o) { // Detect astral symbols // Thanks to @mathiasbynens for this solution // https://mathiasbynens.be/notes/javascript-unicode @@ -35,7 +35,7 @@ const Utils = { } return String.fromCharCode(o); - }, + } /** @@ -48,7 +48,7 @@ const Utils = { * // returns 97 * Utils.ord('a'); */ - ord: function(c) { + static ord(c) { // Detect astral symbols // Thanks to @mathiasbynens for this solution // https://mathiasbynens.be/notes/javascript-unicode @@ -62,7 +62,7 @@ const Utils = { } return c.charCodeAt(0); - }, + } /** @@ -88,8 +88,7 @@ const Utils = { * // returns ["t", "e", "s", "t", 1, 1, 1, 1] * Utils.padBytesRight("test", 8, 1); */ - padBytesRight: function(arr, numBytes, padByte) { - padByte = padByte || 0; + static padBytesRight(arr, numBytes, padByte=0) { const paddedBytes = new Array(numBytes); paddedBytes.fill(padByte); @@ -98,7 +97,7 @@ const Utils = { }); return paddedBytes; - }, + } /** @@ -116,13 +115,12 @@ const Utils = { * // returns "A long s-" * Utils.truncate("A long string", 9, "-"); */ - truncate: function(str, max, suffix) { - suffix = suffix || "..."; + static truncate(str, max, suffix="...") { if (str.length > max) { str = str.slice(0, max - suffix.length) + suffix; } return str; - }, + } /** @@ -139,11 +137,10 @@ const Utils = { * // returns "6e" * Utils.hex(110); */ - hex: function(c, length) { + static hex(c, length=2) { c = typeof c == "string" ? Utils.ord(c) : c; - length = length || 2; return c.toString(16).padStart(length, "0"); - }, + } /** @@ -160,11 +157,10 @@ const Utils = { * // returns "01101110" * Utils.bin(110); */ - bin: function(c, length) { + static bin(c, length=8) { c = typeof c == "string" ? Utils.ord(c) : c; - length = length || 8; return c.toString(2).padStart(length, "0"); - }, + } /** @@ -174,7 +170,7 @@ const Utils = { * @param {boolean} [preserveWs=false] - Whether or not to print whitespace. * @returns {string} */ - printable: function(str, preserveWs) { + static printable(str, preserveWs=false) { if (ENVIRONMENT_IS_WEB() && window.app && !window.app.options.treatAsUtf8) { str = Utils.byteArrayToChars(Utils.strToByteArray(str)); } @@ -185,7 +181,7 @@ const Utils = { str = str.replace(re, "."); if (!preserveWs) str = str.replace(wsRe, "."); return str; - }, + } /** @@ -201,7 +197,7 @@ const Utils = { * // returns "\n" * Utils.parseEscapedChars("\\n"); */ - parseEscapedChars: function(str) { + static parseEscapedChars(str) { return str.replace(/(\\)?\\([bfnrtv0'"]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) { if (a === "\\") return "\\"+b; switch (b[0]) { @@ -232,7 +228,7 @@ const Utils = { return String.fromCharCode(parseInt(b.substr(1), 16)); } }); - }, + } /** @@ -246,9 +242,9 @@ const Utils = { * // returns "\[example\]" * Utils.escapeRegex("[example]"); */ - escapeRegex: function(str) { + static escapeRegex(str) { return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1"); - }, + } /** @@ -267,7 +263,7 @@ const Utils = { * // returns ["a", "b", "c", "d", "0", "-", "3"] * Utils.expandAlphRange("a-d0\\-3") */ - expandAlphRange: function(alphStr) { + static expandAlphRange(alphStr) { const alphArr = []; for (let i = 0; i < alphStr.length; i++) { @@ -291,7 +287,7 @@ const Utils = { } } return alphArr; - }, + } /** @@ -312,7 +308,7 @@ const Utils = { * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] * Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); */ - convertToByteArray: function(str, type) { + static convertToByteArray(str, type) { switch (type.toLowerCase()) { case "hex": return Utils.fromHex(str); @@ -324,7 +320,7 @@ const Utils = { default: return Utils.strToByteArray(str); } - }, + } /** @@ -345,7 +341,7 @@ const Utils = { * // returns "Здравствуйте" * Utils.convertToByteString("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); */ - convertToByteString: function(str, type) { + static convertToByteString(str, type) { switch (type.toLowerCase()) { case "hex": return Utils.byteArrayToChars(Utils.fromHex(str)); @@ -357,7 +353,7 @@ const Utils = { default: return str; } - }, + } /** @@ -374,7 +370,7 @@ const Utils = { * // returns [228,189,160,229,165,189] * Utils.strToByteArray("你好"); */ - strToByteArray: function(str) { + static strToByteArray(str) { const byteArray = new Array(str.length); let i = str.length, b; while (i--) { @@ -384,7 +380,7 @@ const Utils = { if (b > 255) return Utils.strToUtf8ByteArray(str); } return byteArray; - }, + } /** @@ -400,7 +396,7 @@ const Utils = { * // returns [228,189,160,229,165,189] * Utils.strToUtf8ByteArray("你好"); */ - strToUtf8ByteArray: function(str) { + static strToUtf8ByteArray(str) { const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -412,7 +408,7 @@ const Utils = { } return Utils.strToByteArray(utf8Str); - }, + } /** @@ -428,7 +424,7 @@ const Utils = { * // returns [20320,22909] * Utils.strToCharcode("你好"); */ - strToCharcode: function(str) { + static strToCharcode(str) { const charcode = []; for (let i = 0; i < str.length; i++) { @@ -446,7 +442,7 @@ const Utils = { } return charcode; - }, + } /** @@ -462,7 +458,7 @@ const Utils = { * // returns "你好" * Utils.byteArrayToUtf8([228,189,160,229,165,189]); */ - byteArrayToUtf8: function(byteArray) { + static byteArrayToUtf8(byteArray) { const str = Utils.byteArrayToChars(byteArray); try { const utf8Str = utf8.decode(str); @@ -479,7 +475,7 @@ const Utils = { // If it fails, treat it as ANSI return str; } - }, + } /** @@ -495,14 +491,14 @@ const Utils = { * // returns "你好" * Utils.byteArrayToChars([20320,22909]); */ - byteArrayToChars: function(byteArray) { + static byteArrayToChars(byteArray) { if (!byteArray) return ""; let str = ""; for (let i = 0; i < byteArray.length;) { str += String.fromCharCode(byteArray[i++]); } return str; - }, + } /** @@ -516,17 +512,17 @@ const Utils = { * // returns "hello" * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); */ - arrayBufferToStr: function(arrayBuffer, utf8=true) { + static arrayBufferToStr(arrayBuffer, utf8=true) { const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer)); return utf8 ? Utils.byteArrayToUtf8(byteArray) : Utils.byteArrayToChars(byteArray); - }, + } /** * Base64's the input byte array using the given alphabet, returning a string. * * @param {byteArray|Uint8Array|string} data - * @param {string} [alphabet] + * @param {string} [alphabet="A-Za-z0-9+/="] * @returns {string} * * @example @@ -536,15 +532,14 @@ const Utils = { * // returns "SGVsbG8=" * Utils.toBase64("Hello"); */ - toBase64: function(data, alphabet) { + static toBase64(data, alphabet="A-Za-z0-9+/=") { if (!data) return ""; if (typeof data == "string") { data = Utils.strToByteArray(data); } - alphabet = alphabet ? - Utils.expandAlphRange(alphabet).join("") : - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + alphabet = Utils.expandAlphRange(alphabet).join(""); + let output = "", chr1, chr2, chr3, enc1, enc2, enc3, enc4, @@ -571,14 +566,14 @@ const Utils = { } return output; - }, + } /** * UnBase64's the input string using the given alphabet, returning a byte array. * * @param {byteArray} data - * @param {string} [alphabet] + * @param {string} [alphabet="A-Za-z0-9+/="] * @param {string} [returnType="string"] - Either "string" or "byteArray" * @param {boolean} [removeNonAlphChars=true] * @returns {byteArray} @@ -590,18 +585,12 @@ const Utils = { * // returns [72, 101, 108, 108, 111] * Utils.fromBase64("SGVsbG8=", null, "byteArray"); */ - fromBase64: function(data, alphabet, returnType, removeNonAlphChars) { - returnType = returnType || "string"; - + static fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) { if (!data) { return returnType === "string" ? "" : []; } - alphabet = alphabet ? - Utils.expandAlphRange(alphabet).join("") : - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - if (removeNonAlphChars === undefined) - removeNonAlphChars = true; + alphabet = Utils.expandAlphRange(alphabet).join(""); let output = [], chr1, chr2, chr3, @@ -638,7 +627,7 @@ const Utils = { } return returnType === "string" ? Utils.byteArrayToUtf8(output) : output; - }, + } /** @@ -656,11 +645,9 @@ const Utils = { * // returns "0a:14:1e" * Utils.toHex([10,20,30], ":"); */ - toHex: function(data, delim, padding) { + static toHex(data, delim=" ", padding=2) { if (!data) return ""; - delim = typeof delim == "string" ? delim : " "; - padding = padding || 2; let output = ""; for (let i = 0; i < data.length; i++) { @@ -675,7 +662,7 @@ const Utils = { return output.slice(0, -delim.length); else return output; - }, + } /** @@ -688,7 +675,7 @@ const Utils = { * // returns "0a141e" * Utils.toHex([10,20,30]); */ - toHexFast: function(data) { + static toHexFast(data) { if (!data) return ""; const output = []; @@ -699,7 +686,7 @@ const Utils = { } return output.join(""); - }, + } /** @@ -717,11 +704,10 @@ const Utils = { * // returns [10,20,30] * Utils.fromHex("0a:14:1e", "Colon"); */ - fromHex: function(data, delim, byteLen) { + static fromHex(data, delim, byteLen=2) { delim = delim || (data.indexOf(" ") >= 0 ? "Space" : "None"); - byteLen = byteLen || 2; if (delim !== "None") { - const delimRegex = Utils.regexRep[delim]; + const delimRegex = Utils.regexRep(delim); data = data.replace(delimRegex, ""); } @@ -730,7 +716,7 @@ const Utils = { output.push(parseInt(data.substr(i, byteLen), 16)); } return output; - }, + } /** @@ -743,8 +729,7 @@ const Utils = { * // returns [["head1", "head2"], ["data1", "data2"]] * Utils.parseCSV("head1,head2\ndata1,data2"); */ - parseCSV: function(data) { - + static parseCSV(data) { let b, ignoreNext = false, inString = false, @@ -783,26 +768,27 @@ const Utils = { } return lines; - }, + } /** * Removes all HTML (or XML) tags from the input string. * * @param {string} htmlStr - * @param {boolean} removeScriptAndStyle - Flag to specify whether to remove entire script or style blocks + * @param {boolean} [removeScriptAndStyle=false] + * - Flag to specify whether to remove entire script or style blocks * @returns {string} * * @example * // returns "Test" * Utils.stripHtmlTags("
Test
"); */ - stripHtmlTags: function(htmlStr, removeScriptAndStyle) { + static stripHtmlTags(htmlStr, removeScriptAndStyle=false) { if (removeScriptAndStyle) { htmlStr = htmlStr.replace(/<(script|style)[^>]*>.*<\/(script|style)>/gmi, ""); } return htmlStr.replace(/<[^>]+>/g, ""); - }, + } /** @@ -816,7 +802,7 @@ const Utils = { * // return "A <script> tag" * Utils.escapeHtml("A ", - staticSection = "", - padding = ""; - - if (input.length < 1) { - return "Please enter a string."; - } - - // Highlight offset 0 - if (len0 % 4 === 2) { - staticSection = offset0.slice(0, -3); - offset0 = "" + - staticSection + "" + - "" + offset0.substr(offset0.length - 3, 1) + "" + - "" + offset0.substr(offset0.length - 2) + ""; - } else if (len0 % 4 === 3) { - staticSection = offset0.slice(0, -2); - offset0 = "" + - staticSection + "" + - "" + offset0.substr(offset0.length - 2, 1) + "" + - "" + offset0.substr(offset0.length - 1) + ""; - } else { - staticSection = offset0; - offset0 = "" + - staticSection + ""; - } - - if (!showVariable) { - offset0 = staticSection; - } - - - // Highlight offset 1 - padding = "" + offset1.substr(0, 1) + "" + - "" + offset1.substr(1, 1) + ""; - offset1 = offset1.substr(2); - if (len1 % 4 === 2) { - staticSection = offset1.slice(0, -3); - offset1 = padding + "" + - staticSection + "" + - "" + offset1.substr(offset1.length - 3, 1) + "" + - "" + offset1.substr(offset1.length - 2) + ""; - } else if (len1 % 4 === 3) { - staticSection = offset1.slice(0, -2); - offset1 = padding + "" + - staticSection + "" + - "" + offset1.substr(offset1.length - 2, 1) + "" + - "" + offset1.substr(offset1.length - 1) + ""; - } else { - staticSection = offset1; - offset1 = padding + "" + - staticSection + ""; - } - - if (!showVariable) { - offset1 = staticSection; - } - - // Highlight offset 2 - padding = "" + offset2.substr(0, 2) + "" + - "" + offset2.substr(2, 1) + ""; - offset2 = offset2.substr(3); - if (len2 % 4 === 2) { - staticSection = offset2.slice(0, -3); - offset2 = padding + "" + - staticSection + "" + - "" + offset2.substr(offset2.length - 3, 1) + "" + - "" + offset2.substr(offset2.length - 2) + ""; - } else if (len2 % 4 === 3) { - staticSection = offset2.slice(0, -2); - offset2 = padding + "" + - staticSection + "" + - "" + offset2.substr(offset2.length - 2, 1) + "" + - "" + offset2.substr(offset2.length - 1) + ""; - } else { - staticSection = offset2; - offset2 = padding + "" + - staticSection + ""; - } - - if (!showVariable) { - offset2 = staticSection; - } - - return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." + - "\nCharacters highlighted in red are for padding purposes only." + - "\nUnhighlighted characters are static." + - "\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 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-_"}, diff --git a/src/core/operations/FromBase32.mjs b/src/core/operations/FromBase32.mjs new file mode 100644 index 00000000..0334ef51 --- /dev/null +++ b/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; diff --git a/src/core/operations/FromBase64.mjs b/src/core/operations/FromBase64.mjs index e8ac9ae7..cab86835 100644 --- a/src/core/operations/FromBase64.mjs +++ b/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.

This operation decodes data from an ASCII Base64 string back into its raw format.

e.g. aGVsbG8= becomes hello"; - 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); } /** diff --git a/src/core/operations/ShowBase64Offsets.mjs b/src/core/operations/ShowBase64Offsets.mjs new file mode 100644 index 00000000..2f44296f --- /dev/null +++ b/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.

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 = ""; + + if (input.length < 1) { + return "Please enter a string."; + } + + // Highlight offset 0 + if (len0 % 4 === 2) { + staticSection = offset0.slice(0, -3); + offset0 = "" + + staticSection + "" + + "" + offset0.substr(offset0.length - 3, 1) + "" + + "" + offset0.substr(offset0.length - 2) + ""; + } else if (len0 % 4 === 3) { + staticSection = offset0.slice(0, -2); + offset0 = "" + + staticSection + "" + + "" + offset0.substr(offset0.length - 2, 1) + "" + + "" + offset0.substr(offset0.length - 1) + ""; + } else { + staticSection = offset0; + offset0 = "" + + staticSection + ""; + } + + if (!showVariable) { + offset0 = staticSection; + } + + + // Highlight offset 1 + padding = "" + offset1.substr(0, 1) + "" + + "" + offset1.substr(1, 1) + ""; + offset1 = offset1.substr(2); + if (len1 % 4 === 2) { + staticSection = offset1.slice(0, -3); + offset1 = padding + "" + + staticSection + "" + + "" + offset1.substr(offset1.length - 3, 1) + "" + + "" + offset1.substr(offset1.length - 2) + ""; + } else if (len1 % 4 === 3) { + staticSection = offset1.slice(0, -2); + offset1 = padding + "" + + staticSection + "" + + "" + offset1.substr(offset1.length - 2, 1) + "" + + "" + offset1.substr(offset1.length - 1) + ""; + } else { + staticSection = offset1; + offset1 = padding + "" + + staticSection + ""; + } + + if (!showVariable) { + offset1 = staticSection; + } + + // Highlight offset 2 + padding = "" + offset2.substr(0, 2) + "" + + "" + offset2.substr(2, 1) + ""; + offset2 = offset2.substr(3); + if (len2 % 4 === 2) { + staticSection = offset2.slice(0, -3); + offset2 = padding + "" + + staticSection + "" + + "" + offset2.substr(offset2.length - 3, 1) + "" + + "" + offset2.substr(offset2.length - 2) + ""; + } else if (len2 % 4 === 3) { + staticSection = offset2.slice(0, -2); + offset2 = padding + "" + + staticSection + "" + + "" + offset2.substr(offset2.length - 2, 1) + "" + + "" + offset2.substr(offset2.length - 1) + ""; + } else { + staticSection = offset2; + offset2 = padding + "" + + staticSection + ""; + } + + if (!showVariable) { + offset2 = staticSection; + } + + return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." + + "\nCharacters highlighted in red are for padding purposes only." + + "\nUnhighlighted characters are static." + + "\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; diff --git a/src/core/operations/ToBase32.mjs b/src/core/operations/ToBase32.mjs new file mode 100644 index 00000000..1b217a34 --- /dev/null +++ b/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; diff --git a/src/core/operations/ToBase64.mjs b/src/core/operations/ToBase64.mjs index 82a78d7a..967ce8ed 100644 --- a/src/core/operations/ToBase64.mjs +++ b/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.

This operation decodes data from an ASCII Base64 string back into its raw format.

e.g. aGVsbG8= becomes hello"; - 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); } /** diff --git a/src/core/operations/index.mjs b/src/core/operations/index.mjs index 84aadaaf..3f3675ed 100644 --- a/src/core/operations/index.mjs +++ b/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 +}; diff --git a/src/core/operations/legacy/Cipher.js b/src/core/operations/legacy/Cipher.js index f44aca20..b343f218 100755 --- a/src/core/operations/legacy/Cipher.js +++ b/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. diff --git a/src/core/operations/legacy/Image.js b/src/core/operations/legacy/Image.js index 4ddffe74..cf3c568f 100644 --- a/src/core/operations/legacy/Image.js +++ b/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 ""; }, diff --git a/src/core/operations/legacy/PublicKey.js b/src/core/operations/legacy/PublicKey.js index 66b177a5..c0512705 100755 --- a/src/core/operations/legacy/PublicKey.js +++ b/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), "")); diff --git a/src/web/App.js b/src/web/App.js index d38658ea..29b858f2 100755 --- a/src/web/App.js +++ b/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"); diff --git a/src/web/ControlsWaiter.js b/src/web/ControlsWaiter.js index 15d4c095..bf3082cd 100755 --- a/src/web/ControlsWaiter.js +++ b/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, diff --git a/src/web/HTMLIngredient.js b/src/web/HTMLIngredient.js index 3f360f04..ca28ab01 100755 --- a/src/web/HTMLIngredient.js +++ b/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 ? "" : "
 
", + const inline = ( + this.type === "boolean" || + this.type === "number" || + this.type === "option" || + this.type === "shortString" || + this.type === "binaryShortString" + ); + let html = inline ? "" : "
 
", i, m; html += "