pull from master

This commit is contained in:
d98762625 2018-12-07 13:20:54 +00:00
commit 18f6ab451d
34 changed files with 2164 additions and 1798 deletions

View file

@ -1,19 +0,0 @@
{
"presets": [
["@babel/preset-env", {
"targets": {
"chrome": 40,
"firefox": 35,
"edge": 14,
"node": "6.5"
},
"modules": false,
"useBuiltIns": "entry"
}]
],
"plugins": [
["babel-plugin-transform-builtin-extend", {
"globals": ["Error"]
}]
]
}

View file

@ -1,5 +1,11 @@
# Changelog
All notable changes to CyberChef will be documented in this file.
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
### [8.12.0] - 2018-11-21
- 'Citrix CTX1 Encode' and 'Citrix CTX1 Decode' operations added [@bwhitn] | [#428]
### [8.11.0] - 2018-11-13
- 'CSV to JSON' and 'JSON to CSV' operations added [@n1474335] | [#277]
### [8.10.0] - 2018-11-07
- 'Remove Diacritics' operation added [@klaxon1] | [#387]
@ -49,7 +55,7 @@ All notable changes to CyberChef will be documented in this file.
- Added support for loading, processing and downloading files up to 500MB [@n1474335] | [#224]
## [6.0.0] - 2017-09-19
- Threading support added. All recipe processing moved into a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to increase performance and allowing long-running operations to be cancelled [@n1474335] | [#173]
- Threading support added. All recipe processing moved into a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to increase performance and to allow long-running operations to be cancelled [@n1474335] | [#173]
- Module system created so that operations relying on large libraries can be downloaded separately as required, reducing the initial loading time for the app [@n1474335] | [#173]
## [5.0.0] - 2017-03-30
@ -59,6 +65,11 @@ All notable changes to CyberChef will be documented in this file.
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
[8.12.0]: https://github.com/gchq/CyberChef/releases/tag/v8.12.0
[8.11.0]: https://github.com/gchq/CyberChef/releases/tag/v8.11.0
[8.10.0]: https://github.com/gchq/CyberChef/releases/tag/v8.10.0
[8.9.0]: https://github.com/gchq/CyberChef/releases/tag/v8.9.0
[8.8.0]: https://github.com/gchq/CyberChef/releases/tag/v8.8.0
[8.7.0]: https://github.com/gchq/CyberChef/releases/tag/v8.7.0
[8.6.0]: https://github.com/gchq/CyberChef/releases/tag/v8.6.0
@ -84,6 +95,7 @@ All notable changes to CyberChef will be documented in this file.
[@PenguinGeorge]: https://github.com/PenguinGeorge
[@arnydo]: https://github.com/arnydo
[@klaxon1]: https://github.com/klaxon1
[@bwhitn]: https://github.com/bwhitn
[#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173
@ -91,6 +103,7 @@ All notable changes to CyberChef will be documented in this file.
[#239]: https://github.com/gchq/CyberChef/pull/239
[#248]: https://github.com/gchq/CyberChef/pull/248
[#255]: https://github.com/gchq/CyberChef/issues/255
[#277]: https://github.com/gchq/CyberChef/issues/277
[#281]: https://github.com/gchq/CyberChef/pull/281
[#284]: https://github.com/gchq/CyberChef/pull/284
[#294]: https://github.com/gchq/CyberChef/pull/294
@ -105,3 +118,4 @@ All notable changes to CyberChef will be documented in this file.
[#351]: https://github.com/gchq/CyberChef/pull/351
[#387]: https://github.com/gchq/CyberChef/pull/387
[#394]: https://github.com/gchq/CyberChef/pull/394
[#428]: https://github.com/gchq/CyberChef/pull/428

View file

@ -144,7 +144,7 @@ module.exports = function (grunt) {
options: {
configFile: "./.eslintrc.json"
},
configs: ["Gruntfile.js"],
configs: ["*.js"],
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
web: ["src/web/**/*.{js,mjs}"],
node: ["src/node/**/*.{js,mjs}"],

23
babel.config.js Normal file
View file

@ -0,0 +1,23 @@
module.exports = function(api) {
api.cache.forever();
return {
"presets": [
["@babel/preset-env", {
"targets": {
"chrome": 40,
"firefox": 35,
"edge": 14,
"node": "6.5"
},
"modules": false,
"useBuiltIns": "entry"
}]
],
"plugins": [
["babel-plugin-transform-builtin-extend", {
"globals": ["Error"]
}]
]
};
};

2895
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "8.10.1",
"version": "8.12.3",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",

View file

@ -9,6 +9,7 @@ import moment from "moment-timezone";
import {fromBase64} from "./lib/Base64";
import {fromHex} from "./lib/Hex";
import {fromDecimal} from "./lib/Decimal";
import {fromBinary} from "./lib/Binary";
/**
@ -298,7 +299,7 @@ class Utils {
* Accepts hex, Base64, UTF8 and Latin1 strings.
*
* @param {string} str
* @param {string} type - One of "Hex", "Decimal", "Base64", "UTF8" or "Latin1"
* @param {string} type - One of "Binary", "Hex", "Decimal", "Base64", "UTF8" or "Latin1"
* @returns {byteArray}
*
* @example
@ -313,6 +314,8 @@ class Utils {
*/
static convertToByteArray(str, type) {
switch (type.toLowerCase()) {
case "binary":
return fromBinary(str);
case "hex":
return fromHex(str);
case "decimal":
@ -333,7 +336,7 @@ class Utils {
* Accepts hex, Base64, UTF8 and Latin1 strings.
*
* @param {string} str
* @param {string} type - One of "Hex", "Decimal", "Base64", "UTF8" or "Latin1"
* @param {string} type - One of "Binary", "Hex", "Decimal", "Base64", "UTF8" or "Latin1"
* @returns {string}
*
* @example
@ -348,6 +351,8 @@ class Utils {
*/
static convertToByteString(str, type) {
switch (type.toLowerCase()) {
case "binary":
return Utils.byteArrayToChars(fromBinary(str));
case "hex":
return Utils.byteArrayToChars(fromHex(str));
case "decimal":
@ -568,6 +573,10 @@ class Utils {
cell = "";
lines.push(line);
line = [];
// Skip next byte if it is also a line delim (e.g. \r\n)
if (lineDelims.indexOf(next) >= 0 && next !== b) {
i++;
}
} else {
cell += b;
}

View file

@ -54,7 +54,9 @@
"From MessagePack",
"To Braille",
"From Braille",
"Parse TLV"
"Parse TLV",
"CSV to JSON",
"JSON to CSV"
]
},
{
@ -93,6 +95,8 @@
"JWT Sign",
"JWT Verify",
"JWT Decode",
"Citrix CTX1 Encode",
"Citrix CTX1 Decode",
"Pseudo-Random Number Generator"
]
},

View file

@ -126,14 +126,14 @@ export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", r
* Base64 alphabets.
*/
export const ALPHABET_OPTIONS = [
{name: "Standard: A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
{name: "URL safe: A-Za-z0-9-_", value: "A-Za-z0-9-_"},
{name: "Standard (RFC 4648): A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
{name: "URL safe (RFC 4648 \u00A75): A-Za-z0-9-_", value: "A-Za-z0-9-_"},
{name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
{name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
{name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
{name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
{name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
{name: "Radix-64: 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
{name: "Radix-64 (RFC 4880): 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
{name: "Uuencoding: [space]-_", value: " -_"},
{name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
{name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},

70
src/core/lib/Binary.mjs Normal file
View file

@ -0,0 +1,70 @@
/**
* Binary functions.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Utils from "../Utils";
/**
* Convert a byte array into a binary string.
*
* @param {Uint8Array|byteArray} data
* @param {string} [delim="Space"]
* @param {number} [padding=8]
* @returns {string}
*
* @example
* // returns "00010000 00100000 00110000"
* toBinary([10,20,30]);
*
* // returns "00010000 00100000 00110000"
* toBinary([10,20,30], ":");
*/
export function toBinary(data, delim="Space", padding=8) {
if (!data) return "";
delim = Utils.charRep(delim);
let output = "";
for (let i = 0; i < data.length; i++) {
output += data[i].toString(2).padStart(padding, "0") + delim;
}
if (delim.length) {
return output.slice(0, -delim.length);
} else {
return output;
}
}
/**
* Convert a binary string into a byte array.
*
* @param {string} data
* @param {string} [delim]
* @param {number} [byteLen=8]
* @returns {byteArray}
*
* @example
* // returns [10,20,30]
* fromBinary("00010000 00100000 00110000");
*
* // returns [10,20,30]
* fromBinary("00010000:00100000:00110000", "Colon");
*/
export function fromBinary(data, delim="Space", byteLen=8) {
const delimRegex = Utils.regexRep(delim);
data = data.replace(delimRegex, "");
const output = [];
for (let i = 0; i < data.length; i += byteLen) {
output.push(parseInt(data.substr(i, byteLen), 2));
}
return output;
}

View file

@ -116,3 +116,9 @@ export function sub(operand, key) {
const result = operand - key;
return (result < 0) ? 256 + result : result;
}
/**
* Delimiter options for bitwise operations
*/
export const BITWISE_OP_DELIMS = ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"];

View file

@ -6,7 +6,7 @@
import Operation from "../Operation";
import Utils from "../Utils";
import { bitOp, add } from "../lib/BitwiseOp";
import { bitOp, add, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
/**
* ADD operation
@ -30,7 +30,7 @@ class ADD extends Operation {
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
"toggleValues": BITWISE_OP_DELIMS
}
];
}

View file

@ -6,7 +6,7 @@
import Operation from "../Operation";
import Utils from "../Utils";
import { bitOp, and } from "../lib/BitwiseOp";
import { bitOp, and, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
/**
* AND operation
@ -30,7 +30,7 @@ class AND extends Operation {
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
"toggleValues": BITWISE_OP_DELIMS
}
];
}

View file

@ -0,0 +1,80 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
/**
* CSV to JSON operation
*/
class CSVToJSON extends Operation {
/**
* CSVToJSON constructor
*/
constructor() {
super();
this.name = "CSV to JSON";
this.module = "Default";
this.description = "Converts a CSV file to JSON format.";
this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values";
this.inputType = "string";
this.outputType = "JSON";
this.args = [
{
name: "Cell delimiters",
type: "binaryShortString",
value: ","
},
{
name: "Row delimiters",
type: "binaryShortString",
value: "\\r\\n"
},
{
name: "Format",
type: "option",
value: ["Array of dictionaries", "Array of arrays"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {JSON}
*/
run(input, args) {
const [cellDelims, rowDelims, format] = args;
let json, header;
try {
json = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split(""));
} catch (err) {
throw new OperationError("Unable to parse CSV: " + err);
}
switch (format) {
case "Array of dictionaries":
header = json[0];
return json.slice(1).map(row => {
const obj = {};
header.forEach((h, i) => {
obj[h] = row[i];
});
return obj;
});
case "Array of arrays":
default:
return json;
}
}
}
export default CSVToJSON;

View file

@ -0,0 +1,58 @@
/**
* @author bwhitn [brian.m.whitney@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import cptable from "../vendor/js-codepage/cptable.js";
/**
* Citrix CTX1 Decode operation
*/
class CitrixCTX1Decode extends Operation {
/**
* CitrixCTX1Decode constructor
*/
constructor() {
super();
this.name = "Citrix CTX1 Decode";
this.module = "Encodings";
this.description = "Decodes strings in a Citrix CTX1 password format to plaintext.";
this.infoURL = "https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/";
this.inputType = "byteArray";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (input.length % 4 !== 0) {
throw new OperationError("Incorrect hash length");
}
const revinput = input.reverse();
const result = [];
let temp = 0;
for (let i = 0; i < revinput.length; i += 2) {
if (i + 2 >= revinput.length) {
temp = 0;
} else {
temp = ((revinput[i + 2] - 0x41) & 0xf) ^ (((revinput[i + 3]- 0x41) << 4) & 0xf0);
}
temp = (((revinput[i] - 0x41) & 0xf) ^ (((revinput[i + 1] - 0x41) << 4) & 0xf0)) ^ 0xa5 ^ temp;
result.push(temp);
}
// Decodes a utf-16le string
return cptable.utils.decode(1200, result.reverse());
}
}
export default CitrixCTX1Decode;

View file

@ -0,0 +1,50 @@
/**
* @author bwhitn [brian.m.whitney@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import cptable from "../vendor/js-codepage/cptable.js";
/**
* Citrix CTX1 Encode operation
*/
class CitrixCTX1Encode extends Operation {
/**
* CitrixCTX1Encode constructor
*/
constructor() {
super();
this.name = "Citrix CTX1 Encode";
this.module = "Encodings";
this.description = "Encodes strings to Citrix CTX1 password format.";
this.infoURL = "https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/";
this.inputType = "string";
this.outputType = "byteArray";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const utf16pass = Array.from(cptable.utils.encode(1200, input));
const result = [];
let temp = 0;
for (let i = 0; i < utf16pass.length; i++) {
temp = utf16pass[i] ^ 0xa5 ^ temp;
result.push(((temp >>> 4) & 0xf) + 0x41);
result.push((temp & 0xf) + 0x41);
}
return result;
}
}
export default CitrixCTX1Encode;

View file

@ -38,44 +38,44 @@ class FromBase64 extends Operation {
];
this.patterns = [
{
match: "^(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$",
match: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i",
args: ["A-Za-z0-9+/=", false]
args: ["A-Za-z0-9+/=", true]
},
{
match: "^[A-Z\\d\\-_]{20,}$",
match: "^\\s*[A-Z\\d\\-_]{20,}\\s*$",
flags: "i",
args: ["A-Za-z0-9-_", false]
args: ["A-Za-z0-9-_", true]
},
{
match: "^(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?$",
match: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$",
flags: "i",
args: ["A-Za-z0-9+\\-=", false]
args: ["A-Za-z0-9+\\-=", true]
},
{
match: "^(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?$",
match: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$",
flags: "i",
args: ["./0-9A-Za-z=", false]
args: ["./0-9A-Za-z=", true]
},
{
match: "^[A-Z\\d_.]{20,}$",
match: "^\\s*[A-Z\\d_.]{20,}\\s*$",
flags: "i",
args: ["A-Za-z0-9_.", false]
args: ["A-Za-z0-9_.", true]
},
{
match: "^(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?$",
match: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$",
flags: "i",
args: ["A-Za-z0-9._-", false]
args: ["A-Za-z0-9._-", true]
},
{
match: "^(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$",
match: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i",
args: ["0-9a-zA-Z+/=", false]
args: ["0-9a-zA-Z+/=", true]
},
{
match: "^(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$",
match: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i",
args: ["0-9A-Za-z+/=", false]
args: ["0-9A-Za-z+/=", true]
},
{
match: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$",
@ -83,24 +83,24 @@ class FromBase64 extends Operation {
args: [" -_", false]
},
{
match: "^[A-Z\\d+\\-]{20,}$",
match: "^\\s*[A-Z\\d+\\-]{20,}\\s*$",
flags: "i",
args: ["+\\-0-9A-Za-z", false]
args: ["+\\-0-9A-Za-z", true]
},
{
match: "^[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}$",
match: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$",
flags: "",
args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", false]
args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true]
},
{
match: "^(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?$",
match: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$",
flags: "i",
args: ["N-ZA-Mn-za-m0-9+/=", false]
args: ["N-ZA-Mn-za-m0-9+/=", true]
},
{
match: "^[A-Z\\d./]{20,}$",
match: "^\\s*[A-Z\\d./]{20,}\\s*$",
flags: "i",
args: ["./0-9A-Za-z", false]
args: ["./0-9A-Za-z", true]
},
];
}

View file

@ -7,6 +7,7 @@
import Operation from "../Operation";
import Utils from "../Utils";
import {BIN_DELIM_OPTIONS} from "../lib/Delim";
import {fromBinary} from "../lib/Binary";
/**
* From Binary operation
@ -77,15 +78,7 @@ class FromBinary extends Operation {
* @returns {byteArray}
*/
run(input, args) {
const delimRegex = Utils.regexRep(args[0] || "Space");
input = input.replace(delimRegex, "");
const output = [];
const byteLen = 8;
for (let i = 0; i < input.length; i += byteLen) {
output.push(parseInt(input.substr(i, byteLen), 2));
}
return output;
return fromBinary(input, args[0]);
}
/**

View file

@ -30,7 +30,7 @@ class FromQuotedPrintable extends Operation {
this.args = [];
this.patterns = [
{
match: "^[\\x21-\\x3d\\x3f-\\x7e \\t]*(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$",
match: "^[\\x21-\\x3d\\x3f-\\x7e \\t]{0,76}(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$",
flags: "i",
args: []
},

View file

@ -1,5 +1,6 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @author Phillip Nordwall [phillip.nordwall@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
@ -27,7 +28,12 @@ class JSONBeautify extends Operation {
{
"name": "Indent string",
"type": "binaryShortString",
"value": "\\t"
"value": " "
},
{
"name": "Sort Object Keys",
"type": "boolean",
"value": false
}
];
}
@ -38,11 +44,35 @@ class JSONBeautify extends Operation {
* @returns {string}
*/
run(input, args) {
const indentStr = args[0];
const [indentStr, sortBool] = args;
if (!input) return "";
if (sortBool) {
input = JSON.stringify(JSONBeautify._sort(JSON.parse(input)));
}
return vkbeautify.json(input, indentStr);
}
/**
* Sort JSON representation of an object
*
* @author Phillip Nordwall [phillip.nordwall@gmail.com]
* @private
* @param {object} o
* @returns {object}
*/
static _sort(o) {
if (Array.isArray(o)) {
return o.map(JSONBeautify._sort);
} else if ("[object Object]" === Object.prototype.toString.call(o)) {
return Object.keys(o).sort().reduce(function(a, k) {
a[k] = JSONBeautify._sort(o[k]);
return a;
}, {});
}
return o;
}
}
export default JSONBeautify;

View file

@ -0,0 +1,111 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
/**
* JSON to CSV operation
*/
class JSONToCSV extends Operation {
/**
* JSONToCSV constructor
*/
constructor() {
super();
this.name = "JSON to CSV";
this.module = "Default";
this.description = "Converts JSON data to a CSV based on the definition in RFC 4180.";
this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values";
this.inputType = "JSON";
this.outputType = "string";
this.args = [
{
name: "Cell delimiter",
type: "binaryShortString",
value: ","
},
{
name: "Row delimiter",
type: "binaryShortString",
value: "\\r\\n"
}
];
}
/**
* @param {JSON} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [cellDelim, rowDelim] = args;
// Record values so they don't have to be passed to other functions explicitly
this.cellDelim = cellDelim;
this.rowDelim = rowDelim;
const self = this;
try {
// If the JSON is an array of arrays, this is easy
if (input[0] instanceof Array) {
return input
.map(row => row
.map(self.escapeCellContents.bind(self))
.join(cellDelim)
)
.join(rowDelim) +
rowDelim;
}
// If it's an array of dictionaries...
const header = Object.keys(input[0]);
return header
.map(self.escapeCellContents.bind(self))
.join(cellDelim) +
rowDelim +
input
.map(row => header
.map(h => row[h])
.map(self.escapeCellContents.bind(self))
.join(cellDelim)
)
.join(rowDelim) +
rowDelim;
} catch (err) {
throw new OperationError("Unable to parse JSON to CSV: " + err.toString());
}
}
/**
* Correctly escapes a cell's contents based on the cell and row delimiters.
*
* @param {string} data
* @returns {string}
*/
escapeCellContents(data) {
// Double quotes should be doubled up
data = data.replace(/"/g, '""');
// If the cell contains a cell or row delimiter or a double quote, it mut be enclosed in double quotes
if (
data.indexOf(this.cellDelim) >= 0 ||
data.indexOf(this.rowDelim) >= 0 ||
data.indexOf("\n") >= 0 ||
data.indexOf("\r") >= 0 ||
data.indexOf('"') >= 0
) {
data = `"${data}"`;
}
return data;
}
}
export default JSONToCSV;

View file

@ -6,7 +6,7 @@
import Operation from "../Operation";
import Utils from "../Utils";
import { bitOp, or } from "../lib/BitwiseOp";
import { bitOp, or, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
/**
* OR operation
@ -30,7 +30,7 @@ class OR extends Operation {
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
"toggleValues": BITWISE_OP_DELIMS
}
];
}

View file

@ -6,7 +6,7 @@
import Operation from "../Operation";
import Utils from "../Utils";
import { bitOp, sub } from "../lib/BitwiseOp";
import { bitOp, sub, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
/**
* SUB operation
@ -30,7 +30,7 @@ class SUB extends Operation {
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
"toggleValues": BITWISE_OP_DELIMS
}
];
}

View file

@ -20,7 +20,7 @@ class Substitute extends Operation {
this.name = "Substitute";
this.module = "Default";
this.description = "A substitution cipher allowing you to specify bytes to replace with other byte values. This can be used to create Caesar ciphers but is more powerful as any byte value can be substituted, not just letters, and the substitution values need not be in order.<br><br>Enter the bytes you want to replace in the Plaintext field and the bytes to replace them with in the Ciphertext field.<br><br>Non-printable bytes can be specified using string escape notation. For example, a line feed character can be written as either <code>\n</code> or <code>\x0a</code>.<br><br>Byte ranges can be specified using a hyphen. For example, the sequence <code>0123456789</code> can be written as <code>0-9</code>.";
this.description = "A substitution cipher allowing you to specify bytes to replace with other byte values. This can be used to create Caesar ciphers but is more powerful as any byte value can be substituted, not just letters, and the substitution values need not be in order.<br><br>Enter the bytes you want to replace in the Plaintext field and the bytes to replace them with in the Ciphertext field.<br><br>Non-printable bytes can be specified using string escape notation. For example, a line feed character can be written as either <code>\\n</code> or <code>\\x0a</code>.<br><br>Byte ranges can be specified using a hyphen. For example, the sequence <code>0123456789</code> can be written as <code>0-9</code>.<br><br>Note that blackslash characters are used to escape special characters, so will need to be escaped themselves if you want to use them on their own (e.g.<code>\\\\</code>).";
this.infoURL = "https://wikipedia.org/wiki/Substitution_cipher";
this.inputType = "string";
this.outputType = "string";

View file

@ -7,6 +7,7 @@
import Operation from "../Operation";
import Utils from "../Utils";
import {BIN_DELIM_OPTIONS} from "../lib/Delim";
import {toBinary} from "../lib/Binary";
/**
* To Binary operation
@ -40,19 +41,7 @@ class ToBinary extends Operation {
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0] || "Space"),
padding = 8;
let output = "";
for (let i = 0; i < input.length; i++) {
output += input[i].toString(2).padStart(padding, "0") + delim;
}
if (delim.length) {
return output.slice(0, -delim.length);
} else {
return output;
}
return toBinary(input, args[0]);
}
/**

View file

@ -33,7 +33,7 @@ class ToTable extends Operation {
{
"name": "Row delimiters",
"type": "binaryShortString",
"value": "\\n\\r"
"value": "\\r\\n"
},
{
"name": "Make first row header",

View file

@ -6,7 +6,7 @@
import Operation from "../Operation";
import Utils from "../Utils";
import { bitOp, xor } from "../lib/BitwiseOp";
import { bitOp, xor, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
/**
* XOR operation
@ -30,7 +30,7 @@ class XOR extends Operation {
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
"toggleValues": BITWISE_OP_DELIMS
},
{
"name": "Scheme",

View file

@ -39,6 +39,7 @@ import "./tests/operations/Comment";
import "./tests/operations/Compress";
import "./tests/operations/ConditionalJump";
import "./tests/operations/Crypt";
import "./tests/operations/CSV";
import "./tests/operations/DateTime";
import "./tests/operations/ExtractEmailAddresses";
import "./tests/operations/Fork";
@ -49,6 +50,8 @@ import "./tests/operations/HaversineDistance";
import "./tests/operations/Hexdump";
import "./tests/operations/Image";
import "./tests/operations/Jump";
import "./tests/operations/JSONBeautify";
import "./tests/operations/JSONMinify";
import "./tests/operations/JWTDecode";
import "./tests/operations/JWTSign";
import "./tests/operations/JWTVerify";
@ -129,12 +132,12 @@ function handleTestResult(testResult) {
/**
* Fail if the process takes longer than 10 seconds.
* Fail if the process takes longer than 60 seconds.
*/
setTimeout(function() {
console.log("Tests took longer than 10 seconds to run, returning.");
console.log("Tests took longer than 60 seconds to run, returning.");
process.exit(1);
}, 10 * 1000);
}, 60 * 1000);
const start = new Date();

View file

@ -658,7 +658,7 @@ WWFkYSBZYWRh\r
assert.strictEqual(
chef.JSONBeautify("{\"key\" : \"value\"}").toString(),
`{
\\t"key": "value"
"key": "value"
}`);
}),

View file

@ -0,0 +1,179 @@
/**
* CSV tests.
*
* @author n1474335 [n1474335@gmail.com]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
const EXAMPLE_CSV = `A,B,C,D,E,F\r
1,2,3,4,5,6\r
",",;,',"""",,\r
"""hello""","a""1","multi\r
line",,,end\r
`;
TestRegister.addTests([
{
name: "CSV to JSON: Array of dictionaries",
input: EXAMPLE_CSV,
expectedOutput: JSON.stringify([
{
"A": "1",
"B": "2",
"C": "3",
"D": "4",
"E": "5",
"F": "6"
},
{
"A": ",",
"B": ";",
"C": "'",
"D": "\"",
"E": "",
"F": ""
},
{
"A": "\"hello\"",
"B": "a\"1",
"C": "multi\r\nline",
"D": "",
"E": "",
"F": "end"
}
], null, 4),
recipeConfig: [
{
op: "CSV to JSON",
args: [",", "\r\n", "Array of dictionaries"],
}
],
},
{
name: "CSV to JSON: Array of arrays",
input: EXAMPLE_CSV,
expectedOutput: JSON.stringify([
[
"A",
"B",
"C",
"D",
"E",
"F"
],
[
"1",
"2",
"3",
"4",
"5",
"6"
],
[
",",
";",
"'",
"\"",
"",
""
],
[
"\"hello\"",
"a\"1",
"multi\r\nline",
"",
"",
"end"
]
], null, 4),
recipeConfig: [
{
op: "CSV to JSON",
args: [",", "\r\n", "Array of arrays"],
}
],
},
{
name: "JSON to CSV: Array of dictionaries",
input: JSON.stringify([
{
"A": "1",
"B": "2",
"C": "3",
"D": "4",
"E": "5",
"F": "6"
},
{
"A": ",",
"B": ";",
"C": "'",
"D": "\"",
"E": "",
"F": ""
},
{
"A": "\"hello\"",
"B": "a\"1",
"C": "multi\r\nline",
"D": "",
"E": "",
"F": "end"
}
]),
expectedOutput: EXAMPLE_CSV,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\r\n"],
}
],
},
{
name: "JSON to CSV: Array of arrays",
input: JSON.stringify([
[
"A",
"B",
"C",
"D",
"E",
"F"
],
[
"1",
"2",
"3",
"4",
"5",
"6"
],
[
",",
";",
"'",
"\"",
"",
""
],
[
"\"hello\"",
"a\"1",
"multi\r\nline",
"",
"",
"end"
]
]),
expectedOutput: EXAMPLE_CSV,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\r\n"],
}
],
},
]);

View file

@ -220,6 +220,39 @@ TestRegister.addTests([
}
],
},
{
name: "Citrix CTX1 Encode",
input: "Password1",
expectedOutput: "PFFAJEDBOHECJEDBODEGIMCJPOFLJKDPKLAO",
recipeConfig: [
{
"op": "Citrix CTX1 Encode",
"args": []
}
],
},
{
name: "Citrix CTX1 Decode: normal",
input: "PFFAJEDBOHECJEDBODEGIMCJPOFLJKDPKLAO",
expectedOutput: "Password1",
recipeConfig: [
{
"op": "Citrix CTX1 Decode",
"args": []
}
],
},
{
name: "Citrix CTX1 Decode: invalid length",
input: "PFFAJEDBOHECJEDBODEGIMCJPOFLJKDPKLA",
expectedOutput: "Incorrect hash length",
recipeConfig: [
{
"op": "Citrix CTX1 Decode",
"args": []
}
],
},
{
name: "Vigenère Encode: no input",
input: "",

View file

@ -0,0 +1,122 @@
/**
* JSONBeautify tests.
*
* @author Phillip Nordwall [Phillip.Nordwall@gmail.com]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "JSON Beautify: space, ''",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "JSON Beautify",
args: [" ", false],
},
],
},
{
name: "JSON Beautify: space, number",
input: "42",
expectedOutput: "42",
recipeConfig: [
{
op: "JSON Beautify",
args: [" ", false],
},
],
},
{
name: "JSON Beautify: space, string",
input: "\"string\"",
expectedOutput: "\"string\"",
recipeConfig: [
{
op: "JSON Beautify",
args: [" ", false],
},
],
},
{
name: "JSON Beautify: space, boolean",
input: "false",
expectedOutput: "false",
recipeConfig: [
{
op: "JSON Beautify",
args: [" ", false],
},
],
},
{
name: "JSON Beautify: space, emptyList",
input: "[]",
expectedOutput: "[]",
recipeConfig: [
{
op: "JSON Beautify",
args: [" ", false],
},
],
},
{
name: "JSON Beautify: space, list",
input: "[2,1]",
expectedOutput: "[\n 2,\n 1\n]",
recipeConfig: [
{
op: "JSON Beautify",
args: [" ", false],
},
],
},
{
name: "JSON Beautify: tab, list",
input: "[2,1]",
expectedOutput: "[\n\t2,\n\t1\n]",
recipeConfig: [
{
op: "JSON Beautify",
args: ["\t", false],
},
],
},
{
name: "JSON Beautify: space, object",
input: "{\"second\":2,\"first\":3}",
expectedOutput: "{\n \"second\": 2,\n \"first\": 3\n}",
recipeConfig: [
{
op: "JSON Beautify",
args: [" ", false],
},
],
},
{
name: "JSON Beautify: tab, nested",
input: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]",
expectedOutput: "[\n\t2,\n\t{\n\t\t\"second\": 2,\n\t\t\"first\": 3,\n\t\t\"beginning\": {\n\t\t\t\"j\": \"3\",\n\t\t\t\"i\": [\n\t\t\t\t2,\n\t\t\t\t3,\n\t\t\t\tfalse\n\t\t\t]\n\t\t}\n\t},\n\t1,\n\t2,\n\t3\n]",
recipeConfig: [
{
op: "JSON Beautify",
args: ["\t", false],
},
],
},
{
name: "JSON Beautify: tab, nested, sorted",
input: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]",
expectedOutput: "[\n\t2,\n\t{\n\t\t\"beginning\": {\n\t\t\t\"i\": [\n\t\t\t\t2,\n\t\t\t\t3,\n\t\t\t\tfalse\n\t\t\t],\n\t\t\t\"j\": \"3\"\n\t\t},\n\t\t\"first\": 3,\n\t\t\"second\": 2\n\t},\n\t1,\n\t2,\n\t3\n]",
recipeConfig: [
{
op: "JSON Beautify",
args: ["\t", true],
},
],
},
]);

View file

@ -0,0 +1,111 @@
/**
* JSONMinify tests.
*
* @author Phillip Nordwall [Phillip.Nordwall@gmail.com]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "JSON Minify: ''",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
{
name: "JSON Minify: number",
input: "42",
expectedOutput: "42",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
{
name: "JSON Minify: number",
input: "4.2",
expectedOutput: "4.2",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
{
name: "JSON Minify: string",
input: "\"string\"",
expectedOutput: "\"string\"",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
{
name: "JSON Minify: boolean",
input: "false",
expectedOutput: "false",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
{
name: "JSON Minify: emptyList",
input: "[\n \n \t]",
expectedOutput: "[]",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
{
name: "JSON Minify: list",
input: "[2,\n \t1]",
expectedOutput: "[2,1]",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
{
name: "JSON Minify: object",
input: "{\n \"second\": 2,\n \"first\": 3\n}",
expectedOutput: "{\"second\":2,\"first\":3}",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
{
name: "JSON Minify: tab, nested",
input: "[\n\t2,\n\t{\n\t\t\"second\": 2,\n\t\t\"first\": 3,\n\t\t\"beginning\": {\n\t\t\t\"j\": \"3\",\n\t\t\t\"i\": [\n\t\t\t\t2,\n\t\t\t\t3,\n\t\t\t\tfalse\n\t\t\t]\n\t\t}\n\t},\n\t1,\n\t2,\n\t3\n]",
expectedOutput: "[2,{\"second\":2,\"first\":3,\"beginning\":{\"j\":\"3\",\"i\":[2,3,false]}},1,2,3]",
recipeConfig: [
{
op: "JSON Minify",
args: [],
},
],
},
]);

View file

@ -1,5 +1,6 @@
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const path = require("path");
/**
* Webpack configuration details for use with Grunt.
@ -58,13 +59,13 @@ module.exports = {
{
test: /\.m?js$/,
exclude: /node_modules\/(?!jsesc|crypto-api)/,
options: {
configFile: path.resolve(__dirname, "babel.config.js"),
cacheDirectory: true,
compact: false
},
type: "javascript/auto",
loader: "babel-loader?compact=false",
query: {
presets: [
require.resolve("@babel/preset-env"),
]
}
loader: "babel-loader"
},
{
test: /forge.min.js$/,