Merge remote-tracking branch 'upstream/esm' into esmconversion

Also removed substitute operation from legacy module
This commit is contained in:
Matt C 2018-05-14 18:09:21 +01:00
commit 4008dbf38a
48 changed files with 4100 additions and 2523 deletions

View File

@ -280,7 +280,7 @@ module.exports = function (grunt) {
chunks: false,
modules: false,
entrypoints: false,
warningsFilter: /source-map/,
warningsFilter: [/source-map/, /dependency is an expression/],
}
},
start: {

3521
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "7.9.0",
"version": "7.11.1",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
@ -30,14 +30,14 @@
"main": "build/node/CyberChef.js",
"bugs": "https://github.com/gchq/CyberChef/issues",
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.3",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1",
"css-loader": "^0.28.10",
"eslint": "^4.18.1",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"exports-loader": "^0.7.0",
"extract-text-webpack-plugin": "^4.0.0-alpha0",
"file-loader": "^1.1.10",
"file-loader": "^1.1.11",
"grunt": ">=1.0.2",
"grunt-accessibility": "~6.0.0",
"grunt-chmod": "~1.1.1",
@ -48,23 +48,23 @@
"grunt-eslint": "^20.1.0",
"grunt-exec": "~3.0.0",
"grunt-jsdoc": "^2.2.1",
"grunt-webpack": "^3.0.2",
"html-webpack-plugin": "^3.0.4",
"grunt-webpack": "^3.1.1",
"html-webpack-plugin": "^3.2.0",
"imports-loader": "^0.8.0",
"ink-docstrap": "^1.3.2",
"jsdoc-babel": "^0.3.0",
"less": "^3.0.1",
"less-loader": "^4.0.6",
"postcss-css-variables": "^0.8.0",
"jsdoc-babel": "^0.4.0",
"less": "^3.0.2",
"less-loader": "^4.1.0",
"postcss-css-variables": "^0.8.1",
"postcss-import": "^11.1.0",
"postcss-loader": "^2.1.1",
"postcss-loader": "^2.1.4",
"sitemap": "^1.13.0",
"style-loader": "^0.20.2",
"url-loader": "^0.6.2",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"web-resource-inliner": "^4.2.1",
"webpack": "^4.0.1",
"webpack-dev-server": "^3.1.0",
"webpack-node-externals": "^1.6.0",
"webpack": "^4.6.0",
"webpack-dev-server": "^3.1.3",
"webpack-node-externals": "^1.7.2",
"webpack-synchronizable-shell-plugin": "0.0.7",
"worker-loader": "^1.1.1"
},
@ -72,20 +72,21 @@
"babel-polyfill": "^6.26.0",
"babel-plugin-transform-builtin-extend": "1.1.2",
"bcryptjs": "^2.4.3",
"bignumber.js": "^6.0.0",
"bignumber.js": "^7.0.1",
"bootstrap": "^3.3.7",
"bootstrap-colorpicker": "^2.5.2",
"bootstrap-switch": "^3.3.4",
"bson": "^2.0.4",
"bson": "^2.0.6",
"crypto-api": "^0.8.0",
"crypto-js": "^3.1.9-1",
"ctph.js": "0.0.5",
"diff": "^3.4.0",
"diff": "^3.5.0",
"escodegen": "^1.9.1",
"es6-promisify": "^6.0.0",
"esmangle": "^1.0.1",
"esprima": "^4.0.0",
"exif-parser": "^0.1.12",
"file-saver": "^1.3.3",
"file-saver": "^1.3.8",
"highlight.js": "^9.12.0",
"jquery": "^3.3.1",
"js-crc": "^0.2.0",
@ -93,15 +94,16 @@
"jsbn": "^1.1.0",
"jsesc": "^2.5.1",
"jsonpath": "^1.0.0",
"jsrsasign": "8.0.6",
"lodash": "^4.17.5",
"jsrsasign": "8.0.12",
"lodash": "^4.17.10",
"loglevel": "^1.6.1",
"kbpgp": "^2.0.77",
"loglevel-message-prefix": "^3.0.0",
"moment": "^2.20.1",
"moment-timezone": "^0.5.14",
"node-forge": "^0.7.2",
"moment": "^2.22.1",
"moment-timezone": "^0.5.16",
"node-forge": "^0.7.5",
"node-md6": "^0.1.0",
"nwmatcher": "^1.4.3",
"nwmatcher": "^1.4.4",
"otp": "^0.1.3",
"scryptsy": "^2.0.0",
"sladex-blowfish": "^0.8.1",

View File

@ -154,9 +154,9 @@ function loadRequiredModules(recipeConfig) {
const module = self.OperationConfig[op.op].module;
if (!OpModules.hasOwnProperty(module)) {
log.info("Loading module " + module);
self.sendStatusMessage("Loading module " + module);
self.importScripts(self.docURL + "/" + module + ".js");
log.info(`Loading ${module} module`);
self.sendStatusMessage(`Loading ${module} module`);
self.importScripts(`${self.docURL}/${module}.js`);
self.sendStatusMessage("");
}
});

View File

@ -524,36 +524,43 @@ class Utils {
* Parses CSV data and returns it as a two dimensional array or strings.
*
* @param {string} data
* @param {string[]} [cellDelims=[","]]
* @param {string[]} [lineDelims=["\n", "\r"]]
* @returns {string[][]}
*
* @example
* // returns [["head1", "head2"], ["data1", "data2"]]
* Utils.parseCSV("head1,head2\ndata1,data2");
*/
static parseCSV(data) {
static parseCSV(data, cellDelims=[","], lineDelims=["\n", "\r"]) {
let b,
ignoreNext = false,
next,
renderNext = false,
inString = false,
cell = "",
line = [];
const lines = [];
// Remove BOM, often present in Excel CSV files
if (data.length && data[0] === "\uFEFF") data = data.substr(1);
for (let i = 0; i < data.length; i++) {
b = data[i];
if (ignoreNext) {
next = data[i+1] || "";
if (renderNext) {
cell += b;
ignoreNext = false;
renderNext = false;
} else if (b === "\\") {
cell += b;
ignoreNext = true;
renderNext = true;
} else if (b === "\"" && !inString) {
inString = true;
} else if (b === "\"" && inString) {
inString = false;
} else if (b === "," && !inString) {
if (next === "\"") renderNext = true;
else inString = false;
} else if (!inString && cellDelims.indexOf(b) >= 0) {
line.push(cell);
cell = "";
} else if ((b === "\n" || b === "\r") && !inString) {
} else if (!inString && lineDelims.indexOf(b) >= 0) {
line.push(cell);
cell = "";
lines.push(line);

View File

@ -94,7 +94,12 @@
"PEM to Hex",
"Hex to PEM",
"Hex to Object Identifier",
"Object Identifier to Hex"
"Object Identifier to Hex",
"Generate PGP Key Pair",
"PGP Encrypt",
"PGP Decrypt",
"PGP Encrypt and Sign",
"PGP Decrypt and Verify"
]
},
{
@ -164,6 +169,7 @@
"To Lower case",
"Add line numbers",
"Remove line numbers",
"To Table",
"Reverse",
"Sort",
"Unique",

View File

@ -12,6 +12,7 @@
import process from "process";
import fs from "fs";
import path from "path";
import EscapeString from "../../operations/EscapeString";
if (process.argv.length < 4) {
console.log("Pass an operation name and legacy filename as arguments.");
@ -58,6 +59,27 @@ function main() {
const author = legacyFile.match(/@author [^\n]+/)[0];
const copyright = legacyFile.match(/@copyright [^\n]+/)[0];
const utilsUsed = /Utils/.test(legacyFile);
const esc = new EscapeString();
const desc = esc.run(op.description, ["Special chars", "Double"]);
// Attempt to find the operation run function based on the JSDoc comment
const regex = `\\* ${opName} operation[^:]+:(?: function ?\\(input, args\\))? ?{([\\s\\S]+?)\n }`;
let runFunc = "\n";
try {
runFunc = legacyFile.match(new RegExp(regex, "im"))[1];
} catch (err) {}
// List all constants in legacyFile
const constants = [];
try {
const constantsRegex = /\* @constant[^/]+\/\s+([^\n]+)/gim;
let m;
while ((m = constantsRegex.exec(legacyFile)) !== null) {
constants.push(m[1]);
}
} catch (err) {}
const template = `/**
* ${author}
@ -80,7 +102,7 @@ class ${moduleName} extends Operation {
this.name = "${opName}";${op.flowControl ? "\n this.flowControl = true;" : ""}
this.module = "${op.module}";
this.description = "${op.description}";
this.description = "${desc}";
this.inputType = "${op.inputType}";
this.outputType = "${op.outputType}";${op.manualBake ? "\n this.manualBake = true;" : ""}
this.args = ${JSON.stringify(op.args, null, 4).split("\n").join("\n ")};
@ -91,8 +113,7 @@ class ${moduleName} extends Operation {
* @param {Object[]} args
* @returns {${op.outputType}}
*/
run(input, args) {
run(input, args) {${runFunc}
}
${op.highlight ? `
/**
@ -126,17 +147,27 @@ ${op.highlight ? `
export default ${moduleName};
`;
console.log(template);
console.log("\nLegacy operation config\n-----------------------\n");
console.log(JSON.stringify(op, null, 4));
console.log("\n-----------------------\n");
console.log("\nPotentially related constants\n-----------------------\n");
console.log(constants.join("\n"));
console.log("\n-----------------------\n");
const filename = path.join(dir, `../operations/${moduleName}.mjs`);
if (fs.existsSync(filename)) {
console.log(`${filename} already exists. It has NOT been overwritten.`);
console.log(`\x1b[31m\u274c ${filename} already exists. It has NOT been overwritten.\x1b[0m`);
process.exit(0);
}
fs.writeFileSync(filename, template);
console.log("Written to " + filename);
console.log(`Open ${legacyFilename} and copy the relevant code over. Make sure you check imports, args and highlights.`);
console.log("\x1b[32m\u2714\x1b[0m Operation written to \x1b[32m" + filename + "\x1b[0m");
if (runFunc === "\n") {
console.log("\x1b[31m\u274c The run function could not be located automatically.\x1b[0m You will have to copy it accross manually.");
} else {
console.log("\x1b[32m\u2714\x1b[0m The run function was copied across. Double check that it was copied correctly. It may rely on other functions which have not been copied.");
}
console.log(`\nOpen \x1b[32m${legacyFilename}\x1b[0m and copy any relevant code over. Make sure you check imports, args and highlights. Code required by multiple operations should be stored in /src/core/lib/`);
}
@ -672,6 +703,34 @@ const OP_CONFIG = {
}
]
},
"To Table": {
module: "Default",
description: "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.<br><br>Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to <code>\\t</code> to support TSV (Tab Separated Values) or <code>|</code> for PSV (Pipe Separated Values).<br><br>You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter.",
inputType: "string",
outputType: "html",
args: [
{
name: "Cell delimiters",
type: "binaryShortString",
value: ","
},
{
name: "Row delimiters",
type: "binaryShortString",
value: "\\n\\r"
},
{
name: "Make first row header",
type: "boolean",
value: false
},
{
name: "Format",
type: "option",
value: "ToTable.FORMATS"
}
]
},
"From Hex": {
module: "Default",
description: "Converts a hexadecimal byte string back into its raw value.<br><br>e.g. <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code> becomes the UTF-8 encoded string <code>Γειά σου</code>",
@ -3363,14 +3422,14 @@ const OP_CONFIG = {
"CRC-32 Checksum": {
module: "Hashing",
description: "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961; the 32-bit CRC function of Ethernet and many other standards is the work of several researchers and was published in 1975.",
inputType: "string",
inputType: "ArrayBuffer",
outputType: "string",
args: []
},
"CRC-16 Checksum": {
module: "Hashing",
description: "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961.",
inputType: "string",
inputType: "ArrayBuffer",
outputType: "string",
args: []
},
@ -3423,7 +3482,7 @@ const OP_CONFIG = {
},
"Parse X.509 certificate": {
module: "PublicKey",
description: "X.509 is an ITU-T standard for a public key infrastructure (PKI) and Privilege Management Infrastructure (PMI). It is commonly involved with SSL/TLS security.<br><br>This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.",
description: "X.509 is an ITU-T standard for a public key infrastructure (PKI) and Privilege Management Infrastructure (PMI). It is commonly involved with SSL/TLS security.<br><br>This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.<br><br>Tags: X509, server hello, handshake",
inputType: "string",
outputType: "string",
args: [
@ -4219,6 +4278,150 @@ const OP_CONFIG = {
outputType: "string",
args: []
},
"Generate PGP Key Pair": {
module: "PGP",
description: "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key type",
type: "option",
value: "PGP.KEY_TYPES"
},
{
name: "Password (optional)",
type: "string",
value: ""
},
{
name: "Name (optional)",
type: "string",
value: ""
},
{
name: "Email (optional)",
type: "string",
value: ""
},
]
},
"PGP Encrypt": {
module: "PGP",
description: [
"Input: the message you want to encrypt.",
"<br><br>",
"Arguments: the ASCII-armoured PGP public key of the recipient.",
"<br><br>",
"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
"<br><br>",
"This function uses the Keybase implementation of PGP.",
].join("\n"),
inputType: "string",
outputType: "string",
args: [
{
name: "Public key of recipient",
type: "text",
value: ""
},
]
},
"PGP Decrypt": {
module: "PGP",
description: [
"Input: the ASCII-armoured PGP message you want to decrypt.",
"<br><br>",
"Arguments: the ASCII-armoured PGP private key of the recipient, ",
"(and the private key password if necessary).",
"<br><br>",
"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
"<br><br>",
"This function uses the Keybase implementation of PGP.",
].join("\n"),
inputType: "string",
outputType: "string",
args: [
{
name: "Private key of recipient",
type: "text",
value: ""
},
{
name: "Private key passphrase",
type: "string",
value: ""
},
]
},
"PGP Encrypt and Sign": {
module: "PGP",
description: [
"Input: the cleartext you want to sign.",
"<br><br>",
"Arguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)",
"and the ASCII-armoured PGP public key of the recipient.",
"<br><br>",
"This operation uses PGP to produce an encrypted digital signature.",
"<br><br>",
"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
"<br><br>",
"This function uses the Keybase implementation of PGP.",
].join("\n"),
inputType: "string",
outputType: "string",
args: [
{
name: "Private key of signer",
type: "text",
value: ""
},
{
name: "Private key passphrase",
type: "string",
value: ""
},
{
name: "Public key of recipient",
type: "text",
value: ""
},
]
},
"PGP Decrypt and Verify": {
module: "PGP",
description: [
"Input: the ASCII-armoured encrypted PGP message you want to verify.",
"<br><br>",
"Arguments: the ASCII-armoured PGP public key of the signer, ",
"the ASCII-armoured private key of the recipient (and the private key password if necessary).",
"<br><br>",
"This operation uses PGP to decrypt and verify an encrypted digital signature.",
"<br><br>",
"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
"<br><br>",
"This function uses the Keybase implementation of PGP.",
].join("\n"),
inputType: "string",
outputType: "string",
args: [
{
name: "Public key of signer",
type: "text",
value: "",
},
{
name: "Private key of recipient",
type: "text",
value: "",
},
{
name: "Private key password",
type: "string",
value: "",
},
]
},
};
main();

View File

@ -25,3 +25,35 @@ export const LETTER_DELIM_OPTIONS = ["Space", "Line feed", "CRLF", "Forward slas
* Word sequence delimiters.
*/
export const WORD_DELIM_OPTIONS = ["Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"];
/**
* Input sequence delimiters.
*/
export const INPUT_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"];
/**
* Split delimiters.
*/
export const SPLIT_DELIM_OPTIONS = [
{name: "Comma", value: ","},
{name: "Space", value: " "},
{name: "Line feed", value: "\\n"},
{name: "CRLF", value: "\\r\\n"},
{name: "Semi-colon", value: ";"},
{name: "Colon", value: ":"},
{name: "Nothing (separate chars)", value: ""}
];
/**
* Join delimiters.
*/
export const JOIN_DELIM_OPTIONS = [
{name: "Line feed", value: "\\n"},
{name: "CRLF", value: "\\r\\n"},
{name: "Space", value: " "},
{name: "Comma", value: ","},
{name: "Semi-colon", value: ";"},
{name: "Colon", value: ":"},
{name: "Nothing (join chars)", value: ""}
];

View File

@ -84,9 +84,9 @@ export function toHexFast(data) {
* fromHex("0a:14:1e", "Colon");
*/
export function fromHex(data, delim, byteLen=2) {
delim = delim || (data.indexOf(" ") >= 0 ? "Space" : "None");
delim = delim || "Auto";
if (delim !== "None") {
const delimRegex = Utils.regexRep(delim);
const delimRegex = delim === "Auto" ? /[^a-f\d]/gi : Utils.regexRep(delim);
data = data.replace(delimRegex, "");
}
@ -99,6 +99,12 @@ export function fromHex(data, delim, byteLen=2) {
/**
* Hexadecimal delimiters.
* To Hexadecimal delimiters.
*/
export const HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
export const TO_HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
/**
* From Hexadecimal delimiters.
*/
export const FROM_HEX_DELIM_OPTIONS = ["Auto"].concat(TO_HEX_DELIM_OPTIONS);

View File

@ -0,0 +1,46 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Add line numbers operation
*/
class AddLineNumbers extends Operation {
/**
* AddLineNumbers constructor
*/
constructor() {
super();
this.name = "Add line numbers";
this.module = "Default";
this.description = "Adds line numbers to the output.";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const lines = input.split("\n"),
width = lines.length.toString().length;
let output = "";
for (let n = 0; n < lines.length; n++) {
output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
}
return output.slice(0, output.length-1);
}
}
export default AddLineNumbers;

View File

@ -0,0 +1,65 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Count occurrences operation
*/
class CountOccurrences extends Operation {
/**
* CountOccurrences constructor
*/
constructor() {
super();
this.name = "Count occurrences";
this.module = "Default";
this.description = "Counts the number of times the provided string occurs in the input.";
this.inputType = "string";
this.outputType = "number";
this.args = [
{
"name": "Search string",
"type": "toggleString",
"value": "",
"toggleValues": ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {number}
*/
run(input, args) {
let search = args[0].string;
const type = args[0].option;
if (type === "Regex" && search) {
try {
const regex = new RegExp(search, "gi"),
matches = input.match(regex);
return matches.length;
} catch (err) {
return 0;
}
} else if (search) {
if (type.indexOf("Extended") === 0) {
search = Utils.parseEscapedChars(search);
}
return input.count(search);
} else {
return 0;
}
}
}
export default CountOccurrences;

View File

@ -0,0 +1,59 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Decode NetBIOS Name operation
*/
class DecodeNetBIOSName extends Operation {
/**
* DecodeNetBIOSName constructor
*/
constructor() {
super();
this.name = "Decode NetBIOS Name";
this.module = "Default";
this.description = "NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.<br><br>There are two levels of encoding. The first level maps a NetBIOS name into a domain system name. The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.<br><br>This operation decodes the first level of encoding. See RFC 1001 for full details.";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.args = [
{
"name": "Offset",
"type": "number",
"value": 65
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const output = [],
offset = args[0];
if (input.length <= 32 && (input.length % 2) === 0) {
for (let i = 0; i < input.length; i += 2) {
output.push((((input[i] & 0xff) - offset) << 4) |
(((input[i + 1] & 0xff) - offset) & 0xf));
}
for (let i = output.length - 1; i > 0; i--) {
if (output[i] === 32) output.splice(i, i);
else break;
}
}
return output;
}
}
export default DecodeNetBIOSName;

View File

@ -0,0 +1,92 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Drop bytes operation
*/
class DropBytes extends Operation {
/**
* DropBytes constructor
*/
constructor() {
super();
this.name = "Drop bytes";
this.module = "Default";
this.description = "Cuts a slice of the specified number of bytes out of the data.";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
"name": "Start",
"type": "number",
"value": 0
},
{
"name": "Length",
"type": "number",
"value": 5
},
{
"name": "Apply to each line",
"type": "boolean",
"value": false
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
run(input, args) {
const start = args[0],
length = args[1],
applyToEachLine = args[2];
if (start < 0 || length < 0)
throw "Error: Invalid value";
if (!applyToEachLine) {
const left = input.slice(0, start),
right = input.slice(start + length, input.byteLength);
const result = new Uint8Array(left.byteLength + right.byteLength);
result.set(new Uint8Array(left), 0);
result.set(new Uint8Array(right), left.byteLength);
return result.buffer;
}
// Split input into lines
const data = new Uint8Array(input);
const lines = [];
let line = [],
i;
for (i = 0; i < data.length; i++) {
if (data[i] === 0x0a) {
lines.push(line);
line = [];
} else {
line.push(data[i]);
}
}
lines.push(line);
let output = [];
for (i = 0; i < lines.length; i++) {
output = output.concat(lines[i].slice(0, start).concat(lines[i].slice(start+length, lines[i].length)));
output.push(0x0a);
}
return new Uint8Array(output.slice(0, output.length-1)).buffer;
}
}
export default DropBytes;

View File

@ -0,0 +1,59 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Encode NetBIOS Name operation
*/
class EncodeNetBIOSName extends Operation {
/**
* EncodeNetBIOSName constructor
*/
constructor() {
super();
this.name = "Encode NetBIOS Name";
this.module = "Default";
this.description = "NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.<br><br>There are two levels of encoding. The first level maps a NetBIOS name into a domain system name. The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.<br><br>This operation carries out the first level of encoding. See RFC 1001 for full details.";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.args = [
{
"name": "Offset",
"type": "number",
"value": 65
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const output = [],
offset = args[0];
if (input.length <= 16) {
const len = input.length;
input.length = 16;
input.fill(32, len, 16);
for (let i = 0; i < input.length; i++) {
output.push((input[i] >> 4) + offset);
output.push((input[i] & 0xf) + offset);
}
}
return output;
}
}
export default EncodeNetBIOSName;

View File

@ -0,0 +1,87 @@
/**
* @author Vel0x [dalemy@microsoft.com]
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import jsesc from "jsesc";
/**
* Escape string operation
*/
class EscapeString extends Operation {
/**
* EscapeString constructor
*/
constructor() {
super();
this.name = "Escape string";
this.module = "Default";
this.description = "Escapes special characters in a string so that they do not cause conflicts. For example, <code>Don't stop me now</code> becomes <code>Don\\'t stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\n</code> (Line feed/newline)</li><li><code>\\r</code> (Carriage return)</li><li><code>\\t</code> (Horizontal tab)</li><li><code>\\b</code> (Backspace)</li><li><code>\\f</code> (Form feed)</li><li><code>\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\</code> (Backslash)</li><li><code>\\'</code> (Single quote)</li><li><code>\\&quot;</code> (Double quote)</li><li><code>\\unnnn</code> (Unicode character)</li><li><code>\\u{nnnnnn}</code> (Unicode code point)</li></ul>";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Escape level",
"type": "option",
"value": ["Special chars", "Everything", "Minimal"]
},
{
"name": "Escape quote",
"type": "option",
"value": ["Single", "Double", "Backtick"]
},
{
"name": "JSON compatible",
"type": "boolean",
"value": false
},
{
"name": "ES6 compatible",
"type": "boolean",
"value": true
},
{
"name": "Uppercase hex",
"type": "boolean",
"value": false
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*
* @example
* EscapeString.run("Don't do that", [])
* > "Don\'t do that"
* EscapeString.run(`Hello
* World`, [])
* > "Hello\nWorld"
*/
run(input, args) {
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,
});
}
}
export default EscapeString;

View File

@ -0,0 +1,46 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Expand alphabet range operation
*/
class ExpandAlphabetRange extends Operation {
/**
* ExpandAlphabetRange constructor
*/
constructor() {
super();
this.name = "Expand alphabet range";
this.module = "Default";
this.description = "Expand an alphabet range string into a list of the characters in that range.<br><br>e.g. <code>a-z</code> becomes <code>abcdefghijklmnopqrstuvwxyz</code>.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "binaryString",
"value": ""
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return Utils.expandAlphRange(input).join(args[0]);
}
}
export default ExpandAlphabetRange;

View File

@ -0,0 +1,71 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {INPUT_DELIM_OPTIONS} from "../lib/Delim";
/**
* Filter operation
*/
class Filter extends Operation {
/**
* Filter constructor
*/
constructor() {
super();
this.name = "Filter";
this.module = "Default";
this.description = "Splits up the input using the specified delimiter and then filters each branch based on a regular expression.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": INPUT_DELIM_OPTIONS
},
{
"name": "Regex",
"type": "string",
"value": ""
},
{
"name": "Invert condition",
"type": "boolean",
"value": false
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0]),
reverse = args[2];
let regex;
try {
regex = new RegExp(args[1]);
} catch (err) {
return "Invalid regex. Details: " + err.message;
}
const regexFilter = function(value) {
return reverse ^ regex.test(value);
};
return input.split(delim).filter(regexFilter).join(delim);
}
}
export default Filter;

View File

@ -5,7 +5,7 @@
*/
import Operation from "../Operation";
import {fromHex, HEX_DELIM_OPTIONS} from "../lib/Hex";
import {fromHex, FROM_HEX_DELIM_OPTIONS} from "../lib/Hex";
import Utils from "../Utils";
/**
@ -28,7 +28,7 @@ class FromHex extends Operation {
{
name: "Delimiter",
type: "option",
value: HEX_DELIM_OPTIONS
value: FROM_HEX_DELIM_OPTIONS
}
];
}

View File

@ -0,0 +1,96 @@
/**
* @author GCHQ Contributor [2]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {fromHex} from "../lib/Hex";
/**
* Hamming Distance operation
*/
class HammingDistance extends Operation {
/**
* HammingDistance constructor
*/
constructor() {
super();
this.name = "Hamming Distance";
this.module = "Default";
this.description = "In information theory, the Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different. In other words, it measures the minimum number of substitutions required to change one string into the other, or the minimum number of errors that could have transformed one string into the other. In a more general context, the Hamming distance is one of several string metrics for measuring the edit distance between two sequences.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "binaryShortString",
"value": "\\n\\n"
},
{
"name": "Unit",
"type": "option",
"value": ["Byte", "Bit"]
},
{
"name": "Input type",
"type": "option",
"value": ["Raw string", "Hex"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const delim = args[0],
byByte = args[1] === "Byte",
inputType = args[2],
samples = input.split(delim);
if (samples.length !== 2) {
return "Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter.";
}
if (samples[0].length !== samples[1].length) {
return "Error: Both inputs must be of the same length.";
}
if (inputType === "Hex") {
samples[0] = fromHex(samples[0]);
samples[1] = fromHex(samples[1]);
} else {
samples[0] = Utils.strToByteArray(samples[0]);
samples[1] = Utils.strToByteArray(samples[1]);
}
let dist = 0;
for (let i = 0; i < samples[0].length; i++) {
const lhs = samples[0][i],
rhs = samples[1][i];
if (byByte && lhs !== rhs) {
dist++;
} else if (!byByte) {
let xord = lhs ^ rhs;
while (xord) {
dist++;
xord &= xord - 1;
}
}
}
return dist.toString();
}
}
export default HammingDistance;

View File

@ -0,0 +1,68 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {INPUT_DELIM_OPTIONS} from "../lib/Delim";
/**
* Head operation
*/
class Head extends Operation {
/**
* Head constructor
*/
constructor() {
super();
this.name = "Head";
this.module = "Default";
this.description = "Like the UNIX head utility.<br>Gets the first n lines.<br>You can select all but the last n lines by entering a negative value for n.<br>The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": INPUT_DELIM_OPTIONS
},
{
"name": "Number",
"type": "number",
"value": 10
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let delimiter = args[0];
const number = args[1];
delimiter = Utils.charRep(delimiter);
const splitInput = input.split(delimiter);
return splitInput
.filter((line, lineIndex) => {
lineIndex += 1;
if (number < 0) {
return lineIndex <= splitInput.length + number;
} else {
return lineIndex <= number;
}
})
.join(delimiter);
}
}
export default Head;

View File

@ -0,0 +1,106 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Offset checker operation
*/
class OffsetChecker extends Operation {
/**
* OffsetChecker constructor
*/
constructor() {
super();
this.name = "Offset checker";
this.module = "Default";
this.description = "Compares multiple inputs (separated by the specified delimiter) and highlights matching characters which appear at the same position in all samples.";
this.inputType = "string";
this.outputType = "html";
this.args = [
{
"name": "Sample delimiter",
"type": "binaryString",
"value": "\\n\\n"
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const sampleDelim = args[0],
samples = input.split(sampleDelim),
outputs = new Array(samples.length);
let i = 0,
s = 0,
match = false,
inMatch = false,
chr;
if (!samples || samples.length < 2) {
return "Not enough samples, perhaps you need to modify the sample delimiter or add more data?";
}
// Initialise output strings
outputs.fill("", 0, samples.length);
// Loop through each character in the first sample
for (i = 0; i < samples[0].length; i++) {
chr = samples[0][i];
match = false;
// Loop through each sample to see if the chars are the same
for (s = 1; s < samples.length; s++) {
if (samples[s][i] !== chr) {
match = false;
break;
}
match = true;
}
// Write output for each sample
for (s = 0; s < samples.length; s++) {
if (samples[s].length <= i) {
if (inMatch) outputs[s] += "</span>";
if (s === samples.length - 1) inMatch = false;
continue;
}
if (match && !inMatch) {
outputs[s] += "<span class='hl5'>" + Utils.escapeHtml(samples[s][i]);
if (samples[s].length === i + 1) outputs[s] += "</span>";
if (s === samples.length - 1) inMatch = true;
} else if (!match && inMatch) {
outputs[s] += "</span>" + Utils.escapeHtml(samples[s][i]);
if (s === samples.length - 1) inMatch = false;
} else {
outputs[s] += Utils.escapeHtml(samples[s][i]);
if (inMatch && samples[s].length === i + 1) {
outputs[s] += "</span>";
if (samples[s].length - 1 !== i) inMatch = false;
}
}
if (samples[0].length - 1 === i) {
if (inMatch) outputs[s] += "</span>";
outputs[s] += Utils.escapeHtml(samples[s].substring(i + 1));
}
}
}
return outputs.join(sampleDelim);
}
}
export default OffsetChecker;

View File

@ -0,0 +1,70 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Pad lines operation
*/
class PadLines extends Operation {
/**
* PadLines constructor
*/
constructor() {
super();
this.name = "Pad lines";
this.module = "Default";
this.description = "Add the specified number of the specified character to the beginning or end of each line";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Position",
"type": "option",
"value": ["Start", "End"]
},
{
"name": "Length",
"type": "number",
"value": 5
},
{
"name": "Character",
"type": "binaryShortString",
"value": " "
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [position, len, chr] = args,
lines = input.split("\n");
let output = "",
i = 0;
if (position === "Start") {
for (i = 0; i < lines.length; i++) {
output += lines[i].padStart(lines[i].length+len, chr) + "\n";
}
} else if (position === "End") {
for (i = 0; i < lines.length; i++) {
output += lines[i].padEnd(lines[i].length+len, chr) + "\n";
}
}
return output.slice(0, output.length-1);
}
}
export default PadLines;

View File

@ -0,0 +1,39 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Remove line numbers operation
*/
class RemoveLineNumbers extends Operation {
/**
* RemoveLineNumbers constructor
*/
constructor() {
super();
this.name = "Remove line numbers";
this.module = "Default";
this.description = "Removes line numbers from the output if they can be trivially detected.";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return input.replace(/^[ \t]{0,5}\d+[\s:|\-,.)\]]/gm, "");
}
}
export default RemoveLineNumbers;

View File

@ -0,0 +1,43 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Remove null bytes operation
*/
class RemoveNullBytes extends Operation {
/**
* RemoveNullBytes constructor
*/
constructor() {
super();
this.name = "Remove null bytes";
this.module = "Default";
this.description = "Removes all null bytes (<code>0x00</code>) from the input.";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.args = [];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const output = [];
for (let i = 0; i < input.length; i++) {
if (input[i] !== 0) output.push(input[i]);
}
return output;
}
}
export default RemoveNullBytes;

View File

@ -0,0 +1,86 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Remove whitespace operation
*/
class RemoveWhitespace extends Operation {
/**
* RemoveWhitespace constructor
*/
constructor() {
super();
this.name = "Remove whitespace";
this.module = "Default";
this.description = "Optionally removes all spaces, carriage returns, line feeds, tabs and form feeds from the input data.<br><br>This operation also supports the removal of full stops which are sometimes used to represent non-printable bytes in ASCII output.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Spaces",
"type": "boolean",
"value": true
},
{
"name": "Carriage returns (\\r)",
"type": "boolean",
"value": true
},
{
"name": "Line feeds (\\n)",
"type": "boolean",
"value": true
},
{
"name": "Tabs",
"type": "boolean",
"value": true
},
{
"name": "Form feeds (\\f)",
"type": "boolean",
"value": true
},
{
"name": "Full stops",
"type": "boolean",
"value": false
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [
removeSpaces,
removeCariageReturns,
removeLineFeeds,
removeTabs,
removeFormFeeds,
removeFullStops
] = args;
let data = input;
if (removeSpaces) data = data.replace(/ /g, "");
if (removeCariageReturns) data = data.replace(/\r/g, "");
if (removeLineFeeds) data = data.replace(/\n/g, "");
if (removeTabs) data = data.replace(/\t/g, "");
if (removeFormFeeds) data = data.replace(/\f/g, "");
if (removeFullStops) data = data.replace(/\./g, "");
return data;
}
}
export default RemoveWhitespace;

View File

@ -0,0 +1,67 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Reverse operation
*/
class Reverse extends Operation {
/**
* Reverse constructor
*/
constructor() {
super();
this.name = "Reverse";
this.module = "Default";
this.description = "Reverses the input string.";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.args = [
{
"name": "By",
"type": "option",
"value": ["Character", "Line"]
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
let i;
if (args[0] === "Line") {
const lines = [];
let line = [],
result = [];
for (i = 0; i < input.length; i++) {
if (input[i] === 0x0a) {
lines.push(line);
line = [];
} else {
line.push(input[i]);
}
}
lines.push(line);
lines.reverse();
for (i = 0; i < lines.length; i++) {
result = result.concat(lines[i]);
result.push(0x0a);
}
return result.slice(0, input.length);
} else {
return input.reverse();
}
}
}
export default Reverse;

View File

@ -0,0 +1,136 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {INPUT_DELIM_OPTIONS} from "../lib/Delim";
/**
* Sort operation
*/
class Sort extends Operation {
/**
* Sort constructor
*/
constructor() {
super();
this.name = "Sort";
this.module = "Default";
this.description = "Alphabetically sorts strings separated by the specified delimiter.<br><br>The IP address option supports IPv4 only.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": INPUT_DELIM_OPTIONS
},
{
"name": "Reverse",
"type": "boolean",
"value": false
},
{
"name": "Order",
"type": "option",
"value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0]),
sortReverse = args[1],
order = args[2];
let sorted = input.split(delim);
if (order === "Alphabetical (case sensitive)") {
sorted = sorted.sort();
} else if (order === "Alphabetical (case insensitive)") {
sorted = sorted.sort(Sort._caseInsensitiveSort);
} else if (order === "IP address") {
sorted = sorted.sort(Sort._ipSort);
} else if (order === "Numeric") {
sorted = sorted.sort(Sort._numericSort);
}
if (sortReverse) sorted.reverse();
return sorted.join(delim);
}
/**
* Comparison operation for sorting of strings ignoring case.
*
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _caseInsensitiveSort(a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
}
/**
* Comparison operation for sorting of IPv4 addresses.
*
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _ipSort(a, b) {
let a_ = a.split("."),
b_ = b.split(".");
a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1;
b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1;
if (isNaN(a_) && !isNaN(b_)) return 1;
if (!isNaN(a_) && isNaN(b_)) return -1;
if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b);
return a_ - b_;
}
/**
* Comparison operation for sorting of numeric values.
*
* @author Chris van Marle
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _numericSort(a, b) {
const a_ = a.split(/([^\d]+)/),
b_ = b.split(/([^\d]+)/);
for (let i = 0; i < a_.length && i < b.length; ++i) {
if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
if (isNaN(a_[i]) && isNaN(b_[i])) {
const ret = a_[i].localeCompare(b_[i]); // Compare strings
if (ret !== 0) return ret;
}
if (!isNaN(a_[i]) && !isNaN(a_[i])) { // Compare numbers
if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
}
}
return a.localeCompare(b);
}
}
export default Sort;

View File

@ -0,0 +1,55 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import {SPLIT_DELIM_OPTIONS, JOIN_DELIM_OPTIONS} from "../lib/Delim";
/**
* Split operation
*/
class Split extends Operation {
/**
* Split constructor
*/
constructor() {
super();
this.name = "Split";
this.module = "Default";
this.description = "Splits a string into sections around a given delimiter.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Split delimiter",
"type": "editableOption",
"value": SPLIT_DELIM_OPTIONS
},
{
"name": "Join delimiter",
"type": "editableOption",
"value": JOIN_DELIM_OPTIONS
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const splitDelim = args[0],
joinDelim = args[1],
sections = input.split(splitDelim);
return sections.join(joinDelim);
}
}
export default Split;

View File

@ -0,0 +1,69 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {INPUT_DELIM_OPTIONS} from "../lib/Delim";
/**
* Tail operation
*/
class Tail extends Operation {
/**
* Tail constructor
*/
constructor() {
super();
this.name = "Tail";
this.module = "Default";
this.description = "Like the UNIX tail utility.<br>Gets the last n lines.<br>Optionally you can select all lines after line n by entering a negative value for n.<br>The delimiter can be changed so that instead of lines, fields (i.e. commas) are selected instead.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": INPUT_DELIM_OPTIONS
},
{
"name": "Number",
"type": "number",
"value": 10
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let delimiter = args[0];
const number = args[1];
delimiter = Utils.charRep(delimiter);
const splitInput = input.split(delimiter);
return splitInput
.filter((line, lineIndex) => {
lineIndex += 1;
if (number < 0) {
return lineIndex > -number;
} else {
return lineIndex > splitInput.length - number;
}
})
.join(delimiter);
}
}
export default Tail;

View File

@ -0,0 +1,86 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Take bytes operation
*/
class TakeBytes extends Operation {
/**
* TakeBytes constructor
*/
constructor() {
super();
this.name = "Take bytes";
this.module = "Default";
this.description = "Takes a slice of the specified number of bytes from the data.";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
"name": "Start",
"type": "number",
"value": 0
},
{
"name": "Length",
"type": "number",
"value": 5
},
{
"name": "Apply to each line",
"type": "boolean",
"value": false
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
run(input, args) {
const start = args[0],
length = args[1],
applyToEachLine = args[2];
if (start < 0 || length < 0)
throw "Error: Invalid value";
if (!applyToEachLine)
return input.slice(start, start+length);
// Split input into lines
const data = new Uint8Array(input);
const lines = [];
let line = [],
i;
for (i = 0; i < data.length; i++) {
if (data[i] === 0x0a) {
lines.push(line);
line = [];
} else {
line.push(data[i]);
}
}
lines.push(line);
let output = [];
for (i = 0; i < lines.length; i++) {
output = output.concat(lines[i].slice(start, start+length));
output.push(0x0a);
}
return new Uint8Array(output.slice(0, output.length-1)).buffer;
}
}
export default TakeBytes;

View File

@ -5,7 +5,7 @@
*/
import Operation from "../Operation";
import {toHex, HEX_DELIM_OPTIONS} from "../lib/Hex";
import {toHex, TO_HEX_DELIM_OPTIONS} from "../lib/Hex";
import Utils from "../Utils";
/**
@ -28,7 +28,7 @@ class ToHex extends Operation {
{
name: "Delimiter",
type: "option",
value: HEX_DELIM_OPTIONS
value: TO_HEX_DELIM_OPTIONS
}
];
}

View File

@ -0,0 +1,65 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* To Lower case operation
*/
class ToLowerCase extends Operation {
/**
* ToLowerCase constructor
*/
constructor() {
super();
this.name = "To Lower case";
this.module = "Default";
this.description = "Converts every character in the input to lower case.";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return input.toLowerCase();
}
/**
* Highlight To Lower case
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlight(pos, args) {
return pos;
}
/**
* Highlight To Lower case in reverse
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlightReverse(pos, args) {
return pos;
}
}
export default ToLowerCase;

View File

@ -0,0 +1,89 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* To Upper case operation
*/
class ToUpperCase extends Operation {
/**
* ToUpperCase constructor
*/
constructor() {
super();
this.name = "To Upper case";
this.module = "Default";
this.description = "Converts the input string to upper case, optionally limiting scope to only the first character in each word, sentence or paragraph.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Scope",
"type": "option",
"value": ["All", "Word", "Sentence", "Paragraph"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const scope = args[0];
switch (scope) {
case "Word":
return input.replace(/(\b\w)/gi, function(m) {
return m.toUpperCase();
});
case "Sentence":
return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) {
return m.toUpperCase();
});
case "Paragraph":
return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) {
return m.toUpperCase();
});
case "All": /* falls through */
default:
return input.toUpperCase();
}
}
/**
* Highlight To Upper case
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlight(pos, args) {
return pos;
}
/**
* Highlight To Upper case in reverse
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlightReverse(pos, args) {
return pos;
}
}
export default ToUpperCase;

View File

@ -0,0 +1,40 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Unescape string operation
*/
class UnescapeString extends Operation {
/**
* UnescapeString constructor
*/
constructor() {
super();
this.name = "Unescape string";
this.module = "Default";
this.description = "Unescapes characters in a string that have been escaped. For example, <code>Don\\'t stop me now</code> becomes <code>Don't stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\n</code> (Line feed/newline)</li><li><code>\\r</code> (Carriage return)</li><li><code>\\t</code> (Horizontal tab)</li><li><code>\\b</code> (Backspace)</li><li><code>\\f</code> (Form feed)</li><li><code>\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\</code> (Backslash)</li><li><code>\\'</code> (Single quote)</li><li><code>\\&quot;</code> (Double quote)</li><li><code>\\unnnn</code> (Unicode character)</li><li><code>\\u{nnnnnn}</code> (Unicode code point)</li></ul>";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return Utils.parseEscapedChars(input);
}
}
export default UnescapeString;

View File

@ -0,0 +1,48 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {INPUT_DELIM_OPTIONS} from "../lib/Delim";
/**
* Unique operation
*/
class Unique extends Operation {
/**
* Unique constructor
*/
constructor() {
super();
this.name = "Unique";
this.module = "Default";
this.description = "Removes duplicate strings from the input.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": INPUT_DELIM_OPTIONS
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0]);
return input.split(delim).unique().join(delim);
}
}
export default Unique;

View File

@ -29,7 +29,7 @@ const BSON = {
const data = JSON.parse(input);
return bson.serialize(data).buffer;
} catch (err) {
return err.toString();
throw err.toString();
}
},

View File

@ -18,6 +18,16 @@ const ByteRepr = {
* @default
*/
DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"],
/**
* @constant
* @default
*/
TO_HEX_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"],
/**
* @constant
* @default
*/
FROM_HEX_DELIM_OPTIONS: ["Auto", "Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"],
/**
* @constant
* @default

View File

@ -120,7 +120,7 @@ const Checksum = {
/**
* CRC-32 Checksum operation.
*
* @param {string} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
@ -132,7 +132,7 @@ const Checksum = {
/**
* CRC-16 Checksum operation.
*
* @param {string} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/

View File

@ -568,44 +568,6 @@ DES uses a key length of 8 bytes (64 bits).`;
}
},
/**
* @constant
* @default
*/
SUBS_PLAINTEXT: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
/**
* @constant
* @default
*/
SUBS_CIPHERTEXT: "XYZABCDEFGHIJKLMNOPQRSTUVW",
/**
* Substitute operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runSubstitute: function (input, args) {
let plaintext = Utils.expandAlphRange(args[0]).join(""),
ciphertext = Utils.expandAlphRange(args[1]).join(""),
output = "",
index = -1;
if (plaintext.length !== ciphertext.length) {
output = "Warning: Plaintext and Ciphertext lengths differ\n\n";
}
for (let i = 0; i < input.length; i++) {
index = plaintext.indexOf(input[i]);
output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i];
}
return output;
},
/**
* A mapping of string formats to their classes in the CryptoJS library.
*

View File

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

View File

@ -0,0 +1,164 @@
/**
* ToTable operations.
*
* @author Mark Jones [github.com/justanothermark]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*
* @namespace
*/
import Utils from "../Utils.js";
const ToTable = {
/**
* @constant
* @default
*/
FORMATS: [
"ASCII",
"HTML"
],
/**
* To Table operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
runToTable: function (input, args) {
const [cellDelims, rowDelims, firstRowHeader, format] = args;
// Process the input into a nested array of elements.
const tableData = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split(""));
if (!tableData.length) return "";
// Render the data in the requested format.
switch (format) {
case "ASCII":
return asciiOutput(tableData);
case "HTML":
default:
return htmlOutput(tableData);
}
/**
* Outputs an array of data as an ASCII table.
*
* @param {string[][]} tableData
* @returns {string}
*/
function asciiOutput(tableData) {
const horizontalBorder = "-";
const verticalBorder = "|";
const crossBorder = "+";
let output = "";
let longestCells = [];
// Find longestCells value per column to pad cells equally.
tableData.forEach(function(row, index) {
row.forEach(function(cell, cellIndex) {
if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) {
longestCells[cellIndex] = cell.length;
}
});
});
// Add the top border of the table to the output.
output += outputHorizontalBorder(longestCells);
// If the first row is a header, remove the row from the data and
// add it to the output with another horizontal border.
if (firstRowHeader) {
let row = tableData.shift();
output += outputRow(row, longestCells);
output += outputHorizontalBorder(longestCells);
}
// Add the rest of the table rows.
tableData.forEach(function(row, index) {
output += outputRow(row, longestCells);
});
// Close the table with a final horizontal border.
output += outputHorizontalBorder(longestCells);
return output;
/**
* Outputs a row of correctly padded cells.
*/
function outputRow(row, longestCells) {
let rowOutput = verticalBorder;
row.forEach(function(cell, index) {
rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder;
});
rowOutput += "\n";
return rowOutput;
}
/**
* Outputs a horizontal border with a different character where
* the horizontal border meets a vertical border.
*/
function outputHorizontalBorder(longestCells) {
let rowOutput = crossBorder;
longestCells.forEach(function(cellLength) {
rowOutput += horizontalBorder.repeat(cellLength + 2) + crossBorder;
});
rowOutput += "\n";
return rowOutput;
}
}
/**
* Outputs a table of data as a HTML table.
*
* @param {string[][]} tableData
* @returns {string}
*/
function htmlOutput(tableData) {
// Start the HTML output with suitable classes for styling.
let output = "<table class='table table-hover table-condensed table-bordered table-nonfluid'>";
// If the first row is a header then put it in <thead> with <th> cells.
if (firstRowHeader) {
let row = tableData.shift();
output += "<thead>";
output += outputRow(row, "th");
output += "</thead>";
}
// Output the rest of the rows in the <tbody>.
output += "<tbody>";
tableData.forEach(function(row, index) {
output += outputRow(row, "td");
});
// Close the body and table elements.
output += "</tbody></table>";
return output;
/**
* Outputs a table row.
*
* @param {string[]} row
* @param {string} cellType
*/
function outputRow(row, cellType) {
let output = "<tr>";
row.forEach(function(cell) {
output += "<" + cellType + ">" + cell + "</" + cellType + ">";
});
output += "</tr>";
return output;
}
}
}
};
export default ToTable;

View File

@ -83,6 +83,9 @@ App.prototype.loaded = function() {
// Clear the loading message interval
clearInterval(window.loadingMsgsInt);
// Remove the loading error handler
window.removeEventListener("error", window.loadingErrorHandler);
document.dispatchEvent(this.manager.apploaded);
};

View File

@ -42,7 +42,7 @@
}
// Define loading messages
const loadingMsgs = [
var loadingMsgs = [
"Proving P = NP...",
"Computing 6 x 9...",
"Mining bitcoin...",
@ -66,18 +66,18 @@
// Shuffle array using Durstenfeld algorithm
for (let i = loadingMsgs.length - 1; i > 0; --i) {
const j = Math.floor(Math.random() * (i + 1));
const temp = loadingMsgs[i];
var j = Math.floor(Math.random() * (i + 1));
var temp = loadingMsgs[i];
loadingMsgs[i] = loadingMsgs[j];
loadingMsgs[j] = temp;
}
// Show next loading message and move it to the end of the array
function changeLoadingMsg() {
const msg = loadingMsgs.shift();
var msg = loadingMsgs.shift();
loadingMsgs.push(msg);
try {
const el = document.getElementById("preloader-msg");
var el = document.getElementById("preloader-msg");
if (!el.classList.contains("loading"))
el.classList.add("loading"); // Causes CSS transition on first message
el.innerHTML = msg;
@ -86,6 +86,46 @@
changeLoadingMsg();
window.loadingMsgsInt = setInterval(changeLoadingMsg, (Math.random() * 2000) + 1500);
// If any errors are thrown during loading, handle them here
function loadingErrorHandler(e) {
function escapeHtml(str) {
var HTML_CHARS = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;", // &apos; not recommended because it's not in the HTML spec
"/": "&#x2F;", // forward slash is included as it helps end an HTML entity
"`": "&#x60;"
};
return str.replace(/[&<>"'/`]/g, function (match) {
return HTML_CHARS[match];
});
}
var msg = e.message +
(e.filename ? "\nFilename: " + e.filename : "") +
(e.lineno ? "\nLine: " + e.lineno : "") +
(e.colno ? "\nColumn: " + e.colno : "") +
(e.error ? "\nError: " + e.error : "") +
"\nUser-Agent: " + navigator.userAgent +
"\nCyberChef version: <%= htmlWebpackPlugin.options.version %>";
clearInterval(window.loadingMsgsInt);
document.getElementById("preloader").remove();
document.getElementById("preloader-msg").remove();
document.getElementById("preloader-error").innerHTML =
"CyberChef encountered an error while loading.<br><br>" +
"The following browser versions are supported:" +
"<ul><li>Google Chrome 40+</li><li>Mozilla Firefox 35+</li><li>Microsoft Edge 14+</li></ul>" +
"Your user agent is:<br>" + escapeHtml(navigator.userAgent) + "<br><br>" +
"If your browser is supported, please <a href='https://github.com/gchq/CyberChef/issues/new'>" +
"raise an issue</a> including the following details:<br><br>" +
"<pre>" + escapeHtml(msg) + "</pre>";
};
window.addEventListener("error", loadingErrorHandler);
</script>
<% if (htmlWebpackPlugin.options.inline) { %>
<meta name="robots" content="noindex" />
@ -100,6 +140,7 @@
<div id="loader-wrapper">
<div id="preloader" class="loader"></div>
<div id="preloader-msg" class="loading-msg"></div>
<div id="preloader-error" class="loading-error"></div>
</div>
<!-- End preloader overlay -->
<span id="edit-favourites" class="btn btn-default btn-sm"><img aria-hidden="true" src="<%- require('../static/images/favourite-16x16.png') %>" alt="Star Icon"/> Edit</span>

View File

@ -64,3 +64,4 @@ window.compileMessage = COMPILE_MSG;
window.CanvasComponents = CanvasComponents;
document.addEventListener("DOMContentLoaded", main, false);

View File

@ -74,6 +74,14 @@
transition: all 0.1s ease-in;
}
.loading-error {
display: block;
position: relative;
width: 600px;
left: calc(50% - 300px);
top: 10%;
}
/* Loaded */
.loaded .loading-msg {

View File

@ -1,134 +1,135 @@
/* eslint no-console: 0 */
/**
* TestRunner.js
*
* For running the tests in the test register.
*
* @author tlwr [toby@toby.codes]
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import "babel-polyfill";
// Define global environment functions
global.ENVIRONMENT_IS_WORKER = function() {
return typeof importScripts === "function";
};
global.ENVIRONMENT_IS_NODE = function() {
return typeof process === "object" && typeof require === "function";
};
global.ENVIRONMENT_IS_WEB = function() {
return typeof window === "object";
};
import TestRegister from "./TestRegister";
// import "./tests/operations/Base58.js";
import "./tests/operations/Base64";
// import "./tests/operations/BCD.js";
// import "./tests/operations/BitwiseOp.js";
// import "./tests/operations/BSON.js";
// import "./tests/operations/ByteRepr.js";
import "./tests/operations/CartesianProduct";
// import "./tests/operations/CharEnc.js";
import "./tests/operations/Ciphers";
// import "./tests/operations/Code.js";
// 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";
// import "./tests/operations/PHP.js";
// import "./tests/operations/NetBIOS.js";
// import "./tests/operations/OTP.js";
import "./tests/operations/PowerSet";
// import "./tests/operations/Regex.js";
import "./tests/operations/Rotate";
// import "./tests/operations/StrUtils.js";
// import "./tests/operations/SeqUtils.js";
import "./tests/operations/SetDifference";
import "./tests/operations/SetIntersection";
import "./tests/operations/SetUnion";
import "./tests/operations/SymmetricDifference";
let allTestsPassing = true;
const testStatusCounts = {
total: 0,
};
/**
* Helper function to convert a status to an icon.
*
* @param {string} status
* @returns {string}
*/
function statusToIcon(status) {
const icons = {
erroring: "🔥",
failing: "❌",
passing: "✔️️",
};
return icons[status] || "?";
}
/**
* Displays a given test result in the console.
*
* @param {Object} testResult
*/
function handleTestResult(testResult) {
allTestsPassing = allTestsPassing && testResult.status === "passing";
const newCount = (testStatusCounts[testResult.status] || 0) + 1;
testStatusCounts[testResult.status] = newCount;
testStatusCounts.total += 1;
console.log([
statusToIcon(testResult.status),
testResult.test.name
].join(" "));
if (testResult.output) {
console.log(
testResult.output
.trim()
.replace(/^/, "\t")
.replace(/\n/g, "\n\t")
);
}
}
/**
* Fail if the process takes longer than 10 seconds.
*/
setTimeout(function() {
console.log("Tests took longer than 10 seconds to run, returning.");
process.exit(1);
}, 10 * 1000);
TestRegister.runTests()
.then(function(results) {
results.forEach(handleTestResult);
console.log("\n");
for (const testStatus in testStatusCounts) {
const count = testStatusCounts[testStatus];
if (count > 0) {
console.log(testStatus.toUpperCase(), count);
}
}
if (!allTestsPassing) {
console.log("\nNot all tests are passing");
}
process.exit(allTestsPassing ? 0 : 1);
});
/* eslint no-console: 0 */
/**
* TestRunner.js
*
* For running the tests in the test register.
*
* @author tlwr [toby@toby.codes]
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import "babel-polyfill";
// Define global environment functions
global.ENVIRONMENT_IS_WORKER = function() {
return typeof importScripts === "function";
};
global.ENVIRONMENT_IS_NODE = function() {
return typeof process === "object" && typeof require === "function";
};
global.ENVIRONMENT_IS_WEB = function() {
return typeof window === "object";
};
import TestRegister from "./TestRegister";
// import "./tests/operations/Base58.js";
import "./tests/operations/Base64";
// import "./tests/operations/BCD.js";
// import "./tests/operations/BitwiseOp.js";
// import "./tests/operations/BSON.js";
// import "./tests/operations/ByteRepr.js";
import "./tests/operations/CartesianProduct";
// import "./tests/operations/CharEnc.js";
import "./tests/operations/Ciphers";
//import "./tests/operations/Checksum.js";
// import "./tests/operations/Code.js";
// 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";
// import "./tests/operations/PHP.js";
// import "./tests/operations/NetBIOS.js";
// import "./tests/operations/OTP.js";
import "./tests/operations/PowerSet";
// import "./tests/operations/Regex.js";
import "./tests/operations/Rotate";
// import "./tests/operations/StrUtils.js";
// import "./tests/operations/SeqUtils.js";
import "./tests/operations/SetDifference";
import "./tests/operations/SetIntersection";
import "./tests/operations/SetUnion";
import "./tests/operations/SymmetricDifference";
let allTestsPassing = true;
const testStatusCounts = {
total: 0,
};
/**
* Helper function to convert a status to an icon.
*
* @param {string} status
* @returns {string}
*/
function statusToIcon(status) {
const icons = {
erroring: "🔥",
failing: "❌",
passing: "✔️️",
};
return icons[status] || "?";
}
/**
* Displays a given test result in the console.
*
* @param {Object} testResult
*/
function handleTestResult(testResult) {
allTestsPassing = allTestsPassing && testResult.status === "passing";
const newCount = (testStatusCounts[testResult.status] || 0) + 1;
testStatusCounts[testResult.status] = newCount;
testStatusCounts.total += 1;
console.log([
statusToIcon(testResult.status),
testResult.test.name
].join(" "));
if (testResult.output) {
console.log(
testResult.output
.trim()
.replace(/^/, "\t")
.replace(/\n/g, "\n\t")
);
}
}
/**
* Fail if the process takes longer than 10 seconds.
*/
setTimeout(function() {
console.log("Tests took longer than 10 seconds to run, returning.");
process.exit(1);
}, 10 * 1000);
TestRegister.runTests()
.then(function(results) {
results.forEach(handleTestResult);
console.log("\n");
for (const testStatus in testStatusCounts) {
const count = testStatusCounts[testStatus];
if (count > 0) {
console.log(testStatus.toUpperCase(), count);
}
}
if (!allTestsPassing) {
console.log("\nNot all tests are passing");
}
process.exit(allTestsPassing ? 0 : 1);
});

View File

@ -0,0 +1,120 @@
/**
* Checksum tests.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister.js";
const BASIC_STRING = "The ships hung in the sky in much the same way that bricks don't.";
const UTF8_STR = "ნუ პანიკას";
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: "CRC-16: nothing",
input: "",
expectedOutput: "0000",
recipeConfig: [
{
"op": "CRC-16 Checksum",
"args": []
}
]
},
{
name: "CRC-16: basic string",
input: BASIC_STRING,
expectedOutput: "0c70",
recipeConfig: [
{
"op": "CRC-16 Checksum",
"args": []
}
]
},
{
name: "CRC-16: UTF-8",
input: UTF8_STR,
expectedOutput: "dcf6",
recipeConfig: [
{
"op": "CRC-16 Checksum",
"args": []
}
]
},
{
name: "CRC-16: all bytes",
input: ALL_BYTES,
expectedOutput: "bad3",
recipeConfig: [
{
"op": "CRC-16 Checksum",
"args": []
}
]
},
{
name: "CRC-32: nothing",
input: "",
expectedOutput: "00000000",
recipeConfig: [
{
"op": "CRC-32 Checksum",
"args": []
}
]
},
{
name: "CRC-32: basic string",
input: BASIC_STRING,
expectedOutput: "bf4b739c",
recipeConfig: [
{
"op": "CRC-32 Checksum",
"args": []
}
]
},
{
name: "CRC-32: UTF-8",
input: UTF8_STR,
expectedOutput: "87553290",
recipeConfig: [
{
"op": "CRC-32 Checksum",
"args": []
}
]
},
{
name: "CRC-32: all bytes",
input: ALL_BYTES,
expectedOutput: "29058c73",
recipeConfig: [
{
"op": "CRC-32 Checksum",
"args": []
}
]
},
]);

View File

@ -66,7 +66,7 @@ module.exports = {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
exclude: /node_modules\/(?!jsesc)/,
loader: "babel-loader?compact=false"
},
{
@ -118,7 +118,7 @@ module.exports = {
chunks: false,
modules: false,
entrypoints: false,
warningsFilter: /source-map/,
warningsFilter: [/source-map/, /dependency is an expression/],
},
node: {
fs: "empty"