Merge branch 'feature-files'

This commit is contained in:
n1474335 2017-12-28 16:09:11 +00:00
commit 2c9e67ad1d
36 changed files with 1372 additions and 1004 deletions

View file

@ -64,9 +64,9 @@ You can use as many operations as you like in simple or complex ways. Some examp
- Highlighting - Highlighting
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][10]). - When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][10]).
- Save to file and load from file - Save to file and load from file
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field (note that files larger than about 500kb may cause your browser to hang or even crash due to the way that browsers handle large amounts of textual data). - You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 500MB are supported (depending on your browser), however some operations may take a very long time to run over this much data.
- CyberChef is entirely client-side - CyberChef is entirely client-side
- It should be noted that none of your input or recipe configuration is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer. - It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
- Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your desktop. - Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your desktop.

1225
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -78,6 +78,7 @@
"esmangle": "^1.0.1", "esmangle": "^1.0.1",
"esprima": "^4.0.0", "esprima": "^4.0.0",
"exif-parser": "^0.1.12", "exif-parser": "^0.1.12",
"file-saver": "^1.3.3",
"google-code-prettify": "^1.0.5", "google-code-prettify": "^1.0.5",
"jquery": "^3.2.1", "jquery": "^3.2.1",
"js-crc": "^0.2.0", "js-crc": "^0.2.0",
@ -94,6 +95,7 @@
"sladex-blowfish": "^0.8.1", "sladex-blowfish": "^0.8.1",
"sortablejs": "^1.7.0", "sortablejs": "^1.7.0",
"split.js": "^1.3.5", "split.js": "^1.3.5",
"utf8": "^3.0.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"xmldom": "^0.1.27", "xmldom": "^0.1.27",
"xpath": "0.0.27", "xpath": "0.0.27",

View file

@ -19,7 +19,7 @@ const Chef = function() {
/** /**
* Runs the recipe over the input. * Runs the recipe over the input.
* *
* @param {string} inputText - The input data as a string * @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
* @param {Object[]} recipeConfig - The recipe configuration object * @param {Object[]} recipeConfig - The recipe configuration object
* @param {Object} options - The options object storing various user choices * @param {Object} options - The options object storing various user choices
* @param {boolean} options.attempHighlight - Whether or not to attempt highlighting * @param {boolean} options.attempHighlight - Whether or not to attempt highlighting
@ -33,7 +33,7 @@ const Chef = function() {
* @returns {number} response.duration - The number of ms it took to execute the recipe * @returns {number} response.duration - The number of ms it took to execute the recipe
* @returns {number} response.error - The error object thrown by a failed operation (false if no error) * @returns {number} response.error - The error object thrown by a failed operation (false if no error)
*/ */
Chef.prototype.bake = async function(inputText, recipeConfig, options, progress, step) { Chef.prototype.bake = async function(input, recipeConfig, options, progress, step) {
let startTime = new Date().getTime(), let startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig), recipe = new Recipe(recipeConfig),
containsFc = recipe.containsFlowControl(), containsFc = recipe.containsFlowControl(),
@ -62,7 +62,8 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress,
// If starting from scratch, load data // If starting from scratch, load data
if (progress === 0) { if (progress === 0) {
this.dish.set(inputText, Dish.STRING); const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
this.dish.set(input, type);
} }
try { try {
@ -75,10 +76,16 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress,
progress = err.progress; progress = err.progress;
} }
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
// The threshold is specified in KiB.
const threshold = (options.outputFileThreshold || 1024) * 1024;
const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
return { return {
result: this.dish.type === Dish.HTML ? result: this.dish.type === Dish.HTML ?
this.dish.get(Dish.HTML) : this.dish.get(Dish.HTML) :
this.dish.get(Dish.STRING), this.dish.get(returnType),
type: Dish.enumLookup(this.dish.type), type: Dish.enumLookup(this.dish.type),
progress: progress, progress: progress,
duration: new Date().getTime() - startTime, duration: new Date().getTime() - startTime,

View file

@ -8,11 +8,11 @@ import Utils from "./Utils.js";
* @license Apache-2.0 * @license Apache-2.0
* *
* @class * @class
* @param {byteArray|string|number} value - The value of the input data. * @param {byteArray|string|number|ArrayBuffer} value - The value of the input data.
* @param {number} type - The data type of value, see Dish enums. * @param {number} type - The data type of value, see Dish enums.
*/ */
const Dish = function(value, type) { const Dish = function(value, type) {
this.value = value || typeof value == "string" ? value : null; this.value = value || typeof value === "string" ? value : null;
this.type = type || Dish.BYTE_ARRAY; this.type = type || Dish.BYTE_ARRAY;
}; };
@ -41,6 +41,12 @@ Dish.NUMBER = 2;
* @enum * @enum
*/ */
Dish.HTML = 3; Dish.HTML = 3;
/**
* Dish data type enum for ArrayBuffers.
* @readonly
* @enum
*/
Dish.ARRAY_BUFFER = 4;
/** /**
@ -64,6 +70,9 @@ Dish.typeEnum = function(typeStr) {
case "html": case "html":
case "HTML": case "HTML":
return Dish.HTML; return Dish.HTML;
case "arrayBuffer":
case "ArrayBuffer":
return Dish.ARRAY_BUFFER;
default: default:
throw "Invalid data type string. No matching enum."; throw "Invalid data type string. No matching enum.";
} }
@ -87,6 +96,8 @@ Dish.enumLookup = function(typeEnum) {
return "number"; return "number";
case Dish.HTML: case Dish.HTML:
return "html"; return "html";
case Dish.ARRAY_BUFFER:
return "ArrayBuffer";
default: default:
throw "Invalid data type enum. No matching type."; throw "Invalid data type enum. No matching type.";
} }
@ -96,7 +107,7 @@ Dish.enumLookup = function(typeEnum) {
/** /**
* Sets the data value and type and then validates them. * Sets the data value and type and then validates them.
* *
* @param {byteArray|string|number} value - The value of the input data. * @param {byteArray|string|number|ArrayBuffer} value - The value of the input data.
* @param {number} type - The data type of value, see Dish enums. * @param {number} type - The data type of value, see Dish enums.
*/ */
Dish.prototype.set = function(value, type) { Dish.prototype.set = function(value, type) {
@ -114,7 +125,7 @@ Dish.prototype.set = function(value, type) {
* Returns the value of the data in the type format specified. * Returns the value of the data in the type format specified.
* *
* @param {number} type - The data type of value, see Dish enums. * @param {number} type - The data type of value, see Dish enums.
* @returns {byteArray|string|number} The value of the output data. * @returns {byteArray|string|number|ArrayBuffer} The value of the output data.
*/ */
Dish.prototype.get = function(type) { Dish.prototype.get = function(type) {
if (this.type !== type) { if (this.type !== type) {
@ -134,20 +145,23 @@ Dish.prototype.translate = function(toType) {
switch (this.type) { switch (this.type) {
case Dish.STRING: case Dish.STRING:
this.value = this.value ? Utils.strToByteArray(this.value) : []; this.value = this.value ? Utils.strToByteArray(this.value) : [];
this.type = Dish.BYTE_ARRAY;
break; break;
case Dish.NUMBER: case Dish.NUMBER:
this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : []; this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : [];
this.type = Dish.BYTE_ARRAY;
break; break;
case Dish.HTML: case Dish.HTML:
this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : [];
this.type = Dish.BYTE_ARRAY; break;
case Dish.ARRAY_BUFFER:
// Array.from() would be nicer here, but it's slightly slower
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
break; break;
default: default:
break; break;
} }
this.type = Dish.BYTE_ARRAY;
// Convert from byteArray to toType // Convert from byteArray to toType
switch (toType) { switch (toType) {
case Dish.STRING: case Dish.STRING:
@ -159,6 +173,10 @@ Dish.prototype.translate = function(toType) {
this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0; this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0;
this.type = Dish.NUMBER; this.type = Dish.NUMBER;
break; break;
case Dish.ARRAY_BUFFER:
this.value = new Uint8Array(this.value).buffer;
this.type = Dish.ARRAY_BUFFER;
break;
default: default:
break; break;
} }
@ -180,7 +198,7 @@ Dish.prototype.valid = function() {
// Check that every value is a number between 0 - 255 // Check that every value is a number between 0 - 255
for (let i = 0; i < this.value.length; i++) { for (let i = 0; i < this.value.length; i++) {
if (typeof this.value[i] != "number" || if (typeof this.value[i] !== "number" ||
this.value[i] < 0 || this.value[i] < 0 ||
this.value[i] > 255) { this.value[i] > 255) {
return false; return false;
@ -189,18 +207,38 @@ Dish.prototype.valid = function() {
return true; return true;
case Dish.STRING: case Dish.STRING:
case Dish.HTML: case Dish.HTML:
if (typeof this.value == "string") { return typeof this.value === "string";
return true;
}
return false;
case Dish.NUMBER: case Dish.NUMBER:
if (typeof this.value == "number") { return typeof this.value === "number";
return true; case Dish.ARRAY_BUFFER:
} return this.value instanceof ArrayBuffer;
return false;
default: default:
return false; return false;
} }
}; };
/**
* Determines how much space the Dish takes up.
* Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish,
* we measure how many bytes are taken up when the number is written as a string.
*
* @returns {number}
*/
Dish.prototype.size = function() {
switch (this.type) {
case Dish.BYTE_ARRAY:
case Dish.STRING:
case Dish.HTML:
return this.value.length;
case Dish.NUMBER:
return this.value.toString().length;
case Dish.ARRAY_BUFFER:
return this.value.byteLength;
default:
return -1;
}
};
export default Dish; export default Dish;

View file

@ -1,4 +1,4 @@
import CryptoJS from "crypto-js"; import utf8 from "utf8";
/** /**
@ -64,58 +64,6 @@ const Utils = {
}, },
/**
* Adds leading zeros to strings
*
* @param {string} str - String to add leading characters to.
* @param {number} max - Maximum width of the string.
* @param {char} [chr='0'] - The character to pad with.
* @returns {string}
*
* @example
* // returns "0a"
* Utils.padLeft("a", 2);
*
* // returns "000a"
* Utils.padLeft("a", 4);
*
* // returns "xxxa"
* Utils.padLeft("a", 4, "x");
*
* // returns "bcabchello"
* Utils.padLeft("hello", 10, "abc");
*/
padLeft: function(str, max, chr) {
chr = chr || "0";
let startIndex = chr.length - (max - str.length);
startIndex = startIndex < 0 ? 0 : startIndex;
return str.length < max ?
Utils.padLeft(chr.slice(startIndex, chr.length) + str, max, chr) : str;
},
/**
* Adds trailing spaces to strings.
*
* @param {string} str - String to add trailing characters to.
* @param {number} max - Maximum width of the string.
* @param {char} [chr='0'] - The character to pad with.
* @returns {string}
*
* @example
* // returns "a "
* Utils.padRight("a", 4);
*
* // returns "axxx"
* Utils.padRight("a", 4, "x");
*/
padRight: function(str, max, chr) {
chr = chr || " ";
return str.length < max ?
Utils.padRight(str + chr.slice(0, max-str.length), max, chr) : str;
},
/** /**
* Adds trailing bytes to a byteArray. * Adds trailing bytes to a byteArray.
* *
@ -152,14 +100,6 @@ const Utils = {
}, },
/**
* @alias Utils.padLeft
*/
pad: function(str, max, chr) {
return Utils.padLeft(str, max, chr);
},
/** /**
* Truncates a long string to max length and adds suffix. * Truncates a long string to max length and adds suffix.
* *
@ -201,7 +141,7 @@ const Utils = {
hex: function(c, length) { hex: function(c, length) {
c = typeof c == "string" ? Utils.ord(c) : c; c = typeof c == "string" ? Utils.ord(c) : c;
length = length || 2; length = length || 2;
return Utils.pad(c.toString(16), length); return c.toString(16).padStart(length, "0");
}, },
@ -222,7 +162,7 @@ const Utils = {
bin: function(c, length) { bin: function(c, length) {
c = typeof c == "string" ? Utils.ord(c) : c; c = typeof c == "string" ? Utils.ord(c) : c;
length = length || 8; length = length || 8;
return Utils.pad(c.toString(2), length); return c.toString(2).padStart(length, "0");
}, },
@ -340,6 +280,39 @@ const Utils = {
}, },
/**
* Coverts data of varying types to a byteArray.
* Accepts hex, Base64, UTF8 and Latin1 strings.
*
* @param {string} str
* @param {string} type - One of "Hex", "Base64", "UTF8" or "Latin1"
* @returns {byteArray}
*
* @example
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("Привет", "utf8");
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex");
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
*/
convertToByteArray: function(str, type) {
switch (type.toLowerCase()) {
case "hex":
return Utils.fromHex(str);
case "base64":
return Utils.fromBase64(str, null, "byteArray");
case "utf8":
return Utils.strToUtf8ByteArray(str);
case "latin1":
default:
return Utils.strToByteArray(str);
}
},
/** /**
* Converts a string to a byte array. * Converts a string to a byte array.
* Treats the string as UTF-8 if any values are over 255. * Treats the string as UTF-8 if any values are over 255.
@ -381,17 +354,17 @@ const Utils = {
* Utils.strToUtf8ByteArray("你好"); * Utils.strToUtf8ByteArray("你好");
*/ */
strToUtf8ByteArray: function(str) { strToUtf8ByteArray: function(str) {
let wordArray = CryptoJS.enc.Utf8.parse(str), const utf8Str = utf8.encode(str);
byteArray = Utils.wordArrayToByteArray(wordArray);
if (str.length !== wordArray.sigBytes) { if (str.length !== utf8Str.length) {
if (ENVIRONMENT_IS_WORKER()) { if (ENVIRONMENT_IS_WORKER()) {
self.setOption("attemptHighlight", false); self.setOption("attemptHighlight", false);
} else if (ENVIRONMENT_IS_WEB()) { } else if (ENVIRONMENT_IS_WEB()) {
window.app.options.attemptHighlight = false; window.app.options.attemptHighlight = false;
} }
} }
return byteArray;
return Utils.strToByteArray(utf8Str);
}, },
@ -443,26 +416,21 @@ const Utils = {
* Utils.byteArrayToUtf8([228,189,160,229,165,189]); * Utils.byteArrayToUtf8([228,189,160,229,165,189]);
*/ */
byteArrayToUtf8: function(byteArray) { byteArrayToUtf8: function(byteArray) {
const str = Utils.byteArrayToChars(byteArray);
try { try {
// Try to output data as UTF-8 string const utf8Str = utf8.decode(str);
const words = [];
for (let i = 0; i < byteArray.length; i++) {
words[i >>> 2] |= byteArray[i] << (24 - (i % 4) * 8);
}
let wordArray = new CryptoJS.lib.WordArray.init(words, byteArray.length),
str = CryptoJS.enc.Utf8.stringify(wordArray);
if (str.length !== wordArray.sigBytes) { if (str.length !== utf8Str.length) {
if (ENVIRONMENT_IS_WORKER()) { if (ENVIRONMENT_IS_WORKER()) {
self.setOption("attemptHighlight", false); self.setOption("attemptHighlight", false);
} else if (ENVIRONMENT_IS_WEB()) { } else if (ENVIRONMENT_IS_WEB()) {
window.app.options.attemptHighlight = false; window.app.options.attemptHighlight = false;
} }
} }
return str; return utf8Str;
} catch (err) { } catch (err) {
// If it fails, treat it as ANSI // If it fails, treat it as ANSI
return Utils.byteArrayToChars(byteArray); return str;
} }
}, },
@ -470,7 +438,7 @@ const Utils = {
/** /**
* Converts a charcode array to a string. * Converts a charcode array to a string.
* *
* @param {byteArray} byteArray * @param {byteArray|Uint8Array} byteArray
* @returns {string} * @returns {string}
* *
* @example * @example
@ -491,33 +459,25 @@ const Utils = {
/** /**
* Converts a CryptoJS.lib.WordArray to a byteArray. * Converts an ArrayBuffer to a string.
* *
* @param {CryptoJS.lib.WordArray} wordArray * @param {ArrayBuffer} arrayBuffer
* @returns {byteArray} * @returns {string}
* *
* @example * @example
* // returns [84, 101, 115, 116] * // returns "hello"
* Utils.wordArrayToByteArray(CryptoJS.enc.Hex.parse("54657374")); * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
*/ */
wordArrayToByteArray: function(wordArray) { arrayBufferToStr: function(arrayBuffer) {
if (wordArray.sigBytes <= 0) return []; const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer));
return Utils.byteArrayToUtf8(byteArray);
let words = wordArray.words,
byteArray = [];
for (let i = 0; i < wordArray.sigBytes; i++) {
byteArray.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
}
return byteArray;
}, },
/** /**
* Base64's the input byte array using the given alphabet, returning a string. * Base64's the input byte array using the given alphabet, returning a string.
* *
* @param {byteArray|string} data * @param {byteArray|Uint8Array|string} data
* @param {string} [alphabet] * @param {string} [alphabet]
* @returns {string} * @returns {string}
* *
@ -636,7 +596,7 @@ const Utils = {
/** /**
* Convert a byte array into a hex string. * Convert a byte array into a hex string.
* *
* @param {byteArray} data * @param {Uint8Array|byteArray} data
* @param {string} [delim=" "] * @param {string} [delim=" "]
* @param {number} [padding=2] * @param {number} [padding=2]
* @returns {string} * @returns {string}
@ -656,7 +616,7 @@ const Utils = {
let output = ""; let output = "";
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
output += Utils.pad(data[i].toString(16), padding) + delim; output += data[i].toString(16).padStart(padding, "0") + delim;
} }
// Add \x or 0x to beginning // Add \x or 0x to beginning
@ -1248,21 +1208,6 @@ const Utils = {
"None": /\s+/g // Included here to remove whitespace when there shouldn't be any "None": /\s+/g // Included here to remove whitespace when there shouldn't be any
}, },
/**
* A mapping of string formats to their classes in the CryptoJS library.
* @constant
*/
format: {
"Hex": CryptoJS.enc.Hex,
"Base64": CryptoJS.enc.Base64,
"UTF8": CryptoJS.enc.Utf8,
"UTF16": CryptoJS.enc.Utf16,
"UTF16LE": CryptoJS.enc.Utf16LE,
"UTF16BE": CryptoJS.enc.Utf16BE,
"Latin1": CryptoJS.enc.Latin1,
},
}; };
export default Utils; export default Utils;
@ -1376,29 +1321,43 @@ String.prototype.count = function(chr) {
}; };
//////////////////////////////////////////////////////////////////////////////////////////////////// /*
// Library overrides /////////////////////////////////////////////////////////////////////////////// * Polyfills
////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse
* the hex string.
*
* @param {string} hexStr
* @returns {CryptoJS.lib.WordArray}
*/ */
CryptoJS.enc.Hex.parse = function (hexStr) {
// Remove whitespace
hexStr = hexStr.replace(/\s/g, "");
// Shortcut // https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
const hexStrLength = hexStr.length; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
// Convert String.prototype.padStart = function padStart(targetLength, padString) {
const words = []; targetLength = targetLength>>0; //floor if number or convert non-number to 0;
for (let i = 0; i < hexStrLength; i += 2) { padString = String((typeof padString !== "undefined" ? padString : " "));
words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); if (this.length > targetLength) {
return String(this);
} else {
targetLength = targetLength-this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
}
return padString.slice(0, targetLength) + String(this);
}
};
} }
return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2);
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
if (!String.prototype.padEnd) {
String.prototype.padEnd = function padEnd(targetLength, padString) {
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
padString = String((typeof padString !== "undefined" ? padString : " "));
if (this.length > targetLength) {
return String(this);
} else {
targetLength = targetLength-this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
}
return String(this) + padString.slice(0, targetLength);
}
}; };
}

View file

@ -245,7 +245,7 @@ const OperationConfig = {
description: "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation encodes data in an ASCII Base64 string.<br><br>e.g. <code>hello</code> becomes <code>aGVsbG8=</code>", description: "Base64 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.<br><br>This operation encodes data in an ASCII Base64 string.<br><br>e.g. <code>hello</code> becomes <code>aGVsbG8=</code>",
highlight: "func", highlight: "func",
highlightReverse: "func", highlightReverse: "func",
inputType: "byteArray", inputType: "ArrayBuffer",
outputType: "string", outputType: "string",
args: [ args: [
{ {
@ -631,7 +631,7 @@ const OperationConfig = {
description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code>", description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code>",
highlight: "func", highlight: "func",
highlightReverse: "func", highlightReverse: "func",
inputType: "byteArray", inputType: "ArrayBuffer",
outputType: "string", outputType: "string",
args: [ args: [
{ {
@ -782,7 +782,7 @@ const OperationConfig = {
description: "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside.", description: "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside.",
highlight: "func", highlight: "func",
highlightReverse: "func", highlightReverse: "func",
inputType: "byteArray", inputType: "ArrayBuffer",
outputType: "string", outputType: "string",
args: [ args: [
{ {
@ -3287,7 +3287,7 @@ const OperationConfig = {
"Frequency distribution": { "Frequency distribution": {
module: "Default", module: "Default",
description: "Displays the distribution of bytes in the data as a graph.", description: "Displays the distribution of bytes in the data as a graph.",
inputType: "byteArray", inputType: "ArrayBuffer",
outputType: "html", outputType: "html",
args: [ args: [
{ {
@ -3300,7 +3300,7 @@ const OperationConfig = {
"Chi Square": { "Chi Square": {
module: "Default", module: "Default",
description: "Calculates the Chi Square distribution of values.", description: "Calculates the Chi Square distribution of values.",
inputType: "byteArray", inputType: "ArrayBuffer",
outputType: "number", outputType: "number",
args: [] args: []
}, },
@ -3379,14 +3379,14 @@ const OperationConfig = {
"Detect File Type": { "Detect File Type": {
module: "Default", module: "Default",
description: "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.<br><br>Currently supports the following file types: 7z, amr, avi, bmp, bz2, class, cr2, crx, dex, dmg, doc, elf, eot, epub, exe, flac, flv, gif, gz, ico, iso, jpg, jxr, m4a, m4v, mid, mkv, mov, mp3, mp4, mpg, ogg, otf, pdf, png, ppt, ps, psd, rar, rtf, sqlite, swf, tar, tar.z, tif, ttf, utf8, vmdk, wav, webm, webp, wmv, woff, woff2, xls, xz, zip.", description: "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.<br><br>Currently supports the following file types: 7z, amr, avi, bmp, bz2, class, cr2, crx, dex, dmg, doc, elf, eot, epub, exe, flac, flv, gif, gz, ico, iso, jpg, jxr, m4a, m4v, mid, mkv, mov, mp3, mp4, mpg, ogg, otf, pdf, png, ppt, ps, psd, rar, rtf, sqlite, swf, tar, tar.z, tif, ttf, utf8, vmdk, wav, webm, webp, wmv, woff, woff2, xls, xz, zip.",
inputType: "byteArray", inputType: "ArrayBuffer",
outputType: "string", outputType: "string",
args: [] args: []
}, },
"Scan for Embedded Files": { "Scan for Embedded Files": {
module: "Default", module: "Default",
description: "Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.<br><br>WARNING: Files over about 100KB in size will take a VERY long time to process.", description: "Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.<br><br>WARNING: Files over about 100KB in size will take a VERY long time to process.",
inputType: "byteArray", inputType: "ArrayBuffer",
outputType: "string", outputType: "string",
args: [ args: [
{ {
@ -3740,7 +3740,7 @@ const OperationConfig = {
"<br><br>", "<br><br>",
"EXIF data from photos usually contains information about the image file itself as well as the device used to create it.", "EXIF data from photos usually contains information about the image file itself as well as the device used to create it.",
].join("\n"), ].join("\n"),
inputType: "byteArray", inputType: "ArrayBuffer",
outputType: "string", outputType: "string",
args: [], args: [],
}, },

View file

@ -38,7 +38,6 @@ import UUID from "../../operations/UUID.js";
* *
* Libraries: * Libraries:
* - Utils.js * - Utils.js
* - CryptoJS
* - otp * - otp
* - crypto * - crypto
* *

View file

@ -134,11 +134,11 @@ const BCD = {
switch (outputFormat) { switch (outputFormat) {
case "Nibbles": case "Nibbles":
return nibbles.map(n => { return nibbles.map(n => {
return Utils.padLeft(n.toString(2), 4); return n.toString(2).padStart(4, "0");
}).join(" "); }).join(" ");
case "Bytes": case "Bytes":
return bytes.map(b => { return bytes.map(b => {
return Utils.padLeft(b.toString(2), 8); return b.toString(2).padStart(8, "0");
}).join(" "); }).join(" ");
case "Raw": case "Raw":
default: default:

View file

@ -40,13 +40,13 @@ const Base64 = {
/** /**
* To Base64 operation. * To Base64 operation.
* *
* @param {byteArray} input * @param {ArrayBuffer} input
* @param {Object[]} args * @param {Object[]} args
* @returns {string} * @returns {string}
*/ */
runTo: function(input, args) { runTo: function(input, args) {
const alphabet = args[0] || Base64.ALPHABET; const alphabet = args[0] || Base64.ALPHABET;
return Utils.toBase64(input, alphabet); return Utils.toBase64(new Uint8Array(input), alphabet);
}, },

View file

@ -67,7 +67,7 @@ const BitwiseOp = {
* @constant * @constant
* @default * @default
*/ */
KEY_FORMAT: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"], KEY_FORMAT: ["Hex", "Base64", "UTF8", "Latin1"],
/** /**
* XOR operation. * XOR operation.
@ -77,12 +77,10 @@ const BitwiseOp = {
* @returns {byteArray} * @returns {byteArray}
*/ */
runXor: function (input, args) { runXor: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""), const key = Utils.convertToByteArray(args[0].string || "", args[0].option),
scheme = args[1], scheme = args[1],
nullPreserving = args[2]; nullPreserving = args[2];
key = Utils.wordArrayToByteArray(key);
return BitwiseOp._bitOp(input, key, BitwiseOp._xor, nullPreserving, scheme); return BitwiseOp._bitOp(input, key, BitwiseOp._xor, nullPreserving, scheme);
}, },
@ -200,8 +198,7 @@ const BitwiseOp = {
* @returns {byteArray} * @returns {byteArray}
*/ */
runAnd: function (input, args) { runAnd: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""); const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
key = Utils.wordArrayToByteArray(key);
return BitwiseOp._bitOp(input, key, BitwiseOp._and); return BitwiseOp._bitOp(input, key, BitwiseOp._and);
}, },
@ -215,8 +212,7 @@ const BitwiseOp = {
* @returns {byteArray} * @returns {byteArray}
*/ */
runOr: function (input, args) { runOr: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""); const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
key = Utils.wordArrayToByteArray(key);
return BitwiseOp._bitOp(input, key, BitwiseOp._or); return BitwiseOp._bitOp(input, key, BitwiseOp._or);
}, },
@ -230,8 +226,7 @@ const BitwiseOp = {
* @returns {byteArray} * @returns {byteArray}
*/ */
runAdd: function (input, args) { runAdd: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""); const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
key = Utils.wordArrayToByteArray(key);
return BitwiseOp._bitOp(input, key, BitwiseOp._add); return BitwiseOp._bitOp(input, key, BitwiseOp._add);
}, },
@ -245,8 +240,7 @@ const BitwiseOp = {
* @returns {byteArray} * @returns {byteArray}
*/ */
runSub: function (input, args) { runSub: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""); const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
key = Utils.wordArrayToByteArray(key);
return BitwiseOp._bitOp(input, key, BitwiseOp._sub); return BitwiseOp._bitOp(input, key, BitwiseOp._sub);
}, },

View file

@ -31,13 +31,13 @@ const ByteRepr = {
/** /**
* To Hex operation. * To Hex operation.
* *
* @param {byteArray} input * @param {ArrayBuffer} input
* @param {Object[]} args * @param {Object[]} args
* @returns {string} * @returns {string}
*/ */
runToHex: function(input, args) { runToHex: function(input, args) {
const delim = Utils.charRep[args[0] || "Space"]; const delim = Utils.charRep[args[0] || "Space"];
return Utils.toHex(input, delim, 2); return Utils.toHex(new Uint8Array(input), delim, 2);
}, },
@ -266,7 +266,7 @@ const ByteRepr = {
padding = 8; padding = 8;
for (let i = 0; i < input.length; i++) { for (let i = 0; i < input.length; i++) {
output += Utils.pad(input[i].toString(2), padding) + delim; output += input[i].toString(2).padStart(padding, "0") + delim;
} }
if (delim.length) { if (delim.length) {

View file

@ -61,9 +61,9 @@ const Cipher = {
* @returns {string} * @returns {string}
*/ */
_enc: function (algo, input, args) { _enc: function (algo, input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""), let key = Cipher._format[args[0].option].parse(args[0].string || ""),
iv = Utils.format[args[1].option].parse(args[1].string || ""), iv = Cipher._format[args[1].option].parse(args[1].string || ""),
salt = Utils.format[args[2].option].parse(args[2].string || ""), salt = Cipher._format[args[2].option].parse(args[2].string || ""),
mode = CryptoJS.mode[args[3]], mode = CryptoJS.mode[args[3]],
padding = CryptoJS.pad[args[4]], padding = CryptoJS.pad[args[4]],
resultOption = args[5].toLowerCase(), resultOption = args[5].toLowerCase(),
@ -83,12 +83,12 @@ const Cipher = {
let result = ""; let result = "";
if (resultOption === "show all") { if (resultOption === "show all") {
result += "Key: " + encrypted.key.toString(Utils.format[outputFormat]); result += "Key: " + encrypted.key.toString(Cipher._format[outputFormat]);
result += "\nIV: " + encrypted.iv.toString(Utils.format[outputFormat]); result += "\nIV: " + encrypted.iv.toString(Cipher._format[outputFormat]);
if (encrypted.salt) result += "\nSalt: " + encrypted.salt.toString(Utils.format[outputFormat]); if (encrypted.salt) result += "\nSalt: " + encrypted.salt.toString(Cipher._format[outputFormat]);
result += "\n\nCiphertext: " + encrypted.ciphertext.toString(Utils.format[outputFormat]); result += "\n\nCiphertext: " + encrypted.ciphertext.toString(Cipher._format[outputFormat]);
} else { } else {
result = encrypted[resultOption].toString(Utils.format[outputFormat]); result = encrypted[resultOption].toString(Cipher._format[outputFormat]);
} }
return result; return result;
@ -105,9 +105,9 @@ const Cipher = {
* @returns {string} * @returns {string}
*/ */
_dec: function (algo, input, args) { _dec: function (algo, input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""), let key = Cipher._format[args[0].option].parse(args[0].string || ""),
iv = Utils.format[args[1].option].parse(args[1].string || ""), iv = Cipher._format[args[1].option].parse(args[1].string || ""),
salt = Utils.format[args[2].option].parse(args[2].string || ""), salt = Cipher._format[args[2].option].parse(args[2].string || ""),
mode = CryptoJS.mode[args[3]], mode = CryptoJS.mode[args[3]],
padding = CryptoJS.pad[args[4]], padding = CryptoJS.pad[args[4]],
inputFormat = args[5], inputFormat = args[5],
@ -118,7 +118,7 @@ const Cipher = {
return "No input"; return "No input";
} }
const ciphertext = Utils.format[inputFormat].parse(input); const ciphertext = Cipher._format[inputFormat].parse(input);
if (iv.sigBytes === 0) { if (iv.sigBytes === 0) {
// Use passphrase rather than key. Need to convert it to a string. // Use passphrase rather than key. Need to convert it to a string.
@ -136,7 +136,7 @@ const Cipher = {
let result; let result;
try { try {
result = decrypted.toString(Utils.format[outputFormat]); result = decrypted.toString(Cipher._format[outputFormat]);
} catch (err) { } catch (err) {
result = "Decrypt error: " + err.message; result = "Decrypt error: " + err.message;
} }
@ -260,7 +260,7 @@ const Cipher = {
* @returns {string} * @returns {string}
*/ */
runBlowfishEnc: function (input, args) { runBlowfishEnc: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1), let key = Cipher._format[args[0].option].parse(args[0].string).toString(Cipher._format.Latin1),
mode = args[1], mode = args[1],
outputFormat = args[2]; outputFormat = args[2];
@ -272,7 +272,7 @@ const Cipher = {
}), }),
enc = CryptoJS.enc.Hex.parse(encHex); enc = CryptoJS.enc.Hex.parse(encHex);
return enc.toString(Utils.format[outputFormat]); return enc.toString(Cipher._format[outputFormat]);
}, },
@ -284,13 +284,13 @@ const Cipher = {
* @returns {string} * @returns {string}
*/ */
runBlowfishDec: function (input, args) { runBlowfishDec: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1), let key = Cipher._format[args[0].option].parse(args[0].string).toString(Cipher._format.Latin1),
mode = args[1], mode = args[1],
inputFormat = args[2]; inputFormat = args[2];
if (key.length === 0) return "Enter a key"; if (key.length === 0) return "Enter a key";
input = Utils.format[inputFormat].parse(input); input = Cipher._format[inputFormat].parse(input);
return Blowfish.decrypt(input.toString(CryptoJS.enc.Base64), key, { return Blowfish.decrypt(input.toString(CryptoJS.enc.Base64), key, {
outputType: 0, // This actually means inputType. The library is weird. outputType: 0, // This actually means inputType. The library is weird.
@ -329,14 +329,14 @@ const Cipher = {
salt = CryptoJS.enc.Hex.parse(args[3] || ""), salt = CryptoJS.enc.Hex.parse(args[3] || ""),
inputFormat = args[4], inputFormat = args[4],
outputFormat = args[5], outputFormat = args[5],
passphrase = Utils.format[inputFormat].parse(input), passphrase = Cipher._format[inputFormat].parse(input),
key = CryptoJS.PBKDF2(passphrase, salt, { key = CryptoJS.PBKDF2(passphrase, salt, {
keySize: keySize, keySize: keySize,
hasher: CryptoJS.algo[hasher], hasher: CryptoJS.algo[hasher],
iterations: iterations, iterations: iterations,
}); });
return key.toString(Utils.format[outputFormat]); return key.toString(Cipher._format[outputFormat]);
}, },
@ -354,14 +354,14 @@ const Cipher = {
salt = CryptoJS.enc.Hex.parse(args[3] || ""), salt = CryptoJS.enc.Hex.parse(args[3] || ""),
inputFormat = args[4], inputFormat = args[4],
outputFormat = args[5], outputFormat = args[5],
passphrase = Utils.format[inputFormat].parse(input), passphrase = Cipher._format[inputFormat].parse(input),
key = CryptoJS.EvpKDF(passphrase, salt, { key = CryptoJS.EvpKDF(passphrase, salt, {
keySize: keySize, keySize: keySize,
hasher: CryptoJS.algo[hasher], hasher: CryptoJS.algo[hasher],
iterations: iterations, iterations: iterations,
}); });
return key.toString(Utils.format[outputFormat]); return key.toString(Cipher._format[outputFormat]);
}, },
@ -373,11 +373,11 @@ const Cipher = {
* @returns {string} * @returns {string}
*/ */
runRc4: function (input, args) { runRc4: function (input, args) {
let message = Utils.format[args[1]].parse(input), let message = Cipher._format[args[1]].parse(input),
passphrase = Utils.format[args[0].option].parse(args[0].string), passphrase = Cipher._format[args[0].option].parse(args[0].string),
encrypted = CryptoJS.RC4.encrypt(message, passphrase); encrypted = CryptoJS.RC4.encrypt(message, passphrase);
return encrypted.ciphertext.toString(Utils.format[args[2]]); return encrypted.ciphertext.toString(Cipher._format[args[2]]);
}, },
@ -395,12 +395,12 @@ const Cipher = {
* @returns {string} * @returns {string}
*/ */
runRc4drop: function (input, args) { runRc4drop: function (input, args) {
let message = Utils.format[args[1]].parse(input), let message = Cipher._format[args[1]].parse(input),
passphrase = Utils.format[args[0].option].parse(args[0].string), passphrase = Cipher._format[args[0].option].parse(args[0].string),
drop = args[3], drop = args[3],
encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop }); encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop });
return encrypted.ciphertext.toString(Utils.format[args[2]]); return encrypted.ciphertext.toString(Cipher._format[args[2]]);
}, },
@ -783,6 +783,23 @@ const Cipher = {
return output; return output;
}, },
/**
* A mapping of string formats to their classes in the CryptoJS library.
*
* @private
* @constant
*/
_format: {
"Hex": CryptoJS.enc.Hex,
"Base64": CryptoJS.enc.Base64,
"UTF8": CryptoJS.enc.Utf8,
"UTF16": CryptoJS.enc.Utf16,
"UTF16LE": CryptoJS.enc.Utf16LE,
"UTF16BE": CryptoJS.enc.Utf16BE,
"Latin1": CryptoJS.enc.Latin1,
},
}; };
export default Cipher; export default Cipher;
@ -827,3 +844,27 @@ CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) {
// Return params // Return params
return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt }); return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt });
}; };
/**
* Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse
* the hex string.
*
* @param {string} hexStr
* @returns {CryptoJS.lib.WordArray}
*/
CryptoJS.enc.Hex.parse = function (hexStr) {
// Remove whitespace
hexStr = hexStr.replace(/\s/g, "");
// Shortcut
const hexStrLength = hexStr.length;
// Convert
const words = [];
for (let i = 0; i < hexStrLength; i += 2) {
words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
}
return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2);
};

View file

@ -418,9 +418,9 @@ const Compress = {
} }
}; };
const fileSize = Utils.padLeft(input.length.toString(8), 11, "0"); const fileSize = input.length.toString(8).padStart(11, "0");
const currentUnixTimestamp = Math.floor(Date.now() / 1000); const currentUnixTimestamp = Math.floor(Date.now() / 1000);
const lastModTime = Utils.padLeft(currentUnixTimestamp.toString(8), 11, "0"); const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0");
const file = { const file = {
fileName: Utils.padBytesRight(args[0], 100), fileName: Utils.padBytesRight(args[0], 100),
@ -452,7 +452,7 @@ const Compress = {
} }
}); });
} }
checksum = Utils.padBytesRight(Utils.padLeft(checksum.toString(8), 7, "0"), 8); checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8);
file.checksum = checksum; file.checksum = checksum;
const tarball = new Tarball(); const tarball = new Tarball();

View file

@ -81,22 +81,23 @@ const Entropy = {
/** /**
* Frequency distribution operation. * Frequency distribution operation.
* *
* @param {byteArray} input * @param {ArrayBuffer} input
* @param {Object[]} args * @param {Object[]} args
* @returns {html} * @returns {html}
*/ */
runFreqDistrib: function (input, args) { runFreqDistrib: function (input, args) {
if (!input.length) return "No data"; const data = new Uint8Array(input);
if (!data.length) return "No data";
let distrib = new Array(256).fill(0), let distrib = new Array(256).fill(0),
percentages = new Array(256), percentages = new Array(256),
len = input.length, len = data.length,
showZeroes = args[0], showZeroes = args[0],
i; i;
// Count bytes // Count bytes
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
distrib[input[i]]++; distrib[data[i]]++;
} }
// Calculate percentages // Calculate percentages
@ -126,7 +127,7 @@ const Entropy = {
for (i = 0; i < 256; i++) { for (i = 0; i < 256; i++) {
if (distrib[i] || showZeroes) { if (distrib[i] || showZeroes) {
output += " " + Utils.hex(i, 2) + " (" + output += " " + Utils.hex(i, 2) + " (" +
Utils.padRight(percentages[i].toFixed(2).replace(".00", "") + "%)", 8) + (percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") +
Array(Math.ceil(percentages[i])+1).join("|") + "\n"; Array(Math.ceil(percentages[i])+1).join("|") + "\n";
} }
} }
@ -138,21 +139,22 @@ const Entropy = {
/** /**
* Chi Square operation. * Chi Square operation.
* *
* @param {byteArray} data * @param {ArrayBuffer} data
* @param {Object[]} args * @param {Object[]} args
* @returns {number} * @returns {number}
*/ */
runChiSq: function(input, args) { runChiSq: function(input, args) {
const data = new Uint8Array(input);
let distArray = new Array(256).fill(0), let distArray = new Array(256).fill(0),
total = 0; total = 0;
for (let i = 0; i < input.length; i++) { for (let i = 0; i < data.length; i++) {
distArray[input[i]]++; distArray[data[i]]++;
} }
for (let i = 0; i < distArray.length; i++) { for (let i = 0; i < distArray.length; i++) {
if (distArray[i] > 0) { if (distArray[i] > 0) {
total += Math.pow(distArray[i] - input.length / 256, 2) / (input.length / 256); total += Math.pow(distArray[i] - data.length / 256, 2) / (data.length / 256);
} }
} }

View file

@ -15,12 +15,13 @@ const FileType = {
/** /**
* Detect File Type operation. * Detect File Type operation.
* *
* @param {byteArray} input * @param {ArrayBuffer} input
* @param {Object[]} args * @param {Object[]} args
* @returns {string} * @returns {string}
*/ */
runDetect: function(input, args) { runDetect: function(input, args) {
const type = FileType.magicType(input); const data = new Uint8Array(input),
type = FileType.magicType(data);
if (!type) { if (!type) {
return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?";
@ -46,20 +47,21 @@ const FileType = {
/** /**
* Scan for Embedded Files operation. * Scan for Embedded Files operation.
* *
* @param {byteArray} input * @param {ArrayBuffer} input
* @param {Object[]} args * @param {Object[]} args
* @returns {string} * @returns {string}
*/ */
runScanForEmbeddedFiles: function(input, args) { runScanForEmbeddedFiles: function(input, args) {
let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any suffiently long file is likely to contain these magic bytes coincidentally.\n", let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any suffiently long file is likely to contain these magic bytes coincidentally.\n",
type, type,
ignoreCommon = args[0],
commonExts = ["ico", "ttf", ""],
numFound = 0, numFound = 0,
numCommonFound = 0; numCommonFound = 0;
const ignoreCommon = args[0],
commonExts = ["ico", "ttf", ""],
data = new Uint8Array(input);
for (let i = 0; i < input.length; i++) { for (let i = 0; i < data.length; i++) {
type = FileType.magicType(input.slice(i)); type = FileType.magicType(data.slice(i));
if (type) { if (type) {
if (ignoreCommon && commonExts.indexOf(type.ext) > -1) { if (ignoreCommon && commonExts.indexOf(type.ext) > -1) {
numCommonFound++; numCommonFound++;
@ -96,7 +98,7 @@ const FileType = {
* Given a buffer, detects magic byte sequences at specific positions and returns the * Given a buffer, detects magic byte sequences at specific positions and returns the
* extension and mime type. * extension and mime type.
* *
* @param {byteArray} buf * @param {Uint8Array} buf
* @returns {Object} type * @returns {Object} type
* @returns {string} type.ext - File extension * @returns {string} type.ext - File extension
* @returns {string} type.mime - Mime type * @returns {string} type.mime - Mime type

View file

@ -215,9 +215,9 @@ const HTML = {
k = k.toFixed(2); k = k.toFixed(2);
let hex = "#" + let hex = "#" +
Utils.padLeft(Math.round(r).toString(16), 2) + Math.round(r).toString(16).padStart(2, "0") +
Utils.padLeft(Math.round(g).toString(16), 2) + Math.round(g).toString(16).padStart(2, "0") +
Utils.padLeft(Math.round(b).toString(16), 2), Math.round(b).toString(16).padStart(2, "0"),
rgb = "rgb(" + r + ", " + g + ", " + b + ")", rgb = "rgb(" + r + ", " + g + ", " + b + ")",
rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")", rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")",
hsl = "hsl(" + h + ", " + s + "%, " + l + "%)", hsl = "hsl(" + h + ", " + s + "%, " + l + "%)",

View file

@ -31,18 +31,19 @@ const Hexdump = {
/** /**
* To Hexdump operation. * To Hexdump operation.
* *
* @param {byteArray} input * @param {ArrayBuffer} input
* @param {Object[]} args * @param {Object[]} args
* @returns {string} * @returns {string}
*/ */
runTo: function(input, args) { runTo: function(input, args) {
const data = new Uint8Array(input);
const length = args[0] || Hexdump.WIDTH; const length = args[0] || Hexdump.WIDTH;
const upperCase = args[1]; const upperCase = args[1];
const includeFinalLength = args[2]; const includeFinalLength = args[2];
let output = "", padding = 2; let output = "", padding = 2;
for (let i = 0; i < input.length; i += length) { for (let i = 0; i < data.length; i += length) {
const buff = input.slice(i, i+length); const buff = data.slice(i, i+length);
let hexa = ""; let hexa = "";
for (let j = 0; j < buff.length; j++) { for (let j = 0; j < buff.length; j++) {
hexa += Utils.hex(buff[j], padding) + " "; hexa += Utils.hex(buff[j], padding) + " ";
@ -56,10 +57,10 @@ const Hexdump = {
} }
output += lineNo + " " + output += lineNo + " " +
Utils.padRight(hexa, (length*(padding+1))) + hexa.padEnd(length*(padding+1), " ") +
" |" + Utils.padRight(Utils.printable(Utils.byteArrayToChars(buff)), buff.length) + "|\n"; " |" + Utils.printable(Utils.byteArrayToChars(buff)).padEnd(buff.length, " ") + "|\n";
if (includeFinalLength && i+buff.length === input.length) { if (includeFinalLength && i+buff.length === data.length) {
output += Utils.hex(i+buff.length, 8) + "\n"; output += Utils.hex(i+buff.length, 8) + "\n";
} }
} }

View file

@ -20,14 +20,13 @@ const Image = {
* *
* Extracts EXIF data from a byteArray, representing a JPG or a TIFF image. * Extracts EXIF data from a byteArray, representing a JPG or a TIFF image.
* *
* @param {byteArray} input * @param {ArrayBuffer} input
* @param {Object[]} args * @param {Object[]} args
* @returns {string} * @returns {string}
*/ */
runExtractEXIF(input, args) { runExtractEXIF(input, args) {
try { try {
const bytes = Uint8Array.from(input); const parser = ExifParser.create(input);
const parser = ExifParser.create(bytes.buffer);
const result = parser.parse(); const result = parser.parse();
let lines = []; let lines = [];
@ -53,7 +52,7 @@ const Image = {
* @author David Moodie [davidmoodie12@gmail.com] * @author David Moodie [davidmoodie12@gmail.com]
* @param {byteArray} input * @param {byteArray} input
* @param {Object[]} args * @param {Object[]} args
* @returns {string} * @returns {byteArray}
*/ */
runRemoveEXIF(input, args) { runRemoveEXIF(input, args) {
// Do nothing if input is empty // Do nothing if input is empty

View file

@ -121,8 +121,7 @@ const PublicKey = {
// Format Public Key fields // Format Public Key fields
for (let i = 0; i < pkFields.length; i++) { for (let i = 0; i < pkFields.length; i++) {
pkStr += " " + pkFields[i].key + ":" + pkStr += " " + pkFields[i].key + ":" +
Utils.padLeft( (pkFields[i].value + "\n").padStart(
pkFields[i].value + "\n",
18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1,
" " " "
); );
@ -286,9 +285,9 @@ ${extensions}`;
key = fields[i].split("=")[0]; key = fields[i].split("=")[0];
value = fields[i].split("=")[1]; value = fields[i].split("=")[1];
str = Utils.padRight(key, maxKeyLen) + " = " + value + "\n"; str = key.padEnd(maxKeyLen, " ") + " = " + value + "\n";
output += Utils.padLeft(str, indent + str.length, " "); output += str.padStart(indent + str.length, " ");
} }
return output.slice(0, -1); return output.slice(0, -1);
@ -314,7 +313,7 @@ ${extensions}`;
if (i === 0) { if (i === 0) {
output += str; output += str;
} else { } else {
output += Utils.padLeft(str, indent + str.length, " "); output += str.padStart(indent + str.length, " ");
} }
} }

View file

@ -158,7 +158,7 @@ const SeqUtils = {
width = lines.length.toString().length; width = lines.length.toString().length;
for (let n = 0; n < lines.length; n++) { for (let n = 0; n < lines.length; n++) {
output += Utils.pad((n+1).toString(), width, " ") + " " + lines[n] + "\n"; output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
} }
return output.slice(0, output.length-1); return output.slice(0, output.length-1);
}, },

View file

@ -1,6 +1,3 @@
import Utils from "../Utils.js";
/** /**
* Tidy operations. * Tidy operations.
* *
@ -229,11 +226,11 @@ const Tidy = {
if (position === "Start") { if (position === "Start") {
for (i = 0; i < lines.length; i++) { for (i = 0; i < lines.length; i++) {
output += Utils.padLeft(lines[i], lines[i].length+len, chr) + "\n"; output += lines[i].padStart(lines[i].length+len, chr) + "\n";
} }
} else if (position === "End") { } else if (position === "End") {
for (i = 0; i < lines.length; i++) { for (i = 0; i < lines.length; i++) {
output += Utils.padRight(lines[i], lines[i].length+len, chr) + "\n"; output += lines[i].padEnd(lines[i].length+len, chr) + "\n";
} }
} }

View file

@ -1,5 +1,4 @@
/* globals unescape */ /* globals unescape */
import Utils from "../Utils.js";
import url from "url"; import url from "url";
@ -78,7 +77,7 @@ const URL_ = {
output += "Arguments:\n"; output += "Arguments:\n";
for (let key in uri.query) { for (let key in uri.query) {
output += "\t" + Utils.padRight(key, padding); output += "\t" + key.padEnd(padding, " ");
if (uri.query[key].length) { if (uri.query[key].length) {
output += " = " + uri.query[key] + "\n"; output += " = " + uri.query[key] + "\n";
} else { } else {

View file

@ -1,6 +1,3 @@
import Utils from "../core/Utils.js";
/** /**
* Waiter to handle events related to highlighting in CyberChef. * Waiter to handle events related to highlighting in CyberChef.
* *
@ -312,9 +309,9 @@ HighlighterWaiter.prototype.outputHtmlMousemove = function(e) {
HighlighterWaiter.prototype.selectionInfo = function(start, end) { HighlighterWaiter.prototype.selectionInfo = function(start, end) {
const len = end.toString().length; const len = end.toString().length;
const width = len < 2 ? 2 : len; const width = len < 2 ? 2 : len;
const startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, "&nbsp;"); const startStr = start.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const endStr = Utils.pad(end.toString(), width, " ").replace(/ /g, "&nbsp;"); const endStr = end.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const lenStr = Utils.pad((end-start).toString(), width, " ").replace(/ /g, "&nbsp;"); const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, "&nbsp;");
return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr; return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr;
}; };
@ -402,7 +399,7 @@ HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) {
// Check if there is a carriage return in the output dish as this will not // Check if there is a carriage return in the output dish as this will not
// be displayed by the HTML textarea and will mess up highlighting offsets. // be displayed by the HTML textarea and will mess up highlighting offsets.
if (!this.app.dishStr || this.app.dishStr.indexOf("\r") >= 0) return false; if (this.manager.output.containsCR()) return false;
const startPlaceholder = "[startHighlight]"; const startPlaceholder = "[startHighlight]";
const startPlaceholderRegex = /\[startHighlight\]/g; const startPlaceholderRegex = /\[startHighlight\]/g;

View file

@ -1,4 +1,4 @@
import Utils from "../core/Utils.js"; import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js";
/** /**
@ -33,6 +33,9 @@ const InputWaiter = function(app, manager) {
144, //Num 144, //Num
145, //Scroll 145, //Scroll
]; ];
this.loaderWorker = null;
this.fileBuffer = null;
}; };
@ -42,20 +45,47 @@ const InputWaiter = function(app, manager) {
* @returns {string} * @returns {string}
*/ */
InputWaiter.prototype.get = function() { InputWaiter.prototype.get = function() {
return document.getElementById("input-text").value; return this.fileBuffer || document.getElementById("input-text").value;
}; };
/** /**
* Sets the input in the input textarea. * Sets the input in the input area.
* *
* @param {string} input * @param {string|File} input
* *
* @fires Manager#statechange * @fires Manager#statechange
*/ */
InputWaiter.prototype.set = function(input) { InputWaiter.prototype.set = function(input) {
document.getElementById("input-text").value = input; const inputText = document.getElementById("input-text");
if (input instanceof File) {
this.setFile(input);
inputText.value = "";
} else {
inputText.value = input;
window.dispatchEvent(this.manager.statechange); window.dispatchEvent(this.manager.statechange);
}
};
/**
* Shows file details.
*
* @param {File} file
*/
InputWaiter.prototype.setFile = function(file) {
// Display file overlay in input area with details
const fileOverlay = document.getElementById("input-file"),
fileName = document.getElementById("input-file-name"),
fileSize = document.getElementById("input-file-size"),
fileType = document.getElementById("input-file-type"),
fileLoaded = document.getElementById("input-file-loaded");
fileOverlay.style.display = "block";
fileName.textContent = file.name;
fileSize.textContent = file.size.toLocaleString() + " bytes";
fileType.textContent = file.type || "unknown";
fileLoaded.textContent = "0%";
}; };
@ -69,8 +99,8 @@ InputWaiter.prototype.setInputInfo = function(length, lines) {
let width = length.toString().length; let width = length.toString().length;
width = width < 2 ? 2 : width; width = width < 2 ? 2 : width;
const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, "&nbsp;"); const lengthStr = length.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, "&nbsp;"); const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
document.getElementById("input-info").innerHTML = "length: " + lengthStr + "<br>lines: " + linesStr; document.getElementById("input-info").innerHTML = "length: " + lengthStr + "<br>lines: " + linesStr;
}; };
@ -118,7 +148,7 @@ InputWaiter.prototype.inputDragover = function(e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
e.target.classList.add("dropping-file"); e.target.closest("#input-text,#input-file").classList.add("dropping-file");
}; };
@ -131,7 +161,8 @@ InputWaiter.prototype.inputDragover = function(e) {
InputWaiter.prototype.inputDragleave = function(e) { InputWaiter.prototype.inputDragleave = function(e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
e.target.classList.remove("dropping-file"); document.getElementById("input-text").classList.remove("dropping-file");
document.getElementById("input-file").classList.remove("dropping-file");
}; };
@ -149,55 +180,57 @@ InputWaiter.prototype.inputDrop = function(e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
const el = e.target;
const file = e.dataTransfer.files[0]; const file = e.dataTransfer.files[0];
const text = e.dataTransfer.getData("Text"); const text = e.dataTransfer.getData("Text");
const reader = new FileReader();
let inputCharcode = "";
let offset = 0;
const CHUNK_SIZE = 20480; // 20KB
const setInput = function() { document.getElementById("input-text").classList.remove("dropping-file");
const recipeConfig = this.app.getRecipeConfig(); document.getElementById("input-file").classList.remove("dropping-file");
if (!recipeConfig[0] || recipeConfig[0].op !== "From Hex") {
recipeConfig.unshift({op: "From Hex", args: ["Space"]});
this.app.setRecipeConfig(recipeConfig);
}
this.set(inputCharcode); if (text) {
this.closeFile();
el.classList.remove("loadingFile"); this.set(text);
}.bind(this);
const seek = function() {
if (offset >= file.size) {
setInput();
return; return;
} }
el.value = "Processing... " + Math.round(offset / file.size * 100) + "%";
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.readAsArrayBuffer(slice);
};
reader.onload = function(e) {
const data = new Uint8Array(reader.result);
inputCharcode += Utils.toHexFast(data);
offset += CHUNK_SIZE;
seek();
};
el.classList.remove("dropping-file");
if (file) { if (file) {
el.classList.add("loadingFile"); this.closeFile();
seek(); this.loaderWorker = new LoaderWorker();
} else if (text) { this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
this.set(text); this.loaderWorker.postMessage({"file": file});
this.set(file);
} }
}; };
/**
* Handler for messages sent back by the LoaderWorker.
*
* @param {MessageEvent} e
*/
InputWaiter.prototype.handleLoaderMessage = function(e) {
const r = e.data;
if (r.hasOwnProperty("progress")) {
const fileLoaded = document.getElementById("input-file-loaded");
fileLoaded.textContent = r.progress + "%";
}
if (r.hasOwnProperty("fileBuffer")) {
this.fileBuffer = r.fileBuffer;
window.dispatchEvent(this.manager.statechange);
}
};
/**
* Handler for file close events.
*/
InputWaiter.prototype.closeFile = function() {
if (this.loaderWorker) this.loaderWorker.terminate();
this.fileBuffer = null;
document.getElementById("input-file").style.display = "none";
};
/** /**
* Handler for clear IO events. * Handler for clear IO events.
* Resets the input, output and info areas. * Resets the input, output and info areas.
@ -205,6 +238,8 @@ InputWaiter.prototype.inputDrop = function(e) {
* @fires Manager#statechange * @fires Manager#statechange
*/ */
InputWaiter.prototype.clearIoClick = function() { InputWaiter.prototype.clearIoClick = function() {
this.closeFile();
this.manager.output.closeFile();
this.manager.highlighter.removeHighlights(); this.manager.highlighter.removeHighlights();
document.getElementById("input-text").value = ""; document.getElementById("input-text").value = "";
document.getElementById("output-text").value = ""; document.getElementById("output-text").value = "";

50
src/web/LoaderWorker.js Normal file
View file

@ -0,0 +1,50 @@
/**
* Web Worker to load large amounts of data without locking up the UI.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
/**
* Respond to message from parent thread.
*/
self.addEventListener("message", function(e) {
const r = e.data;
if (r.hasOwnProperty("file")) {
self.loadFile(r.file);
}
});
/**
* Loads a file object into an ArrayBuffer, then transfers it back to the parent thread.
*
* @param {File} file
*/
self.loadFile = function(file) {
const reader = new FileReader();
let data = new Uint8Array(file.size);
let offset = 0;
const CHUNK_SIZE = 10485760; // 10MiB
const seek = function() {
if (offset >= file.size) {
self.postMessage({"progress": 100});
self.postMessage({"fileBuffer": data.buffer}, [data.buffer]);
return;
}
self.postMessage({"progress": Math.round(offset / file.size * 100)});
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.readAsArrayBuffer(slice);
};
reader.onload = function(e) {
data.set(new Uint8Array(reader.result), offset);
offset += CHUNK_SIZE;
seek();
};
seek();
};

View file

@ -135,13 +135,14 @@ Manager.prototype.initialiseEventListeners = function() {
this.addMultiEventListener("#input-text", "keyup paste", this.input.inputChange, this.input); this.addMultiEventListener("#input-text", "keyup paste", this.input.inputChange, this.input);
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input)); document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
document.getElementById("input-text").addEventListener("dragover", this.input.inputDragover.bind(this.input)); this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input);
document.getElementById("input-text").addEventListener("dragleave", this.input.inputDragleave.bind(this.input)); this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input);
document.getElementById("input-text").addEventListener("drop", this.input.inputDrop.bind(this.input)); this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input);
document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter)); document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter));
document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter)); document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter));
document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter)); document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter));
this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter); this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter);
document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input));
// Output // Output
document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output));
@ -157,6 +158,9 @@ Manager.prototype.initialiseEventListeners = function() {
this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); 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.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter);
this.addDynamicListener(".file-switch", "click", this.output.fileSwitch, this.output); this.addDynamicListener(".file-switch", "click", this.output.fileSwitch, this.output);
this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, this.output);
document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));
// Options // Options
document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));

View file

@ -1,4 +1,5 @@
import Utils from "../core/Utils.js"; import Utils from "../core/Utils.js";
import FileSaver from "file-saver";
/** /**
@ -15,6 +16,9 @@ import Utils from "../core/Utils.js";
const OutputWaiter = function(app, manager) { const OutputWaiter = function(app, manager) {
this.app = app; this.app = app;
this.manager = manager; this.manager = manager;
this.dishBuffer = null;
this.dishStr = null;
}; };
@ -31,27 +35,40 @@ OutputWaiter.prototype.get = function() {
/** /**
* Sets the output in the output textarea. * Sets the output in the output textarea.
* *
* @param {string} dataStr - The output string/HTML * @param {string|ArrayBuffer} data - The output string/HTML/ArrayBuffer
* @param {string} type - The data type of the output * @param {string} type - The data type of the output
* @param {number} duration - The length of time (ms) it took to generate the output * @param {number} duration - The length of time (ms) it took to generate the output
* @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer
*/ */
OutputWaiter.prototype.set = function(dataStr, type, duration) { OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
const outputText = document.getElementById("output-text"); const outputText = document.getElementById("output-text");
const outputHtml = document.getElementById("output-html"); const outputHtml = document.getElementById("output-html");
const outputFile = document.getElementById("output-file");
const outputHighlighter = document.getElementById("output-highlighter"); const outputHighlighter = document.getElementById("output-highlighter");
const inputHighlighter = document.getElementById("input-highlighter"); const inputHighlighter = document.getElementById("input-highlighter");
let scriptElements, lines, length;
if (type === "html") { if (!preserveBuffer) {
this.closeFile();
document.getElementById("show-file-overlay").style.display = "none";
}
switch (type) {
case "html":
outputText.style.display = "none"; outputText.style.display = "none";
outputHtml.style.display = "block"; outputHtml.style.display = "block";
outputFile.style.display = "none";
outputHighlighter.display = "none"; outputHighlighter.display = "none";
inputHighlighter.display = "none"; inputHighlighter.display = "none";
outputText.value = ""; outputText.value = "";
outputHtml.innerHTML = dataStr; outputHtml.innerHTML = data;
this.dishStr = Utils.stripHtmlTags(data, true);
length = data.length;
lines = this.dishStr.count("\n") + 1;
// Execute script sections // Execute script sections
const scriptElements = outputHtml.querySelectorAll("script"); scriptElements = outputHtml.querySelectorAll("script");
for (let i = 0; i < scriptElements.length; i++) { for (let i = 0; i < scriptElements.length; i++) {
try { try {
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
@ -59,19 +76,109 @@ OutputWaiter.prototype.set = function(dataStr, type, duration) {
console.error(err); console.error(err);
} }
} }
} else { break;
case "ArrayBuffer":
outputText.style.display = "block"; outputText.style.display = "block";
outputHtml.style.display = "none"; outputHtml.style.display = "none";
outputHighlighter.display = "none";
inputHighlighter.display = "none";
outputText.value = "";
outputHtml.innerHTML = "";
this.dishStr = "";
length = data.byteLength;
this.setFile(data);
break;
case "string":
default:
outputText.style.display = "block";
outputHtml.style.display = "none";
outputFile.style.display = "none";
outputHighlighter.display = "block"; outputHighlighter.display = "block";
inputHighlighter.display = "block"; inputHighlighter.display = "block";
outputText.value = Utils.printable(dataStr, true); outputText.value = Utils.printable(data, true);
outputHtml.innerHTML = ""; outputHtml.innerHTML = "";
lines = data.count("\n") + 1;
length = data.length;
this.dishStr = data;
break;
} }
this.manager.highlighter.removeHighlights(); this.manager.highlighter.removeHighlights();
const lines = dataStr.count("\n") + 1; this.setOutputInfo(length, lines, duration);
this.setOutputInfo(dataStr.length, lines, duration); };
/**
* Shows file details.
*
* @param {ArrayBuffer} buf
*/
OutputWaiter.prototype.setFile = function(buf) {
this.dishBuffer = buf;
const file = new File([buf], "output.dat");
// Display file overlay in output area with details
const fileOverlay = document.getElementById("output-file"),
fileSize = document.getElementById("output-file-size");
fileOverlay.style.display = "block";
fileSize.textContent = file.size.toLocaleString() + " bytes";
};
/**
* Removes the output file and nulls its memory.
*/
OutputWaiter.prototype.closeFile = function() {
this.dishBuffer = null;
document.getElementById("output-file").style.display = "none";
};
/**
* Handler for file download events.
*/
OutputWaiter.prototype.downloadFile = function() {
this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat");
const file = new File([this.dishBuffer], this.filename);
if (this.filename) FileSaver.saveAs(file, this.filename, false);
};
/**
* Handler for file slice display events.
*/
OutputWaiter.prototype.displayFileSlice = function() {
const startTime = new Date().getTime(),
showFileOverlay = document.getElementById("show-file-overlay"),
sliceFromEl = document.getElementById("output-file-slice-from"),
sliceToEl = document.getElementById("output-file-slice-to"),
sliceFrom = parseInt(sliceFromEl.value, 10),
sliceTo = parseInt(sliceToEl.value, 10),
str = Utils.arrayBufferToStr(this.dishBuffer.slice(sliceFrom, sliceTo));
showFileOverlay.style.display = "block";
this.set(str, "string", new Date().getTime() - startTime, true);
};
/**
* Handler for show file overlay events.
*
* @param {Event} e
*/
OutputWaiter.prototype.showFileOverlayClick = function(e) {
const outputFile = document.getElementById("output-file"),
showFileOverlay = e.target;
outputFile.style.display = "block";
showFileOverlay.style.display = "none";
this.setOutputInfo(this.dishBuffer.byteLength, null, 0);
}; };
@ -86,13 +193,17 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) {
let width = length.toString().length; let width = length.toString().length;
width = width < 4 ? 4 : width; width = width < 4 ? 4 : width;
const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, "&nbsp;"); const lengthStr = length.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, "&nbsp;"); const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, "&nbsp;");
const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, "&nbsp;");
document.getElementById("output-info").innerHTML = "time: " + timeStr + let msg = "time: " + timeStr + "<br>length: " + lengthStr;
"<br>length: " + lengthStr +
"<br>lines: " + linesStr; if (typeof lines === "number") {
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
msg += "<br>lines: " + linesStr;
}
document.getElementById("output-info").innerHTML = msg;
document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("input-selection-info").innerHTML = "";
document.getElementById("output-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = "";
}; };
@ -129,24 +240,13 @@ OutputWaiter.prototype.adjustWidth = function() {
/** /**
* Handler for save click events. * Handler for save click events.
* Saves the current output to a file, downloaded as a URL octet stream. * Saves the current output to a file.
*/ */
OutputWaiter.prototype.saveClick = function() { OutputWaiter.prototype.saveClick = function() {
const data = Utils.toBase64(this.app.dishStr); if (!this.dishBuffer) {
const filename = window.prompt("Please enter a filename:", "download.dat"); this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.dishStr)).buffer;
if (filename) {
const el = document.createElement("a");
el.setAttribute("href", "data:application/octet-stream;base64;charset=utf-8," + data);
el.setAttribute("download", filename);
// Firefox requires that the element be added to the DOM before it can be clicked
el.style.display = "none";
document.body.appendChild(el);
el.click();
el.remove();
} }
this.downloadFile();
}; };
@ -165,14 +265,14 @@ OutputWaiter.prototype.copyClick = function() {
textarea.style.height = 0; textarea.style.height = 0;
textarea.style.border = "none"; textarea.style.border = "none";
textarea.value = this.app.dishStr; textarea.value = this.dishStr;
document.body.appendChild(textarea); document.body.appendChild(textarea);
// Select and copy the contents of this textarea // Select and copy the contents of this textarea
let success = false; let success = false;
try { try {
textarea.select(); textarea.select();
success = document.execCommand("copy"); success = this.dishStr && document.execCommand("copy");
} catch (err) { } catch (err) {
success = false; success = false;
} }
@ -195,7 +295,17 @@ OutputWaiter.prototype.copyClick = function() {
OutputWaiter.prototype.switchClick = function() { OutputWaiter.prototype.switchClick = function() {
this.switchOrigData = this.manager.input.get(); this.switchOrigData = this.manager.input.get();
document.getElementById("undo-switch").disabled = false; document.getElementById("undo-switch").disabled = false;
this.app.setInput(this.app.dishStr); if (this.dishBuffer) {
this.manager.input.setFile(new File([this.dishBuffer], "output.dat"));
this.manager.input.handleLoaderMessage({
data: {
progress: 100,
fileBuffer: this.dishBuffer
}
});
} else {
this.app.setInput(this.dishStr);
}
}; };
@ -210,7 +320,7 @@ OutputWaiter.prototype.undoSwitchClick = function() {
/** /**
* Handler for file switch click events. * Handler for file switch click events.
* Moves a files data for items created via Utils.displayFilesAsHTML to the input. * Moves a file's data for items created via Utils.displayFilesAsHTML to the input.
*/ */
OutputWaiter.prototype.fileSwitch = function(e) { OutputWaiter.prototype.fileSwitch = function(e) {
e.preventDefault(); e.preventDefault();
@ -282,4 +392,14 @@ OutputWaiter.prototype.setStatusMsg = function(msg) {
el.textContent = msg; el.textContent = msg;
}; };
/**
* Returns true if the output contains carriage returns
*
* @returns {boolean}
*/
OutputWaiter.prototype.containsCR = function() {
return this.dishStr.indexOf("\r") >= 0;
};
export default OutputWaiter; export default OutputWaiter;

View file

@ -1,4 +1,3 @@
import Utils from "../core/Utils.js";
import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.js"; import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.js";
/** /**
@ -111,7 +110,6 @@ WorkerWaiter.prototype.bakingComplete = function(response) {
this.app.handleError(response.error); this.app.handleError(response.error);
} }
this.app.dishStr = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result;
this.app.progress = response.progress; this.app.progress = response.progress;
this.manager.recipe.updateBreakpointIndicator(response.progress); this.manager.recipe.updateBreakpointIndicator(response.progress);
this.manager.output.set(response.result, response.type, response.duration); this.manager.output.set(response.result, response.type, response.duration);

View file

@ -181,6 +181,20 @@
<div class="textarea-wrapper no-select"> <div class="textarea-wrapper no-select">
<div id="input-highlighter" class="no-select"></div> <div id="input-highlighter" class="no-select"></div>
<textarea id="input-text"></textarea> <textarea id="input-text"></textarea>
<div id="input-file">
<div style="position: relative; height: 100%;">
<div class="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>
Name: <span id="input-file-name"></span><br>
Size: <span id="input-file-size"></span><br>
Type: <span id="input-file-type"></span><br>
Loaded: <span id="input-file-loaded"></span>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
@ -202,6 +216,26 @@
<div id="output-highlighter" class="no-select"></div> <div id="output-highlighter" class="no-select"></div>
<div id="output-html"></div> <div id="output-html"></div>
<textarea id="output-text" readonly="readonly"></textarea> <textarea id="output-text" readonly="readonly"></textarea>
<img id="show-file-overlay" aria-hidden="true" src="<%- require('../static/images/file-32x32.png') %>" alt="Show file overlay" title="Show file overlay"/>
<div id="output-file">
<div style="position: relative; height: 100%;">
<div class="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>
<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>
</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>
<input type="number" class="form-control" id="output-file-slice-to" placeholder="To" value="1024" step="1024" min="0">
</div>
</div>
</div>
</div>
</div>
<div id="output-loader"> <div id="output-loader">
<div class="loader"></div> <div class="loader"></div>
<div class="loading-msg"></div> <div class="loading-msg"></div>
@ -334,6 +368,10 @@
<input type="number" option="errorTimeout" id="errorTimeout" /> <input type="number" option="errorTimeout" id="errorTimeout" />
<label for="errorTimeout"> Operation error timeout in ms (0 for never)</label> <label for="errorTimeout"> Operation error timeout in ms (0 for never)</label>
</div> </div>
<div class="option-item">
<input type="number" option="outputFileThreshold" id="outputFileThreshold" />
<label for="outputFileThreshold"> Size threshold for treating the output as a file (KiB)</label>
</div>
</div> </div>
<div class="modal-footer"> <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" id="reset-options">Reset options to default</button>
@ -438,8 +476,9 @@
</a> </a>
</blockquote> </blockquote>
<div class="collapse" id="faq-load-files"> <div class="collapse" id="faq-load-files">
<p>Yes! Just drag your file over the input box and drop it. The contents of the file will be converted into hexadecimal and the 'From Hex' operation will be added to the beginning of the recipe (if it's not already there). This is so that special characters like carriage returns aren't removed by your browser.</p> <p>Yes! Just drag your file over the input box and drop it.</p>
<p>Please note that loading large files is likely to cause a crash. There's not a lot that can be done about this - browsers just aren't very good at handling and displaying large amounts of data.</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>
<blockquote> <blockquote>
<a data-toggle="collapse" data-target="#faq-fork"> <a data-toggle="collapse" data-target="#faq-fork">
@ -482,7 +521,7 @@
<br> <br>
<p>There are around 150 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>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> <p>Its the Cyber Swiss Army Knife.</p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;"> <div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;">

View file

@ -46,7 +46,8 @@ function main() {
errorTimeout: 4000, errorTimeout: 4000,
attemptHighlight: true, attemptHighlight: true,
theme: "classic", theme: "classic",
useMetaKey: false useMetaKey: false,
outputFileThreshold: 1024
}; };
document.removeEventListener("DOMContentLoaded", main, false); document.removeEventListener("DOMContentLoaded", main, false);

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -28,3 +28,56 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.card {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s;
width: 400px;
height: 150px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: var(--primary-font-family);
color: var(--primary-font-colour);
line-height: 30px;
background-color: var(--primary-background-colour);
}
.card:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
.card>img {
float: left;
width: 128px;
height: 128px;
margin-left: 10px;
margin-top: 11px;
}
.card-body .close {
position: absolute;
right: 10px;
top: 10px;
}
.card-body {
float: left;
padding: 16px;
width: 250px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
}
.card-body>.btn {
margin-bottom: 15px;
margin-top: 5px;
}
.card input[type=number] {
padding-right: 6px;
padding-left: 6px;
}

View file

@ -39,7 +39,7 @@
} }
.textarea-wrapper textarea, .textarea-wrapper textarea,
.textarea-wrapper div { .textarea-wrapper>div {
font-family: var(--fixed-width-font-family); font-family: var(--fixed-width-font-family);
font-size: var(--fixed-width-font-size); font-size: var(--fixed-width-font-size);
color: var(--fixed-width-font-colour); color: var(--fixed-width-font-colour);
@ -77,6 +77,25 @@
transition: all 0.5s ease; transition: all 0.5s ease;
} }
#input-file,
#output-file {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: var(--title-background-colour);
display: none;
}
#show-file-overlay {
position: absolute;
right: 15px;
top: 15px;
cursor: pointer;
display: none;
}
.io-btn-group { .io-btn-group {
float: right; float: right;
margin-top: -4px; margin-top: -4px;
@ -92,6 +111,8 @@
font-family: var(--fixed-width-font-family); font-family: var(--fixed-width-font-family);
font-weight: normal; font-weight: normal;
font-size: 8pt; font-size: 8pt;
display: flex;
align-items: center;
} }
#input-info { #input-info {

View file

@ -64,7 +64,8 @@ a:focus {
.alert, .alert,
.modal-content, .modal-content,
.tooltip-inner, .tooltip-inner,
.dropdown-menu { .dropdown-menu,
.input-group-addon {
border-radius: 0 !important; border-radius: 0 !important;
} }
@ -187,6 +188,15 @@ optgroup {
color: var(--primary-font-colour); color: var(--primary-font-colour);
} }
.input-group-addon:not(:first-child):not(:last-child) {
border-left: 0;
border-right: 0;
}
.input-group-btn:first-child>.btn {
border-right: 0;
}
/* Bootstrap-switch */ /* Bootstrap-switch */