merge 8.0.1 release -> node-lib

This commit is contained in:
d98762625 2018-08-11 22:15:09 +01:00
commit 7c1ac4392e
63 changed files with 4608 additions and 4331 deletions

36
CHANGELOG.md Normal file
View File

@ -0,0 +1,36 @@
# Changelog
All notable changes to this project will be documented in this file.
## [8.0.0] - 2018-08-05
- Codebase rewritten using [ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) and [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) #284
- Operation architecture restructured to make adding new operations a lot simpler #284
- A script has been added to aid in the creation of new operations by running `npm run newop` @n1474335 #284
- 'Magic' operation added - [automated detection of encoded data](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) @n1474335 #239
- UI updated to use [Bootstrap Material Design](https://fezvrasta.github.io/bootstrap-material-design/) @n1474335 #248
- `JSON`, `File` and `List<File>` Dish types added @n1474335 #284
- `OperationError` type added for better handling of errors thrown by operations @d98762625 #296
- A `present()` method has been added, allowing operations to pass machine-friendly data to subsequent operations whilst presenting human-friendly data to the user @n1474335 #284
- Set operations added @d98762625 #281
- 'To Table' operation added @JustAnotherMark #294
- 'Haversine distance' operation added @Dachande663 #325
- Started keeping a changelog @n1474335
## [7.0.0] - 2017-12-28
- Added support for loading, processing and downloading files up to 500MB @n1474335 #224
## [6.0.0] - 2017-09-19
- Added threading support, moving all recipe processing into a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to increase performance and allow long-running operations to be cancelled @n1474335 #173
- Created modules 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
- Configured Webpack build process, Babel transpilation and ES6 imports and exports @n1474335 #95
## [4.0.0] - 2016-11-28
- Initial open source commit @n1474335
[8.0.0]: https://github.com/gchq/CyberChef/releases/tag/v8.0.0
[7.0.0]: https://github.com/gchq/CyberChef/releases/tag/v7.0.0
[6.0.0]: https://github.com/gchq/CyberChef/releases/tag/v6.0.0
[5.0.0]: https://github.com/gchq/CyberChef/releases/tag/v5.0.0
[4.0.0]: https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306

View File

@ -12,7 +12,7 @@
CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more.
The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years. Every effort has been made to structure the code in a readable and extendable format, however it should be noted that the analyst is not a professional developer.
The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years.
## Live demo
@ -43,16 +43,19 @@ You can use as many operations as you like in simple or complex ways. Some examp
- [Carry out different operations on data of different types][8]
- [Use parts of the input as arguments to operations][9]
- [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10]
- [Automagically detect several layers of nested encoding][12]
## Features
- Drag and drop
- Operations can be dragged in and out of the recipe list, or reorganised.
- Files can be dragged over the input box to load them directly into the browser.
- Files up to 500MB can be dragged over the input box to load them directly into the browser.
- Auto Bake
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
- Automated encoding detection
- CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation which can make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data.
- Breakpoints
- You can set breakpoints on any operation in your recipe to pause execution before running it.
- You can also step through the recipe one operation at a time to see what the data looks like at each stage.
@ -81,6 +84,8 @@ CyberChef is built to support
## Contributing
Contributing a new operation to CyberChef is super easy! There is a quickstart script which will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki).
- Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0)
@ -104,3 +109,4 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice
[9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ
[10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
[11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4
[12]: https://gchq.github.io/CyberChef/#recipe=Magic(3,false,false)&input=V1VhZ3dzaWFlNm1QOGdOdENDTFVGcENwQ0IyNlJtQkRvREQ4UGFjZEFtekF6QlZqa0syUXN0RlhhS2hwQzZpVVM3UkhxWHJKdEZpc29SU2dvSjR3aGptMWFybTg2NHFhTnE0UmNmVW1MSHJjc0FhWmM1VFhDWWlmTmRnUzgzZ0RlZWpHWDQ2Z2FpTXl1QlY2RXNrSHQxc2NnSjg4eDJ0TlNvdFFEd2JHWTFtbUNvYjJBUkdGdkNLWU5xaU45aXBNcTFaVTFtZ2tkYk51R2NiNzZhUnRZV2hDR1VjOGc5M1VKdWRoYjhodHNoZVpud1RwZ3FoeDgzU1ZKU1pYTVhVakpUMnptcEM3dVhXdHVtcW9rYmRTaTg4WXRrV0RBYzFUb291aDJvSDRENGRkbU5LSldVRHBNd21uZ1VtSzE0eHdtb21jY1BRRTloTTE3MkFQblNxd3hkS1ExNzJSa2NBc3lzbm1qNWdHdFJtVk5OaDJzMzU5d3I2bVMyUVJQ

6415
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "7.11.1",
"version": "8.0.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",
@ -31,12 +31,14 @@
"module": "src/node/index.mjs",
"bugs": "https://github.com/gchq/CyberChef/issues",
"devDependencies": {
"autoprefixer": "^9.1.0",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1",
"colors": "^1.3.0",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0",
"bootstrap": "^4.1.3",
"colors": "^1.3.1",
"css-loader": "^1.0.0",
"eslint": "^5.3.0",
"exports-loader": "^0.7.0",
"extract-text-webpack-plugin": "^4.0.0-alpha0",
"file-loader": "^1.1.11",
@ -46,49 +48,49 @@
"grunt-concurrent": "^2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-watch": "^1.0.1",
"grunt-eslint": "^20.1.0",
"grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^21.0.0",
"grunt-exec": "~3.0.0",
"grunt-jsdoc": "^2.2.1",
"grunt-webpack": "^3.1.1",
"grunt-webpack": "^3.1.2",
"html-webpack-plugin": "^3.2.0",
"imports-loader": "^0.8.0",
"ink-docstrap": "^1.3.2",
"js-to-mjs": "^0.2.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.4",
"node-sass": "^4.9.2",
"postcss-css-variables": "^0.9.0",
"postcss-import": "^12.0.0",
"postcss-loader": "^2.1.6",
"prompt": "^1.0.0",
"sass-loader": "^7.1.0",
"sitemap": "^1.13.0",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"web-resource-inliner": "^4.2.1",
"webpack": "^4.6.0",
"webpack-dev-server": "^3.1.3",
"webpack": "^4.16.4",
"webpack-dev-server": "^3.1.5",
"webpack-node-externals": "^1.7.2",
"worker-loader": "^1.1.1"
"worker-loader": "^2.0.0"
},
"dependencies": {
"arrive": "^2.4.1",
"babel-plugin-transform-builtin-extend": "1.1.2",
"babel-polyfill": "^6.26.0",
"bcryptjs": "^2.4.3",
"bignumber.js": "^7.0.1",
"bootstrap": "^3.3.7",
"bootstrap-colorpicker": "^2.5.2",
"bootstrap-switch": "^3.3.4",
"bson": "^2.0.6",
"bignumber.js": "^7.2.1",
"bootstrap-colorpicker": "^2.5.3",
"bootstrap-material-design": "^4.1.1",
"bson": "^3.0.2",
"chi-squared": "^1.1.0",
"crypto-api": "^0.8.0",
"crypto-js": "^3.1.9-1",
"ctph.js": "0.0.5",
"diff": "^3.5.0",
"es6-promisify": "^6.0.0",
"escodegen": "^1.9.1",
"escodegen": "^1.11.0",
"esmangle": "^1.0.1",
"esprima": "^4.0.0",
"esprima": "^4.0.1",
"exif-parser": "^0.1.12",
"file-saver": "^1.3.8",
"highlight.js": "^9.12.0",
@ -103,22 +105,24 @@
"lodash": "^4.17.10",
"loglevel": "^1.6.1",
"loglevel-message-prefix": "^3.0.0",
"moment": "^2.22.1",
"moment-timezone": "^0.5.16",
"moment": "^2.22.2",
"moment-timezone": "^0.5.21",
"node-forge": "^0.7.5",
"node-md6": "^0.1.0",
"nwmatcher": "^1.4.4",
"otp": "^0.1.3",
"popper.js": "^1.14.4",
"scryptsy": "^2.0.0",
"snackbarjs": "^1.1.0",
"sortablejs": "^1.7.0",
"split.js": "^1.3.5",
"ssdeep.js": "0.0.2",
"ua-parser-js": "^0.7.17",
"ua-parser-js": "^0.7.18",
"utf8": "^3.0.0",
"vkbeautify": "^0.99.3",
"xmldom": "^0.1.27",
"xpath": "0.0.27",
"xregexp": "^4.1.1",
"xregexp": "^4.2.0",
"zlibjs": "^0.3.1"
},
"scripts": {

View File

@ -96,7 +96,7 @@ class Chef {
const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
// Create a raw version of the dish, unpresented
const rawDish = new Dish(this.dish);
const rawDish = this.dish.clone();
// Present the raw result
await recipe.present(this.dish);

View File

@ -301,6 +301,69 @@ class Dish {
}
}
/**
* Returns a deep clone of the current Dish.
*
* @returns {Dish}
*/
clone() {
const newDish = new Dish();
switch (this.type) {
case Dish.STRING:
case Dish.HTML:
case Dish.NUMBER:
case Dish.BIG_NUMBER:
// These data types are immutable so it is acceptable to copy them by reference
newDish.set(
this.value,
this.type
);
break;
case Dish.BYTE_ARRAY:
case Dish.JSON:
// These data types are mutable so they need to be copied by value
newDish.set(
JSON.parse(JSON.stringify(this.value)),
this.type
);
break;
case Dish.ARRAY_BUFFER:
// Slicing an ArrayBuffer returns a new ArrayBuffer with a copy its contents
newDish.set(
this.value.slice(0),
this.type
);
break;
case Dish.FILE:
// A new file can be created by copying over all the values from the original
newDish.set(
new File([this.value], this.value.name, {
"type": this.value.type,
"lastModified": this.value.lastModified
}),
this.type
);
break;
case Dish.LIST_FILE:
newDish.set(
this.value.map(f =>
new File([f], f.name, {
"type": f.type,
"lastModified": f.lastModified
})
),
this.type
);
break;
default:
throw new Error("Cannot clone Dish, unknown type");
}
return newDish;
}
}

View File

@ -21,7 +21,10 @@ class Ingredient {
this.name = "";
this.type = "";
this._value = null;
this.disabled = false;
this.hint = "";
this.toggleValues = [];
this.target = null;
if (ingredientConfig) {
this._parseConfig(ingredientConfig);
@ -39,7 +42,10 @@ class Ingredient {
this.name = ingredientConfig.name;
this.type = ingredientConfig.type;
this.defaultValue = ingredientConfig.value;
this.disabled = !!ingredientConfig.disabled;
this.hint = ingredientConfig.hint || false;
this.toggleValues = ingredientConfig.toggleValues;
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
}

View File

@ -170,12 +170,17 @@ class Operation {
*/
get args() {
return this._ingList.map(ing => {
return {
const conf = {
name: ing.name,
type: ing.type,
value: ing.defaultValue,
toggleValues: ing.toggleValues || []
value: ing.defaultValue
};
if (ing.toggleValues) conf.toggleValues = ing.toggleValues;
if (ing.hint) conf.hint = ing.hint;
if (ing.disabled) conf.disabled = ing.disabled;
if (ing.target) conf.target = ing.target;
return conf;
});
}

View File

@ -828,11 +828,11 @@ class Utils {
*/
static async displayFilesAsHTML(files) {
const formatDirectory = function(file) {
const html = `<div class='panel panel-default' style='white-space: normal;'>
<div class='panel-heading' role='tab'>
<h4 class='panel-title'>
const html = `<div class='card' style='white-space: normal;'>
<div class='card-header'>
<h6 class="mb-0">
${Utils.escapeHtml(file.name)}
</h4>
</h6>
</div>
</div>`;
return html;
@ -845,29 +845,29 @@ class Utils {
{type: "octet/stream"}
);
const html = `<div class='panel panel-default' style='white-space: normal;'>
<div class='panel-heading' role='tab' id='heading${i}'>
<h4 class='panel-title'>
<div>
<a href='#collapse${i}'
class='collapsed'
data-toggle='collapse'
aria-expanded='true'
aria-controls='collapse${i}'
title="Show/hide contents of '${Utils.escapeHtml(file.name)}'">${Utils.escapeHtml(file.name)}</a>
<a href='${URL.createObjectURL(blob)}'
title='Download ${Utils.escapeHtml(file.name)}'
download='${Utils.escapeHtml(file.name)}'>&#x1f4be;</a>
<span class='pull-right'>
${file.size.toLocaleString()} bytes
</span>
</div>
</h4>
const html = `<div class='card' style='white-space: normal;'>
<div class='card-header' id='heading${i}'>
<h6 class='mb-0'>
<a class='collapsed'
data-toggle='collapse'
href='#collapse${i}'
aria-expanded='false'
aria-controls='collapse${i}'
title="Show/hide contents of '${Utils.escapeHtml(file.name)}'">
${Utils.escapeHtml(file.name)}</a>
<span class='float-right' style="margin-top: -3px">
${file.size.toLocaleString()} bytes
<a title="Download ${Utils.escapeHtml(file.name)}"
href='${URL.createObjectURL(blob)}'
download='${Utils.escapeHtml(file.name)}'>
<i class="material-icons" style="vertical-align: bottom">save</i>
</a>
</span>
</h6>
</div>
<div id='collapse${i}' class='panel-collapse collapse'
role='tabpanel' aria-labelledby='heading${i}'>
<div class='panel-body'>
<pre><code>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</code></pre>
<div id='collapse${i}' class='collapse' aria-labelledby='heading${i}' data-parent="#files">
<div class='card-body'>
<pre>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</pre>
</div>
</div>
</div>`;
@ -875,8 +875,8 @@ class Utils {
};
let html = `<div style='padding: 5px; white-space: normal;'>
${files.length} file(s) found<NL>
</div>`;
${files.length} file(s) found
</div><div id="files" style="padding: 20px">`;
for (let i = 0; i < files.length; i++) {
if (files[i].name.endsWith("/")) {
@ -886,7 +886,7 @@ class Utils {
}
}
return html;
return html += "</div>";
}

View File

@ -328,6 +328,7 @@
"Generate UUID",
"Generate TOTP",
"Generate HOTP",
"Haversine distance",
"Render Image",
"Remove EXIF",
"Extract EXIF",

View File

@ -48,7 +48,7 @@ export function drawBarChart(canvas, scores, xAxisLabel, yAxisLabel, numXLabels,
leftPadding = canvas.width * 0.08,
rightPadding = canvas.width * 0.03,
topPadding = canvas.height * 0.08,
bottomPadding = canvas.height * 0.15,
bottomPadding = canvas.height * 0.2,
graphHeight = canvas.height - topPadding - bottomPadding,
graphWidth = canvas.width - leftPadding - rightPadding,
base = topPadding + graphHeight,
@ -146,7 +146,7 @@ export function drawScaleBar(canvas, score, max, markings) {
leftPadding = canvas.width * 0.01,
rightPadding = canvas.width * 0.01,
topPadding = canvas.height * 0.1,
bottomPadding = canvas.height * 0.3,
bottomPadding = canvas.height * 0.35,
barHeight = canvas.height - topPadding - bottomPadding,
barWidth = canvas.width - leftPadding - rightPadding;

View File

@ -49,8 +49,8 @@ export const DATETIME_FORMATS = [
* MomentJS DateTime formatting examples.
*/
export const FORMAT_EXAMPLES = `Format string tokens:
<table class="table table-striped table-hover table-condensed table-bordered" style="font-family: sans-serif">
<thead>
<table class="table table-striped table-hover table-sm table-bordered" style="font-family: sans-serif">
<thead class="thead-dark">
<tr>
<th>Category</th>
<th>Token</th>

View File

@ -287,6 +287,8 @@ class Magic {
useful: useful
});
const prevOp = recipeConfig[recipeConfig.length - 1];
// Execute each of the matching operations, then recursively call the speculativeExecution()
// method on the resulting data, recording the properties of each option.
await Promise.all(matchingOps.map(async op => {
@ -294,8 +296,14 @@ class Magic {
op: op.op,
args: op.args
},
output = await this._runRecipe([opConfig]),
magic = new Magic(output, this.opPatterns),
output = await this._runRecipe([opConfig]);
// If the recipe is repeating and returning the same data, do not continue
if (prevOp && op.op === prevOp.op && _buffersEqual(output, this.inputBuffer)) {
return;
}
const magic = new Magic(output, this.opPatterns),
speculativeResults = await magic.speculativeExecution(
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful);
@ -315,13 +323,16 @@ class Magic {
}));
}
// Prune branches that do not match anything
// Prune branches that result in unhelpful outputs
results = results.filter(r =>
r.languageScores[0].probability > 0 ||
r.fileType ||
r.isUTF8 ||
r.matchingOps.length ||
r.useful);
(r.useful || r.data.length > 0) && // The operation resulted in ""
( // One of the following must be true
r.languageScores[0].probability > 0 || // Some kind of language was found
r.fileType || // A file was found
r.isUTF8 || // UTF-8 was found
r.matchingOps.length // A matching op was found
)
);
// Return a sorted list of possible recipes along with their properties
return results.sort((a, b) => {
@ -374,7 +385,7 @@ class Magic {
const recipe = new Recipe(recipeConfig);
try {
await recipe.execute(dish, 0);
await recipe.execute(dish);
return dish.get(Dish.ARRAY_BUFFER);
} catch (err) {
// If there are errors, return an empty buffer
@ -395,7 +406,10 @@ class Magic {
let i = len;
const counts = new Array(256).fill(0);
if (!len) return counts;
if (!len) {
this.freqDist = counts;
return this.freqDist;
}
while (i--) {
counts[this.inputBuffer[i]]++;

View File

@ -47,9 +47,10 @@ class Diff extends Operation {
"value": true
},
{
"name": "Ignore whitespace (relevant for word and line)",
"name": "Ignore whitespace",
"type": "boolean",
"value": false
"value": false,
"hint": "Relevant for word and line"
}
];
}

View File

@ -20,7 +20,7 @@ class Entropy extends Operation {
this.name = "Entropy";
this.module = "Default";
this.description = "Calculates the Shannon entropy of the input data which gives an idea of its randomness. 8 is the maximum.";
this.description = "Shannon Entropy, in the context of information theory, is a measure of the rate at which information is produced by a source of data. It can be used, in a broad sense, to detect whether data is likely to be structured or unstructured. 8 is the maximum, representing highly unstructured, 'random' data. English language text usually falls somewhere between 3.5 and 5. Properly encrypted or compressed data should have an entropy of over 7.5.";
this.inputType = "byteArray";
this.outputType = "number";
this.presentType = "html";

View File

@ -26,7 +26,7 @@ class FromHexdump extends Operation {
this.args = [];
this.patterns = [
{
match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?)+$",
match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?){2,}$",
flags: "i",
args: []
},

View File

@ -0,0 +1,58 @@
/**
* @author Dachande663 [dachande663@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
/**
* HaversineDistance operation
*/
class HaversineDistance extends Operation {
/**
* HaversineDistance constructor
*/
constructor() {
super();
this.name = "Haversine distance";
this.module = "Default";
this.description = "Returns the distance between two pairs of GPS latitude and longitude co-ordinates in metres.<br><br>e.g. <code>51.487263,-0.124323, 38.9517,-77.1467</code>";
this.inputType = "string";
this.outputType = "number";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {number}
*/
run(input, args) {
const values = input.match(/^(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?)$/);
if (!values) {
throw new OperationError("Input must in the format lat1, lng1, lat2, lng2");
}
const lat1 = parseFloat(values[1]);
const lng1 = parseFloat(values[3]);
const lat2 = parseFloat(values[6]);
const lng2 = parseFloat(values[8]);
const TO_RAD = Math.PI / 180;
const dLat = (lat2-lat1) * TO_RAD;
const dLng = (lng2-lng1) * TO_RAD;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * TO_RAD) * Math.cos(lat2 * TO_RAD) * Math.sin(dLng/2) * Math.sin(dLng/2);
const metres = 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return metres;
}
}
export default HaversineDistance;

View File

@ -23,7 +23,7 @@ class Magic extends Operation {
this.name = "Magic";
this.flowControl = true;
this.module = "Default";
this.description = "The Magic operation attempts to detect various properties of the input data and suggests which operations could help to make more sense of it.<br><br><b>Options</b><br><u>Depth:</u> If an operation appears to match the data, it will be run and the result will be analysed further. This argument controls the maximum number of levels of recursion.<br><br><u>Intensive mode:</u> When this is turned on, various operations like XOR, bit rotates, and character encodings are brute-forced to attempt to detect valid data underneath. To improve performance, only the first 100 bytes of the data is brute-forced.<br><br><u>Extensive language support:</u> At each stage, the relative byte frequencies of the data will be compared to average frequencies for a number of languages. The default set consists of ~40 of the most commonly used languages on the Internet. The extensive list consists of 284 languages and can result in many languages matching the data if their byte frequencies are similar.";
this.description = "The Magic operation attempts to detect various properties of the input data and suggests which operations could help to make more sense of it.<br><br><b>Options</b><br><u>Depth:</u> If an operation appears to match the data, it will be run and the result will be analysed further. This argument controls the maximum number of levels of recursion.<br><br><u>Intensive mode:</u> When this is turned on, various operations like XOR, bit rotates, and character encodings are brute-forced to attempt to detect valid data underneath. To improve performance, only the first 100 bytes of the data is brute-forced.<br><br><u>Extensive language support:</u> At each stage, the relative byte frequencies of the data will be compared to average frequencies for a number of languages. The default set consists of ~40 of the most commonly used languages on the Internet. The extensive list consists of 284 languages and can result in many languages matching the data if their byte frequencies are similar.<br><br>A technical explanation of the Magic operation can be found <a href='https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic'>here</a>.";
this.inputType = "ArrayBuffer";
this.outputType = "JSON";
this.presentType = "html";
@ -77,7 +77,7 @@ class Magic extends Operation {
const currentRecipeConfig = this.state.opList.map(op => op.config);
let output = `<table
class='table table-hover table-condensed table-bordered'
class='table table-hover table-sm table-bordered'
style='table-layout: fixed;'>
<tr>
<th>Recipe (click to load)</th>

View File

@ -113,7 +113,7 @@ CMYK: ${cmyk}
document.getElementById('input-text').value = 'rgba(' +
color.r + ', ' + color.g + ', ' + color.b + ', ' + color.a + ')';
window.app.autoBake();
});
}).children(".colorpicker").removeClass('dropdown-menu');
</script>`;
}

View File

@ -7,7 +7,6 @@
import Operation from "../Operation";
import moment from "moment-timezone";
import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime";
import OperationError from "../errors/OperationError";
/**
* Parse DateTime operation
@ -60,7 +59,7 @@ class ParseDateTime extends Operation {
date = moment.tz(input, inputFormat, inputTimezone);
if (!date || date.format() === "Invalid date") throw Error;
} catch (err) {
throw new OperationError(`Invalid format.\n\n${FORMAT_EXAMPLES}`);
return `Invalid format.\n\n${FORMAT_EXAMPLES}`;
}
output += "Date: " + date.format("dddd Do MMMM YYYY") +

View File

@ -97,7 +97,7 @@ class ParseIPv4Header extends Operation {
checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")";
}
output = `<table class='table table-hover table-condensed table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
<tr><td>Version</td><td>${version}</td></tr>
<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>
<tr><td>Differentiated Services Code Point (DSCP)</td><td>${dscp}</td></tr>

View File

@ -26,7 +26,8 @@ class RenderImage extends Operation {
this.module = "Image";
this.description = "Displays the input as an image. Supports the following formats:<br><br><ul><li>jpg/jpeg</li><li>png</li><li>gif</li><li>webp</li><li>bmp</li><li>ico</li></ul>";
this.inputType = "string";
this.outputType = "html";
this.outputType = "byteArray";
this.presentType = "html";
this.args = [
{
"name": "Input format",
@ -51,9 +52,8 @@ class RenderImage extends Operation {
*/
run(input, args) {
const inputFormat = args[0];
let dataURI = "data:";
if (!input.length) return "";
if (!input.length) return [];
// Convert input to raw bytes
switch (inputFormat) {
@ -73,6 +73,26 @@ class RenderImage extends Operation {
// Determine file type
const type = Magic.magicFileType(input);
if (!(type && type.mime.indexOf("image") === 0)) {
throw new OperationError("Invalid file type");
}
return input;
}
/**
* Displays the image using HTML for web apps.
*
* @param {byteArray} data
* @returns {html}
*/
async present(data) {
if (!data.length) return "";
let dataURI = "data:";
// Determine file type
const type = Magic.magicFileType(data);
if (type && type.mime.indexOf("image") === 0) {
dataURI += type.mime + ";";
} else {
@ -80,7 +100,7 @@ class RenderImage extends Operation {
}
// Add image data to URI
dataURI += "base64," + toBase64(input);
dataURI += "base64," + toBase64(data);
return "<img src='" + dataURI + "'>";
}

View File

@ -147,12 +147,12 @@ class ToTable extends Operation {
*/
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'>";
let output = "<table class='table table-hover table-sm table-bordered table-nonfluid'>";
// If the first row is a header then put it in <thead> with <th> cells.
if (firstRowHeader) {
const row = tableData.shift();
output += "<thead>";
output += "<thead class='thead-light'>";
output += outputRow(row, "th");
output += "</thead>";
}

View File

@ -7,7 +7,6 @@
import Operation from "../Operation";
import moment from "moment-timezone";
import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime";
import OperationError from "../errors/OperationError";
/**
* Translate DateTime Format operation
@ -68,7 +67,7 @@ class TranslateDateTimeFormat extends Operation {
date = moment.tz(input, inputFormat, inputTimezone);
if (!date || date.format() === "Invalid date") throw Error;
} catch (err) {
throw new OperationError(`Invalid format.\n\n${FORMAT_EXAMPLES}`);
return `Invalid format.\n\n${FORMAT_EXAMPLES}`;
}
return date.tz(outputTimezone).format(outputFormat);

20
src/test.mjs Normal file
View File

@ -0,0 +1,20 @@
import Dish from "./core/Dish";
const a = new Dish();
const i = "original";
a.set(i, Dish.STRING);
console.log(a);
const b = a.clone();
console.log(b);
console.log("changing a");
a.value.toUpperCase();
// const c = new Uint8Array([1,2,3,4,5,6,7,8,9,0]).buffer;
// a.set(c, Dish.ARRAY_BUFFER);
console.log(a);
console.log(b);

View File

@ -105,7 +105,7 @@ class App {
handleError(err, logToConsole) {
if (logToConsole) log.error(err);
const msg = err.displayStr || err.toString();
this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors);
this.alert(msg, this.options.errorTimeout, !this.options.showErrors);
}
@ -183,9 +183,10 @@ class App {
* Sets the user's input data.
*
* @param {string} input - The string to set the input to
* @param {boolean} [silent=false] - Suppress statechange event
*/
setInput(input) {
this.manager.input.set(input);
setInput(input, silent=false) {
this.manager.input.set(input, silent);
}
@ -240,17 +241,16 @@ class App {
initialiseSplitter() {
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
sizes: [20, 30, 50],
minSize: [240, 325, 450],
minSize: [240, 370, 450],
gutterSize: 4,
onDrag: function() {
this.manager.controls.adjustWidth();
this.manager.output.adjustWidth();
this.manager.recipe.adjustWidth();
}.bind(this)
});
this.ioSplitter = Split(["#input", "#output"], {
direction: "vertical",
gutterSize: 4,
gutterSize: 4
});
this.resetLayout();
@ -320,8 +320,8 @@ class App {
if (this.operations.hasOwnProperty(favourites[i])) {
validFavs.push(favourites[i]);
} else {
this.alert("The operation \"" + Utils.escapeHtml(favourites[i]) +
"\" is no longer available. It has been removed from your favourites.", "info");
this.alert(`The operation "${Utils.escapeHtml(favourites[i])}" is no longer available. ` +
"It has been removed from your favourites.");
}
}
return validFavs;
@ -337,7 +337,6 @@ class App {
if (!this.isLocalStorageAvailable()) {
this.alert(
"Your security settings do not allow access to local storage so your favourites cannot be saved.",
"danger",
5000
);
return false;
@ -368,7 +367,7 @@ class App {
const favourites = JSON.parse(localStorage.favourites);
if (favourites.indexOf(name) >= 0) {
this.alert("'" + name + "' is already in your favourites", "info", 2000);
this.alert(`'${name}' is already in your favourites`, 3000);
return;
}
@ -421,12 +420,12 @@ class App {
if (this.uriParams.input) {
try {
const inputData = fromBase64(this.uriParams.input);
this.setInput(inputData);
this.setInput(inputData, true);
} catch (err) {}
}
this.autoBakePause = false;
this.autoBake();
window.dispatchEvent(this.manager.statechange);
}
@ -476,9 +475,8 @@ class App {
} else if (args[j].classList.contains("toggle-string")) {
// toggleString
args[j].value = recipeConfig[i].args[j].string;
args[j].previousSibling.children[0].innerHTML =
Utils.escapeHtml(recipeConfig[i].args[j].option) +
" <span class='caret'></span>";
args[j].parentNode.parentNode.querySelector("button").innerHTML =
Utils.escapeHtml(recipeConfig[i].args[j].option);
} else {
// all others
args[j].value = recipeConfig[i].args[j];
@ -507,9 +505,7 @@ class App {
resetLayout() {
this.columnSplitter.setSizes([20, 30, 50]);
this.ioSplitter.setSizes([50, 50]);
this.manager.controls.adjustWidth();
this.manager.output.adjustWidth();
this.manager.recipe.adjustWidth();
}
@ -529,9 +525,9 @@ class App {
else if (prev[1] > 0) prev[1]--;
else prev[0]--;
const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`;
//const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`;
let compileInfo = `<a href='${compareURL}'>Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago</a>`;
let compileInfo = `<a href='https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md'>Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago</a>`;
if (window.compileMessage !== "") {
compileInfo += " - " + window.compileMessage;
@ -561,63 +557,35 @@ class App {
* Pops up a message to the user and writes it to the console log.
*
* @param {string} str - The message to display (HTML supported)
* @param {string} style - The colour of the popup
* "danger" = red
* "warning" = amber
* "info" = blue
* "success" = green
* @param {number} timeout - The number of milliseconds before the popup closes automatically
* @param {number} timeout - The number of milliseconds before the alert closes automatically
* 0 for never (until the user closes it)
* @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the
* console
*
* @example
* // Pops up a red box with the message "[current time] Error: Something has gone wrong!"
* // that will need to be dismissed by the user.
* this.alert("Error: Something has gone wrong!", "danger", 0);
* // Pops up a box with the message "Error: Something has gone wrong!" that will need to be
* // dismissed by the user.
* this.alert("Error: Something has gone wrong!", 0);
*
* // Pops up a blue information box with the message "[current time] Happy Christmas!"
* // that will disappear after 5 seconds.
* this.alert("Happy Christmas!", "info", 5000);
* // Pops up a box with the message "Happy Christmas!" that will disappear after 5 seconds.
* this.alert("Happy Christmas!", 5000);
*/
alert(str, style, timeout, silent) {
alert(str, timeout, silent) {
const time = new Date();
log.info("[" + time.toLocaleString() + "] " + str);
if (silent) return;
style = style || "danger";
timeout = timeout || 0;
const alertEl = document.getElementById("alert"),
alertContent = document.getElementById("alert-content");
alertEl.classList.remove("alert-danger");
alertEl.classList.remove("alert-warning");
alertEl.classList.remove("alert-info");
alertEl.classList.remove("alert-success");
alertEl.classList.add("alert-" + style);
// If the box hasn't been closed, append to it rather than replacing
if (alertEl.style.display === "block") {
alertContent.innerHTML +=
"<br><br>[" + time.toLocaleTimeString() + "] " + str;
} else {
alertContent.innerHTML =
"[" + time.toLocaleTimeString() + "] " + str;
}
// Stop the animation if it is in progress
$("#alert").stop();
alertEl.style.display = "block";
alertEl.style.opacity = 1;
if (timeout > 0) {
clearTimeout(this.alertTimeout);
this.alertTimeout = setTimeout(function(){
$("#alert").slideUp(100);
}, timeout);
}
this.currentSnackbar = $.snackbar({
content: str,
timeout: timeout,
htmlAllowed: true,
onClose: () => {
this.currentSnackbar.remove();
}
});
}
@ -658,15 +626,6 @@ class App {
}
/**
* Handler for the alert close button click event.
* Closes the alert box.
*/
alertCloseClick() {
document.getElementById("alert").style.display = "none";
}
/**
* Handler for CyerChef statechange events.
* Fires whenever the input or recipe changes in any way.
@ -712,42 +671,6 @@ class App {
this.loadURIParams();
}
/**
* Function to call an external API from this view.
*/
callApi(url, type, data, dataType, contentType) {
type = type || "POST";
data = data || {};
dataType = dataType || undefined;
contentType = contentType || "application/json";
let response = null,
success = false;
$.ajax({
url: url,
async: false,
type: type,
data: data,
dataType: dataType,
contentType: contentType,
success: function(data) {
success = true;
response = data;
},
error: function(data) {
success = false;
response = data;
},
});
return {
success: success,
response: response
};
}
}
export default App;

View File

@ -120,7 +120,7 @@ class BackgroundWorkerWaiter {
* @param {string|ArrayBuffer} input
*/
magic(input) {
// If we're still working on the previous bake, cancel it before stating a new one.
// If we're still working on the previous bake, cancel it before starting a new one.
if (this.completedCallback + 1 < this.callbackID) {
clearTimeout(this.timeout);
this.cancelBake();

View File

@ -26,44 +26,16 @@ class ControlsWaiter {
/**
* Adjusts the display properties of the control buttons so that they fit within the current width
* without wrapping or overflowing.
* Initialise Bootstrap componenets
*/
adjustWidth() {
const controls = document.getElementById("controls");
const step = document.getElementById("step");
const clrBreaks = document.getElementById("clr-breaks");
const saveImg = document.querySelector("#save img");
const loadImg = document.querySelector("#load img");
const stepImg = document.querySelector("#step img");
const clrRecipImg = document.querySelector("#clr-recipe img");
const clrBreaksImg = document.querySelector("#clr-breaks img");
if (controls.clientWidth < 470) {
step.childNodes[1].nodeValue = " Step";
} else {
step.childNodes[1].nodeValue = " Step through";
}
if (controls.clientWidth < 400) {
saveImg.style.display = "none";
loadImg.style.display = "none";
stepImg.style.display = "none";
clrRecipImg.style.display = "none";
clrBreaksImg.style.display = "none";
} else {
saveImg.style.display = "inline";
loadImg.style.display = "inline";
stepImg.style.display = "inline";
clrRecipImg.style.display = "inline";
clrBreaksImg.style.display = "inline";
}
if (controls.clientWidth < 330) {
clrBreaks.childNodes[1].nodeValue = " Clear breaks";
} else {
clrBreaks.childNodes[1].nodeValue = " Clear breakpoints";
}
initComponents() {
$("body").bootstrapMaterialDesign();
$("[data-toggle=tooltip]").tooltip({
animation: false,
container: "body",
boundary: "viewport",
trigger: "hover"
});
}
@ -105,18 +77,7 @@ class ControlsWaiter {
* Handler for changes made to the Auto Bake checkbox.
*/
autoBakeChange() {
const autoBakeLabel = document.getElementById("auto-bake-label");
const autoBakeCheckbox = document.getElementById("auto-bake");
this.app.autoBake_ = autoBakeCheckbox.checked;
if (autoBakeCheckbox.checked) {
autoBakeLabel.classList.add("btn-success");
autoBakeLabel.classList.remove("btn-default");
} else {
autoBakeLabel.classList.add("btn-default");
autoBakeLabel.classList.remove("btn-success");
}
this.app.autoBake_ = document.getElementById("auto-bake").checked;
}
@ -128,20 +89,6 @@ class ControlsWaiter {
}
/**
* Handler for the 'Clear breakpoints' command. Removes all breakpoints from operations in the
* recipe.
*/
clearBreaksClick() {
const bps = document.querySelectorAll("#rec-list li.operation .breakpoint");
for (let i = 0; i < bps.length; i++) {
bps[i].setAttribute("break", "false");
bps[i].classList.remove("breakpoint-selected");
}
}
/**
* Populates the save disalog box with a URL incorporating the recipe and input.
*
@ -264,7 +211,6 @@ class ControlsWaiter {
if (!this.app.isLocalStorageAvailable()) {
this.app.alert(
"Your security settings do not allow access to local storage so your recipe cannot be saved.",
"danger",
5000
);
return false;
@ -274,7 +220,7 @@ class ControlsWaiter {
const recipeStr = document.querySelector("#save-texts .tab-pane.active textarea").value;
if (!recipeName) {
this.app.alert("Please enter a recipe name", "danger", 2000);
this.app.alert("Please enter a recipe name", 3000);
return;
}
@ -291,7 +237,7 @@ class ControlsWaiter {
localStorage.savedRecipes = JSON.stringify(savedRecipes);
localStorage.recipeId = recipeId;
this.app.alert("Recipe saved as \"" + recipeName + "\".", "success", 2000);
this.app.alert(`Recipe saved as "${recipeName}".`, 3000);
}
@ -323,7 +269,10 @@ class ControlsWaiter {
}
// Populate textarea with first recipe
document.getElementById("load-text").value = savedRecipes.length ? savedRecipes[0].recipe : "";
const loadText = document.getElementById("load-text");
const evt = new Event("change");
loadText.value = savedRecipes.length ? savedRecipes[0].recipe : "";
loadText.dispatchEvent(evt);
}
@ -372,7 +321,7 @@ class ControlsWaiter {
$("#rec-list [data-toggle=popover]").popover();
} catch (e) {
this.app.alert("Invalid recipe", "danger", 2000);
this.app.alert("Invalid recipe", 2000);
}
}
@ -391,7 +340,7 @@ class ControlsWaiter {
if (reportBugInfo) {
reportBugInfo.innerHTML = `* Version: ${PKG_VERSION + (typeof INLINE === "undefined" ? "" : "s")}
* Compile time: ${COMPILE_TIME}
* User-Agent:
* User-Agent:
${navigator.userAgent}
* [Link to reproduce](${saveLink})
@ -406,9 +355,7 @@ ${navigator.userAgent}
*/
showStaleIndicator() {
const staleIndicator = document.getElementById("stale-indicator");
staleIndicator.style.visibility = "visible";
staleIndicator.style.opacity = 1;
staleIndicator.classList.remove("hidden");
}
@ -418,9 +365,7 @@ ${navigator.userAgent}
*/
hideStaleIndicator() {
const staleIndicator = document.getElementById("stale-indicator");
staleIndicator.style.opacity = 0;
staleIndicator.style.visibility = "hidden";
staleIndicator.classList.add("hidden");
}

View File

@ -39,13 +39,12 @@ class HTMLCategory {
*/
toHtml() {
const catName = "cat" + this.name.replace(/[\s/-:_]/g, "");
let html = "<div class='panel category'>\
<a class='category-title' data-toggle='collapse'\
data-parent='#categories' href='#" + catName + "'>\
" + this.name + "\
</a>\
<div id='" + catName + "' class='panel-collapse collapse\
" + (this.selected ? " in" : "") + "'><ul class='op-list'>";
let html = `<div class="panel category">
<a class="category-title" data-toggle="collapse" data-target="#${catName}">
${this.name}
</a>
<div id="${catName}" class="panel-collapse collapse ${(this.selected ? " show" : "")}" data-parent="#categories">
<ul class="op-list">`;
for (let i = 0; i < this.opList.length; i++) {
html += this.opList[i].toStubHtml();

View File

@ -24,8 +24,7 @@ class HTMLIngredient {
this.type = config.type;
this.value = config.value;
this.disabled = config.disabled || false;
this.disableArgs = config.disableArgs || false;
this.placeholder = config.placeholder || false;
this.hint = config.hint || false;
this.target = config.target;
this.toggleValues = config.toggleValues;
this.id = "ing-" + this.app.nextIngId();
@ -38,154 +37,180 @@ class HTMLIngredient {
* @returns {string}
*/
toHtml() {
const inline = (
this.type === "boolean" ||
this.type === "number" ||
this.type === "option" ||
this.type === "shortString" ||
this.type === "binaryShortString"
);
let html = inline ? "" : "<div class='clearfix'>&nbsp;</div>",
let html = "",
i, m;
html += "<div class='arg-group" + (inline ? " inline-args" : "") +
(this.type === "text" ? " arg-group-text" : "") + "'><label class='arg-label' for='" +
this.id + "'>" + this.name + "</label>";
switch (this.type) {
case "string":
case "binaryString":
case "byteArray":
html += "<input type='text' id='" + this.id + "' class='arg arg-input' arg-name='" +
this.name + "' value='" + this.value + "'" +
(this.disabled ? " disabled='disabled'" : "") +
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
html += `<div class="form-group">
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
<input type="text"
class="form-control arg"
id="${this.id}"
arg-name="${this.name}"
value="${this.value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break;
case "shortString":
case "binaryShortString":
html += "<input type='text' id='" + this.id +
"'class='arg arg-input short-string' arg-name='" + this.name + "'value='" +
this.value + "'" + (this.disabled ? " disabled='disabled'" : "") +
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
html += `<div class="form-group inline">
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
<input type="text"
class="form-control arg inline"
id="${this.id}"
arg-name="${this.name}"
value="${this.value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break;
case "toggleString":
html += "<div class='input-group'><div class='input-group-btn'>\
<button type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown'\
aria-haspopup='true' aria-expanded='false'" +
(this.disabled ? " disabled='disabled'" : "") + ">" + this.toggleValues[0] +
" <span class='caret'></span></button><ul class='dropdown-menu'>";
html += `<div class="form-group input-group">
<div class="toggle-string">
<label for="${this.id}" class="bmd-label-floating toggle-string">${this.name}</label>
<input type="text"
class="form-control arg toggle-string"
id="${this.id}"
arg-name="${this.name}"
value="${this.value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>
<div class="input-group-append">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">${this.toggleValues[0]}</button>
<div class="dropdown-menu toggle-dropdown">`;
for (i = 0; i < this.toggleValues.length; i++) {
html += "<li><a href='#'>" + this.toggleValues[i] + "</a></li>";
html += `<a class="dropdown-item" href="#">${this.toggleValues[i]}</a>`;
}
html += "</ul></div><input type='text' class='arg arg-input toggle-string'" +
(this.disabled ? " disabled='disabled'" : "") +
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + "></div>";
html += `</div>
</div>
</div>`;
break;
case "number":
html += "<input type='number' id='" + this.id + "'class='arg arg-input' arg-name='" +
this.name + "'value='" + this.value + "'" +
(this.disabled ? " disabled='disabled'" : "") +
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
html += `<div class="form-group inline">
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
<input type="number"
class="form-control arg inline"
id="${this.id}"
arg-name="${this.name}"
value="${this.value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break;
case "boolean":
html += "<input type='checkbox' id='" + this.id + "'class='arg' arg-name='" +
this.name + "'" + (this.value ? " checked='checked' " : "") +
(this.disabled ? " disabled='disabled'" : "") + ">";
if (this.disableArgs) {
this.manager.addDynamicListener("#" + this.id, "click", this.toggleDisableArgs, this);
}
html += `<div class="form-group inline boolean-arg">
<div class="checkbox">
<label>
<input type="checkbox"
class="arg"
id="${this.id}"
arg-name="${this.name}"
${this.value ? " checked" : ""}
${this.disabled ? " disabled" : ""}
value="${this.name}"> ${this.name}
</label>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>
</div>`;
break;
case "option":
html += "<select class='arg' id='" + this.id + "'arg-name='" + this.name + "'" +
(this.disabled ? " disabled='disabled'" : "") + ">";
html += `<div class="form-group inline">
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
<select
class="form-control arg inline"
id="${this.id}"
arg-name="${this.name}"
${this.disabled ? "disabled" : ""}>`;
for (i = 0; i < this.value.length; i++) {
if ((m = this.value[i].match(/\[([a-z0-9 -()^]+)\]/i))) {
html += "<optgroup label='" + m[1] + "'>";
html += `<optgroup label="${m[1]}">`;
} else if ((m = this.value[i].match(/\[\/([a-z0-9 -()^]+)\]/i))) {
html += "</optgroup>";
} else {
html += "<option>" + this.value[i] + "</option>";
html += `<option>${this.value[i]}</option>`;
}
}
html += "</select>";
html += `</select>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break;
case "populateOption":
html += "<select class='arg' id='" + this.id + "'arg-name='" + this.name + "'" +
(this.disabled ? " disabled='disabled'" : "") + ">";
html += `<div class="form-group">
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
<select
class="form-control arg"
id="${this.id}"
arg-name="${this.name}"
${this.disabled ? "disabled" : ""}>`;
for (i = 0; i < this.value.length; i++) {
if ((m = this.value[i].name.match(/\[([a-z0-9 -()^]+)\]/i))) {
html += "<optgroup label='" + m[1] + "'>";
html += `<optgroup label="${m[1]}">`;
} else if ((m = this.value[i].name.match(/\[\/([a-z0-9 -()^]+)\]/i))) {
html += "</optgroup>";
} else {
html += "<option populate-value='" + this.value[i].value + "'>" +
this.value[i].name + "</option>";
html += `<option populate-value="${this.value[i].value}">${this.value[i].name}</option>`;
}
}
html += "</select>";
html += `</select>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this);
break;
case "editableOption":
html += "<div class='editable-option'>";
html += "<select class='editable-option-select' id='sel-" + this.id + "'" +
(this.disabled ? " disabled='disabled'" : "") + ">";
html += `<div class="form-group input-group inline">
<label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
<input type="text"
class="form-control arg inline"
id="${this.id}"
arg-name="${this.name}"
value="${this.value[0].value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
<div class="input-group-append inline">
<button type="button"
class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
data-toggle="dropdown"
data-boundary="scrollParent"
aria-haspopup="true"
aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu editable-option-menu">`;
for (i = 0; i < this.value.length; i++) {
html += "<option value='" + this.value[i].value + "'>" + this.value[i].name + "</option>";
html += `<a class="dropdown-item" href="#" value="${this.value[i].value}">${this.value[i].name}</a>`;
}
html += "</select>";
html += "<input class='arg arg-input editable-option-input' id='" + this.id +
"'arg-name='" + this.name + "'" + " value='" + this.value[0].value + "'" +
(this.disabled ? " disabled='disabled'" : "") +
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
html += "</div>";
html += `</div>
</div>
</div>`;
this.manager.addDynamicListener("#sel-" + this.id, "change", this.editableOptionChange, this);
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
break;
case "text":
html += "<textarea id='" + this.id + "' class='arg' arg-name='" +
this.name + "'" + (this.disabled ? " disabled='disabled'" : "") +
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">" +
this.value + "</textarea>";
html += `<div class="form-group">
<label for="${this.id}" class="bmd-label-floating">${this.name}</label>
<textarea
class="form-control arg"
id="${this.id}"
arg-name="${this.name}"
${this.disabled ? "disabled" : ""}>${this.value}</textarea>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break;
default:
break;
}
html += "</div>";
return html;
}
/**
* Handler for argument disable toggle.
* Toggles disabled state for all arguments in the disableArgs list for this ingredient.
*
* @param {event} e
*/
toggleDisableArgs(e) {
const el = e.target;
const op = el.parentNode.parentNode;
const args = op.querySelectorAll(".arg-group");
for (let i = 0; i < this.disableArgs.length; i++) {
const els = args[this.disableArgs[i]].querySelectorAll("input, select, button");
for (let j = 0; j < els.length; j++) {
if (els[j].getAttribute("disabled")) {
els[j].removeAttribute("disabled");
} else {
els[j].setAttribute("disabled", "disabled");
}
}
}
this.manager.recipe.ingChange();
}
/**
* Handler for populate option changes.
* Populates the relevant argument with the specified value.
@ -195,25 +220,32 @@ class HTMLIngredient {
populateOptionChange(e) {
const el = e.target;
const op = el.parentNode.parentNode;
const target = op.querySelectorAll(".arg-group")[this.target].querySelector("input, select, textarea");
const target = op.querySelectorAll(".arg")[this.target];
target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value");
const evt = new Event("change");
target.dispatchEvent(evt);
this.manager.recipe.ingChange();
}
/**
* Handler for editable option changes.
* Handler for editable option clicks.
* Populates the input box with the selected value.
*
* @param {event} e
*/
editableOptionChange(e) {
const select = e.target,
input = select.nextSibling;
editableOptionClick(e) {
e.preventDefault();
e.stopPropagation();
input.value = select.childNodes[select.selectedIndex].value;
const link = e.target,
input = link.parentNode.parentNode.parentNode.querySelector("input");
input.value = link.getAttribute("value");
const evt = new Event("change");
input.dispatchEvent(evt);
this.manager.recipe.ingChange();
}

View File

@ -6,9 +6,6 @@
import HTMLIngredient from "./HTMLIngredient";
const INFO_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAByElEQVR4XqVTzWoaYRQ9KZJmoVaS1J1QiYTIuOgqi9lEugguQhYhdGs3hTyAi0CWJTvJIks30ZBNsimUtlqkVLoQCuJsphRriyFjabWtEyf/Rv3iWcwwymTlgQuH851z5hu43wRGkEwmXwCIA4hiGAUAmUQikQbhEHwyGCWVSglVVUW73RYmyKnxjB56ncJ6NpsVxHGrI/ZLuniVb3DIqQmCHnrNkgcggNeSJPlisRgyJR2b737j/TcDsQUPwv6H5NR4BnroZcb6Z16N2PvyX6yna9Z8qp6JQ0Uf0ughmGHWBSAuyzJqrQ7eqKewY/dzE363C71e39LoWQq5wUwul4uzIBoIBHD01RgyrkZ8eDbvwUWnj623v2DHx4qB51IAzLIAXq8XP/7W0bUVVJtXWIk8wvlN364TA+/1IDMLwmWK/Hq3axmhaBdoGLeklm73ElaBYRgIzkyifHIOO4QQJKM3oJcZq6CgaVp0OTyHw9K/kQI4FiyHfdC0n2CWe5ApFosIPZ7C2tNpXpcDOehGyD/FIbd0euhlhllzFxRzC3fydbG4XRYbB9/tQ41n9m1U7l3lyp9LkfygiZeZCoecmtMqj/+Yxn7Od3v0j50qCO3zAAAAAElFTkSuQmCC";
const REMOVE_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwklEQVR42qRTPU8CQRB9K2CCMRJ6NTQajOUaqfxIbLCRghhjQixosLAgFNBQ3l8wsabxLxBJbCyVUBiMCVQEQkOEKBbCnefM3p4eohWXzM3uvHlv52b2hG3bmOWZw4yPn1/XQkCQ9wFxcgZZ0QLKpifpN8Z1n1L13griBBjHhYK0nMT4b+wom53ClAAFQacZJ/m8rNfrSOZy0vxJjPP6IJ2WzWYTO6mUwiwtILiJJSHUKVSWkchkZK1WQzQaxU2pVGUglkjIbreLUCiEx0qlStlFCpfPiPstYDtVKJH9ZFI2Gw1FGA6H6LTbCAaDeGu1FJl6UuYjpwTGzucokZW1NfnS66kyfT4fXns9RaZmlgNcuhZQU+jowLzuOK/HgwEW3E5ZlhLXVWKk11P3wNYNWw+HZdA0sUgx1zjGmD05nckx0ilGjBJdUq3fr7K5e8bGf43RdL7fOPSQb4lI8SLbrUfkUIuY32VTI1bJn5BqDnh4Dodt9ryPUDzyD7aquWoKQohl2i9sAbubwPkTcHkP3FHsg+yT+7sN7G0AF3Xg6sHB3onbdgWWKBDQg/BcTuVt51dQA/JrnIcyIu6rmPV3/hJgACPc0BMEYTg+AAAAAElFTkSuQmCC";
/**
* Object to handle the creation of operations.
@ -49,19 +46,15 @@ class HTMLOperation {
let html = "<li class='operation'";
if (this.description) {
html += " data-container='body' data-toggle='popover' data-placement='auto right'\
data-content=\"" + this.description + "\" data-html='true' data-trigger='hover'";
html += ` data-container='body' data-toggle='popover' data-placement='right'
data-content="${this.description}" data-html='true' data-trigger='hover'
data-boundary='viewport'`;
}
html += ">" + this.name;
if (removeIcon) {
html += "<img src='data:image/png;base64," + REMOVE_ICON +
"' class='op-icon remove-icon'>";
}
if (this.description) {
html += "<img src='data:image/png;base64," + INFO_ICON + "' class='op-icon'>";
html += "<i class='material-icons remove-icon op-icon'>delete</i>";
}
html += "</li>";
@ -76,19 +69,19 @@ class HTMLOperation {
* @returns {string}
*/
toFullHtml() {
let html = "<div class='arg-title'>" + this.name + "</div>";
let html = `<div class="op-title">${this.name}</div>
<div class="ingredients">`;
for (let i = 0; i < this.ingList.length; i++) {
html += this.ingList[i].toHtml();
}
html += "<div class='recip-icons'>\
<div class='breakpoint' title='Set breakpoint' break='false'></div>\
<div class='disable-icon recip-icon' title='Disable operation'\
disabled='false'></div>";
html += "</div>\
<div class='clearfix'>&nbsp;</div>";
html += `</div>
<div class="recip-icons">
<i class="material-icons breakpoint" title="Set breakpoint" break="false">pause</i>
<i class="material-icons disable-icon" title="Disable operation" disabled="false">not_interested</i>
</div>
<div class="clearfix">&nbsp;</div>`;
return html;
}

View File

@ -60,10 +60,11 @@ class InputWaiter {
* Sets the input in the input area.
*
* @param {string|File} input
* @param {boolean} [silent=false] - Suppress statechange event
*
* @fires Manager#statechange
*/
set(input) {
set(input, silent=false) {
const inputText = document.getElementById("input-text");
if (input instanceof File) {
this.setFile(input);
@ -72,7 +73,7 @@ class InputWaiter {
} else {
inputText.value = input;
this.closeFile();
window.dispatchEvent(this.manager.statechange);
if (!silent) window.dispatchEvent(this.manager.statechange);
const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ?
input.count("\n") + 1 : null;
this.setInputInfo(input.length, lines);
@ -264,7 +265,7 @@ class InputWaiter {
}
if (r.hasOwnProperty("error")) {
this.app.alert(r.error, "danger", 10000);
this.app.alert(r.error, 10000);
}
if (r.hasOwnProperty("fileBuffer")) {

View File

@ -84,6 +84,7 @@ class Manager {
setup() {
this.worker.registerChefWorker();
this.recipe.initialiseOperationDragNDrop();
this.controls.initComponents();
this.controls.autoBakeChange();
this.bindings.updateKeybList();
this.background.registerChefWorker();
@ -107,7 +108,6 @@ class Manager {
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));
document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls));
document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls));
document.getElementById("clr-breaks").addEventListener("click", this.controls.clearBreaksClick.bind(this.controls));
document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls));
document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls));
document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls));
@ -125,8 +125,6 @@ class Manager {
document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops));
document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops));
document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops));
this.addDynamicListener(".op-list .op-icon", "mouseover", this.ops.opIconMouseover, this.ops);
this.addDynamicListener(".op-list .op-icon", "mouseleave", this.ops.opIconMouseleave, this.ops);
this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops);
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe);
@ -137,7 +135,7 @@ class Manager {
this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe);
this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe);
this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe);
this.addDynamicListener("#rec-list .input-group .dropdown-menu a", "click", this.recipe.dropdownToggleClick, this.recipe);
this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe);
this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
// Input
@ -160,6 +158,7 @@ class Manager {
document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output));
document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output));
document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output));
document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output));
document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter));
document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter));
document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter));
@ -168,15 +167,15 @@ class Manager {
this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter);
this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter);
this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, this.output);
this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output);
document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));
// Options
document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));
document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options));
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options));
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options));
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings));
this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.switchChange, this.options);
this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.setWordWrap, this.options);
this.addDynamicListener(".option-item input[type=checkbox]#useMetaKey", "change", this.bindings.updateKeybList, this.bindings);
this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options);
this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options);
this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
@ -185,7 +184,6 @@ class Manager {
// Misc
window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));
document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app));
}

View File

@ -79,12 +79,12 @@ class OperationsWaiter {
while (searchResultsEl.firstChild) {
try {
$(searchResultsEl.firstChild).popover("destroy");
$(searchResultsEl.firstChild).popover("dispose");
} catch (err) {}
searchResultsEl.removeChild(searchResultsEl.firstChild);
}
$("#categories .in").collapse("hide");
$("#categories .show").collapse("hide");
if (str) {
const matchedOps = this.filterOperations(str, true);
const matchedOpsHtml = matchedOps
@ -185,7 +185,7 @@ class OperationsWaiter {
setTimeout(function() {
// Determine if the popover associated with this element is being hovered over
if ($(_this).data("bs.popover") &&
($(_this).data("bs.popover").$tip && !$(_this).data("bs.popover").$tip.is(":hover"))) {
($(_this).data("bs.popover").tip && !$($(_this).data("bs.popover").tip).is(":hover"))) {
$(_this).popover("hide");
}
}, 50);
@ -237,13 +237,13 @@ class OperationsWaiter {
onFilter: function (evt) {
const el = editableList.closest(evt.item);
if (el && el.parentNode) {
$(el).popover("destroy");
$(el).popover("dispose");
el.parentNode.removeChild(el);
}
},
onEnd: function(evt) {
if (this.removeIntent) {
$(evt.item).popover("destroy");
$(evt.item).popover("dispose");
evt.item.remove();
}
}.bind(this),
@ -268,7 +268,7 @@ class OperationsWaiter {
*/
saveFavouritesClick() {
const favs = document.querySelectorAll("#edit-favourites-list li");
const favouritesList = Array.from(favs, e => e.textContent);
const favouritesList = Array.from(favs, e => e.childNodes[0].textContent);
this.app.saveFavourites(favouritesList);
this.app.loadFavourites();
@ -285,37 +285,6 @@ class OperationsWaiter {
this.app.resetFavourites();
}
/**
* Handler for opIcon mouseover events.
* Hides any popovers already showing on the operation so that there aren't two at once.
*
* @param {event} e
*/
opIconMouseover(e) {
const opEl = e.target.parentNode;
if (e.target.getAttribute("data-toggle") === "popover") {
$(opEl).popover("hide");
}
}
/**
* Handler for opIcon mouseleave events.
* If this icon created a popover and we're moving back to the operation element, display the
* operation popover again.
*
* @param {event} e
*/
opIconMouseleave(e) {
const opEl = e.target.parentNode;
const toEl = e.toElement || e.relatedElement;
if (e.target.getAttribute("data-toggle") === "popover" && toEl === opEl) {
$(opEl).popover("show");
}
}
}
export default OperationsWaiter;

View File

@ -20,11 +20,6 @@ const OptionsWaiter = function(app, manager) {
* @param {Object} options
*/
OptionsWaiter.prototype.load = function(options) {
$(".option-item input:checkbox").bootstrapSwitch({
size: "small",
animate: false,
});
for (const option in options) {
this.app.options[option] = options[option];
}
@ -33,7 +28,7 @@ OptionsWaiter.prototype.load = function(options) {
const cboxes = document.querySelectorAll("#options-body input[type=checkbox]");
let i;
for (i = 0; i < cboxes.length; i++) {
$(cboxes[i]).bootstrapSwitch("state", this.app.options[cboxes[i].getAttribute("option")]);
cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")];
}
const nboxes = document.querySelectorAll("#options-body input[type=number]");
@ -81,11 +76,11 @@ OptionsWaiter.prototype.resetOptionsClick = function() {
* Modifies the option state and saves it to local storage.
*
* @param {event} e
* @param {boolean} state
*/
OptionsWaiter.prototype.switchChange = function(e, state) {
OptionsWaiter.prototype.switchChange = function(e) {
const el = e.target;
const option = el.getAttribute("option");
const state = el.checked;
log.debug(`Setting ${option} to ${state}`);
this.app.options[option] = state;

View File

@ -228,35 +228,6 @@ class OutputWaiter {
}
/**
* Adjusts the display properties of the output buttons so that they fit within the current width
* without wrapping or overflowing.
*/
adjustWidth() {
const output = document.getElementById("output");
const saveToFile = document.getElementById("save-to-file");
const copyOutput = document.getElementById("copy-output");
const switchIO = document.getElementById("switch");
const undoSwitch = document.getElementById("undo-switch");
const maximiseOutput = document.getElementById("maximise-output");
if (output.clientWidth < 680) {
saveToFile.childNodes[1].nodeValue = "";
copyOutput.childNodes[1].nodeValue = "";
switchIO.childNodes[1].nodeValue = "";
undoSwitch.childNodes[1].nodeValue = "";
maximiseOutput.childNodes[1].nodeValue = "";
} else {
saveToFile.childNodes[1].nodeValue = " Save to file";
copyOutput.childNodes[1].nodeValue = " Copy output";
switchIO.childNodes[1].nodeValue = " Move output to input";
undoSwitch.childNodes[1].nodeValue = " Undo";
maximiseOutput.childNodes[1].nodeValue =
maximiseOutput.getAttribute("title") === "Maximise" ? " Max" : " Restore";
}
}
/**
* Handler for save click events.
* Saves the current output to a file.
@ -296,9 +267,9 @@ class OutputWaiter {
}
if (success) {
this.app.alert("Copied raw output successfully.", "success", 2000);
this.app.alert("Copied raw output successfully.", 2000);
} else {
this.app.alert("Sorry, the output could not be copied.", "danger", 2000);
this.app.alert("Sorry, the output could not be copied.", 3000);
}
// Clean up
@ -334,7 +305,9 @@ class OutputWaiter {
*/
undoSwitchClick() {
this.app.setInput(this.switchOrigData);
document.getElementById("undo-switch").disabled = true;
const undoSwitch = document.getElementById("undo-switch");
undoSwitch.disabled = true;
$(undoSwitch).tooltip("hide");
}
@ -345,17 +318,16 @@ class OutputWaiter {
maximiseOutputClick(e) {
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
if (el.getAttribute("title") === "Maximise") {
if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
this.app.columnSplitter.collapse(0);
this.app.columnSplitter.collapse(1);
this.app.ioSplitter.collapse(0);
el.setAttribute("title", "Restore");
el.innerHTML = "<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAlUlEQVQ4y93RwQpBQRQG4C9ba1fxBteGPIj38BTejFJKLFnwCJIiCsW1mcV0k9yx82/OzGK+OXMGOpiiLTFjFNiilQI0sQ7IJiAjLKsgGVYB2YdaVO0kwy46/BVQi9ZDNPyQWen2ub/KufS8y7shfkq9tF9U7SC+/YluKvAI9YZeFeCECXJcA3JHP2WgMXJM/ZUcBwxeM+YuSWTgMtUAAAAASUVORK5CYII='> Restore";
this.adjustWidth();
$(el).attr("data-original-title", "Restore output pane");
el.querySelector("i").innerHTML = "fullscreen_exit";
} else {
el.setAttribute("title", "Maximise");
el.innerHTML = "<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAi0lEQVQ4y83TMQrCQBCF4S+5g4rJEdJ7KE+RQ1lrIQQCllroEULuoM0Ww3a7aXwwLAzMPzDvLcz4hnooUItT1rsoVNy+4lgLWNL7RlcCmDBij2eCfNCrUITc0dRCrhj8m5otw0O6SV8LuAV3uhrAAa8sJ2Np7KPFawhgscVLjH9bCDhjt8WNKft88w/HjCvuVqu53QAAAABJRU5ErkJggg=='> Max";
$(el).attr("data-original-title", "Maximise output pane");
el.querySelector("i").innerHTML = "fullscreen";
this.app.resetLayout();
}
}
@ -450,6 +422,9 @@ class OutputWaiter {
* Triggers the BackgroundWorker to attempt Magic on the current output.
*/
backgroundMagic() {
this.hideMagicButton();
if (!this.app.options.autoMagic) return;
const sample = this.dishStr ? this.dishStr.slice(0, 1000) :
this.dishBuffer ? this.dishBuffer.slice(0, 1000) : "";
@ -469,16 +444,52 @@ class OutputWaiter {
!options[0].recipe.length)
return;
//console.log(options);
const currentRecipeConfig = this.app.getRecipeConfig();
const newRecipeConfig = currentRecipeConfig.concat(options[0].recipe);
const recipeURL = "#recipe=" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(newRecipeConfig));
const opSequence = options[0].recipe.map(o => o.op).join(", ");
log.log(`Running <a href="${recipeURL}">${opSequence}</a> will result in "${Utils.truncate(options[0].data, 20)}"`);
//this.app.setRecipeConfig(newRecipeConfig);
//this.app.autoBake();
this.showMagicButton(opSequence, options[0].data, newRecipeConfig);
}
/**
* Handler for Magic click events.
*
* Loads the Magic recipe.
*
* @fires Manager#statechange
*/
magicClick() {
const magicButton = document.getElementById("magic");
this.app.setRecipeConfig(JSON.parse(magicButton.getAttribute("data-recipe")));
window.dispatchEvent(this.manager.statechange);
this.hideMagicButton();
}
/**
* Displays the Magic button with a title and adds a link to a complete recipe.
*
* @param {string} opSequence
* @param {string} result
* @param {Object[]} recipeConfig
*/
showMagicButton(opSequence, result, recipeConfig) {
const magicButton = document.getElementById("magic");
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.truncate(result, 30)}"</span>`);
magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, "");
magicButton.classList.remove("hidden");
}
/**
* Hides the Magic button and resets its values.
*/
hideMagicButton() {
const magicButton = document.getElementById("magic");
magicButton.classList.add("hidden");
magicButton.setAttribute("data-recipe", "");
magicButton.setAttribute("data-original-title", "Magic!");
}
}

View File

@ -39,10 +39,10 @@ class RecipeWaiter {
sort: true,
animation: 0,
delay: 0,
filter: ".arg-input,.arg",
filter: ".arg",
preventOnFilter: false,
setData: function(dataTransfer, dragEl) {
dataTransfer.setData("Text", dragEl.querySelector(".arg-title").textContent);
dataTransfer.setData("Text", dragEl.querySelector(".op-title").textContent);
},
onEnd: function(evt) {
if (this.removeIntent) {
@ -100,9 +100,15 @@ class RecipeWaiter {
// Removes popover element and event bindings from the dragged operation but not the
// event bindings from the one left in the operations list. Without manually removing
// these bindings, we cannot re-initialise the popover on the stub operation.
$(evt.item).popover("destroy").removeData("bs.popover").off("mouseenter").off("mouseleave");
$(evt.clone).off(".popover").removeData("bs.popover");
evt.item.setAttribute("data-toggle", "popover-disabled");
$(evt.item)
.popover("dispose")
.removeData("bs.popover")
.off("mouseenter")
.off("mouseleave")
.attr("data-toggle", "popover-disabled");
$(evt.clone)
.off(".popover")
.removeData("bs.popover");
},
onEnd: this.opSortEnd.bind(this)
});
@ -299,7 +305,7 @@ class RecipeWaiter {
} else if (ingList[j].classList.contains("toggle-string")) {
// toggleString
ingredients[j] = {
option: ingList[j].previousSibling.children[0].textContent.slice(0, -1),
option: ingList[j].parentNode.parentNode.querySelector("button").textContent,
string: ingList[j].value
};
} else if (ingList[j].getAttribute("type") === "number") {
@ -312,7 +318,7 @@ class RecipeWaiter {
}
item = {
op: operations[i].querySelector(".arg-title").textContent,
op: operations[i].querySelector(".op-title").textContent,
args: ingredients
};
@ -366,7 +372,7 @@ class RecipeWaiter {
// Disable auto-bake if this is a manual op
if (op.manualBake && this.app.autoBake_) {
this.manager.controls.setAutoBake(false);
this.app.alert("Auto-Bake is disabled by default when using this operation.", "info", 5000);
this.app.alert("Auto-Bake is disabled by default when using this operation.", 5000);
}
}
@ -411,10 +417,13 @@ class RecipeWaiter {
* @param {event} e
*/
dropdownToggleClick(e) {
const el = e.target;
const button = el.parentNode.parentNode.previousSibling;
e.stopPropagation();
e.preventDefault();
button.innerHTML = el.textContent + " <span class='caret'></span>";
const el = e.target;
const button = el.parentNode.parentNode.querySelector("button");
button.innerHTML = el.textContent;
this.ingChange();
}
@ -427,7 +436,7 @@ class RecipeWaiter {
* @param {event} e
*/
opAdd(e) {
log.debug(`'${e.target.querySelector(".arg-title").textContent}' added to recipe`);
log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`);
window.dispatchEvent(this.manager.statechange);
}
@ -470,6 +479,44 @@ class RecipeWaiter {
op.insertAdjacentHTML("beforeend", registerListEl);
}
/**
* Adjusts the number of ingredient columns as the width of the recipe changes.
*/
adjustWidth() {
const recList = document.getElementById("rec-list");
if (!this.ingredientRuleID) {
this.ingredientRuleID = null;
this.ingredientChildRuleID = null;
// Find relevant rules in the stylesheet
for (const i in document.styleSheets[0].cssRules) {
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
this.ingredientRuleID = i;
}
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
this.ingredientChildRuleID = i;
}
}
}
if (!this.ingredientRuleID || !this.ingredientChildRuleID) return;
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID],
ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
if (recList.clientWidth < 450) {
ingredientRule.style.gridTemplateColumns = "auto auto";
ingredientChildRule.style.gridColumn = "1 / span 2";
} else if (recList.clientWidth < 620) {
ingredientRule.style.gridTemplateColumns = "auto auto auto";
ingredientChildRule.style.gridColumn = "1 / span 3";
} else {
ingredientRule.style.gridTemplateColumns = "auto auto auto auto";
ingredientChildRule.style.gridColumn = "1 / span 4";
}
}
}
export default RecipeWaiter;

View File

@ -143,21 +143,19 @@
<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>
<div id="alert" class="alert alert-danger">
<button type="button" class="close" id="alert-close">&times;</button>
<span id="alert-content"></span>
</div>
<button type="button" class="btn btn-warning bmd-btn-icon" id="edit-favourites" data-toggle="tooltip" title="Edit favourites">
<i class="material-icons">star</i>
</button>
<div id="content-wrapper">
<div id="banner">
<div class="col-md-4" style="text-align: left; padding-left: 10px;">
<div id="banner" class="row">
<div class="col" style="text-align: left; padding-left: 10px;">
<% if (htmlWebpackPlugin.options.inline) { %>
<span>Version <%= htmlWebpackPlugin.options.version %></span>
<% } else { %>
<a href="cyberchef.htm" download>Download CyberChef<img aria-hidden="true" src="<%- require('../static/images/download-24x24.png') %>" alt="Download Icon"/></a>
<a href="cyberchef.htm" download>Download CyberChef <i class="material-icons">file_download</i></a>
<% } %>
</div>
<div class="col-md-4" style="text-align: center;">
<div class="col" style="text-align: center;">
<span id="notice">
<script type="text/javascript">
// Must be text/javascript rather than application/javascript otherwise IE won't recognise it...
@ -169,9 +167,9 @@
<noscript>JavaScript is not enabled. Good luck.</noscript>
</span>
</div>
<div class="col-md-4" style="text-align: right; padding-right: 0;">
<a href="#" id="options">Options<img aria-hidden="true" src="<%- require('../static/images/settings-22x22.png') %>" alt="Settings Icon"/></a>
<a href="#" id="support" data-toggle="modal" data-target="#support-modal">About / Support<img aria-hidden="true" src="<%- require('../static/images/help-22x22.png') %>" alt="Question Mark Icon"/></a>
<div class="col" style="text-align: right; padding-right: 0;">
<a href="#" id="options">Options <i class="material-icons">settings</i></a>
<a href="#" id="support" data-toggle="modal" data-target="#support-modal">About / Support <i class="material-icons">help</i></a>
</div>
</div>
<div id="workspace-wrapper">
@ -183,32 +181,41 @@
</div>
<div id="recipe" class="split split-horizontal no-select">
<div class="title no-select">Recipe</div>
<div class="title no-select">
Recipe
<span class="float-right">
<button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe">
<i class="material-icons">save</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="load" data-toggle="tooltip" title="Load recipe">
<i class="material-icons">folder</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-recipe" data-toggle="tooltip" title="Clear recipe">
<i class="material-icons">delete</i>
</button>
</span>
</div>
<ul id="rec-list" class="list-area no-select"></ul>
<div id="controls" class="no-select">
<div id="operational-controls">
<div id="bake-group">
<button type="button" class="btn btn-success btn-lg" id="bake">
<img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/>
<span>Bake!</span>
</button>
<label class="btn btn-success btn-lg" id="auto-bake-label" for="auto-bake">
<input type="checkbox" checked="checked" id="auto-bake">
<div>Auto Bake</div>
</label>
</div>
<div class="d-flex align-items-center">
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
Step
</button>
<div class="btn-group" style="padding-top: 10px;">
<button type="button" class="btn btn-default" id="step"><img aria-hidden="true" src="<%- require('../static/images/step-16x16.png') %>" alt="Footstep Icon"/> Step through</button>
<button type="button" class="btn btn-default" id="clr-breaks"><img aria-hidden="true" src="<%- require('../static/images/erase-16x16.png') %>" alt="Eraser Icon"/> Clear breakpoints</button>
</div>
</div>
<button type="button" class="mx-2 btn btn-lg btn-success btn-raised btn-block" id="bake">
<img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/>
<span>Bake!</span>
</button>
<div class="btn-group-vertical" id="extra-controls">
<button type="button" class="btn btn-default" id="save"><img aria-hidden="true" src="<%- require('../static/images/save-16x16.png') %>" alt="Save Icon"/> Save recipe</button>
<button type="button" class="btn btn-default" id="load"><img aria-hidden="true" src="<%- require('../static/images/open_yellow-16x16.png') %>" alt="Open Icon"/> Load recipe</button>
<button type="button" class="btn btn-default" id="clr-recipe"><img aria-hidden="true" src="<%- require('../static/images/clean-16x16.png') %>" alt="Broom Icon"/> Clear recipe</button>
<div class="form-group" style="display: contents;">
<div class="mx-1 checkbox">
<label id="auto-bake-label">
<input type="checkbox" checked="checked" id="auto-bake">
<br>Auto Bake
</label>
</div>
</div>
</div>
</div>
</div>
@ -217,10 +224,14 @@
<div id="input" class="split no-select">
<div class="title no-select">
<label for="input-text">Input</label>
<div class="btn-group io-btn-group">
<button type="button" class="btn btn-default btn-sm" id="clr-io"><img aria-hidden="true" src="<%- require('../static/images/recycle-16x16.png') %>" alt="Recycle Icon"/> Clear I/O</button>
<button type="button" class="btn btn-default btn-sm" id="reset-layout"><img aria-hidden="true" src="<%- require('../static/images/layout-16x16.png') %>" alt="Grid Icon"/> Reset layout</button>
</div>
<span class="float-right">
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-io" data-toggle="tooltip" title="Clear input and output">
<i class="material-icons">delete</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="reset-layout" data-toggle="tooltip" title="Reset pane layout">
<i class="material-icons">view_compact</i>
</button>
</span>
<div class="io-info" id="input-info"></div>
<div class="io-info" id="input-selection-info"></div>
</div>
@ -230,7 +241,7 @@
<div id="input-file">
<div class="file-overlay"></div>
<div style="position: relative; height: 100%;">
<div class="card">
<div class="io-card card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
<div class="card-body">
<button type="button" class="close" id="input-file-close">&times;</button>
@ -248,16 +259,33 @@
<div id="output" class="split">
<div class="title no-select">
<label for="output-text">Output</label>
<div class="btn-group io-btn-group">
<button type="button" class="btn btn-default btn-sm" id="save-to-file" title="Save to file"><img aria-hidden="true" src="<%- require('../static/images/save_as-16x16.png') %>" alt="Save Icon"/> Save to file</button>
<button type="button" class="btn btn-default btn-sm" id="copy-output" title="Copy output"><img aria-hidden="true" src="<%- require('../static/images/copy-16x16.png') %>" alt="Copy Icon"/> Copy raw output</button>
<button type="button" class="btn btn-default btn-sm" id="switch" title="Move output to input"><img aria-hidden="true" src="<%- require('../static/images/switch-16x16.png') %>" alt="Switch Icon"/> Move output to input</button>
<button type="button" class="btn btn-default btn-sm" id="undo-switch" title="Undo move" disabled="disabled"><img aria-hidden="true" src="<%- require('../static/images/undo-16x16.png') %>" alt="Undo Icon"/> Undo</button>
<button type="button" class="btn btn-default btn-sm" id="maximise-output" title="Maximise"><img aria-hidden="true" src="<%- require('../static/images/maximise-16x16.png') %>" alt="Maximise Icon"/> Max</button>
</div>
<span class="float-right">
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-to-file" data-toggle="tooltip" title="Save output to file">
<i class="material-icons">save</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="copy-output" data-toggle="tooltip" title="Copy raw output to the clipboard">
<i class="material-icons">content_copy</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Move output to input">
<i class="material-icons">loop</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="undo-switch" data-toggle="tooltip" title="Undo" disabled="disabled">
<i class="material-icons">undo</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="maximise-output" data-toggle="tooltip" title="Maximise output pane">
<i class="material-icons">fullscreen</i>
</button>
</span>
<div class="io-info" id="output-info"></div>
<div class="io-info" id="output-selection-info"></div>
<span id="stale-indicator" title="The output is stale.&#10;The input or recipe has changed since this output was generated. Bake again to get the new value.">&#x1F551;</span>
<button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true">
<svg width="22" height="22" viewBox="0 0 24 24">
<path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />
</svg>
</button>
<span id="stale-indicator" class="hidden" data-toggle="tooltip" title="The output is stale. The input or recipe has changed since this output was generated. Bake again to get the new value.">
<i class="material-icons">access_time</i>
</span>
</div>
<div class="textarea-wrapper">
<div id="output-highlighter" class="no-select"></div>
@ -267,14 +295,16 @@
<div id="output-file">
<div class="file-overlay"></div>
<div style="position: relative; height: 100%;">
<div class="card">
<div class="io-card card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
<div class="card-body">
Size: <span id="output-file-size"></span><br>
<button id="output-file-download" type="button" class="btn btn-primary">Download</button>
<button id="output-file-download" type="button" class="btn btn-primary btn-outline">Download</button>
<div class="input-group">
<span class="input-group-btn">
<button id="output-file-slice" type="button" class="btn btn-default" title="View slice">&#x1f50d;</button>
<button id="output-file-slice" type="button" class="btn btn-secondary bmd-btn-icon" title="View slice">
<i class="material-icons">search</i>
</button>
</span>
<input type="number" class="form-control" id="output-file-slice-from" placeholder="From" value="0" step="1024" min="0">
<div class="input-group-addon">to</div>
@ -295,19 +325,23 @@
</div>
<div class="modal" id="save-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/save-22x22.png') %>" alt="Save Icon"/>
<h4 class="modal-title">Save recipe</h4>
<h5 class="modal-title">Save recipe</h5>
</div>
<div class="modal-body">
<div class="form-group">
<label for="save-text">Save your recipe to local storage or copy the following string to load later</label>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#chef-format" role="tab" data-toggle="tab">Chef format</a></li>
<li role="presentation"><a href="#clean-json" role="tab" data-toggle="tab">Clean JSON</a></li>
<li role="presentation"><a href="#compact-json" role="tab" data-toggle="tab">Compact JSON</a></li>
<li class="nav-item">
<a class="nav-link active" href="#chef-format" role="tab" data-toggle="tab">Chef format</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#clean-json" role="tab" data-toggle="tab">Clean JSON</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#compact-json" role="tab" data-toggle="tab">Compact JSON</a>
</li>
</ul>
<div class="tab-content" id="save-texts">
<div role="tabpanel" class="tab-pane active" id="chef-format">
@ -322,22 +356,27 @@
</div>
</div>
<div class="form-group">
<label for="save-name">Recipe name</label>
<input type="text" class="form-control" id="save-name" placeholder="Recipe name">
<label for="save-name" class="bmd-label-floating">Recipe name</label>
<input type="text" class="form-control" id="save-name">
<span class="bmd-help">Save your recipe to local storage using this name, or copy it to load later</span>
</div>
</div>
<div class="modal-footer" id="save-footer">
<button type="button" class="btn btn-primary" id="save-button" data-dismiss="modal">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Done</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Done</button>
</div>
<div class="modal-body">
<div class="form-group" id="save-link-group">
<label>Data link</label>
<h6 style="display: inline">Data link</h6>
<div class="save-link-options">
<input type="checkbox" id="save-link-recipe-checkbox" checked> <label for="save-link-recipe-checkbox"> Include recipe </label>
<input type="checkbox" id="save-link-input-checkbox" checked> <label for="save-link-input-checkbox"> Include input </label>
<label class="checkbox-inline">
<input type="checkbox" id="save-link-recipe-checkbox" checked> Include recipe
</label>
<label class="checkbox-inline">
<input type="checkbox" id="save-link-input-checkbox" checked> Include input
</label>
</div>
<br>
<br><br>
<a id="save-link" style="word-wrap: break-word;"></a>
</div>
</div>
@ -346,82 +385,63 @@
</div>
<div class="modal" id="load-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/open_yellow-24x24.png') %>" alt="Open Icon"/>
<h4 class="modal-title">Load recipe</h4>
<h5 class="modal-title">Load recipe</h5>
</div>
<div class="modal-body">
<div class="form-group">
<label for="load-name">Load your recipe from local storage by selecting its name from the drop-down</label>
<label for="load-name" class="bmd-label-floating">Recipe name</label>
<select class="form-control" id="load-name"></select>
<span class="bmd-help">Load your recipe from local storage by selecting its name from the drop-down</span>
</div>
<div class="form-group">
<label for="load-text">Load your recipe by pasting it in the box below</label>
<label for="load-text" class="bmd-label-floating">Recipe</label>
<textarea class="form-control" id="load-text" rows="5"></textarea>
<span class="bmd-help">Load your recipe by pasting it into this box</span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="load-button" data-dismiss="modal">Load</button>
<button type="button" class="btn btn-danger" id="load-delete-button">Delete</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<div class="modal" id="options-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/settings-22x22.png') %>" alt="Settings Icon"/>
<h4 class="modal-title">Options</h4>
<h5 class="modal-title">Options</h5>
</div>
<div class="modal-body" id="options-body">
<p style="font-weight: bold">Please note that these options will persist between sessions.</p>
<div class="option-item">
<select option="theme" id="theme">
<div class="form-group option-item">
<label for="theme" class="bmd-label-floating"> Theme (only supported in modern browsers)</label>
<select class="form-control" option="theme" id="theme">
<option value="classic">Classic</option>
<option value="dark">Dark</option>
<option value="geocities">GeoCities</option>
</select>
<label for="theme"> Theme (only supported in modern browsers)</label>
</div>
<div class="option-item">
<input type="checkbox" option="updateUrl" id="updateUrl" checked />
<label for="updateUrl"> Update the URL when the input or recipe changes</label>
<div class="form-group option-item">
<label for="errorTimeout" class="bmd-label-floating">Operation error timeout in ms (0 for never)</label>
<input type="number" class="form-control" option="errorTimeout" id="errorTimeout">
</div>
<div class="option-item">
<input type="checkbox" option="showHighlighter" id="showHighlighter" checked />
<label for="showHighlighter"> Highlight selected bytes in output and input (when possible)</label>
<div class="form-group option-item">
<label for="ioDisplayThreshold" class="bmd-label-floating">Size threshold for treating the input and output as a file (KiB)</label>
<input type="number" class="form-control" option="ioDisplayThreshold" id="ioDisplayThreshold">
</div>
<div class="option-item">
<input type="checkbox" option="treatAsUtf8" id="treatAsUtf8" checked />
<label for="treatAsUtf8"> Treat output as UTF-8 if possible</label>
</div>
<div class="option-item">
<input type="checkbox" option="wordWrap" id="wordWrap" checked />
<label for="wordWrap"> Word wrap the input and output</label>
</div>
<div class="option-item">
<input type="checkbox" option="showErrors" id="showErrors" checked />
<label for="showErrors"> Operation error reporting (recommended)</label>
</div>
<div class="option-item">
<input type="checkbox" option="useMetaKey" id="useMetaKey" />
<label for="useMetaKey"> Use meta key for keybindings (Windows ⊞/Command ⌘)</label>
</div>
<div class="option-item">
<input type="number" option="errorTimeout" id="errorTimeout" />
<label for="errorTimeout"> Operation error timeout in ms (0 for never)</label>
</div>
<div class="option-item">
<input type="number" option="ioDisplayThreshold" id="ioDisplayThreshold" />
<label for="ioDisplayThreshold"> Size threshold for treating the input and output as a file (KiB)</label>
</div>
<div class="option-item">
<select option="logLevel" id="logLevel">
<div class="form-group option-item">
<label for="logLevel" class="bmd-label-floating">Console logging level</label>
<select class="form-control" option="logLevel" id="logLevel">
<option value="silent">Silent</option>
<option value="error">Error</option>
<option value="warn">Warn</option>
@ -429,23 +449,70 @@
<option value="debug">Debug</option>
<option value="trace">Trace</option>
</select>
<label for="logLevel"> Console logging level</label>
</div>
<div class="checkbox option-item">
<label for="updateUrl">
<input type="checkbox" option="updateUrl" id="updateUrl" checked>
Update the URL when the input or recipe changes
</label>
</div>
<div class="checkbox option-item">
<label for="showHighlighter">
<input type="checkbox" option="showHighlighter" id="showHighlighter" checked>
Highlight selected bytes in output and input (when possible)
</label>
</div>
<div class="checkbox option-item">
<label for="treatAsUtf8">
<input type="checkbox" option="treatAsUtf8" id="treatAsUtf8" checked>
Treat output as UTF-8 if possible
</label>
</div>
<div class="checkbox option-item">
<label for="wordWrap">
<input type="checkbox" option="wordWrap" id="wordWrap" checked>
Word wrap the input and output
</label>
</div>
<div class="checkbox option-item">
<label for="showErrors">
<input type="checkbox" option="showErrors" id="showErrors" checked>
Operation error reporting (recommended)
</label>
</div>
<div class="checkbox option-item">
<label for="useMetaKey">
<input type="checkbox" option="useMetaKey" id="useMetaKey">
Use meta key for keybindings (Windows ⊞/Command ⌘)
</label>
</div>
<div class="checkbox option-item">
<label for="autoMagic">
<input type="checkbox" option="autoMagic" id="autoMagic">
Attempt to detect encoded data automagically
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="reset-options">Reset options to default</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-secondary" id="reset-options">Reset options to default</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="modal" id="favourites-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/favourite-24x24.png') %>" alt="Star Icon"/>
<h4 class="modal-title">Edit Favourites</h4>
<h5 class="modal-title">Edit Favourites</h5>
</div>
<div class="modal-body" id="favourites-body">
<ul>
@ -459,7 +526,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" id="reset-favourites">Reset favourites to default</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="reset-favourites">Reset favourites to default</button>
<button type="button" class="btn btn-success" data-dismiss="modal" id="save-favourites">Save</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
</div>
@ -468,11 +535,10 @@
</div>
<div class="modal" id="support-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/help-22x22.png') %>" alt="Question Mark Icon"/>
<h4 class="modal-title">CyberChef - The Cyber Swiss Army Knife</h4>
<h5 class="modal-title">CyberChef - The Cyber Swiss Army Knife</h5>
</div>
<div class="modal-body">
<img aria-hidden="true" class="about-img-left" src="<%- require('../static/images/cyberchef-128x128.png') %>" alt="CyberChef Logo"/>
@ -482,115 +548,120 @@
</p>
<p>&copy; Crown Copyright 2016.</p>
<p>Released under the Apache Licence, Version 2.0.</p>
<p>
<a href="https://gitter.im/gchq/CyberChef">
<img src="<%- require('../static/images/gitter-badge.svg') %>">
</a>
</p>
<br>
<br>
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#faqs" aria-controls="profile" role="tab" data-toggle="tab">
<img aria-hidden="true" src="<%- require('../static/images/help-16x16.png') %>" alt="Question Mark Icon"/>
<p><a href="https://gitter.im/gchq/CyberChef">
<img src="<%- require('../static/images/gitter-badge.svg') %>">
</a></p>
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" href="#faqs" aria-controls="profile" role="tab" data-toggle="tab">
FAQs
</a></li>
<li role="presentation"><a href="#report-bug" aria-controls="messages" role="tab" data-toggle="tab">
<img aria-hidden="true" src="<%- require('../static/images/bug-16x16.png') %>" alt="Bug Icon"/>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" href="#report-bug" aria-controls="messages" role="tab" data-toggle="tab">
Report a bug
</a></li>
<li role="presentation"><a href="#about" aria-controls="messages" role="tab" data-toggle="tab">
<img aria-hidden="true" src="<%- require('../static/images/speech-16x16.png') %>" alt="Speech Balloon Icon"/>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" href="#about" aria-controls="messages" role="tab" data-toggle="tab">
About
</a></li>
<li role="presentation"><a href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
<img aria-hidden="true" src="<%- require('../static/images/code-16x16.png') %>" alt="List Icon"/>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
Keybindings
</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="faqs">
<br>
<blockquote>
<a data-toggle="collapse" data-target="#faq-examples">
What sort of things can I do with CyberChef?
</a>
</blockquote>
<div class="collapse" id="faq-examples">
<p>There are around 200 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
<ul>
<li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
<li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>
<li><a href="#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy">Parse a Teredo IPv6 address</a></li>
<li><a href="#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw">Convert data from a hexdump, then decompress</a></li>
<li><a href="#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ">Decrypt and disassemble shellcode</a></li>
<li><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Display multiple timestamps as full dates</a></li>
<li><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA">Carry out different operations on data of different types</a></li>
<li><a href="#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ">Use parts of the input as arguments to operations</a></li>
</ul>
</div>
<blockquote>
<a data-toggle="collapse" data-target="#faq-load-files">
Can I load input directly from files?
</a>
</blockquote>
<div class="collapse" id="faq-load-files">
<p>Yes! Just drag your file over the input box and drop it.</p>
<p>CyberChef can handle files up to around 500MB (depending on your browser), however some of the operations may take a very long time to run over this much data.</p>
<p>If the output is larger than a certain threshold (default 1MiB), it will be presented to you as a file available for download. Slices of the file can be viewed in the output if you need to inspect them.</p>
</div>
<blockquote>
<a data-toggle="collapse" data-target="#faq-fork">
How do I run operation X over multiple inputs at once?
</a>
</blockquote>
<div class="collapse" id="faq-fork">
<p>Maybe you have 10 timestamps that you want to parse or 16 encoded strings that all have the same key.</p>
<p>The 'Fork' operation (found in the 'Flow control' category) splits up the input line by line and runs all subsequent operations on each line separately. Each output is then displayed on a separate line. These delimiters can be changed, so if your inputs are separated by commas, you can change the split delimiter to a comma instead.</p>
<p><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Click here</a> for an example.</p>
</div>
</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="faqs">
<br>
<a class="btn btn-primary" data-toggle="collapse" data-target="#faq-examples">
What sort of things can I do with CyberChef?
</a>
<div class="collapse" id="faq-examples">
<p>There are around 200 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
<ul>
<li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
<li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>
<li><a href="#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy">Parse a Teredo IPv6 address</a></li>
<li><a href="#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw">Convert data from a hexdump, then decompress</a></li>
<li><a href="#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ">Decrypt and disassemble shellcode</a></li>
<li><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Display multiple timestamps as full dates</a></li>
<li><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA">Carry out different operations on data of different types</a></li>
<li><a href="#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ">Use parts of the input as arguments to operations</a></li>
</ul>
</div>
<div role="tabpanel" class="tab-pane" id="report-bug">
<br>
<p>If you find a bug in CyberChef, please raise an issue in our GitHub repository explaining it in as much detail as possible. Copy and include the following information if relevant.</p>
<br>
<pre id="report-bug-info"></pre>
<br>
<a class="btn btn-primary" href="https://github.com/gchq/CyberChef/issues/new" role="button">Raise issue on GitHub</a>
<br>
<a class="btn btn-primary" data-toggle="collapse" data-target="#faq-load-files">
Can I load input directly from files?
</a>
<div class="collapse" id="faq-load-files">
<p>Yes! Just drag your file over the input box and drop it.</p>
<p>CyberChef can handle files up to around 500MB (depending on your browser), however some of the operations may take a very long time to run over this much data.</p>
<p>If the output is larger than a certain threshold (default 1MiB), it will be presented to you as a file available for download. Slices of the file can be viewed in the output if you need to inspect them.</p>
</div>
<div role="tabpanel" class="tab-pane" id="about" style="padding: 20px;">
<h5><strong>What</strong></h5>
<p>A simple, intuitive web app for analysing and decoding data without having to deal with complex tools or programming languages. CyberChef encourages both technical and non-technical people to explore data formats, encryption and compression.</p><br>
<br>
<h5><strong>Why</strong></h5>
<p>Digital data comes in all shapes, sizes and formats in the modern world CyberChef helps to make sense of this data all on one easy-to-use platform.</p><br>
<h5><strong>How</strong></h5>
<p>The interface is designed with simplicity at its heart. Complex techniques are now as trivial as drag-and-drop. Simple functions can be combined to build up a "recipe", potentially resulting in complex analysis, which can be shared with other users and used with their input.</p>
<p>For those comfortable writing code, CyberChef is a quick and efficient way to prototype solutions to a problem which can then be scripted once proven to work.</p><br>
<h5><strong>Who</strong></h5>
<p>It is expected that CyberChef will be useful for cybersecurity and antivirus companies. It should also appeal to the academic world and any individuals or companies involved in the analysis of digital data, be that software developers, analysts, mathematicians or casual puzzle solvers.</p><br>
<h5><strong>Aim</strong></h5>
<p>It is hoped that by releasing CyberChef through <a href="https://github.com/gchq/CyberChef">GitHub</a>, contributions can be added which can be rolled out into future versions of the tool.</p><br>
<br>
<p>There are around 200 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
<p>Its the Cyber Swiss Army Knife.</p>
<a class="btn btn-primary" data-toggle="collapse" data-target="#faq-fork">
How do I run operation X over multiple inputs at once?
</a>
<div class="collapse" id="faq-fork">
<p>Maybe you have 10 timestamps that you want to parse or 16 encoded strings that all have the same key.</p>
<p>The 'Fork' operation (found in the 'Flow control' category) splits up the input line by line and runs all subsequent operations on each line separately. Each output is then displayed on a separate line. These delimiters can be changed, so if your inputs are separated by commas, you can change the split delimiter to a comma instead.</p>
<p><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Click here</a> for an example.</p>
</div>
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;">
<table class="table table-condensed table-bordered table-hover" id="keybList"></table>
<br>
<a class="btn btn-primary" data-toggle="collapse" data-target="#faq-magic">
How does the 'Magic' operation work?
</a>
<div class="collapse" id="faq-magic">
<p>The 'Magic' operation uses a number of methods to detect encoded data and the operations which can be used to make sense of it. A technical description of these methods can be found <a href="https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic">here</a>.</p>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="report-bug">
<br>
<p>If you find a bug in CyberChef, please raise an issue in our GitHub repository explaining it in as much detail as possible. Copy and include the following information if relevant.</p>
<br>
<pre id="report-bug-info"></pre>
<br>
<a class="btn btn-primary" href="https://github.com/gchq/CyberChef/issues/new" role="button">Raise issue on GitHub</a>
</div>
<div role="tabpanel" class="tab-pane" id="about" style="padding: 20px;">
<h5><strong>What</strong></h5>
<p>A simple, intuitive web app for analysing and decoding data without having to deal with complex tools or programming languages. CyberChef encourages both technical and non-technical people to explore data formats, encryption and compression.</p><br>
<h5><strong>Why</strong></h5>
<p>Digital data comes in all shapes, sizes and formats in the modern world CyberChef helps to make sense of this data all on one easy-to-use platform.</p><br>
<h5><strong>How</strong></h5>
<p>The interface is designed with simplicity at its heart. Complex techniques are now as trivial as drag-and-drop. Simple functions can be combined to build up a "recipe", potentially resulting in complex analysis, which can be shared with other users and used with their input.</p>
<p>For those comfortable writing code, CyberChef is a quick and efficient way to prototype solutions to a problem which can then be scripted once proven to work.</p><br>
<h5><strong>Who</strong></h5>
<p>It is expected that CyberChef will be useful for cybersecurity and antivirus companies. It should also appeal to the academic world and any individuals or companies involved in the analysis of digital data, be that software developers, analysts, mathematicians or casual puzzle solvers.</p><br>
<h5><strong>Aim</strong></h5>
<p>It is hoped that by releasing CyberChef through <a href="https://github.com/gchq/CyberChef">GitHub</a>, contributions can be added which can be rolled out into future versions of the tool.</p><br>
<br>
<p>There are around 200 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
<p>Its the Cyber Swiss Army Knife.</p>
</div>
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;">
<table class="table table-condensed table-bordered table-hover" id="keybList"></table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
<a href="https://github.com/gchq/CyberChef">
<img aria-hidden="true" style="position: absolute; top: 0; right: 0; border: 0;" src="<%- require('../static/images/fork_me.png') %>" alt="Fork me on GitHub">
@ -600,19 +671,17 @@
</div>
<div class="modal" id="confirm-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="confirm-title"></h4>
<h5 class="modal-title" id="confirm-title"></h5>
</div>
<div class="modal-body" id="confirm-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-success" id="confirm-yes">
<img aria-hidden="true" src="<%- require('../static/images/thumb_up-16x16.png') %>" alt="Thumbs Up"/>
Yes
</button>
<button type="button" class="btn btn-danger" id="confirm-no" data-dismiss="modal">
<img aria-hidden="true" src="<%- require('../static/images/thumb_down-16x16.png') %>" alt="Thumbs Down"/>
No
</button>
</div>

View File

@ -9,8 +9,9 @@ import "./stylesheets/index.js";
// Libs
import "babel-polyfill";
import "bootstrap";
import "bootstrap-switch";
import "arrive";
import "snackbarjs";
import "bootstrap-material-design";
import "bootstrap-colorpicker";
import moment from "moment-timezone";
import * as CanvasComponents from "../core/lib/CanvasComponents";
@ -50,7 +51,8 @@ function main() {
theme: "classic",
useMetaKey: false,
ioDisplayThreshold: 512,
logLevel: "info"
logLevel: "info",
autoMagic: true,
};
document.removeEventListener("DOMContentLoaded", main, false);

Binary file not shown.

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" /></svg>

After

Width:  |  Height:  |  Size: 719 B

View File

@ -1,22 +0,0 @@
/**
* Alert styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
#alert {
position: fixed;
width: 30%;
margin: 30px auto;
top: 10px;
left: 0;
right: 0;
z-index: 2000;
display: none;
}
#alert a {
text-decoration: underline;
}

View File

@ -7,7 +7,7 @@
*/
.operation {
cursor: pointer;
cursor: grab;
padding: 10px;
list-style-type: none;
position: relative;
@ -18,110 +18,154 @@
border-right: none;
}
.arg-group {
display: table;
width: 100%;
margin-top: 10px;
#rec-list .operation {
padding: 14px;
}
.arg-group-text {
display: block;
.op-title {
font-weight: var(--op-title-font-weight);
}
.inline-args {
float: left;
width: auto;
margin-right: 30px;
height: 34px;
.ingredients {
display: grid;
grid-template-columns: auto auto auto;
grid-column-gap: 14px;
}
.inline-args input[type="checkbox"] {
margin-top: 10px;
.ingredients > div {
grid-column: 1 / span 3;
}
.inline-args input[type="number"] {
width: 100px;
.ingredients > div.inline {
grid-column: unset;
}
.arg-title {
font-weight: var(--arg-title-font-weight);
.ingredients .form-group {
margin-top: 1rem;
padding-top: 0;
}
.arg-input {
display: table-cell;
width: 100%;
padding: 6px 12px;
vertical-align: middle;
height: var(--arg-input-height);
font-size: var(--arg-input-font-size);
line-height: var(--arg-input-line-height);
color: var(--arg-font-colour);
background-color: var(--arg-background);
border: 1px solid var(--arg-border-colour);
.arg {
font-family: var(--fixed-width-font-family);
text-overflow: ellipsis;
}
.short-string {
width: 150px;
}
select {
display: block;
padding: 6px 8px;
height: 34px;
border: 1px solid var(--arg-border-colour);
background-color: var(--arg-background);
color: var(--arg-font-colour);
}
.arg[disabled] {
cursor: not-allowed;
opacity: 1;
background-color: var(--arg-disabled-background);
select.arg {
font-family: var(--primary-font-family);
min-width: 100px;
}
textarea.arg {
width: 100%;
min-height: 50px;
height: 70px;
margin-top: 5px;
border: 1px solid var(--arg-border-colour);
min-height: 74px;
resize: vertical;
color: var(--arg-font-colour);
}
div.toggle-string {
flex: 1;
}
.operation [class^='bmd-label'],
.operation [class*=' bmd-label'] {
top: 13px !important;
left: 12px;
z-index: 10;
}
.operation label,
.operation .checkbox label {
color: var(--arg-label-colour);
}
.operation .is-focused [class^='bmd-label'],
.operation .is-focused [class*=' bmd-label'],
.operation .is-focused [class^='bmd-label'],
.operation .is-focused [class*=' bmd-label'],
.operation .is-focused label,
.operation .checkbox label:hover {
color: #1976d2;
}
.operation .form-control {
padding: 20px 12px 6px 12px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-image: none;
background-color: var(--arg-background);
font-family: var(--fixed-width-font-family);
background-position-y: 100%, 100%;
color: var(--arg-font-colour);
}
.arg-label {
display: table-cell;
width: 1px;
padding-right: 10px;
font-weight: normal;
vertical-align: middle;
white-space: pre;
.operation .form-control:hover {
background-image:
linear-gradient(to top, #1976d2 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);
filter: brightness(97%);
}
.editable-option {
position: relative;
display: inline-block;
.operation .form-control:focus {
background-color: var(--arg-background);
background-image:
linear-gradient(to top, #1976d2 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);
filter: brightness(100%);
}
.editable-option-select {
min-width: 250px;
.operation .bmd-form-group.is-filled label.bmd-label-floating,
.operation .bmd-form-group.is-focused label.bmd-label-floating {
top: 4px !important;
left: 12px;
}
.editable-option-input {
position: absolute;
top: 1px;
left: 1px;
width: calc(100% - 20px);
height: calc(100% - 2px) !important;
border: none !important;
.operation .bmd-form-group .bmd-help {
margin-top: -17px;
}
button.dropdown-toggle {
background-color: var(--secondary-background-colour);
.input-group .form-control {
border-top-left-radius: 4px !important;
}
.input-group-append button {
border-top-right-radius: 4px;
background-color: var(--arg-background) !important;
margin: unset;
}
.input-group-append button:hover {
filter: brightness(97%);
}
.editable-option-menu {
height: auto;
max-height: 350px;
overflow-x: hidden;
}
.editable-option-menu .dropdown-item {
padding: 0.3rem 1rem 0.3rem 1rem;
min-height: 1.6rem;
max-width: 20rem;
}
.ingredients .dropdown-toggle-split {
height: 41px !important;
}
.boolean-arg {
height: 46px;
}
.boolean-arg .checkbox {
height: 100%;
}
.boolean-arg .checkbox label {
height: 100%;
display: flex;
align-items: center;
}
.boolean-arg .checkbox-decorator {
top: 13px;
}
.register-list {
@ -132,8 +176,9 @@ button.dropdown-toggle {
.op-icon {
float: right;
margin-left: 10px;
margin-top: 3px;
color: #f44336;
font-size: 18px;
cursor: pointer;
}
.recip-icons {
@ -143,33 +188,28 @@ button.dropdown-toggle {
height: 16px;
}
.recip-icon {
.recip-icons i {
margin-right: 10px;
vertical-align: baseline;
float: right;
font-size: 18px;
cursor: pointer;
}
.disable-icon {
width: 16px;
height: 16px;
margin-top: -1px;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAACfElEQVQ4y6WTPWgTYRjHf8nlfVvuoDVIP4Yuki4WHKoUqggFRUTsUEGkVG2hmCq6OnTwIxYHB+eijZOKdLNDW1pKKyGigh8dBHUJElxyBgx3vEnukvdyDrUhRXDxGR+e/+/583xEwjDkfyIGwNVTzURm4tYAMA6MAoN/0tvAMrA48uL+l2bx4w0iYRjSuHKC6OnTZLqHk8CcaZq9bW1tSCkBqNVq+L5PpVIpAHdGfr5LN9bXiT7Z2nGgteb1/qFkLBJZ6OjowHEc8vk8pVIJgHg8TldXF52dnb2u6y5s7R/iuF5JSyAKkLl4eyAMwznLsrBtm1wu99Z13amk+BFJih8R13WXANrb27EsizAM5zIXbw+wC9Baj0spe5VSFAqFt4ZhXJ6ufXuK55E5cDKVSCTGenp6yGazKKWQUvZqrcebgCAIRqWUOI6DEOLR1K8POapVMgfPpoC7u2LLspYcx0FKSRAEo60OBg3DwPd9Jr5vPqWvj8zh83vEwL2J75vnfN/HMAy01oPNNQZBQBAEO1OvVsl0D/8lTuZfpYDd7gRBQKuD7XK5jGmarB679PIv8deVFJUKq8cuTZqmSblcRmu93QpYVkohhMCyrLE94n2/UlSrbJy5kRBCXBNCoJRCa73cClh0XbfgeR6WZZHNZunv719KvnmeYnWVVxdmJ2Ox2DMhxFHP83Bdt6C1XgR2LvHzQDvvb84npZQL8Xgc0zSJRqN7br7RaFCpVCiVStRqtZmhh9fTh754TQdMr82nPc+bsW27UCwWUUpRr9ep1+sopSgWi9i2XfA8b2Z6bT6ttabp4GMi0uz0aXbhn890+MFM85mO5MIdwP/Eb1pMUCdctYRzAAAAAElFTkSuQmCC') no-repeat;
color: #9e9e9e;
}
.disable-icon-selected {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACFUlEQVR4XqWTP0tbURjGn9zY3mjBwsUhBQtS6XKxiNypIGZJ6SKYUYdaKWg7OrrE3pYO+Qit3dpFuuQO6lI7Z4nESQdjlJbkJh0MksSb3Jvk9H0gjZFu9YWH83LO7zn/3nNCSincJobAeP1sEDBFi6J50UyPy4l2RNuioz756Ts0tt1OB4jH2a52Ne2HGh9PwrJm2EcxZx/HyPRYMDgB2u02/N3d1c7w8BZMM1ptNJBPp3GwsUExB/s4RoYsPf0JOkFgdoH34YkJ/D48xC/HyTTOzl5ayWSIktwxqlVo0SjIkKWnP0Hg+4swjGitVMJFNpu5o+svptfXv6DZBDIZezoWS3Db3A0ZsvRcH8H354dGR9EoFHA3EvlorqycwvOAXM4G8Pav+f7YmEOGLD1gsIzl54+V+vBK/Yw9ZAv1LQW1FrdFSnKVfQTK5liPUfRI9I8ArqiPjLAF9vcHVybyzlpasgcZeq7voNXKNSsV3DMMXB4fp/8xLyzYuLri2DIZsvQM3sFOzXURiUR4zsQNcyrFleFVKpNyP2/IkKVnsArbF65bbkqplJSJZrl5x5qbs7G3h3artSyV+arr+lMyZOnpP2Wp6ZFos3R+vvUgCGDNzgKalkA4rECIr07662J2i0X4nrfJJ33jJT6Zmvpcr9XWCicn5WI+j7rrAmKgmLOPY2TI0sPgb8TBZOi/PpN1qnDr7/wH3jxgB/FKIXkAAAAASUVORK5CYII=') no-repeat;
color: #f44336;
}
.breakpoint {
float: right;
width: 14px;
height: 14px;
background-color: #eee;
border: 1px solid #aaa;
color: #9e9e9e;
}
.breakpoint-selected {
background: #eee url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAXVBMVEXIUkvzUVHzTEzzn5785eXrbW24BgbzWVnze3vzVVXzY2Pyion509PzbW3zXV1UMxj0l5f1srKbRTRgOxzJDg796ur74ODfIyP5zs6LLx3pNTXYGxuxdkVZNhn////sCC1eAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAABWSURBVBjTnc+7EoAgDERRTOQVxMEZFAf//z8FjAUFDbfb060QU2FwxngimxnCea3bjegSgz+0tguAfBgIy64QGfZQdg91dgAtqUZgnfz6IacYVWvu2AvR4wNAv0nxrAAAAABJRU5ErkJggg==') no-repeat -2px -2px;
color: #f44336;
}
.break {
@ -178,30 +218,40 @@ button.dropdown-toggle {
border-color: var(--breakpoint-border-colour) !important;
}
.break .form-group * { color: var(--breakpoint-font-colour) !important; }
.selected-op {
color: var(--selected-operation-font-color) !important;
background-color: var(--selected-operation-bg-colour) !important;
border-color: var(--selected-operation-border-colour) !important;
}
.selected-op .form-group * { color: var(--selected-operation-font-color) !important; }
.flow-control-op {
color: var(--fc-operation-font-colour) !important;
background-color: var(--fc-operation-bg-colour) !important;
border-color: var(--fc-operation-border-colour) !important;
}
.flow-control-op .form-group *:not(.arg) { color: var(--fc-operation-font-colour) }
.flow-control-op.break {
color: var(--fc-breakpoint-operation-font-colour) !important;
background-color: var(--fc-breakpoint-operation-bg-colour) !important;
border-color: var(--fc-breakpoint-operation-border-colour) !important;
}
.flow-control-op.break .form-group * { color: var(--fc-breakpoint-operation-font-colour) !important; }
.disabled {
color: var(--disabled-font-colour) !important;
background-color: var(--disabled-bg-colour) !important;
border-color: var(--disabled-border-colour) !important;
}
.disabled .form-group * { color: var(--disabled-font-colour) !important; }
.break .register-list {
color: var(--fc-breakpoint-operation-font-colour) !important;
background-color: var(--fc-breakpoint-operation-border-colour) !important;

View File

@ -7,16 +7,25 @@
*/
:root {
--title-height: 43px;
--title-height: 48px;
}
.title {
padding: 10px;
padding: 8px;
padding-left: 12px;
padding-right: 12px;
height: var(--title-height);
border-bottom: 1px solid var(--primary-border-colour);
font-weight: var(--title-weight);
font-size: var(--title-size);
color: var(--title-colour);
background-color: var(--title-background-colour);
line-height: calc(var(--title-height) - 14px);
}
.title>span,
.title>.btn {
margin-top: -4px;
}
.list-area {
@ -29,7 +38,7 @@
padding: 0;
}
.card {
.io-card.card {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s;
width: 400px;
@ -42,13 +51,14 @@
color: var(--primary-font-colour);
line-height: 30px;
background-color: var(--primary-background-colour);
flex-direction: row;
}
.card:hover {
.io-card.card:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
.card>img {
.io-card.card>img {
float: left;
width: 128px;
height: 128px;
@ -56,13 +66,13 @@
margin-top: 11px;
}
.card-body .close {
.io-card.card .card-body .close {
position: absolute;
right: 10px;
top: 10px;
top: 4px;
}
.card-body {
.io-card.card .card-body {
float: left;
padding: 16px;
width: 250px;
@ -72,12 +82,12 @@
user-select: text;
}
.card-body>.btn {
margin-bottom: 15px;
.io-card.card .card-body>.btn {
margin-bottom: 5px;
margin-top: 5px;
}
.card input[type=number] {
.io-card.card input[type=number] {
padding-right: 6px;
padding-left: 6px;
}

View File

@ -19,7 +19,6 @@
@import "./preloader.css";
/* Components */
@import "./components/_alert.css";
@import "./components/_button.css";
@import "./components/_list.css";
@import "./components/_operation.css";

View File

@ -10,8 +10,7 @@
import "highlight.js/styles/vs.css";
/* Frameworks */
import "./vendors/bootstrap.less";
import "bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css";
import "./vendors/bootstrap.scss";
import "bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css";
/* CyberChef styles */

View File

@ -14,11 +14,10 @@
border-bottom: 1px solid var(--primary-border-colour);
color: var(--banner-font-colour);
background-color: var(--banner-bg-colour);
margin: 0;
}
#banner img {
margin-bottom: 2px;
margin-left: 8px;
#banner i {
vertical-align: middle;
padding-right: 10px;
}

View File

@ -7,8 +7,7 @@
*/
:root {
--controls-height: 120px;
--controls-division: 65%;
--controls-height: 75px;
}
#controls {
@ -17,49 +16,30 @@
height: var(--controls-height);
bottom: 0;
padding: 10px;
padding-top: 12px;
border-top: 1px solid var(--primary-border-colour);
background-color: var(--secondary-background-colour);
}
#operational-controls {
width: var(--controls-division);
float: left;
#auto-bake-label {
display: inline-block;
width: 100px;
padding: 0;
margin: 0;
text-align: center;
color: var(--primary-font-colour);
font-size: 14px;
cursor: pointer;
}
#bake-group {
display: table;
width: 100%;
#auto-bake-label .checkbox-decorator {
position: relative;
}
#bake {
display: table-cell;
width: 100%;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
box-shadow: none;
}
#auto-bake-label {
display: table-cell;
padding: 1px;
line-height: 1.35;
width: 60px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 1px solid transparent;
}
#auto-bake-label:hover {
border-left-color: var(--btn-success-hover-border-colour);
}
#auto-bake-label div {
font-size: 10px;
padding: 2px;
}
#extra-controls {
float: right;
width: calc(100% - var(--controls-division));
padding-left: 10px;
#controls .btn {
border-radius: 30px;
}

View File

@ -32,7 +32,7 @@
.textarea-wrapper {
position: absolute;
top: 43px;
top: var(--title-height);
bottom: 0;
width: 100%;
overflow: hidden;
@ -103,18 +103,13 @@
display: none;
}
.io-btn-group {
float: right;
margin-top: -4px;
}
.io-info {
margin-right: 20px;
margin-top: -4px;
margin-top: 1px;
float: right;
height: 30px;
text-align: right;
line-height: 10px;
line-height: 12px;
font-family: var(--fixed-width-font-family);
font-weight: normal;
font-size: 8pt;
@ -131,20 +126,47 @@
}
#stale-indicator {
visibility: hidden;
transition: all 0.3s;
opacity: 1;
visibility: visibile;
transition: margin 0s, opacity 0.3s;
margin-left: 5px;
font-size: larger;
font-weight: normal;
cursor: help;
}
#stale-indicator i {
vertical-align: middle;
margin-bottom: 5px;
}
#output-loader .loading-msg {
opacity: 1;
font-family: var(--primary-font-family);
line-height: var(--primary-line-height);
color: var(--primary-font-colour);
top: 50%;
transition: all 0.5s ease;
}
#magic {
opacity: 1;
visibility: visibile;
transition: margin 0s 0.3s, opacity 0.3s 0.3s, visibility 0.3s 0.3s;
margin-left: 5px;
margin-bottom: 5px;
}
#magic.hidden,
#stale-indicator.hidden {
visibility: hidden;
transition: opacity 0.3s, margin 0.3s 0.3s, visibility 0.3s;
opacity: 0;
}
#magic.hidden {
margin-left: -32px;
}
#magic svg path {
fill: var(--primary-font-colour);
}

View File

@ -6,34 +6,12 @@
* @license Apache-2.0
*/
.option-item .bootstrap-switch {
margin: 15px 10px;
.modal-content {
background-color: var(--primary-background-colour);
}
.option-item button {
margin: 10px;
}
.option-item label {
font-weight: normal;
}
.option-item input[type=number] {
margin: 15px 10px;
width: 80px;
height: 28px;
padding: 3px 10px;
vertical-align: middle;
font-size: calc(var(--arg-input-font-size) - 1px);
line-height: var(--arg-input-line-height);
color: var(--arg-font-colour);
background-color: var(--arg-background);
border: 1px solid var(--primary-border-colour);
}
.option-item select {
margin: 10px;
display: inline-block;
.option-item {
margin-bottom: 20px;
}
#edit-favourites-list {
@ -60,11 +38,15 @@
margin: 10px 0 20px 20px;
}
#save-link-group {
padding-top: 0;
}
.save-link-options {
float: right;
}
.save-link-options input{
.save-link-options label {
margin-left: 10px;
}
@ -84,7 +66,14 @@
}
#save-texts textarea {
border-top: none;
box-shadow: none;
height: 200px;
}
#faqs a.btn {
text-transform: unset;
}
#faqs > div {
padding: 20px;
border-left: 2px solid var(--primary-border-colour);
}

View File

@ -13,15 +13,16 @@
}
#search {
border-radius: 0;
border: none;
border-bottom: 1px solid var(--primary-border-colour);
color: var(--primary-font-colour);
padding-left: 10px;
padding-right: 10px;
background-image:
linear-gradient(to top, #1976d2 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);
}
#edit-favourites {
float: right;
margin-top: -5px;
margin-top: -7px;
}
.favourites-hover {
@ -30,3 +31,13 @@
border: 2px dashed var(--rec-list-operation-font-colour) !important;
padding: 8px 8px 9px 8px;
}
#categories a {
color: #1976d2;
cursor: pointer;
}
#categories a:hover,
.op-list .operation:hover {
filter: brightness(98%);
}

View File

@ -8,12 +8,14 @@
:root,
:root.classic {
--primary-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
--primary-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, "Helvetica Neue", Arial, sans-serif;
--primary-font-colour: #333;
--primary-font-size: 14px;
--primary-line-height: 20px;
--fixed-width-font-family: "Consolas", monospace;
--fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
--fixed-width-font-colour: inherit;
--fixed-width-font-size: inherit;
@ -28,6 +30,7 @@
--title-colour: #424242;
--title-weight: bold;
--title-size: 16px;
--title-background-colour: #fafafa;
--banner-font-colour: #468847;
@ -41,7 +44,7 @@
--rec-list-operation-font-colour: #468847;
--rec-list-operation-bg-colour: #dff0d8;
--rec-list-operation-border-colour: #d6e9c6;
--rec-list-operation-border-colour: #d3e8c0;
--selected-operation-font-color: #c09853;
--selected-operation-bg-colour: #fcf8e3;
@ -65,14 +68,12 @@
/* Operation arguments */
--arg-title-font-weight: bold;
--arg-input-height: 34px;
--arg-input-line-height: 20px;
--arg-input-font-size: 15px;
--op-title-font-weight: bold;
--arg-font-colour: #424242;
--arg-background: #fff;
--arg-border-colour: #ddd;
--arg-disabled-background: #eee;
--arg-label-colour: #388e3c;
/* Buttons */

View File

@ -64,14 +64,12 @@
/* Operation arguments */
--arg-title-font-weight: bold;
--arg-input-height: 34px;
--arg-input-line-height: 20px;
--arg-input-font-size: 15px;
--op-title-font-weight: bold;
--arg-font-colour: #bbb;
--arg-background: #3c3c3c;
--arg-border-colour: #3c3c3c;
--arg-disabled-background: #4f4f4f;
--arg-label-colour: rgb(25, 118, 210);
/* Buttons */

View File

@ -64,14 +64,12 @@
/* Operation arguments */
--arg-title-font-weight: bold;
--arg-input-height: 34px;
--arg-input-line-height: 20px;
--arg-input-font-size: 15px;
--op-title-font-weight: bold;
--arg-font-colour: white;
--arg-background: black;
--arg-border-colour: lime;
--arg-disabled-background: grey;
--arg-label-colour: red;
/* Buttons */

View File

@ -20,6 +20,10 @@ body {
color: var(--subtext-font-colour);
}
.data-text {
font-family: var(--fixed-width-font-family);
}
.word-wrap {
white-space: pre !important;
word-wrap: normal !important;
@ -29,6 +33,7 @@ body {
.clearfix {
clear: both;
height: 0;
line-height: 0;
}
.blur {
@ -36,7 +41,7 @@ body {
text-shadow: rgba(0, 0, 0, 0.95) 0 0 10px !important;
}
.no-select {
.no-select {
user-select: none;
}

View File

@ -8,98 +8,104 @@
/* Bootstrap */
/* fallback */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url("../static/fonts/MaterialIcons-Regular.woff2") format('woff2');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
.form-group {
margin-bottom: 0;
}
button,
a:focus {
outline: none;
}
.btn-default {
.btn.btn-raised.btn-secondary {
color: var(--btn-default-font-colour);
background-color: var(--btn-default-bg-colour);
border-color: var(--btn-default-border-colour);
}
.btn-default:hover,
.btn-default:active,
.btn-default:hover:active,
.open>.dropdown-toggle.btn-default {
.btn.btn-raised.btn-secondary:hover,
.btn.btn-raised.btn-secondary:active,
.btn.btn-raised.btn-secondary:hover:active {
color: var(--btn-default-hover-font-colour);
background-color: var(--btn-default-hover-bg-colour);
border-color: var(--btn-default-hover-border-colour);
}
.btn-default:focus,
.open>.dropdown-toggle.btn-default:hover,
.open>.dropdown-toggle.btn-default:focus {
.btn.btn-raised.btn-secondary:focus {
color: var(--btn-default-font-colour);
background-color: var(--btn-default-bg-colour);
border-color: var(--btn-default-hover-border-colour);
}
.btn-default[disabled]:hover {
.btn.btn-raised.btn-secondary[disabled]:hover {
background-color: var(--primary-background-colour);
border-color: var(--primary-border-colour);
}
.btn-success {
.btn.btn-raised.btn-success {
color: var(--btn-success-font-colour);
background-color: var(--btn-success-bg-colour);
border-color: var(--btn-success-border-colour);
}
.btn-success:hover,
.btn-success:active,
.btn-success:focus,
.btn-success:hover:active {
.btn.btn-raised.btn-success:hover,
.btn.btn-raised.btn-success:active,
.btn.btn-raised.btn-success:focus,
.btn.btn-raised.btn-success:hover:active {
color: var(--btn-success-hover-font-colour);
background-color: var(--btn-success-hover-bg-colour);
border-color: var(--btn-success-hover-border-colour);
}
.btn,
.btn-lg,
.nav-tabs>li>a,
.form-control,
.popover,
.alert,
.panel,
.modal-content,
.tooltip-inner,
.dropdown-menu,
.input-group-addon {
border-radius: 0 !important;
}
.btn.dropdown-toggle {
height: 34px;
}
input[type="search"] {
-webkit-appearance: searchfield;
box-shadow: none;
appearance: searchfield;
}
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: searchfield-cancel-button;
appearance: searchfield-cancel-button;
}
.modal {
overflow-y: auto;
select.form-control:not([size]):not([multiple]), select.custom-file-control:not([size]):not([multiple]) {
height: unset !important;
}
.modal-content {
background-color: var(--primary-background-colour);
}
.modal-header,
.modal-footer {
border-color: var(--primary-border-colour);
.checkbox label,
.checkbox-inline,
.is-focused .checkbox-inline,
.is-focused .checkbox-inline:hover,
[class^="bmd-label"],
.form-control,
.is-focused .form-control {
color: var(--primary-font-colour);
}
.form-control {
background-color: transparent;
border-color: var(--primary-border-colour);
color: var(--primary-font-colour);
background-image: linear-gradient(to top, rgb(25, 118, 210) 2px, rgba(25, 118, 210, 0) 2px), linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);
}
code {
@ -144,22 +150,27 @@ optgroup {
border-color: var(--popover-border-colour);
}
.popover-content {
max-height: 90vh;
.popover-body {
max-height: 95vh;
overflow-y: auto;
color: var(--primary-font-colour);
}
.popover.right>.arrow {
.bs-popover-right>.arrow {
border-right-color: var(--popover-border-colour);
}
.popover.right>.arrow:after {
.bs-popover-right>.arrow:after {
border-right-color: var(--popover-background);
}
.nav-tabs>li.active>a, .nav-tabs>li.active>a:focus, .nav-tabs>li.active>a:hover {
background-color: var(--primary-background-colour);
border-color: var(--primary-border-colour);
.nav-tabs .nav-link {
color: var(--subtext-font-colour);
}
.nav-tabs>li>a.nav-link.active, .nav-tabs>li>a.nav-link.active:focus, .nav-tabs>li>a.nav-link.active:hover {
background-color: var(--secondary-background-colour);
border-color: var(--secondary-border-colour);
border-bottom-color: transparent;
color: var(--primary-font-colour);
}
@ -168,11 +179,11 @@ optgroup {
border-color: var(--primary-border-colour);
}
.nav>li>a:focus, .nav>li>a:hover {
.nav a.nav-link:focus, .nav a.nav-link:hover {
background-color: var(--secondary-border-colour);
}
.nav-tabs>li>a:hover {
.nav-tabs a.nav-link:hover {
border-color: var(--secondary-border-colour) var(--secondary-border-colour) var(--primary-border-colour);
}
@ -180,11 +191,11 @@ optgroup {
background-color: var(--primary-background-colour);
}
.dropdown-menu>li>a {
.dropdown-menu a {
color: var(--primary-font-colour);
}
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
.dropdown-menu a:focus, .dropdown-menu a:hover {
background-color: var(--secondary-background-colour);
color: var(--primary-font-colour);
}
@ -199,30 +210,6 @@ optgroup {
}
/* Bootstrap-switch */
.bootstrap-switch,
.bootstrap-switch-container,
.bootstrap-switch-handle-on,
.bootstrap-switch-handle-off,
.bootstrap-switch-label {
border-radius: 0 !important;
}
.bootstrap-switch .bootstrap-switch-label {
background-color: transparent;
}
.bootstrap-switch {
border-color: var(--primary-border-colour);
}
.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default {
background-color: var(--primary-border-colour);
color: var(--primary-font-colour);
}
/* Sortable */
.sortable-ghost {

View File

@ -1,58 +0,0 @@
/**
* Bootstrap imports
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
// Core variables and mixins
@import "~bootstrap/less/variables.less";
@import "~bootstrap/less/mixins.less";
// Reset and dependencies
@import "~bootstrap/less/normalize.less";
@import "~bootstrap/less/print.less";
// @import "~bootstrap/less/glyphicons.less";
// Core CSS
@import "~bootstrap/less/scaffolding.less";
@import "~bootstrap/less/type.less";
@import "~bootstrap/less/code.less";
@import "~bootstrap/less/grid.less";
@import "~bootstrap/less/tables.less";
@import "~bootstrap/less/forms.less";
@import "~bootstrap/less/buttons.less";
// Components
@import "~bootstrap/less/component-animations.less";
@import "~bootstrap/less/dropdowns.less";
@import "~bootstrap/less/button-groups.less";
@import "~bootstrap/less/input-groups.less";
@import "~bootstrap/less/navs.less";
// @import "~bootstrap/less/navbar.less";
// @import "~bootstrap/less/breadcrumbs.less";
// @import "~bootstrap/less/pagination.less";
// @import "~bootstrap/less/pager.less";
@import "~bootstrap/less/labels.less";
// @import "~bootstrap/less/badges.less";
// @import "~bootstrap/less/jumbotron.less";
// @import "~bootstrap/less/thumbnails.less";
@import "~bootstrap/less/alerts.less";
@import "~bootstrap/less/progress-bars.less";
// @import "~bootstrap/less/media.less";
@import "~bootstrap/less/list-group.less";
@import "~bootstrap/less/panels.less";
// @import "~bootstrap/less/responsive-embed.less";
// @import "~bootstrap/less/wells.less";
@import "~bootstrap/less/close.less";
// Components w/ JavaScript
@import "~bootstrap/less/modals.less";
@import "~bootstrap/less/tooltip.less";
@import "~bootstrap/less/popovers.less";
// @import "~bootstrap/less/carousel.less";
// Utility classes
@import "~bootstrap/less/utilities.less";
// @import "~bootstrap/less/responsive-utilities.less";

View File

@ -0,0 +1,23 @@
/**
* Bootstrap Material Design with overrides
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
@import "~bootstrap-material-design/scss/variables/colors";
$theme-colors: (
primary: $blue-700,
success: $green,
info: $light-blue,
warning: $deep-orange,
danger: $red,
light: $grey-100,
dark: $grey-800
);
$bmd-form-line-height: 1.25;
@import "~bootstrap-material-design/scss/core";

View File

@ -78,11 +78,15 @@ class TestRegister {
ret.output = "Expected an error but did not receive one.";
} else if (result.result === test.expectedOutput) {
ret.status = "passing";
} else if (test.hasOwnProperty("expectedMatch") && test.expectedMatch.test(result.result)) {
ret.status = "passing";
} else {
ret.status = "failing";
const expected = test.expectedOutput ? test.expectedOutput :
test.expectedMatch ? test.expectedMatch.toString() : "unknown";
ret.output = [
"Expected",
"\t" + test.expectedOutput.replace(/\n/g, "\n\t"),
"\t" + expected.replace(/\n/g, "\n\t"),
"Received",
"\t" + result.result.replace(/\n/g, "\n\t"),
].join("\n");

View File

@ -44,6 +44,7 @@ import "./tests/operations/ConditionalJump";
import "./tests/operations/Register";
import "./tests/operations/Comment";
import "./tests/operations/Hash";
import "./tests/operations/HaversineDistance";
import "./tests/operations/Hexdump";
import "./tests/operations/Image";
import "./tests/operations/MorseCode";
@ -61,6 +62,7 @@ import "./tests/operations/SetDifference";
import "./tests/operations/SetIntersection";
import "./tests/operations/SetUnion";
import "./tests/operations/SymmetricDifference";
import "./tests/operations/Magic";
import "./tests/nodeApi/nodeApi";
import "./tests/nodeApi/ops";

View File

@ -0,0 +1,22 @@
/**
* Haversine distance tests.
*
* @author Dachande663 [dachande663@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "Haversine distance",
input: "51.487263,-0.124323, 38.9517,-77.1467",
expectedOutput: "5619355.701829259",
recipeConfig: [
{
"op": "Haversine distance",
"args": []
}
],
}
]);

View File

@ -0,0 +1,57 @@
/**
* Magic tests.
*
* @author n1474335 [n1474335@gmail.com]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "Magic: nothing",
input: "",
expectedOutput: "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?",
recipeConfig: [
{
op: "Magic",
args: [3, false, false]
}
],
},
{
name: "Magic: hex",
input: "41 42 43 44 45",
expectedMatch: /"#recipe=From_Hex\('Space'\)"/,
recipeConfig: [
{
op: "Magic",
args: [3, false, false]
}
],
},
{
name: "Magic: jpeg",
input: "\xFF\xD8\xFF",
expectedMatch: /Render_Image\('Raw'\)/,
recipeConfig: [
{
op: "Magic",
args: [3, false, false]
}
],
},
{
name: "Magic: mojibake",
input: "d091d18bd100d182d180d0b0d10020d0bad0bed180d0b8d187d0bdd0b5d0b2d0b0d10020d0bbd0b8d100d0b020d0bfd180d18bd0b3d0b0d0b5d18220d187d0b5d180d0b5d0b720d0bbd0b5d0bdd0b8d0b2d183d18e20d100d0bed0b1d0b0d0bad1832e",
expectedMatch: /Быртрар коричневар лира прыгает через ленивую робаку./,
recipeConfig: [
{
op: "Magic",
args: [3, true, false]
}
],
},
]);

View File

@ -61,22 +61,25 @@ module.exports = {
test: /forge.min.js$/,
loader: "imports-loader?jQuery=>null"
},
{
test: /bootstrap-material-design/,
loader: "imports-loader?Popper=popper.js/dist/umd/popper.js"
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: [
{ loader: "css-loader?minimize" },
{ loader: "css-loader" },
{ loader: "postcss-loader" },
]
})
},
{
test: /\.less$/,
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: [
{ loader: "css-loader?minimize" },
{ loader: "postcss-loader" },
{ loader: "less-loader" }
{ loader: "css-loader" },
{ loader: "sass-loader" }
]
})
},