Merge branch 'master' of https://github.com/gchq/CyberChef into feature/advanced-entropy

This commit is contained in:
mshwed 2019-06-27 13:15:19 -04:00
commit b99af58636
21 changed files with 1908 additions and 2278 deletions

View File

@ -1,14 +1 @@
<!-- Prefix the title above with one of the following: --> <!-- Prefix the title above with 'Misc:' -->
<!-- Bug report: -->
<!-- Operation request: -->
<!-- Feature request: -->
<!-- Misc: -->
### Summary
### Example
<!-- If describing a bug, tell us what happens instead of the expected behavior -->
<!-- Include a link that triggers the bug if possible -->
<!-- If you are requesting a new operation, include example input and output -->

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
title: 'Bug report: <Insert title here>'
labels: bug
assignees: ''
---
<!-- Prefix the title above with 'Bug report:' -->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior or a link to the recipe / input used to cause the bug:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (if relevant, please complete the following information):**
- OS: [e.g. Windows]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for the project
title: 'Feature request: <Insert title here>'
labels: feature
assignees: ''
---
<!-- Prefix the title above with 'Feature request:' -->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,16 @@
---
name: Operation request
about: Suggest a new operation
title: 'Operation request: <Insert title here>'
labels: operation
assignees: ''
---
<!-- Prefix the title above with 'Operation request:' -->
## Summary
### Example Input
### Example Output

View File

@ -2,6 +2,12 @@
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). 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.33.0] - 2019-06-27
- 'Bzip2 Compress' operation added and 'Bzip2 Decompress' operation greatly improved [@artemisbot] | [#531]
### [8.32.0] - 2019-06-27
- 'Indec of Coincidence' operation added [@Ge0rg3] | [#571]
### [8.31.0] - 2019-04-12 ### [8.31.0] - 2019-04-12
- The downloadable version of CyberChef is now a .zip file containing separate modules rather than a single .htm file. It is still completely standalone and will not make any external network requests. This change reduces the complexity of the build process significantly. [@n1474335] - The downloadable version of CyberChef is now a .zip file containing separate modules rather than a single .htm file. It is still completely standalone and will not make any external network requests. This change reduces the complexity of the build process significantly. [@n1474335]
@ -130,6 +136,8 @@ All major and minor version changes will be documented in this file. Details of
[8.33.0]: https://github.com/gchq/CyberChef/releases/tag/v8.33.0
[8.32.0]: https://github.com/gchq/CyberChef/releases/tag/v8.32.0
[8.31.0]: https://github.com/gchq/CyberChef/releases/tag/v8.31.0 [8.31.0]: https://github.com/gchq/CyberChef/releases/tag/v8.31.0
[8.30.0]: https://github.com/gchq/CyberChef/releases/tag/v8.30.0 [8.30.0]: https://github.com/gchq/CyberChef/releases/tag/v8.30.0
[8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0 [8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0
@ -189,6 +197,7 @@ All major and minor version changes will be documented in this file. Details of
[@Cynser]: https://github.com/Cynser [@Cynser]: https://github.com/Cynser
[@anthony-arnold]: https://github.com/anthony-arnold [@anthony-arnold]: https://github.com/anthony-arnold
[@masq]: https://github.com/masq [@masq]: https://github.com/masq
[@Ge0rg3]: https://github.com/Ge0rg3
[#95]: https://github.com/gchq/CyberChef/pull/299 [#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173 [#173]: https://github.com/gchq/CyberChef/pull/173
@ -231,4 +240,6 @@ All major and minor version changes will be documented in this file. Details of
[#506]: https://github.com/gchq/CyberChef/pull/506 [#506]: https://github.com/gchq/CyberChef/pull/506
[#516]: https://github.com/gchq/CyberChef/pull/516 [#516]: https://github.com/gchq/CyberChef/pull/516
[#525]: https://github.com/gchq/CyberChef/pull/525 [#525]: https://github.com/gchq/CyberChef/pull/525
[#531]: https://github.com/gchq/CyberChef/pull/531
[#533]: https://github.com/gchq/CyberChef/pull/533 [#533]: https://github.com/gchq/CyberChef/pull/533
[#571]: https://github.com/gchq/CyberChef/pull/571

3364
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "cyberchef", "name": "cyberchef",
"version": "8.31.6", "version": "8.33.1",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>", "author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef", "homepage": "https://gchq.github.io/CyberChef",
@ -29,29 +29,35 @@
}, },
"main": "build/node/CyberChef.js", "main": "build/node/CyberChef.js",
"bugs": "https://github.com/gchq/CyberChef/issues", "bugs": "https://github.com/gchq/CyberChef/issues",
"browserslist": [
"Chrome >= 40",
"Firefox >= 35",
"Edge >= 14",
"node >= 6.5"
],
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.4", "@babel/core": "^7.4.5",
"@babel/plugin-transform-runtime": "^7.4.4", "@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.4", "@babel/preset-env": "^7.4.5",
"autoprefixer": "^9.5.1", "autoprefixer": "^9.6.0",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.2",
"babel-loader": "^8.0.5", "babel-loader": "^8.0.6",
"babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-syntax-dynamic-import": "^6.18.0",
"chromedriver": "^74.0.0", "chromedriver": "^75.0.0",
"colors": "^1.3.3", "colors": "^1.3.3",
"css-loader": "^2.1.1", "css-loader": "^3.0.0",
"eslint": "^5.16.0", "eslint": "^6.0.1",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"file-loader": "^3.0.1", "file-loader": "^4.0.0",
"grunt": "^1.0.4", "grunt": "^1.0.4",
"grunt-accessibility": "~6.0.0", "grunt-accessibility": "~6.0.0",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",
"grunt-concurrent": "^2.3.1", "grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "~2.0.0", "grunt-contrib-clean": "~2.0.0",
"grunt-contrib-connect": "^2.0.0", "grunt-contrib-connect": "^2.0.0",
"grunt-contrib-copy": "~1.0.0", "grunt-contrib-copy": "~1.0.0",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^21.0.0", "grunt-eslint": "^21.1.0",
"grunt-exec": "~3.0.0", "grunt-exec": "~3.0.0",
"grunt-jsdoc": "^2.4.0", "grunt-jsdoc": "^2.4.0",
"grunt-webpack": "^3.1.3", "grunt-webpack": "^3.1.3",
@ -60,43 +66,43 @@
"imports-loader": "^0.8.0", "imports-loader": "^0.8.0",
"ink-docstrap": "^1.3.2", "ink-docstrap": "^1.3.2",
"jsdoc-babel": "^0.5.0", "jsdoc-babel": "^0.5.0",
"mini-css-extract-plugin": "^0.6.0", "mini-css-extract-plugin": "^0.7.0",
"nightwatch": "^1.0.19", "nightwatch": "^1.1.12",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"postcss-css-variables": "^0.12.0", "postcss-css-variables": "^0.13.0",
"postcss-import": "^12.0.1", "postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"prompt": "^1.0.0", "prompt": "^1.0.0",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"sitemap": "^2.2.0", "sitemap": "^2.2.0",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"svg-url-loader": "^2.3.2", "svg-url-loader": "^2.3.3",
"url-loader": "^1.1.2", "url-loader": "^2.0.1",
"webpack": "^4.31.0", "webpack": "^4.35.0",
"webpack-bundle-analyzer": "^3.3.2", "webpack-bundle-analyzer": "^3.3.2",
"webpack-dev-server": "^3.3.1", "webpack-dev-server": "^3.7.2",
"webpack-node-externals": "^1.7.2", "webpack-node-externals": "^1.7.2",
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.4.4", "@babel/polyfill": "^7.4.4",
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.5",
"arrive": "^2.4.1", "arrive": "^2.4.1",
"babel-plugin-transform-builtin-extend": "1.1.2", "babel-plugin-transform-builtin-extend": "1.1.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bignumber.js": "^8.1.1", "bignumber.js": "^9.0.0",
"blakejs": "^1.1.0", "blakejs": "^1.1.0",
"bootstrap": "4.2.1", "bootstrap": "4.3.1",
"bootstrap-colorpicker": "^2.5.3", "bootstrap-colorpicker": "^2.5.3",
"bootstrap-material-design": "^4.1.1", "bootstrap-material-design": "^4.1.2",
"bson": "^4.0.2", "bson": "^4.0.2",
"chi-squared": "^1.1.0", "chi-squared": "^1.1.0",
"clippyjs": "0.0.3", "clippyjs": "0.0.3",
"core-js": "^3.0.1", "core-js": "^3.1.4",
"crypto-api": "^0.8.3", "crypto-api": "^0.8.3",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"ctph.js": "0.0.5", "ctph.js": "0.0.5",
"d3": "^5.9.2", "d3": "^5.9.4",
"d3-hexbin": "^0.2.2", "d3-hexbin": "^0.2.2",
"diff": "^4.0.1", "diff": "^4.0.1",
"es6-promisify": "^6.0.1", "es6-promisify": "^6.0.1",
@ -104,27 +110,28 @@
"esmangle": "^1.0.1", "esmangle": "^1.0.1",
"esprima": "^4.0.1", "esprima": "^4.0.1",
"exif-parser": "^0.1.12", "exif-parser": "^0.1.12",
"file-saver": "^2.0.1", "file-saver": "^2.0.2",
"geodesy": "^1.1.3", "geodesy": "^1.1.3",
"highlight.js": "^9.15.6", "highlight.js": "^9.15.8",
"jimp": "^0.6.4", "jimp": "^0.6.4",
"jquery": "3.4.1", "jquery": "3.4.1",
"js-crc": "^0.2.0", "js-crc": "^0.2.0",
"js-sha3": "^0.8.0", "js-sha3": "^0.8.0",
"jsesc": "^2.5.2", "jsesc": "^2.5.2",
"jsonpath": "^1.0.1", "jsonpath": "^1.0.2",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"jsqr": "^1.2.0", "jsqr": "^1.2.0",
"jsrsasign": "8.0.12", "jsrsasign": "8.0.12",
"kbpgp": "2.1.0", "kbpgp": "2.1.2",
"libbzip2-wasm": "0.0.4",
"libyara-wasm": "0.0.12", "libyara-wasm": "0.0.12",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"loglevel": "^1.6.1", "loglevel": "^1.6.3",
"loglevel-message-prefix": "^3.0.0", "loglevel-message-prefix": "^3.0.0",
"moment": "^2.24.0", "moment": "^2.24.0",
"moment-timezone": "^0.5.25", "moment-timezone": "^0.5.25",
"ngeohash": "^0.6.3", "ngeohash": "^0.6.3",
"node-forge": "^0.8.2", "node-forge": "^0.8.5",
"node-md6": "^0.1.0", "node-md6": "^0.1.0",
"nodom": "^2.2.0", "nodom": "^2.2.0",
"notepack.io": "^2.2.0", "notepack.io": "^2.2.0",
@ -132,12 +139,12 @@
"otp": "^0.1.3", "otp": "^0.1.3",
"popper.js": "^1.15.0", "popper.js": "^1.15.0",
"qr-image": "^3.2.0", "qr-image": "^3.2.0",
"scryptsy": "^2.0.0", "scryptsy": "^2.1.0",
"snackbarjs": "^1.1.0", "snackbarjs": "^1.1.0",
"sortablejs": "^1.9.0", "sortablejs": "^1.9.0",
"split.js": "^1.5.10", "split.js": "^1.5.11",
"ssdeep.js": "0.0.2", "ssdeep.js": "0.0.2",
"ua-parser-js": "^0.7.19", "ua-parser-js": "^0.7.20",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"xmldom": "^0.1.27", "xmldom": "^0.1.27",

View File

@ -1,13 +1,7 @@
module.exports = { module.exports = {
plugins: [ plugins: [
require("postcss-import"), require("postcss-import"),
require("autoprefixer")({ require("autoprefixer"),
browsers: [
"Chrome >= 40",
"Firefox >= 35",
"Edge >= 14"
]
}),
require("postcss-css-variables")({ require("postcss-css-variables")({
preserve: true preserve: true
}), }),

View File

@ -201,11 +201,18 @@ class Utils {
* Utils.parseEscapedChars("\\n"); * Utils.parseEscapedChars("\\n");
*/ */
static parseEscapedChars(str) { static parseEscapedChars(str) {
return str.replace(/(\\)?\\([bfnrtv0'"]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) { return str.replace(/(\\)?\\([bfnrtv'"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) {
if (a === "\\") return "\\"+b; if (a === "\\") return "\\"+b;
switch (b[0]) { switch (b[0]) {
case "0": case "0":
return "\0"; case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
return String.fromCharCode(parseInt(b, 8));
case "b": case "b":
return "\b"; return "\b";
case "t": case "t":

View File

@ -395,6 +395,7 @@
"ops": [ "ops": [
"Entropy", "Entropy",
"Frequency distribution", "Frequency distribution",
"Index of Coincidence",
"Chi Square", "Chi Square",
"Disassemble x86", "Disassemble x86",
"Pseudo-Random Number Generator", "Pseudo-Random Number Generator",

View File

@ -0,0 +1,72 @@
/**
* @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Bzip2 from "libbzip2-wasm";
/**
* Bzip2 Compress operation
*/
class Bzip2Compress extends Operation {
/**
* Bzip2Compress constructor
*/
constructor() {
super();
this.name = "Bzip2 Compress";
this.module = "Compression";
this.description = "Bzip2 is a compression library developed by Julian Seward (of GHC fame) that uses the Burrows-Wheeler algorithm. It only supports compressing single files and its compression is slow, however is more effective than Deflate (.gz & .zip).";
this.infoURL = "https://wikipedia.org/wiki/Bzip2";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
name: "Block size (100s of kb)",
type: "number",
value: 9,
min: 1,
max: 9
},
{
name: "Work factor",
type: "number",
value: 30
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {File}
*/
run(input, args) {
const [blockSize, workFactor] = args;
if (input.byteLength <= 0) {
throw new OperationError("Please provide an input.");
}
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Loading Bzip2...");
return new Promise((resolve, reject) => {
Bzip2().then(bzip2 => {
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Compressing data...");
const inpArray = new Uint8Array(input);
const bzip2cc = bzip2.compressBZ2(inpArray, blockSize, workFactor);
if (bzip2cc.error !== 0) {
reject(new OperationError(bzip2cc.error_msg));
} else {
const output = bzip2cc.output;
resolve(output.buffer.slice(output.byteOffset, output.byteLength + output.byteOffset));
}
});
});
}
}
export default Bzip2Compress;

View File

@ -1,12 +1,12 @@
/** /**
* @author n1474335 [n1474335@gmail.com] * @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2016 * @copyright Crown Copyright 2019
* @license Apache-2.0 * @license Apache-2.0
*/ */
import Operation from "../Operation"; import Operation from "../Operation";
import bzip2 from "../vendor/bzip2";
import OperationError from "../errors/OperationError"; import OperationError from "../errors/OperationError";
import Bzip2 from "libbzip2-wasm";
/** /**
* Bzip2 Decompress operation * Bzip2 Decompress operation
@ -23,9 +23,15 @@ class Bzip2Decompress extends Operation {
this.module = "Compression"; this.module = "Compression";
this.description = "Decompresses data using the Bzip2 algorithm."; this.description = "Decompresses data using the Bzip2 algorithm.";
this.infoURL = "https://wikipedia.org/wiki/Bzip2"; this.infoURL = "https://wikipedia.org/wiki/Bzip2";
this.inputType = "byteArray"; this.inputType = "ArrayBuffer";
this.outputType = "string"; this.outputType = "ArrayBuffer";
this.args = []; this.args = [
{
name: "Use low-memory, slower decompression algorithm",
type: "boolean",
value: false
}
];
this.patterns = [ this.patterns = [
{ {
"match": "^\\x42\\x5a\\x68", "match": "^\\x42\\x5a\\x68",
@ -41,14 +47,24 @@ class Bzip2Decompress extends Operation {
* @returns {string} * @returns {string}
*/ */
run(input, args) { run(input, args) {
const compressed = new Uint8Array(input); const [small] = args;
if (input.byteLength <= 0) {
try { throw new OperationError("Please provide an input.");
const bzip2Reader = bzip2.array(compressed);
return bzip2.simple(bzip2Reader);
} catch (err) {
throw new OperationError(err);
} }
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Loading Bzip2...");
return new Promise((resolve, reject) => {
Bzip2().then(bzip2 => {
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Decompressing data...");
const inpArray = new Uint8Array(input);
const bzip2cc = bzip2.decompressBZ2(inpArray, small ? 1 : 0);
if (bzip2cc.error !== 0) {
reject(new OperationError(bzip2cc.error_msg));
} else {
const output = bzip2cc.output;
resolve(output.buffer.slice(output.byteOffset, output.byteLength + output.byteOffset));
}
});
});
} }
} }

View File

@ -0,0 +1,107 @@
/**
* @author George O [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Index of Coincidence operation
*/
class IndexOfCoincidence extends Operation {
/**
* IndexOfCoincidence constructor
*/
constructor() {
super();
this.name = "Index of Coincidence";
this.module = "Default";
this.description = "Index of Coincidence (IC) is the probability of two randomly selected characters being the same. This can be used to determine whether text is readable or random, with English text having an IC of around 0.066. IC can therefore be a sound method to automate frequency analysis.";
this.infoURL = "https://wikipedia.org/wiki/Index_of_coincidence";
this.inputType = "string";
this.outputType = "number";
this.presentType = "html";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {number}
*/
run(input, args) {
const text = input.toLowerCase().replace(/[^a-z]/g, ""),
frequencies = new Array(26).fill(0),
alphabet = Utils.expandAlphRange("a-z");
let coincidence = 0.00,
density = 0.00,
result = 0.00,
i;
for (i=0; i < alphabet.length; i++) {
frequencies[i] = text.count(alphabet[i]);
}
for (i=0; i < frequencies.length; i++) {
coincidence += frequencies[i] * (frequencies[i] - 1);
}
density = frequencies.sum();
// Ensure that we don't divide by 0
if (density < 2) density = 2;
result = coincidence / (density * (density - 1));
return result;
}
/**
* Displays the IC as a scale bar for web apps.
*
* @param {number} ic
* @returns {html}
*/
present(ic) {
return `Index of Coincidence: ${ic}
Normalized: ${ic * 26}
<br><canvas id='chart-area'></canvas><br>
- 0 represents complete randomness (all characters are unique), whereas 1 represents no randomness (all characters are identical).
- English text generally has an IC of between 0.67 to 0.78.
- 'Random' text is determined by the probability that each letter occurs the same number of times as another.
The graph shows the IC of the input data. A low IC generally means that the text is random, compressed or encrypted.
<script type='application/javascript'>
var canvas = document.getElementById("chart-area"),
parentRect = canvas.parentNode.getBoundingClientRect(),
ic = ${ic};
canvas.width = parentRect.width * 0.95;
canvas.height = parentRect.height * 0.25;
ic = ic > 0.25 ? 0.25 : ic;
CanvasComponents.drawScaleBar(canvas, ic, 0.25, [
{
label: "English text",
min: 0.05,
max: 0.08
},
{
label: "> 0.25",
min: 0.24,
max: 0.25
}
]);
</script>
`;
}
}
export default IndexOfCoincidence;

View File

@ -51,6 +51,10 @@ class JSONToCSV extends Operation {
this.rowDelim = rowDelim; this.rowDelim = rowDelim;
const self = this; const self = this;
if (!(input instanceof Array)) {
input = [input];
}
try { try {
// If the JSON is an array of arrays, this is easy // If the JSON is an array of arrays, this is easy
if (input[0] instanceof Array) { if (input[0] instanceof Array) {
@ -89,6 +93,8 @@ class JSONToCSV extends Operation {
* @returns {string} * @returns {string}
*/ */
escapeCellContents(data) { escapeCellContents(data) {
if (typeof data === "number") data = data.toString();
// Double quotes should be doubled up // Double quotes should be doubled up
data = data.replace(/"/g, '""'); data = data.replace(/"/g, '""');

View File

@ -20,7 +20,7 @@ class UnescapeString extends Operation {
this.name = "Unescape string"; this.name = "Unescape string";
this.module = "Default"; 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.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>\\nnn</code> (Octal, where n is 0-7)</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.infoURL = "https://wikipedia.org/wiki/Escape_sequence"; this.infoURL = "https://wikipedia.org/wiki/Escape_sequence";
this.inputType = "string"; this.inputType = "string";
this.outputType = "string"; this.outputType = "string";

View File

@ -1,265 +0,0 @@
/** @license
========================================================================
bzip2.js - a small bzip2 decompression implementation
Copyright 2011 by antimatter15 (antimatter15@gmail.com)
Based on micro-bunzip by Rob Landley (rob@landley.net).
Copyright (c) 2011 by antimatter15 (antimatter15@gmail.com).
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
"use strict";
var bzip2 = {};
bzip2.array = function(bytes){
var bit = 0, byte = 0;
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ];
return function(n){
var result = 0;
while(n > 0){
var left = 8 - bit;
if(n >= left){
result <<= left;
result |= (BITMASK[left] & bytes[byte++]);
bit = 0;
n -= left;
}else{
result <<= n;
result |= ((bytes[byte] & (BITMASK[n] << (8 - n - bit))) >> (8 - n - bit));
bit += n;
n = 0;
}
}
return result
}
}
bzip2.simple = function(bits){
var size = bzip2.header(bits);
var all = '', chunk = '';
do{
all += chunk;
chunk = bzip2.decompress(bits, size);
}while(chunk != -1);
return all;
}
bzip2.header = function(bits){
if(bits(8*3) != 4348520) throw "No magic number found";
var i = bits(8) - 48;
if(i < 1 || i > 9) throw "Not a BZIP archive";
return i;
};
//takes a function for reading the block data (starting with 0x314159265359)
//a block size (0-9) (optional, defaults to 9)
//a length at which to stop decompressing and return the output
bzip2.decompress = function(bits, size, len){
var MAX_HUFCODE_BITS = 20;
var MAX_SYMBOLS = 258;
var SYMBOL_RUNA = 0;
var SYMBOL_RUNB = 1;
var GROUP_SIZE = 50;
var bufsize = 100000 * size;
for(var h = '', i = 0; i < 6; i++) h += bits(8).toString(16);
if(h == "177245385090") return -1; //last block
if(h != "314159265359") throw "Not valid bzip data";
bits(32); //ignore CRC codes
if(bits(1)) throw "Unsupported obsolete version";
var origPtr = bits(24);
if(origPtr > bufsize) throw "Initial position larger than buffer size";
var t = bits(16);
var symToByte = new Uint8Array(256),
symTotal = 0;
for (i = 0; i < 16; i++) {
if(t & (1 << (15 - i))) {
var k = bits(16);
for(j = 0; j < 16; j++){
if(k & (1 << (15 - j))){
symToByte[symTotal++] = (16 * i) + j;
}
}
}
}
var groupCount = bits(3);
if(groupCount < 2 || groupCount > 6) throw "Error 1";
var nSelectors = bits(15);
if(nSelectors == 0) throw "Error";
var mtfSymbol = []; //TODO: possibly replace JS array with typed arrays
for(var i = 0; i < groupCount; i++) mtfSymbol[i] = i;
var selectors = new Uint8Array(32768);
for(var i = 0; i < nSelectors; i++){
for(var j = 0; bits(1); j++) if(j >= groupCount) throw "Error 2";
var uc = mtfSymbol[j];
mtfSymbol.splice(j, 1); //this is a probably inefficient MTF transform
mtfSymbol.splice(0, 0, uc);
selectors[i] = uc;
}
var symCount = symTotal + 2;
var groups = [];
for(var j = 0; j < groupCount; j++){
var length = new Uint8Array(MAX_SYMBOLS),
temp = new Uint8Array(MAX_HUFCODE_BITS+1);
t = bits(5); //lengths
for(var i = 0; i < symCount; i++){
while(true){
if (t < 1 || t > MAX_HUFCODE_BITS) throw "Error 3";
if(!bits(1)) break;
if(!bits(1)) t++;
else t--;
}
length[i] = t;
}
var minLen, maxLen;
minLen = maxLen = length[0];
for(var i = 1; i < symCount; i++){
if(length[i] > maxLen) maxLen = length[i];
else if(length[i] < minLen) minLen = length[i];
}
var hufGroup;
hufGroup = groups[j] = {};
hufGroup.permute = new Uint32Array(MAX_SYMBOLS);
hufGroup.limit = new Uint32Array(MAX_HUFCODE_BITS + 1);
hufGroup.base = new Uint32Array(MAX_HUFCODE_BITS + 1);
hufGroup.minLen = minLen;
hufGroup.maxLen = maxLen;
var base = hufGroup.base.subarray(1);
var limit = hufGroup.limit.subarray(1);
var pp = 0;
for(var i = minLen; i <= maxLen; i++)
for(var t = 0; t < symCount; t++)
if(length[t] == i) hufGroup.permute[pp++] = t;
for(i = minLen; i <= maxLen; i++) temp[i] = limit[i] = 0;
for(i = 0; i < symCount; i++) temp[length[i]]++;
pp = t = 0;
for(i = minLen; i < maxLen; i++) {
pp += temp[i];
limit[i] = pp - 1;
pp <<= 1;
base[i+1] = pp - (t += temp[i]);
}
limit[maxLen]=pp+temp[maxLen]-1;
base[minLen]=0;
}
var byteCount = new Uint32Array(256);
for(var i = 0; i < 256; i++) mtfSymbol[i] = i;
var runPos, count, symCount, selector;
runPos = count = symCount = selector = 0;
var buf = new Uint32Array(bufsize);
while(true){
if(!(symCount--)){
symCount = GROUP_SIZE - 1;
if(selector >= nSelectors) throw "Error 4";
hufGroup = groups[selectors[selector++]];
base = hufGroup.base.subarray(1);
limit = hufGroup.limit.subarray(1);
}
i = hufGroup.minLen;
j = bits(i);
while(true){
if(i > hufGroup.maxLen) throw "Error 5";
if(j <= limit[i]) break;
i++;
j = (j << 1) | bits(1);
}
j -= base[i];
if(j < 0 || j >= MAX_SYMBOLS) throw "Error 6";
var nextSym = hufGroup.permute[j];
if (nextSym == SYMBOL_RUNA || nextSym == SYMBOL_RUNB) {
if(!runPos){
runPos = 1;
t = 0;
}
if(nextSym == SYMBOL_RUNA) t += runPos;
else t += 2 * runPos;
runPos <<= 1;
continue;
}
if(runPos){
runPos = 0;
if(count + t >= bufsize) throw "Error 7";
uc = symToByte[mtfSymbol[0]];
byteCount[uc] += t;
while(t--) buf[count++] = uc;
}
if(nextSym > symTotal) break;
if(count >= bufsize) throw "Error 8";
i = nextSym -1;
uc = mtfSymbol[i];
mtfSymbol.splice(i, 1);
mtfSymbol.splice(0, 0, uc);
uc = symToByte[uc];
byteCount[uc]++;
buf[count++] = uc;
}
if(origPtr < 0 || origPtr >= count) throw "Error 9";
var j = 0;
for(var i = 0; i < 256; i++){
k = j + byteCount[i];
byteCount[i] = j;
j = k;
}
for(var i = 0; i < count; i++){
uc = buf[i] & 0xff;
buf[byteCount[uc]] |= (i << 8);
byteCount[uc]++;
}
var pos = 0, current = 0, run = 0;
if(count) {
pos = buf[origPtr];
current = (pos & 0xff);
pos >>= 8;
run = -1;
}
count = count;
var output = '';
var copies, previous, outbyte;
if(!len) len = Infinity;
while(count){
count--;
previous = current;
pos = buf[pos];
current = pos & 0xff;
pos >>= 8;
if(run++ == 3){
copies = current;
outbyte = previous;
current = -1;
}else{
copies = 1;
outbyte = current;
}
while(copies--){
output += (String.fromCharCode(outbyte));
if(!--len) return output;
}
if(current != previous) run = 0;
}
return output;
}
export default bzip2;

View File

@ -124,16 +124,21 @@ class RecipeWaiter {
* @param {event} evt * @param {event} evt
*/ */
opSortEnd(evt) { opSortEnd(evt) {
if (this.removeIntent) { if (this.removeIntent && evt.item.parentNode.id === "rec-list") {
if (evt.item.parentNode.id === "rec-list") { evt.item.remove();
evt.item.remove();
}
return; return;
} }
// Reinitialise the popover on the original element in the ops list because for some reason it // Reinitialise the popover on the original element in the ops list because for some reason it
// gets destroyed and recreated. // gets destroyed and recreated. If the clone isn't in the ops list, we use the original item instead.
this.manager.ops.enableOpsListPopovers(evt.clone); let enableOpsElement;
if (evt.clone.parentNode && evt.clone.parentNode.classList.contains("op-list")) {
enableOpsElement = evt.clone;
} else {
enableOpsElement = evt.item;
$(evt.item).attr("data-toggle", "popover");
}
this.manager.ops.enableOpsListPopovers(enableOpsElement);
if (evt.item.parentNode.id !== "rec-list") { if (evt.item.parentNode.id !== "rec-list") {
return; return;

View File

@ -86,7 +86,7 @@ div.toggle-string {
} }
.operation .form-control { .operation .form-control {
padding: 20px 12px 6px 12px; padding: 20px 12px 6px 12px !important;
border-top-left-radius: 4px; border-top-left-radius: 4px;
border-top-right-radius: 4px; border-top-right-radius: 4px;
background-image: none; background-image: none;

View File

@ -49,9 +49,11 @@ import "./tests/Hash";
import "./tests/HaversineDistance"; import "./tests/HaversineDistance";
import "./tests/Hexdump"; import "./tests/Hexdump";
import "./tests/Image"; import "./tests/Image";
import "./tests/IndexOfCoincidence";
import "./tests/Jump"; import "./tests/Jump";
import "./tests/JSONBeautify"; import "./tests/JSONBeautify";
import "./tests/JSONMinify"; import "./tests/JSONMinify";
import "./tests/JSONtoCSV";
import "./tests/JWTDecode"; import "./tests/JWTDecode";
import "./tests/JWTSign"; import "./tests/JWTSign";
import "./tests/JWTVerify"; import "./tests/JWTVerify";

View File

@ -0,0 +1,22 @@
/**
* Index of Coincidence tests.
*
* @author George O [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import TestRegister from "../TestRegister";
TestRegister.addTests([
{
name: "Index of Coincidence",
input: "Hello world, this is a test to determine the correct IC value.",
expectedMatch: /^Index of Coincidence: 0\.07142857142857142\nNormalized: 1\.857142857142857/,
recipeConfig: [
{
"op": "Index of Coincidence",
"args": []
},
],
},
]);

View File

@ -0,0 +1,93 @@
/**
* JSON to CSV tests.
*
* @author mshwed [m@ttshwed.com]
*
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import TestRegister from "../TestRegister";
const EXPECTED_CSV_SINGLE = "a,b,c\r\n1,2,3\r\n";
const EXPECTED_CSV_MULTIPLE = "a,b,c\r\n1,2,3\r\n1,2,3\r\n";
const EXPECTED_CSV_EMPTY = "\r\n\r\n";
TestRegister.addTests([
{
name: "JSON to CSV: strings as values",
input: JSON.stringify({a: "1", b: "2", c: "3"}),
expectedOutput: EXPECTED_CSV_SINGLE,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: numbers as values",
input: JSON.stringify({a: 1, b: 2, c: 3}),
expectedOutput: EXPECTED_CSV_SINGLE,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: numbers and strings as values",
input: JSON.stringify({a: 1, b: "2", c: 3}),
expectedOutput: EXPECTED_CSV_SINGLE,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: JSON as an array",
input: JSON.stringify([{a: 1, b: "2", c: 3}]),
expectedOutput: EXPECTED_CSV_SINGLE,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: multiple JSON values in an array",
input: JSON.stringify([{a: 1, b: "2", c: 3}, {a: 1, b: "2", c: 3}]),
expectedOutput: EXPECTED_CSV_MULTIPLE,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: empty JSON",
input: JSON.stringify({}),
expectedOutput: EXPECTED_CSV_EMPTY,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
},
{
name: "JSON to CSV: empty JSON in array",
input: JSON.stringify([{}]),
expectedOutput: EXPECTED_CSV_EMPTY,
recipeConfig: [
{
op: "JSON to CSV",
args: [",", "\\r\\n"]
},
],
}
]);