From be2080259ec9ae7d64945fc5640188ec4b773ba6 Mon Sep 17 00:00:00 2001 From: Kyle Parrish Date: Wed, 2 Oct 2019 09:57:50 -0400 Subject: [PATCH 001/225] Add Fang URL to categories --- src/core/config/Categories.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 94f7fd309f..18fc19ffc8 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -183,6 +183,7 @@ "Encode NetBIOS Name", "Decode NetBIOS Name", "Defang URL", + "Fang URL", "Defang IP Addresses" ] }, From cd15a8c406726bf06d55b879d271ac3f79b3ba99 Mon Sep 17 00:00:00 2001 From: Kyle Parrish Date: Wed, 2 Oct 2019 09:58:28 -0400 Subject: [PATCH 002/225] Create FangURL.mjs --- src/core/operations/FangURL.mjs | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/core/operations/FangURL.mjs diff --git a/src/core/operations/FangURL.mjs b/src/core/operations/FangURL.mjs new file mode 100644 index 0000000000..5badaae75b --- /dev/null +++ b/src/core/operations/FangURL.mjs @@ -0,0 +1,77 @@ +/** + * @author arnydo [github@arnydo.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * FangURL operation + */ +class FangURL extends Operation { + + /** + * FangURL constructor + */ + constructor() { + super(); + + this.name = "Fang URL"; + this.module = "Default"; + this.description = "Takes a 'Defanged' Universal Resource Locator (URL) and 'Fangs' it. Meaning, it removes the alterations (defanged) that render it useless so that it can be used again."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Escape [.]", + type: "boolean", + value: true + }, + { + name: "Escape hxxp", + type: "boolean", + value: true + }, + { + name: "Escape ://", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dots, http, slashes] = args; + + input = fangURL(input, dots, http, slashes); + + return input; + } + +} + + +/** + * Defangs a given URL + * + * @param {string} url + * @param {boolean} dots + * @param {boolean} http + * @param {boolean} slashes + * @returns {string} + */ +function fangURL(url, dots, http, slashes) { + if (dots) url = url.replace(/\[\.\]/g, "."); + if (http) url = url.replace(/hxxp/g, "http"); + if (slashes) url = url.replace(/\[\:\/\/\]/g, "://"); + + return url; +} + +export default FangURL; From 3546ee30a22611f6af16c00532a31eb08fdd2501 Mon Sep 17 00:00:00 2001 From: Kyle Parrish Date: Mon, 7 Oct 2019 16:09:22 -0400 Subject: [PATCH 003/225] Update escaped chars --- src/core/operations/FangURL.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/FangURL.mjs b/src/core/operations/FangURL.mjs index 5badaae75b..7390c1a9ce 100644 --- a/src/core/operations/FangURL.mjs +++ b/src/core/operations/FangURL.mjs @@ -69,7 +69,7 @@ class FangURL extends Operation { function fangURL(url, dots, http, slashes) { if (dots) url = url.replace(/\[\.\]/g, "."); if (http) url = url.replace(/hxxp/g, "http"); - if (slashes) url = url.replace(/\[\:\/\/\]/g, "://"); + if (slashes) url = url.replace(/[://]/g, "://"); return url; } From 06f95edd2e5299ac3d028475157e9e77421499db Mon Sep 17 00:00:00 2001 From: Emil Henry Date: Sun, 29 Mar 2020 23:15:03 +0200 Subject: [PATCH 004/225] Add 'Cut' operation --- src/core/config/Categories.json | 1 + src/core/operations/Cut.mjs | 217 ++++++++++++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/Cut.mjs | 101 +++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 src/core/operations/Cut.mjs create mode 100644 tests/operations/tests/Cut.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 77e3d31941..191cda244a 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -208,6 +208,7 @@ { "name": "Utils", "ops": [ + "Cut", "Diff", "Remove whitespace", "Remove null bytes", diff --git a/src/core/operations/Cut.mjs b/src/core/operations/Cut.mjs new file mode 100644 index 0000000000..d824e6958f --- /dev/null +++ b/src/core/operations/Cut.mjs @@ -0,0 +1,217 @@ +/** + * @author emilhf [emil@cyberops.no] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {SPLIT_DELIM_OPTIONS, JOIN_DELIM_OPTIONS} from "../lib/Delim.mjs"; +import XRegExp from "xregexp"; + +/** + * Cut operation + */ +class Cut extends Operation { + + /** + * Cut constructor + */ + constructor() { + super(); + + this.name = "Cut"; + this.module = "Utils"; + this.description = "Extract fields from records similarly to awk and cut. The expression 1, 3-4 will extract the 2nd, 4th and 5th fields. 3, 1 "T" 2 will extract the 4th field, then combine the 2nd and 3rd field into a new field (with the letter 'T' separating the original values).

If no input field delimiter is set, fixed width mode is enabled: Fields become the indices of the payload, and ranges will be appended to the current output field instead of creating new fields. This aids in carving e.g. CSVs from fixed width data."; + this.infoURL = "https://en.wikipedia.org/wiki/Cut_(Unix)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Common input type", + "type": "populateOption", + "value": [ + { + name: "User defined", + value: "" + }, + { + name: "CSV", + value: "," + }, + { + name: "TSV", + value: "\\t" + }, + { + name: "PSV", + value: "\\|" + }, + { + name: "Space aligned", + value: "\\s+" + } + ], + "target": 4 + }, + { + "name": "Expression", + "type": "text", + "value": "0-" + }, + { + "name": "Input record delimiter", + "type": "editableOptionShort", + "value": SPLIT_DELIM_OPTIONS, + "defaultIndex": 2 + }, + { + "name": "Output record delimiter", + "type": "editableOptionShort", + "value": SPLIT_DELIM_OPTIONS, + "defaultIndex": 2 + }, + { + "name": "Input field delimiter", + "type": "shortString", + "value": "" + }, + { + "name": "Output field delimiter", + "type": "editableOptionShort", + "value": JOIN_DELIM_OPTIONS, + "defaultIndex": 3 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [, expr, inRecordDelim, outRecordDelim, inFieldDelim, outFieldDelim] = args; + const split = new XRegExp(inFieldDelim); + const fixedWidth = inFieldDelim === ""; + + /** + * @param {Array[]} + * @returns {Array[]} + */ + const gr = (data) => { + data = fixedWidth ? data : data.split(split); + return this.extract(data, expr, fixedWidth).join(outFieldDelim); + }; + + return input.split(inRecordDelim).map(gr).join(outRecordDelim); + // return gr(input); + } + + /** + * Extracts fields as specified by the extraction expression. If fixedWidth + * is true, ranges do not introduce new fields, but rather append to the + * current field being dealt with. + * + * The extract expression is a lightweight DSL similar to the fields flag + * (-f) of cut in UNIX, and also incorporates elements of the awk print + * statement. It departs from cut in a few noteworthy ways: + * + * - Reverse ranges are supported, e.g. 4-1. + * + * - Negative field values, e.g. -1, are offsets from the end of the data. + * Note that negative ranges are not supported. + * + * - Fields are numbered from 0 instead of 1. + * + * - New fields can be constructed by combining existing fields. This + * operation also supports appending strings: '1 "@" 2' will join field 1 + * and 2 with "@" in between them. + * + * @param {Array[]} data + * @param {string} expr + * @param {Boolean} fixedWidth + * @returns {Array[Number]} + */ + extract(data, expr, fixedWidth) { + const maxOffset = data.length - 1; + + /** + * @param {Number} n + * @returns {Array[]} + */ + const pick = (n) => n < 0 ? data[maxOffset + n + 1] : data[n]; + + const fields = []; + let currentField = []; + let previousToken = null; + const tokens = expr.trim().match(/((".*?")|(\d+-\d*)|(-?\d+)|(,))/g); + tokens.forEach(token => { + // Field separator + if (token.match(/^,$/)) { + previousToken = "delimiter"; + if (currentField.length) { + fields.push(currentField.join("")); + currentField = []; + } + return; + } + + if (!fixedWidth && previousToken === "range") { + throw new OperationError( + `Cannot join '${token}', as previous term was a range. Requires fixed width mode.` + ); + } + + if (token.match("^-?[0-9]+$")) { + previousToken = "extraction"; + const n = Number(token); + currentField.push(pick(n)); + return; + } + if (token.match(/^\d+-\d*$/)) { + previousToken = "range"; + if (!fixedWidth && currentField.length) { + throw new OperationError( + `Cannot join range '${token}' with rest of field: ${currentField.join("")}. Requires fixed width mode.` + ); + } + const m = token.match(/^([0-9]+)-([0-9]*)$/); + const a = Number(m[1]); + const b = m[2] === "" ? maxOffset: Number(m[2]); + + const vals = []; + if (a <= b) { + for (let i = a; i <= b && i <= maxOffset; i++) { + vals.push(pick(i)); + } + } else { + for (let i = a; i >= b && i <= maxOffset; i--) { + vals.push(pick(i)); + } + } + + if (fixedWidth) { + currentField.push(...vals); + } else { + fields.push(...vals); + } + return; + } + if (token.match(/^".*"$/)) { + previousToken = "string"; + const m = token.match(/"(.*)"/); + currentField.push(m[1]); + } + // NOT REACHED + }); + // Terminal condition + if (currentField.length) { + fields.push(currentField.join("")); + } + return fields; + } + +} + +export default Cut; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 8d3cd623d8..1f41cfef50 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -37,6 +37,7 @@ import "./tests/Compress.mjs"; import "./tests/ConditionalJump.mjs"; import "./tests/Crypt.mjs"; import "./tests/CSV.mjs"; +import "./tests/Cut.mjs"; import "./tests/DateTime.mjs"; import "./tests/ExtractEmailAddresses.mjs"; import "./tests/Fork.mjs"; diff --git a/tests/operations/tests/Cut.mjs b/tests/operations/tests/Cut.mjs new file mode 100644 index 0000000000..d0d18986df --- /dev/null +++ b/tests/operations/tests/Cut.mjs @@ -0,0 +1,101 @@ +/** + * Cut operation tests + * + * @author emilhf [emil@cyberops.no] + * + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Extract single field", + input: "test1,test2,test3", + expectedOutput: "test2", + recipeConfig: [ + { + op: "Cut", + args: ["User defined", "1", "\\n", "\\n", ",", ","], + }, + ], + }, + { + name: "Extract range", + input: "test1,test2,test3", + expectedOutput: "test2,test3", + recipeConfig: [ + { + op: "Cut", + args: ["User defined", "1-2", "\\n", "\\n", ",", ","], + }, + ], + }, + { + name: "Extract reverse range", + input: "test1,test2,test3", + expectedOutput: "test2,test1", + recipeConfig: [ + { + op: "Cut", + args: ["User defined", "2-1", "\\n", "\\n", ",", ","], + }, + ], + }, + { + name: "Extract multiple ranges", + input: "test1,test2,test3", + expectedOutput: "test2,test3,test1", + recipeConfig: [ + { + op: "Cut", + args: ["User defined", "1-2,0", "\\n", "\\n", ",", ","], + }, + ], + }, + { + name: "Combine two existing fields", + input: "john.doe,CONTOSO\nadams,CONTOSO", + expectedOutput: "john.doe@CONTOSO\nadams@CONTOSO", + recipeConfig: [ + { + op: "Cut", + args: ["User defined", "0 \"@\" 1", "\\n", "\\n", ",", ","], + }, + ], + }, + { + name: "Fixed width to CSV", + input: "abcdefghijklmnopqrstuvxyz", + expectedOutput: "abc,xyz", + recipeConfig: [ + { + op: "Cut", + args: ["User defined", "0-2, 22-24", "\\n", "\\n", "", ","], + }, + ], + }, + { + name: "Extract and convert CSV to TSV", + input: "ITEM,VALUE\nflamingo,439\nvodka,14", + expectedOutput: "ITEM\tVALUE\nflamingo\t439\nvodka\t14", + recipeConfig: [ + { + op: "Cut", + args: ["User defined", "0-", "\\n", "\\n", ",", "\\t"], + } + ], + }, + { + name: "Extract with wrong delimiter", + input: "test1,test2", + expectedOutput: "test1,test2", + recipeConfig: [ + { + op: "Cut", + args: ["User defined", "0-", "\\n", "\\n", "\\t", ";"], + }, + ], + }, +]); From 1ab3baf7be410c2bc6cbb2112866469d014d0b87 Mon Sep 17 00:00:00 2001 From: Emil Henry Date: Mon, 30 Mar 2020 08:58:18 +0200 Subject: [PATCH 005/225] Fix typo in Cut test --- tests/operations/tests/Cut.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/operations/tests/Cut.mjs b/tests/operations/tests/Cut.mjs index d0d18986df..8856587939 100644 --- a/tests/operations/tests/Cut.mjs +++ b/tests/operations/tests/Cut.mjs @@ -39,7 +39,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Cut", - args: ["User defined", "2-1", "\\n", "\\n", ",", ","], + args: ["User defined", "1-0", "\\n", "\\n", ",", ","], }, ], }, From 758ff9b6041606454de3b4bc3238fb3e0bd39378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lteri=C5=9F=20Ya=C4=9F=C4=B1ztegin=20Ero=C4=9Flu?= Date: Sat, 19 Sep 2020 15:24:15 +0300 Subject: [PATCH 006/225] feat(Modhex): Introduce basic Modhex conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: İlteriş Yağıztegin Eroğlu --- src/core/config/Categories.json | 2 + src/core/lib/Modhex.mjs | 65 +++++++++++++ src/core/operations/FromModhex.mjs | 123 +++++++++++++++++++++++++ src/core/operations/ToModhex.mjs | 110 ++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/Modhex.mjs | 143 +++++++++++++++++++++++++++++ 6 files changed, 444 insertions(+) create mode 100644 src/core/lib/Modhex.mjs create mode 100644 src/core/operations/FromModhex.mjs create mode 100644 src/core/operations/ToModhex.mjs create mode 100644 tests/operations/tests/Modhex.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 77e3d31941..def4933952 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -10,6 +10,8 @@ "From Hexdump", "To Hex", "From Hex", + "To Modhex", + "From Modhex", "To Charcode", "From Charcode", "To Decimal", diff --git a/src/core/lib/Modhex.mjs b/src/core/lib/Modhex.mjs new file mode 100644 index 0000000000..e1568e85dd --- /dev/null +++ b/src/core/lib/Modhex.mjs @@ -0,0 +1,65 @@ +/** + * Modhex helpers. + * + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + + +/** + * Modhex to Hex conversion map. + */ +export const MODHEX_TO_HEX_CONVERSION_MAP = { + "c": "0", + "b": "1", + "d": "2", + "e": "3", + "f": "4", + "g": "5", + "h": "6", + "i": "7", + "j": "8", + "k": "9", + "l": "a", + "n": "b", + "r": "c", + "t": "d", + "u": "e", + "v": "f" +}; + + +/** + * Hex to Modhex conversion map. + */ +export const HEX_TO_MODHEX_CONVERSION_MAP = { + "0": "c", + "1": "b", + "2": "d", + "3": "e", + "4": "f", + "5": "g", + "6": "h", + "7": "i", + "8": "j", + "9": "k", + "a": "l", + "b": "n", + "c": "r", + "d": "t", + "e": "u", + "f": "v" +}; + + +/** + * To Modhex delimiters. + */ +export const TO_MODHEX_DELIM_OPTIONS = ["Space", "Percent", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"]; + + +/** + * From Modhex delimiters. + */ +export const FROM_MODHEX_DELIM_OPTIONS = ["Auto"].concat(TO_MODHEX_DELIM_OPTIONS); diff --git a/src/core/operations/FromModhex.mjs b/src/core/operations/FromModhex.mjs new file mode 100644 index 0000000000..054c259e1f --- /dev/null +++ b/src/core/operations/FromModhex.mjs @@ -0,0 +1,123 @@ +/** + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { FROM_MODHEX_DELIM_OPTIONS, MODHEX_TO_HEX_CONVERSION_MAP } from "../lib/Modhex.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * From Modhex operation + */ +class FromModhex extends Operation { + + /** + * FromModhex constructor + */ + constructor() { + super(); + + this.name = "From Modhex"; + this.module = "Default"; + this.description = "Converts a modhex byte string back into its raw value."; + this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: FROM_MODHEX_DELIM_OPTIONS + } + ]; + this.checks = [ + { + pattern: "^(?:[cbdefghijklnrtuv]{2})+$", + flags: "i", + args: ["None"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?: [cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Space"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:,[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Comma"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:;[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Semi-colon"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?::[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Colon"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:\\n[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Line feed"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:\\r\\n[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["CRLF"] + } + ]; + } + + /** + * Convert a hex string into a byte array. + * + * @param {string} data + * @param {string} [delim] + * @param {number} [byteLen=2] + * @returns {byteArray} + * + * @example + * // returns [10,20,30] + * fromModhex("cl bf bu"); + * + * // returns [10,20,30] + * fromModhex("cl:bf:bu", "Colon"); + */ + fromModhex(data, delim="Auto", byteLen=2) { + if (!data || data.length === 0) return []; + + data = data.toLowerCase(); + + if (delim !== "None") { + const delimRegex = delim === "Auto" ? /[^cbdefghijklnrtuv]/gi : Utils.regexRep(delim); + data = data.replace(delimRegex, ""); + } + + data = data.replace(/\s/g, ""); + + let hexconv = ""; + for (const letter of data.split("")) { + hexconv += MODHEX_TO_HEX_CONVERSION_MAP[letter]; + } + + const output = fromHex(hexconv, "None", byteLen); + return output; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = args[0] || "Auto"; + return this.fromModhex(input, delim, 2); + } + +} + +export default FromModhex; diff --git a/src/core/operations/ToModhex.mjs b/src/core/operations/ToModhex.mjs new file mode 100644 index 0000000000..2d07fa31e3 --- /dev/null +++ b/src/core/operations/ToModhex.mjs @@ -0,0 +1,110 @@ +/** + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { TO_MODHEX_DELIM_OPTIONS, HEX_TO_MODHEX_CONVERSION_MAP } from "../lib/Modhex.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To Modhex operation + */ +class ToModhex extends Operation { + + /** + * ToModhex constructor + */ + constructor() { + super(); + + this.name = "To Modhex"; + this.module = "Default"; + this.description = "Converts the input string to modhex bytes separated by the specified delimiter."; + this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: TO_MODHEX_DELIM_OPTIONS + }, + { + name: "Bytes per line", + type: "number", + value: 0 + } + ]; + } + + /** + * Convert a byte array into a modhex string. + * + * @param {byteArray|Uint8Array|ArrayBuffer} data + * @param {string} [delim=" "] + * @param {number} [padding=2] + * @returns {string} + * + * @example + * // returns "cl bf bu" + * toModhex([10,20,30]); + * + * // returns "cl:bf:bu" + * toModhex([10,20,30], ":"); + */ + toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0) { + if (!data || data.length === 0) return ""; + if (data instanceof ArrayBuffer) data = new Uint8Array(data); + + const hexconv = toHex(data, "", padding, "", 0); + let modhexconv = ""; + let output = ""; + + for (const letter of hexconv.split("")) { + modhexconv += HEX_TO_MODHEX_CONVERSION_MAP[letter]; + } + + const groupedModhex = modhexconv.match(/.{1,2}/g); + + for (let i = 0; i < groupedModhex.length; i++) { + const group = groupedModhex[i]; + output += group + delim; + + if (extraDelim) { + output += extraDelim; + } + // Add LF after each lineSize amount of bytes but not at the end + if ((i !== groupedModhex.length - 1) && ((i + 1) % lineSize === 0)) { + output += "\n"; + } + } + + // Remove the extraDelim at the end (if there is one) + // and remove the delim at the end, but if it's prepended there's nothing to remove + const rTruncLen = extraDelim.length + delim.length; + if (rTruncLen) { + // If rTruncLen === 0 then output.slice(0,0) will be returned, which is nothing + return output.slice(0, -rTruncLen); + } else { + return output; + } + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]); + const lineSize = args[1]; + + return this.toModhex(new Uint8Array(input), delim, 2, "", lineSize); + } + +} + +export default ToModhex; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 8d3cd623d8..4b8a44dd61 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -58,6 +58,7 @@ import "./tests/JWTSign.mjs"; import "./tests/JWTVerify.mjs"; import "./tests/MS.mjs"; import "./tests/Magic.mjs"; +import "./tests/Modhex.mjs"; import "./tests/MorseCode.mjs"; import "./tests/NetBIOS.mjs"; import "./tests/NormaliseUnicode.mjs"; diff --git a/tests/operations/tests/Modhex.mjs b/tests/operations/tests/Modhex.mjs new file mode 100644 index 0000000000..2d3041207a --- /dev/null +++ b/tests/operations/tests/Modhex.mjs @@ -0,0 +1,143 @@ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "ASCII to Modhex stream", + input: "aberystwyth", + expectedOutput: "hbhdhgidikieifiiikifhj", + recipeConfig: [ + { + "op": "To Modhex", + "args": [ + "None", + 0 + ] + }, + ] + }, + { + name: "ASCII to Modhex with colon deliminator", + input: "aberystwyth", + expectedOutput: "hb:hd:hg:id:ik:ie:if:ii:ik:if:hj", + recipeConfig: [ + { + "op": "To Modhex", + "args": [ + "Colon", + 0 + ] + } + ] + }, + { + name: "Modhex stream to UTF-8", + input: "uhkgkbuhkgkbugltlkugltkc", + expectedOutput: "救救孩子", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Auto" + ] + } + ] + + }, + { + name: "Mixed case Modhex stream to UTF-8", + input: "uhKGkbUHkgkBUGltlkugltkc", + expectedOutput: "救救孩子", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Auto" + ] + } + ] + + }, + { + name: "Mutiline Modhex with comma to ASCII (Auto Mode)", + input: "fk,dc,ie,hb,ii,dc,ht,ik,ie,hg,hr,hh,dc,ie,hk,\n\ +if,if,hk,hu,hi,dc,hk,hu,dc,if,hj,hg,dc,he,id,\n\ +hv,if,he,hj,dc,hv,hh,dc,if,hj,hg,dc,if,hj,hk,\n\ +ie,dc,hh,hk,hi,dc,if,id,hg,hg,dr,dc,ie,if,hb,\n\ +id,ih,hk,hu,hi,dc,if,hv,dc,hf,hg,hb,if,hj,dr,\n\ +dc,hl,ig,ie,if,dc,hd,hg,he,hb,ig,ie,hg,dc,fk,\n\ +dc,he,hv,ig,hr,hf,hu,di,if,dc,ht,hb,hn,hg,dc,\n\ +ig,ic,dc,ht,ik,dc,ht,hk,hu,hf,dc,ii,hj,hk,he,\n\ +hj,dc,hv,hh,dc,if,hj,hg,dc,hh,hk,hi,ie,dc,fk,\n\ +dc,ii,hv,ig,hr,hf,dc,he,hj,hv,hv,ie,hg,du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Auto" + ] + } + ] + + }, + { + name: "Mutiline Modhex with percent to ASCII (Percent Mode)", + input: "fk%dc%ie%hb%ii%dc%ht%ik%ie%hg%hr%hh%dc%ie%hk%\n\ +if%if%hk%hu%hi%dc%hk%hu%dc%if%hj%hg%dc%he%id%\n\ +hv%if%he%hj%dc%hv%hh%dc%if%hj%hg%dc%if%hj%hk%\n\ +ie%dc%hh%hk%hi%dc%if%id%hg%hg%dr%dc%ie%if%hb%\n\ +id%ih%hk%hu%hi%dc%if%hv%dc%hf%hg%hb%if%hj%dr%\n\ +dc%hl%ig%ie%if%dc%hd%hg%he%hb%ig%ie%hg%dc%fk%\n\ +dc%he%hv%ig%hr%hf%hu%di%if%dc%ht%hb%hn%hg%dc%\n\ +ig%ic%dc%ht%ik%dc%ht%hk%hu%hf%dc%ii%hj%hk%he%\n\ +hj%dc%hv%hh%dc%if%hj%hg%dc%hh%hk%hi%ie%dc%fk%\n\ +dc%ii%hv%ig%hr%hf%dc%he%hj%hv%hv%ie%hg%du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Percent" + ] + } + ] + + }, + { + name: "Mutiline Modhex with semicolon to ASCII (Semi-colon Mode)", + input: "fk;dc;ie;hb;ii;dc;ht;ik;ie;hg;hr;hh;dc;ie;hk;\n\ +if;if;hk;hu;hi;dc;hk;hu;dc;if;hj;hg;dc;he;id;\n\ +hv;if;he;hj;dc;hv;hh;dc;if;hj;hg;dc;if;hj;hk;\n\ +ie;dc;hh;hk;hi;dc;if;id;hg;hg;dr;dc;ie;if;hb;\n\ +id;ih;hk;hu;hi;dc;if;hv;dc;hf;hg;hb;if;hj;dr;\n\ +dc;hl;ig;ie;if;dc;hd;hg;he;hb;ig;ie;hg;dc;fk;\n\ +dc;he;hv;ig;hr;hf;hu;di;if;dc;ht;hb;hn;hg;dc;\n\ +ig;ic;dc;ht;ik;dc;ht;hk;hu;hf;dc;ii;hj;hk;he;\n\ +hj;dc;hv;hh;dc;if;hj;hg;dc;hh;hk;hi;ie;dc;fk;\n\ +dc;ii;hv;ig;hr;hf;dc;he;hj;hv;hv;ie;hg;du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Semi-colon" + ] + } + ] + + }, + { + name: "ASCII to Modhex with comma and line breaks", + input: "aberystwyth", + expectedOutput: "hb,hd,hg,id,\nik,ie,if,ii,\nik,if,hj", + recipeConfig: [ + { + "op": "To Modhex", + "args": [ + "Comma", + 4 + ] + } + ] + }, +]); From 012b54d44fa1a14208aa96f3ddc7c645493397a7 Mon Sep 17 00:00:00 2001 From: Dave York Date: Sat, 19 Jun 2021 22:28:02 -0400 Subject: [PATCH 007/225] adding a dockerfile --- Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..ce17e0b88e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:dubnium-buster-slim +RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/* + +COPY . / +ENV NODE_OPTIONS="--max_old_space_size=2048" +RUN npm install node-sass +RUN npm install -g grunt-cli +RUN npm i +CMD grunt dev From 0668a9d4cff5e4983595c7a8dd43b2d6cc2d3ff6 Mon Sep 17 00:00:00 2001 From: Dave York Date: Sat, 19 Jun 2021 22:33:14 -0400 Subject: [PATCH 008/225] update readme with docker instructions --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index ac9b8174e4..38d0bfd105 100755 --- a/README.md +++ b/README.md @@ -22,6 +22,16 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur [A live demo can be found here][1] - have fun! +## Running in Docker + +If you would like to run the app locally in docker please follow the steps below: + +``` +git clone https://github.com/gchq/CyberChef.git +cd CyberChef +docker build --tag cyberchef . +docker run --rm --name cyberchef -it -p 8080:8080 cyberchef +``` ## How it works From f3abb053429fc0c49342a2f447c19c1720e23045 Mon Sep 17 00:00:00 2001 From: Dave York Date: Thu, 1 Jul 2021 09:32:02 -0400 Subject: [PATCH 009/225] update readme per suggestion --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 38d0bfd105..6e80684568 100755 --- a/README.md +++ b/README.md @@ -22,17 +22,6 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur [A live demo can be found here][1] - have fun! -## Running in Docker - -If you would like to run the app locally in docker please follow the steps below: - -``` -git clone https://github.com/gchq/CyberChef.git -cd CyberChef -docker build --tag cyberchef . -docker run --rm --name cyberchef -it -p 8080:8080 cyberchef -``` - ## How it works There are four main areas in CyberChef: @@ -114,6 +103,17 @@ An installation walkthrough, how-to guides for adding new operations and themes, - Submit a pull request. If you are doing this for the first time, you will be prompted to sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef) via the CLA assistant on the pull request. This will also ask whether you are happy for GCHQ to contact you about a token of thanks for your contribution, or about job opportunities at GCHQ. +## Running in Docker + +If you would like to run the app locally in docker please follow the steps below: + +``` +git clone https://github.com/gchq/CyberChef.git +cd CyberChef +docker build --tag cyberchef . +docker run --rm --name cyberchef -it -p 8080:8080 cyberchef +``` + ## Licencing CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/copyright-and-re-use/crown-copyright/). From daef66372713bd08096799679a0e326b466ac5aa Mon Sep 17 00:00:00 2001 From: Erik Hansson Date: Sat, 11 Feb 2023 17:27:47 +0100 Subject: [PATCH 010/225] Add X.509 output formats --- src/core/operations/ParseX509Certificate.mjs | 238 +++++++++++-------- 1 file changed, 142 insertions(+), 96 deletions(-) diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs index 88678de942..e0aa38b712 100644 --- a/src/core/operations/ParseX509Certificate.mjs +++ b/src/core/operations/ParseX509Certificate.mjs @@ -5,8 +5,8 @@ */ import r from "jsrsasign"; -import { fromBase64 } from "../lib/Base64.mjs"; -import { toHex } from "../lib/Hex.mjs"; +import { fromBase64, toBase64 } from "../lib/Base64.mjs"; +import { fromHex, toHex } from "../lib/Hex.mjs"; import { formatByteStr, formatDnObj } from "../lib/PublicKey.mjs"; import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; @@ -33,6 +33,11 @@ class ParseX509Certificate extends Operation { "name": "Input format", "type": "option", "value": ["PEM", "DER Hex", "Base64", "Raw"] + }, + { + "name": "Output format", + "type": "option", + "value": ["Text", "JSON", "PEM", "DER Hex", "Base64", "Raw"] } ]; this.checks = [ @@ -55,9 +60,13 @@ class ParseX509Certificate extends Operation { } const cert = new r.X509(), - inputFormat = args[0]; + inputFormat = args[0], + outputFormat = args[1]; + + let undefinedInputFormat = false, + undefinedOuputFormat = false, + output = ""; - let undefinedInputFormat = false; try { switch (inputFormat) { case "DER Hex": @@ -81,107 +90,146 @@ class ParseX509Certificate extends Operation { } if (undefinedInputFormat) throw "Undefined input format"; - const sn = cert.getSerialNumberHex(), - issuer = cert.getIssuer(), - subject = cert.getSubject(), - pk = cert.getPublicKey(), - pkFields = [], - sig = cert.getSignatureValueHex(); + try { + switch (outputFormat) { + case "Text": + output = formatText(cert); + break; + case "JSON": + output = JSON.stringify(cert.getParam()); + break; + case "DER Hex": + output = cert.hex; + break; + case "PEM": + output = r.hextopem(cert.hex, "CERTIFICATE"); + break; + case "Base64": + output = toBase64(fromHex(cert.hex)); + break; + case "Raw": + output = Utils.byteArrayToChars(fromHex(cert.hex)); + break; + default: + undefinedOuputFormat = true; + } + } catch (e) { + throw "Certificate encoding error (what even hapened?)"; + } + if (undefinedOuputFormat) throw "Undefined output format"; + + return output; + } - let pkStr = "", - sigStr = "", - extensions = ""; +} +/** + * Format X.509 certificate. + * + * @param {r.X509} cert + * @returns {string} + */ +function formatText(cert) { + const sn = cert.getSerialNumberHex(), + issuer = cert.getIssuer(), + subject = cert.getSubject(), + pk = cert.getPublicKey(), + pkFields = [], + sig = cert.getSignatureValueHex(); + + let pkStr = "", + sigStr = "", + extensions = ""; + + // Public Key fields + pkFields.push({ + key: "Algorithm", + value: pk.type + }); - // Public Key fields + if (pk.type === "EC") { // ECDSA pkFields.push({ - key: "Algorithm", - value: pk.type + key: "Curve Name", + value: pk.curveName }); + pkFields.push({ + key: "Length", + value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength() - 3) / 2) + " bits" + }); + pkFields.push({ + key: "pub", + value: formatByteStr(pk.pubKeyHex, 16, 18) + }); + } else if (pk.type === "DSA") { // DSA + pkFields.push({ + key: "pub", + value: formatByteStr(pk.y.toString(16), 16, 18) + }); + pkFields.push({ + key: "P", + value: formatByteStr(pk.p.toString(16), 16, 18) + }); + pkFields.push({ + key: "Q", + value: formatByteStr(pk.q.toString(16), 16, 18) + }); + pkFields.push({ + key: "G", + value: formatByteStr(pk.g.toString(16), 16, 18) + }); + } else if (pk.e) { // RSA + pkFields.push({ + key: "Length", + value: pk.n.bitLength() + " bits" + }); + pkFields.push({ + key: "Modulus", + value: formatByteStr(pk.n.toString(16), 16, 18) + }); + pkFields.push({ + key: "Exponent", + value: pk.e + " (0x" + pk.e.toString(16) + ")" + }); + } else { + pkFields.push({ + key: "Error", + value: "Unknown Public Key type" + }); + } - if (pk.type === "EC") { // ECDSA - pkFields.push({ - key: "Curve Name", - value: pk.curveName - }); - pkFields.push({ - key: "Length", - value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + " bits" - }); - pkFields.push({ - key: "pub", - value: formatByteStr(pk.pubKeyHex, 16, 18) - }); - } else if (pk.type === "DSA") { // DSA - pkFields.push({ - key: "pub", - value: formatByteStr(pk.y.toString(16), 16, 18) - }); - pkFields.push({ - key: "P", - value: formatByteStr(pk.p.toString(16), 16, 18) - }); - pkFields.push({ - key: "Q", - value: formatByteStr(pk.q.toString(16), 16, 18) - }); - pkFields.push({ - key: "G", - value: formatByteStr(pk.g.toString(16), 16, 18) - }); - } else if (pk.e) { // RSA - pkFields.push({ - key: "Length", - value: pk.n.bitLength() + " bits" - }); - pkFields.push({ - key: "Modulus", - value: formatByteStr(pk.n.toString(16), 16, 18) - }); - pkFields.push({ - key: "Exponent", - value: pk.e + " (0x" + pk.e.toString(16) + ")" - }); - } else { - pkFields.push({ - key: "Error", - value: "Unknown Public Key type" - }); - } - - // Format Public Key fields - for (let i = 0; i < pkFields.length; i++) { - pkStr += ` ${pkFields[i].key}:${(pkFields[i].value + "\n").padStart( - 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, - " " - )}`; - } + // Format Public Key fields + for (let i = 0; i < pkFields.length; i++) { + pkStr += ` ${pkFields[i].key}:${(pkFields[i].value + "\n").padStart( + 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, + " " + )}`; + } - // Signature fields - let breakoutSig = false; - try { - breakoutSig = r.ASN1HEX.dump(sig).indexOf("SEQUENCE") === 0; - } catch (err) { - // Error processing signature, output without further breakout - } + // Signature fields + let breakoutSig = false; + try { + breakoutSig = r.ASN1HEX.dump(sig).indexOf("SEQUENCE") === 0; + } catch (err) { + // Error processing signature, output without further breakout + } - if (breakoutSig) { // DSA or ECDSA - sigStr = ` r: ${formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18)} + if (breakoutSig) { // DSA or ECDSA + sigStr = ` r: ${formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18)} s: ${formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18)}`; - } else { // RSA or unknown - sigStr = ` Signature: ${formatByteStr(sig, 16, 18)}`; - } + } else { // RSA or unknown + sigStr = ` Signature: ${formatByteStr(sig, 16, 18)}`; + } - // Extensions - try { - extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; - } catch (err) {} + // Extensions + try { + extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; + } catch (err) { } - const issuerStr = formatDnObj(issuer, 2), - nbDate = formatDate(cert.getNotBefore()), - naDate = formatDate(cert.getNotAfter()), - subjectStr = formatDnObj(subject, 2); + const issuerStr = formatDnObj(issuer, 2), + nbDate = formatDate(cert.getNotBefore()), + naDate = formatDate(cert.getNotAfter()), + subjectStr = formatDnObj(subject, 2); - return `Version: ${cert.version} (0x${Utils.hex(cert.version - 1)}) + return `Version: ${cert.version} (0x${Utils.hex(cert.version - 1)}) Serial number: ${new r.BigInteger(sn, 16).toString()} (0x${sn}) Algorithm ID: ${cert.getSignatureAlgorithmField()} Validity @@ -199,8 +247,6 @@ ${sigStr} Extensions ${extensions}`; - } - } /** From 3e8c5d945cd65e920930ddd6dfb012123cd6a0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 27 Feb 2023 06:09:30 +0000 Subject: [PATCH 011/225] Add "XOR Checksum" operation --- src/core/config/Categories.json | 3 +- src/core/operations/XORChecksum.mjs | 59 +++++++++++++++++++ tests/operations/tests/Checksum.mjs | 90 ++++++++++++++++++++++++++++- 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 src/core/operations/XORChecksum.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 075e8d6662..97b20bd76c 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -390,7 +390,8 @@ "CRC-8 Checksum", "CRC-16 Checksum", "CRC-32 Checksum", - "TCP/IP Checksum" + "TCP/IP Checksum", + "XOR Checksum" ] }, { diff --git a/src/core/operations/XORChecksum.mjs b/src/core/operations/XORChecksum.mjs new file mode 100644 index 0000000000..1603a26518 --- /dev/null +++ b/src/core/operations/XORChecksum.mjs @@ -0,0 +1,59 @@ +/** + * @author Thomas Weißschuh [thomas@t-8ch.de] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; + +/** + * XOR Checksum operation + */ +class XORChecksum extends Operation { + + /** + * XORChecksum constructor + */ + constructor() { + super(); + + this.name = "XOR Checksum"; + this.module = "Crypto"; + this.description = "XOR Checksum splits the input into blocks of a configurable size and performs the XOR operation on these blocks."; + this.infoURL = "https://wikipedia.org/wiki/XOR"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Blocksize", + type: "number", + value: 4 + }, + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const blocksize = args[0]; + input = new Uint8Array(input); + + const res = Array(blocksize); + res.fill(0); + + for (const chunk of Utils.chunked(input, blocksize)) { + for (let i = 0; i < blocksize; i++) { + res[i] ^= chunk[i]; + } + } + + return toHex(res, ""); + } +} + +export default XORChecksum; diff --git a/tests/operations/tests/Checksum.mjs b/tests/operations/tests/Checksum.mjs index 142ee26787..5266ab9929 100644 --- a/tests/operations/tests/Checksum.mjs +++ b/tests/operations/tests/Checksum.mjs @@ -237,5 +237,93 @@ TestRegister.addTests([ "args": [] } ] - } + }, + { + name: "XOR Checksum (1): nothing", + input: "", + expectedOutput: "00", + recipeConfig: [ + { + "op": "XOR Checksum", + "args": [1] + } + ] + }, + { + name: "XOR Checksum (1): basic string", + input: BASIC_STRING, + expectedOutput: "08", + recipeConfig: [ + { + "op": "XOR Checksum", + "args": [1] + } + ] + }, + { + name: "XOR Checksum (1): UTF-8", + input: UTF8_STR, + expectedOutput: "df", + recipeConfig: [ + { + "op": "XOR Checksum", + "args": [1] + } + ] + }, + { + name: "XOR Checksum (1): all bytes", + input: ALL_BYTES, + expectedOutput: "00", + recipeConfig: [ + { + "op": "XOR Checksum", + "args": [1] + } + ] + }, + { + name: "XOR Checksum (4): nothing", + input: "", + expectedOutput: "00000000", + recipeConfig: [ + { + "op": "XOR Checksum", + "args": [4] + } + ] + }, + { + name: "XOR Checksum (4): basic string", + input: BASIC_STRING, + expectedOutput: "4918421b", + recipeConfig: [ + { + "op": "XOR Checksum", + "args": [4] + } + ] + }, + { + name: "XOR Checksum (4): UTF-8", + input: UTF8_STR, + expectedOutput: "83a424dc", + recipeConfig: [ + { + "op": "XOR Checksum", + "args": [4] + } + ] + }, + { + name: "XOR Checksum (4): all bytes", + input: ALL_BYTES, + expectedOutput: "00000000", + recipeConfig: [ + { + "op": "XOR Checksum", + "args": [4] + } + ] + }, ]); From 5f0f037c46c551d8af7106654ab5b0f8c4660aca Mon Sep 17 00:00:00 2001 From: sg5506844 <130462468+sg5506844@users.noreply.github.com> Date: Wed, 12 Apr 2023 10:37:16 +0530 Subject: [PATCH 012/225] Feature: Add Base92 operations --- src/core/config/Categories.json | 2 + src/core/lib/Base92.mjs | 44 +++++++++++++++ src/core/operations/FromBase92.mjs | 55 ++++++++++++++++++ src/core/operations/ToBase92.mjs | 67 ++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/Base92.mjs | 89 ++++++++++++++++++++++++++++++ 6 files changed, 258 insertions(+) create mode 100644 src/core/lib/Base92.mjs create mode 100644 src/core/operations/FromBase92.mjs create mode 100644 src/core/operations/ToBase92.mjs create mode 100644 tests/operations/tests/Base92.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index ce2f01f5ec..277951ea88 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -29,6 +29,8 @@ "To Base64", "From Base64", "Show Base64 offsets", + "To Base92", + "From Base92", "To Base85", "From Base85", "To Base", diff --git a/src/core/lib/Base92.mjs b/src/core/lib/Base92.mjs new file mode 100644 index 0000000000..902c3e7de4 --- /dev/null +++ b/src/core/lib/Base92.mjs @@ -0,0 +1,44 @@ +/** + * Base92 resources. + * + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; + +/** + * Base92 alphabet char + * + * @param {number} val + * @returns {number} + */ +export function base92Chr(val) { + if (val < 0 || val >= 91) { + throw new OperationError("Invalid value"); + } + if (val === 0) + return "!".charCodeAt(0); + else if (val <= 61) + return "#".charCodeAt(0) + val - 1; + else + return "a".charCodeAt(0) + val - 62; +} + +/** + * Base92 alphabet ord + * + * @param {string} val + * @returns {number} + */ +export function base92Ord(val) { + if (val === "!") + return 0; + else if ("#" <= val && val <= "_") + return val.charCodeAt(0) - "#".charCodeAt(0) + 1; + else if ("a" <= val && val <= "}") + return val.charCodeAt(0) - "a".charCodeAt(0) + 62; + throw new OperationError(`${val} is not a base92 character`); +} + diff --git a/src/core/operations/FromBase92.mjs b/src/core/operations/FromBase92.mjs new file mode 100644 index 0000000000..8315a51cf0 --- /dev/null +++ b/src/core/operations/FromBase92.mjs @@ -0,0 +1,55 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import { base92Ord } from "../lib/Base92.mjs"; +import Operation from "../Operation.mjs"; + +/** + * From Base92 operation + */ +class FromBase92 extends Operation { + /** + * FromBase92 constructor + */ + constructor() { + super(); + + this.name = "From Base92"; + this.module = "Default"; + this.description = "Base92 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.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const res = []; + let bitString = ""; + + for (let i = 0; i < input.length; i += 2) { + if (i + 1 !== input.length) { + const x = base92Ord(input[i]) * 91 + base92Ord(input[i + 1]); + bitString += x.toString(2).padStart(13, "0"); + } else { + const x = base92Ord(input[i]); + bitString += x.toString(2).padStart(6, "0"); + } + while (bitString.length >= 8) { + res.push(parseInt(bitString.slice(0, 8), 2)); + bitString = bitString.slice(8); + } + } + + return res; + } +} + +export default FromBase92; diff --git a/src/core/operations/ToBase92.mjs b/src/core/operations/ToBase92.mjs new file mode 100644 index 0000000000..bca8e8722d --- /dev/null +++ b/src/core/operations/ToBase92.mjs @@ -0,0 +1,67 @@ +/** + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import { base92Chr } from "../lib/Base92.mjs"; +import Operation from "../Operation.mjs"; + +/** + * To Base92 operation + */ +class ToBase92 extends Operation { + /** + * ToBase92 constructor + */ + constructor() { + super(); + + this.name = "To Base92"; + this.module = "Default"; + this.description = "Base92 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.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems"; + this.inputType = "string"; + this.outputType = "byteArray"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const res = []; + let bitString = ""; + + while (input.length > 0) { + while (bitString.length < 13 && input.length > 0) { + bitString += input[0].charCodeAt(0).toString(2).padStart(8, "0"); + input = input.slice(1); + } + if (bitString.length < 13) + break; + const i = parseInt(bitString.slice(0, 13), 2); + res.push(base92Chr(Math.floor(i / 91))); + res.push(base92Chr(i % 91)); + bitString = bitString.slice(13); + } + + if (bitString.length > 0) { + if (bitString.length < 7) { + bitString = bitString.padEnd(6, "0"); + res.push(base92Chr(parseInt(bitString, 2))); + } else { + bitString = bitString.padEnd(13, "0"); + const i = parseInt(bitString.slice(0, 13), 2); + res.push(base92Chr(Math.floor(i / 91))); + res.push(base92Chr(i % 91)); + } + } + + return res; + + } +} + +export default ToBase92; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 56f432e090..8d433954e9 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -25,6 +25,7 @@ import "./tests/Base58.mjs"; import "./tests/Base64.mjs"; import "./tests/Base62.mjs"; import "./tests/Base85.mjs"; +import "./tests/Base92.mjs"; import "./tests/BitwiseOp.mjs"; import "./tests/ByteRepr.mjs"; import "./tests/CartesianProduct.mjs"; diff --git a/tests/operations/tests/Base92.mjs b/tests/operations/tests/Base92.mjs new file mode 100644 index 0000000000..2811182d4a --- /dev/null +++ b/tests/operations/tests/Base92.mjs @@ -0,0 +1,89 @@ +/** + * Base92 tests. + * + * @author sg5506844 [sg5506844@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "To Base92: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "To Base92", + args: [], + }, + ], + }, + { + name: "To Base92: Spec encoding example 1", + input: "AB", + expectedOutput: "8y2", + recipeConfig: [ + { + op: "To Base92", + args: [], + }, + ], + }, + { + name: "To Base92: Spec encoding example 2", + input: "Hello!!", + expectedOutput: ";K_$aOTo&", + recipeConfig: [ + { + op: "To Base92", + args: [], + }, + ], + }, + { + name: "To Base92: Spec encoding example 3", + input: "base-92", + expectedOutput: "DX2?V Date: Sun, 16 Apr 2023 21:47:43 +1200 Subject: [PATCH 013/225] [181] 'fix' dev to get started --- .gitignore | 1 + src/core/Recipe.mjs | 4 +++- src/core/lib/Magic.mjs | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3b7449c40c..616ca224e3 100755 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ src/node/index.mjs **/*.DS_Store tests/browser/output/* .node-version +.idea diff --git a/src/core/Recipe.mjs b/src/core/Recipe.mjs index 3ce40aa4d6..86265b65b7 100755 --- a/src/core/Recipe.mjs +++ b/src/core/Recipe.mjs @@ -4,7 +4,9 @@ * @license Apache-2.0 */ -import OperationConfig from "./config/OperationConfig.json" assert {type: "json"}; +// @TODO: NTS return this to original? ( it breaks dev though ) +// import OperationConfig from "./config/OperationConfig.json" assert {type: "json"}; +import OperationConfig from "./config/OperationConfig.json"; import OperationError from "./errors/OperationError.mjs"; import Operation from "./Operation.mjs"; import DishError from "./errors/DishError.mjs"; diff --git a/src/core/lib/Magic.mjs b/src/core/lib/Magic.mjs index 921fc3f6f3..8a57969acc 100644 --- a/src/core/lib/Magic.mjs +++ b/src/core/lib/Magic.mjs @@ -1,4 +1,6 @@ -import OperationConfig from "../config/OperationConfig.json" assert {type: "json"}; +// @TODO: NTS return this to original? ( it breaks dev though ) +// import OperationConfig from "../config/OperationConfig.json" assert {type: "json"}; +import OperationConfig from "../config/OperationConfig.json"; import Utils, { isWorkerEnvironment } from "../Utils.mjs"; import Recipe from "../Recipe.mjs"; import Dish from "../Dish.mjs"; From 3102adc73d3cb3b85d2ef321ef0d1575fb8d2b0a Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:06:26 +1200 Subject: [PATCH 014/225] [#181] update pane-controls display --- src/web/stylesheets/components/_pane.css | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/web/stylesheets/components/_pane.css b/src/web/stylesheets/components/_pane.css index 54e67b3bd6..b3fa81b632 100755 --- a/src/web/stylesheets/components/_pane.css +++ b/src/web/stylesheets/components/_pane.css @@ -12,9 +12,7 @@ } .title { - padding: 8px; - padding-left: 12px; - padding-right: 12px; + padding: 8px 12px; height: var(--title-height); border-bottom: 1px solid var(--primary-border-colour); font-weight: var(--title-weight); @@ -25,11 +23,9 @@ } .pane-controls { - position: absolute; right: 8px; top: 8px; - display: flex; - flex-direction: row; + display: inline-flex; } .pane-controls .btn { @@ -49,3 +45,4 @@ #files .card-header .float-right a:hover { text-decoration: none; } + From daf2c684a9c8e05b44542b150ac82741c433dd7c Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:07:26 +1200 Subject: [PATCH 015/225] [#181] add justify-content: space-between to the banner --- src/web/stylesheets/layout/_banner.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/web/stylesheets/layout/_banner.css b/src/web/stylesheets/layout/_banner.css index 5985695858..6849b87bdc 100755 --- a/src/web/stylesheets/layout/_banner.css +++ b/src/web/stylesheets/layout/_banner.css @@ -15,6 +15,9 @@ color: var(--banner-font-colour); background-color: var(--banner-bg-colour); margin: 0; + + /*///////// dolphin additions //////*/ + justify-content: space-between; } #banner i { From 32d8ce7069b404febee22f57d0e71be619f2cd5b Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:09:01 +1200 Subject: [PATCH 016/225] [#181] update some css to shorthand --- src/web/stylesheets/layout/_io.css | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 0146bf27f7..344efe3c25 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -131,10 +131,7 @@ .output-tab-content { width: 100%; max-width: 100%; - padding-left: 5px; - padding-right: 5px; - padding-top: 10px; - padding-bottom: 10px; + padding: 10px 5px; height: var(--tab-height); vertical-align: middle; overflow: hidden; @@ -319,8 +316,7 @@ #output-num-results-container { width: 20%; float: right; - margin: 0; - margin-left: 10%; + margin: 0 0 0 10%; } #input-find-options-checkboxes, @@ -355,10 +351,7 @@ #input-search-results li, #output-search-results li { - padding-left: 5px; - padding-right: 5px; - padding-top: 10px; - padding-bottom: 10px; + padding: 10px 5px; text-align: center; width: 100%; color: var(--op-list-operation-font-colour); From e998324194ccd7bade5e4b5dc0ac871830334169 Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:13:26 +1200 Subject: [PATCH 017/225] [#181] comment out edit-favourites for now --- src/web/stylesheets/layout/_operations.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/web/stylesheets/layout/_operations.css b/src/web/stylesheets/layout/_operations.css index b73dfa84a3..1766f6dc14 100755 --- a/src/web/stylesheets/layout/_operations.css +++ b/src/web/stylesheets/layout/_operations.css @@ -20,10 +20,10 @@ linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); } -#edit-favourites { - float: right; - margin-top: -7px; -} +/*#edit-favourites {*/ +/* float: right;*/ +/* margin-top: -7px;*/ +/*}*/ .favourites-hover { color: var(--rec-list-operation-font-colour); @@ -41,3 +41,4 @@ .op-list .operation:hover { filter: brightness(98%); } + From 3608d4ac127cb4b238d8ff6ba0d6d0666964cab6 Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:14:46 +1200 Subject: [PATCH 018/225] [#181] set up mobile UI, unsorted CSS blocks ( will move them to appropriate places when the UI is solid ) --- src/web/stylesheets/layout/_structure.css | 95 +++++++++++++---------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/src/web/stylesheets/layout/_structure.css b/src/web/stylesheets/layout/_structure.css index 4f262029c2..a23b0d50d6 100755 --- a/src/web/stylesheets/layout/_structure.css +++ b/src/web/stylesheets/layout/_structure.css @@ -10,62 +10,79 @@ body { overflow: hidden; } -#content-wrapper { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; +.gutter { + background-color: var(--secondary-border-colour); + background-repeat: no-repeat; + background-position: 50%; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+EFBhEwBDmIiYYAAAAjSURBVBjTYzxz5sx/BgYGBiYGKGB89+4dA4oIy71790aGGgCn+DBbOcAB0wAAAABJRU5ErkJggg=='); + cursor: ew-resize; +} + +.gutter.gutter-vertical { + background-image: url('data:image/png;base64,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'); + cursor: ns-resize; } +/*//////// dolphins mobile ui below ///////////*/ + #workspace-wrapper { - position: absolute; - top: 30px; - bottom: 0; - width: 100%; + margin-top: 30px; } -div#operations, -div#recipe { - width: 50%; - height: 100%; +#content-wrapper, +#workspace-wrapper, +#operations, +#recipe, +#input, +#output, +#search { + width: 100vw; } -div#input, -div#output { - width: 100%; - height: 50%; +#recipe { + height: 15vh; + overflow-y: scroll; } -.split { - box-sizing: border-box; - /* overflow: auto; */ - /* Removed to enable Background Magic button pulse to overflow. - Replace this rule if it seems to be causing problems. */ - position: relative; +#input .cm-scroller, +#output .cm-scroller { + height: 20vh; + overflow-y: scroll; } -#operations.split { - overflow: auto; +#recipe .title, +#input .title, +#output .title { + display: flex; + justify-content: space-between; } -.split.split-horizontal, .gutter.gutter-horizontal { - height: 100%; - float: left; +#content-wrapper { + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; } -.gutter { - background-color: var(--secondary-border-colour); - background-repeat: no-repeat; - background-position: 50%; +/*@TODO: move elsewhere*/ +.desktop-only { + display: none; } -.gutter.gutter-horizontal { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+EFBhEwBDmIiYYAAAAjSURBVBjTYzxz5sx/BgYGBiYGKGB89+4dA4oIy71790aGGgCn+DBbOcAB0wAAAABJRU5ErkJggg=='); - cursor: ew-resize; +@media only screen and ( min-width: 1024px ) { + .desktop-only { + display: inline-block; + } } -.gutter.gutter-vertical { - background-image: url('data:image/png;base64,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'); - cursor: ns-resize; +/*@TODO: rename*/ +/* reverse the pane controls and magic/stale indicator in order +to prevent weird empty spaces to the right on mobile*/ +.foo { + display: flex; + flex-direction: row-reverse; } From 8ca5cfaf488fa18e12bc2651d99bab22370be4c9 Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:15:41 +1200 Subject: [PATCH 019/225] [#181] add generic default as per the norm --- src/web/stylesheets/utils/_overrides.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css index a2f8b02929..f03acc34e7 100755 --- a/src/web/stylesheets/utils/_overrides.css +++ b/src/web/stylesheets/utils/_overrides.css @@ -17,7 +17,7 @@ } .material-icons { - font-family: 'Material Icons'; + font-family: 'Material Icons', sans-serif; font-weight: normal; font-style: normal; font-size: 24px; From 683c323fd500ef96900c73fa1457997129c8666a Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:19:18 +1200 Subject: [PATCH 020/225] [#181] add this.breakpoint and move some user layout choices into an if statement checking the breakpoint --- src/web/App.mjs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/web/App.mjs b/src/web/App.mjs index cce91b1e76..7b98e109ea 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -46,6 +46,8 @@ class App { this.appLoaded = false; this.workerLoaded = false; this.waitersLoaded = false; + + this.breakpoint = 1024; } @@ -57,15 +59,17 @@ class App { setup() { document.dispatchEvent(this.manager.appstart); - this.initialiseSplitter(); + if ( window.innerWidth >= this.breakpoint ) { + this.initialiseSplitter(); + this.setCompileMessage(); + this.adjustComponentSizes(); + } + this.loadLocalStorage(); this.populateOperationsList(); this.manager.setup(); this.manager.output.saveBombe(); - this.adjustComponentSizes(); - this.setCompileMessage(); this.uriParams = this.getURIParams(); - log.debug("App loaded"); this.appLoaded = true; this.loaded(); From a1d89464ad5c7ffb4ea775ff3df2ce062566f45b Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:19:42 +1200 Subject: [PATCH 021/225] [#181] update breakpoint to 768, add a TODO --- src/web/App.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/web/App.mjs b/src/web/App.mjs index 7b98e109ea..5db558b7c6 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -47,7 +47,7 @@ class App { this.workerLoaded = false; this.waitersLoaded = false; - this.breakpoint = 1024; + this.breakpoint = 768; } @@ -59,6 +59,7 @@ class App { setup() { document.dispatchEvent(this.manager.appstart); + // @TODO: add a window resize listener if ( window.innerWidth >= this.breakpoint ) { this.initialiseSplitter(); this.setCompileMessage(); From ab996c32519a84a0540ea122fb7d8535bc8e15b6 Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:20:38 +1200 Subject: [PATCH 022/225] [#181] update breakpoint for .desktop-only --- src/web/stylesheets/layout/_structure.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/stylesheets/layout/_structure.css b/src/web/stylesheets/layout/_structure.css index a23b0d50d6..6b36cd2dea 100755 --- a/src/web/stylesheets/layout/_structure.css +++ b/src/web/stylesheets/layout/_structure.css @@ -73,7 +73,7 @@ body { display: none; } -@media only screen and ( min-width: 1024px ) { +@media only screen and ( min-width: 768px ) { .desktop-only { display: inline-block; } From 3e76bc563c0f65afc12bbd49331f4b8ccec0f996 Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Apr 2023 10:21:58 +1200 Subject: [PATCH 023/225] [#181] update template for mobile UI. I will incorporate / probably revert some things once the mobile UI is solid, then patch up desktop view to its original state --- src/web/html/index.html | 135 ++++++++++++++++++++++++---------------- 1 file changed, 83 insertions(+), 52 deletions(-) diff --git a/src/web/html/index.html b/src/web/html/index.html index c602c275f0..d70ca3e0c8 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -31,6 +31,7 @@ + + -
-
-
-
-
+ + + + + - +
+