mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-25 01:06:16 -04:00
Input and output character encodings can now be set
This commit is contained in:
parent
7c8a185a3d
commit
e93aa42697
15 changed files with 482 additions and 423 deletions
|
@ -10,6 +10,7 @@ import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker
|
|||
import Utils, {debounce} from "../../core/Utils.mjs";
|
||||
import {toBase64} from "../../core/lib/Base64.mjs";
|
||||
import {isImage} from "../../core/lib/FileType.mjs";
|
||||
import cptable from "codepage";
|
||||
|
||||
import {
|
||||
EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor, dropCursor
|
||||
|
@ -39,6 +40,7 @@ class InputWaiter {
|
|||
this.manager = manager;
|
||||
|
||||
this.inputTextEl = document.getElementById("input-text");
|
||||
this.inputChrEnc = 0;
|
||||
this.initEditor();
|
||||
|
||||
this.inputWorker = null;
|
||||
|
@ -84,7 +86,9 @@ class InputWaiter {
|
|||
// Custom extensions
|
||||
statusBar({
|
||||
label: "Input",
|
||||
eolHandler: this.eolChange.bind(this)
|
||||
eolHandler: this.eolChange.bind(this),
|
||||
chrEncHandler: this.chrEncChange.bind(this),
|
||||
initialChrEncVal: this.inputChrEnc
|
||||
}),
|
||||
|
||||
// Mutable state
|
||||
|
@ -122,19 +126,30 @@ class InputWaiter {
|
|||
/**
|
||||
* Handler for EOL change events
|
||||
* Sets the line separator
|
||||
* @param {string} eolVal
|
||||
*/
|
||||
eolChange(eolval) {
|
||||
eolChange(eolVal) {
|
||||
const oldInputVal = this.getInput();
|
||||
|
||||
// Update the EOL value
|
||||
this.inputEditorView.dispatch({
|
||||
effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval))
|
||||
effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal))
|
||||
});
|
||||
|
||||
// Reset the input so that lines are recalculated, preserving the old EOL values
|
||||
this.setInput(oldInputVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for Chr Enc change events
|
||||
* Sets the input character encoding
|
||||
* @param {number} chrEncVal
|
||||
*/
|
||||
chrEncChange(chrEncVal) {
|
||||
this.inputChrEnc = chrEncVal;
|
||||
this.inputChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets word wrap on the input editor
|
||||
* @param {boolean} wrap
|
||||
|
@ -380,7 +395,7 @@ class InputWaiter {
|
|||
this.showLoadingInfo(r.data, true);
|
||||
break;
|
||||
case "setInput":
|
||||
this.set(r.data.inputObj, r.data.silent);
|
||||
this.set(r.data.inputNum, r.data.inputObj, r.data.silent);
|
||||
break;
|
||||
case "inputAdded":
|
||||
this.inputAdded(r.data.changeTab, r.data.inputNum);
|
||||
|
@ -403,9 +418,6 @@ class InputWaiter {
|
|||
case "setUrl":
|
||||
this.setUrl(r.data);
|
||||
break;
|
||||
case "inputSwitch":
|
||||
this.manager.output.inputSwitch(r.data);
|
||||
break;
|
||||
case "getInput":
|
||||
case "getInputNums":
|
||||
this.callbacks[r.data.id](r.data);
|
||||
|
@ -435,22 +447,36 @@ class InputWaiter {
|
|||
/**
|
||||
* Sets the input in the input area
|
||||
*
|
||||
* @param {object} inputData - Object containing the input and its metadata
|
||||
* @param {number} inputData.inputNum - The unique inputNum for the selected input
|
||||
* @param {string | object} inputData.input - The actual input data
|
||||
* @param {string} inputData.name - The name of the input file
|
||||
* @param {number} inputData.size - The size in bytes of the input file
|
||||
* @param {string} inputData.type - The MIME type of the input file
|
||||
* @param {number} inputData.progress - The load progress of the input file
|
||||
* @param {number} inputNum
|
||||
* @param {Object} inputData - Object containing the input and its metadata
|
||||
* @param {string} type
|
||||
* @param {ArrayBuffer} buffer
|
||||
* @param {string} stringSample
|
||||
* @param {Object} file
|
||||
* @param {string} file.name
|
||||
* @param {number} file.size
|
||||
* @param {string} file.type
|
||||
* @param {string} status
|
||||
* @param {number} progress
|
||||
* @param {boolean} [silent=false] - If false, fires the manager statechange event
|
||||
*/
|
||||
async set(inputData, silent=false) {
|
||||
async set(inputNum, inputData, silent=false) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const activeTab = this.manager.tabs.getActiveInputTab();
|
||||
if (inputData.inputNum !== activeTab) return;
|
||||
if (inputNum !== activeTab) return;
|
||||
|
||||
if (typeof inputData.input === "string") {
|
||||
this.setInput(inputData.input);
|
||||
if (inputData.file) {
|
||||
this.setFile(inputNum, inputData, silent);
|
||||
} else {
|
||||
// TODO Per-tab encodings?
|
||||
let inputVal;
|
||||
if (this.inputChrEnc > 0) {
|
||||
inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer));
|
||||
} else {
|
||||
inputVal = Utils.arrayBufferToStr(inputData.buffer);
|
||||
}
|
||||
|
||||
this.setInput(inputVal);
|
||||
const fileOverlay = document.getElementById("input-file"),
|
||||
fileName = document.getElementById("input-file-name"),
|
||||
fileSize = document.getElementById("input-file-size"),
|
||||
|
@ -466,8 +492,8 @@ class InputWaiter {
|
|||
this.inputTextEl.classList.remove("blur");
|
||||
|
||||
// Set URL to current input
|
||||
const inputStr = toBase64(inputData.input, "A-Za-z0-9+/");
|
||||
if (inputStr.length >= 0 && inputStr.length <= 68267) {
|
||||
if (inputVal.length >= 0 && inputVal.length <= 51200) {
|
||||
const inputStr = toBase64(inputVal, "A-Za-z0-9+/");
|
||||
this.setUrl({
|
||||
includeInput: true,
|
||||
input: inputStr
|
||||
|
@ -475,8 +501,6 @@ class InputWaiter {
|
|||
}
|
||||
|
||||
if (!silent) window.dispatchEvent(this.manager.statechange);
|
||||
} else {
|
||||
this.setFile(inputData, silent);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
|
@ -485,18 +509,22 @@ class InputWaiter {
|
|||
/**
|
||||
* Displays file details
|
||||
*
|
||||
* @param {object} inputData - Object containing the input and its metadata
|
||||
* @param {number} inputData.inputNum - The unique inputNum for the selected input
|
||||
* @param {string | object} inputData.input - The actual input data
|
||||
* @param {string} inputData.name - The name of the input file
|
||||
* @param {number} inputData.size - The size in bytes of the input file
|
||||
* @param {string} inputData.type - The MIME type of the input file
|
||||
* @param {number} inputData.progress - The load progress of the input file
|
||||
* @param {number} inputNum
|
||||
* @param {Object} inputData - Object containing the input and its metadata
|
||||
* @param {string} type
|
||||
* @param {ArrayBuffer} buffer
|
||||
* @param {string} stringSample
|
||||
* @param {Object} file
|
||||
* @param {string} file.name
|
||||
* @param {number} file.size
|
||||
* @param {string} file.type
|
||||
* @param {string} status
|
||||
* @param {number} progress
|
||||
* @param {boolean} [silent=true] - If false, fires the manager statechange event
|
||||
*/
|
||||
setFile(inputData, silent=true) {
|
||||
setFile(inputNum, inputData, silent=true) {
|
||||
const activeTab = this.manager.tabs.getActiveInputTab();
|
||||
if (inputData.inputNum !== activeTab) return;
|
||||
if (inputNum !== activeTab) return;
|
||||
|
||||
const fileOverlay = document.getElementById("input-file"),
|
||||
fileName = document.getElementById("input-file-name"),
|
||||
|
@ -505,9 +533,9 @@ class InputWaiter {
|
|||
fileLoaded = document.getElementById("input-file-loaded");
|
||||
|
||||
fileOverlay.style.display = "block";
|
||||
fileName.textContent = inputData.name;
|
||||
fileSize.textContent = inputData.size + " bytes";
|
||||
fileType.textContent = inputData.type;
|
||||
fileName.textContent = inputData.file.name;
|
||||
fileSize.textContent = inputData.file.size + " bytes";
|
||||
fileType.textContent = inputData.file.type;
|
||||
if (inputData.status === "error") {
|
||||
fileLoaded.textContent = "Error";
|
||||
fileLoaded.style.color = "#FF0000";
|
||||
|
@ -516,7 +544,7 @@ class InputWaiter {
|
|||
fileLoaded.textContent = inputData.progress + "%";
|
||||
}
|
||||
|
||||
this.displayFilePreview(inputData);
|
||||
this.displayFilePreview(inputNum, inputData);
|
||||
|
||||
if (!silent) window.dispatchEvent(this.manager.statechange);
|
||||
}
|
||||
|
@ -583,19 +611,18 @@ class InputWaiter {
|
|||
/**
|
||||
* Shows a chunk of the file in the input behind the file overlay
|
||||
*
|
||||
* @param {number} inputNum - The inputNum of the file being displayed
|
||||
* @param {Object} inputData - Object containing the input data
|
||||
* @param {number} inputData.inputNum - The inputNum of the file being displayed
|
||||
* @param {ArrayBuffer} inputData.input - The actual input to display
|
||||
* @param {string} inputData.stringSample - The first 4096 bytes of input as a string
|
||||
*/
|
||||
displayFilePreview(inputData) {
|
||||
displayFilePreview(inputNum, inputData) {
|
||||
const activeTab = this.manager.tabs.getActiveInputTab(),
|
||||
input = inputData.input;
|
||||
if (inputData.inputNum !== activeTab) return;
|
||||
input = inputData.buffer;
|
||||
if (inputNum !== activeTab) return;
|
||||
this.inputTextEl.classList.add("blur");
|
||||
this.setInput(Utils.arrayBufferToStr(input.slice(0, 4096)));
|
||||
this.setInput(input.stringSample);
|
||||
|
||||
this.renderFileThumb();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -623,46 +650,40 @@ class InputWaiter {
|
|||
*
|
||||
* @param {number} inputNum
|
||||
* @param {string | ArrayBuffer} value
|
||||
* @param {boolean} [force=false] - If true, forces the value to be updated even if the type is different to the currently stored type
|
||||
*/
|
||||
updateInputValue(inputNum, value, force=false) {
|
||||
let includeInput = false;
|
||||
const recipeStr = toBase64(value, "A-Za-z0-9+/"); // B64 alphabet with no padding
|
||||
if (recipeStr.length > 0 && recipeStr.length <= 68267) {
|
||||
includeInput = true;
|
||||
// Prepare the value as a buffer (full value) and a string sample (up to 4096 bytes)
|
||||
let buffer;
|
||||
let stringSample = "";
|
||||
|
||||
// If value is a string, interpret it using the specified character encoding
|
||||
if (typeof value === "string") {
|
||||
stringSample = value.slice(0, 4096);
|
||||
if (this.inputChrEnc > 0) {
|
||||
buffer = cptable.utils.encode(this.inputChrEnc, value);
|
||||
buffer = new Uint8Array(buffer).buffer;
|
||||
} else {
|
||||
buffer = Utils.strToArrayBuffer(value);
|
||||
}
|
||||
} else {
|
||||
buffer = value;
|
||||
stringSample = Utils.arrayBufferToStr(value.slice(0, 4096));
|
||||
}
|
||||
|
||||
|
||||
const recipeStr = buffer.byteLength < 51200 ? toBase64(buffer, "A-Za-z0-9+/") : ""; // B64 alphabet with no padding
|
||||
this.setUrl({
|
||||
includeInput: includeInput,
|
||||
includeInput: recipeStr.length > 0 && buffer.byteLength < 51200,
|
||||
input: recipeStr
|
||||
});
|
||||
|
||||
// Value is either a string set by the input or an ArrayBuffer from a LoaderWorker,
|
||||
// so is safe to use typeof === "string"
|
||||
const transferable = (typeof value !== "string") ? [value] : undefined;
|
||||
const transferable = [buffer];
|
||||
this.inputWorker.postMessage({
|
||||
action: "updateInputValue",
|
||||
data: {
|
||||
inputNum: inputNum,
|
||||
value: value,
|
||||
force: force
|
||||
}
|
||||
}, transferable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the .data property for the input of the specified inputNum.
|
||||
* Used for switching the output into the input
|
||||
*
|
||||
* @param {number} inputNum - The inputNum of the input we're changing
|
||||
* @param {object} inputData - The new data object
|
||||
*/
|
||||
updateInputObj(inputNum, inputData) {
|
||||
const transferable = (typeof inputData !== "string") ? [inputData.fileBuffer] : undefined;
|
||||
this.inputWorker.postMessage({
|
||||
action: "updateInputObj",
|
||||
data: {
|
||||
inputNum: inputNum,
|
||||
data: inputData
|
||||
buffer: buffer,
|
||||
stringSample: stringSample
|
||||
}
|
||||
}, transferable);
|
||||
}
|
||||
|
@ -1052,9 +1073,8 @@ class InputWaiter {
|
|||
|
||||
this.updateInputValue(inputNum, "", true);
|
||||
|
||||
this.set({
|
||||
inputNum: inputNum,
|
||||
input: ""
|
||||
this.set(inputNum, {
|
||||
buffer: new ArrayBuffer()
|
||||
});
|
||||
|
||||
this.manager.tabs.updateInputTabHeader(inputNum, "");
|
||||
|
|
|
@ -9,6 +9,7 @@ import Utils, {debounce} from "../../core/Utils.mjs";
|
|||
import Dish from "../../core/Dish.mjs";
|
||||
import FileSaver from "file-saver";
|
||||
import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs";
|
||||
import cptable from "codepage";
|
||||
|
||||
import {
|
||||
EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor
|
||||
|
@ -48,6 +49,7 @@ class OutputWaiter {
|
|||
html: "",
|
||||
changed: false
|
||||
};
|
||||
this.outputChrEnc = 0;
|
||||
this.initEditor();
|
||||
|
||||
this.outputs = {};
|
||||
|
@ -86,7 +88,9 @@ class OutputWaiter {
|
|||
statusBar({
|
||||
label: "Output",
|
||||
bakeStats: this.bakeStats,
|
||||
eolHandler: this.eolChange.bind(this)
|
||||
eolHandler: this.eolChange.bind(this),
|
||||
chrEncHandler: this.chrEncChange.bind(this),
|
||||
initialChrEncVal: this.outputChrEnc
|
||||
}),
|
||||
htmlPlugin(this.htmlOutput),
|
||||
copyOverride(),
|
||||
|
@ -119,19 +123,29 @@ class OutputWaiter {
|
|||
/**
|
||||
* Handler for EOL change events
|
||||
* Sets the line separator
|
||||
* @param {string} eolVal
|
||||
*/
|
||||
eolChange(eolval) {
|
||||
eolChange(eolVal) {
|
||||
const oldOutputVal = this.getOutput();
|
||||
|
||||
// Update the EOL value
|
||||
this.outputEditorView.dispatch({
|
||||
effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval))
|
||||
effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal))
|
||||
});
|
||||
|
||||
// Reset the output so that lines are recalculated, preserving the old EOL values
|
||||
this.setOutput(oldOutputVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for Chr Enc change events
|
||||
* Sets the output character encoding
|
||||
* @param {number} chrEncVal
|
||||
*/
|
||||
chrEncChange(chrEncVal) {
|
||||
this.outputChrEnc = chrEncVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets word wrap on the output editor
|
||||
* @param {boolean} wrap
|
||||
|
@ -193,7 +207,8 @@ class OutputWaiter {
|
|||
});
|
||||
|
||||
// Execute script sections
|
||||
const scriptElements = document.getElementById("output-html").querySelectorAll("script");
|
||||
const outputHTML = document.getElementById("output-html");
|
||||
const scriptElements = outputHTML ? outputHTML.querySelectorAll("script") : [];
|
||||
for (let i = 0; i < scriptElements.length; i++) {
|
||||
try {
|
||||
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
|
||||
|
@ -405,8 +420,6 @@ class OutputWaiter {
|
|||
removeAllOutputs() {
|
||||
this.outputs = {};
|
||||
|
||||
this.resetSwitch();
|
||||
|
||||
const tabsList = document.getElementById("output-tabs");
|
||||
const tabsListChildren = tabsList.children;
|
||||
|
||||
|
@ -418,19 +431,18 @@ class OutputWaiter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the output in the output textarea.
|
||||
* Sets the output in the output pane.
|
||||
*
|
||||
* @param {number} inputNum
|
||||
*/
|
||||
async set(inputNum) {
|
||||
inputNum = parseInt(inputNum, 10);
|
||||
if (inputNum !== this.manager.tabs.getActiveOutputTab() ||
|
||||
!this.outputExists(inputNum)) return;
|
||||
this.toggleLoader(true);
|
||||
|
||||
return new Promise(async function(resolve, reject) {
|
||||
const output = this.outputs[inputNum],
|
||||
activeTab = this.manager.tabs.getActiveOutputTab();
|
||||
if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10);
|
||||
const output = this.outputs[inputNum];
|
||||
|
||||
const outputFile = document.getElementById("output-file");
|
||||
|
||||
|
@ -491,17 +503,33 @@ class OutputWaiter {
|
|||
switch (output.data.type) {
|
||||
case "html":
|
||||
outputFile.style.display = "none";
|
||||
// TODO what if the HTML content needs to be in a certain character encoding?
|
||||
// Grey out chr enc selection? Set back to Raw Bytes?
|
||||
|
||||
this.setHTMLOutput(output.data.result);
|
||||
break;
|
||||
case "ArrayBuffer":
|
||||
case "ArrayBuffer": {
|
||||
this.outputTextEl.style.display = "block";
|
||||
outputFile.style.display = "none";
|
||||
|
||||
this.clearHTMLOutput();
|
||||
this.setOutput("");
|
||||
|
||||
this.setFile(await this.getDishBuffer(output.data.dish), activeTab);
|
||||
let outputVal = "";
|
||||
if (this.outputChrEnc === 0) {
|
||||
outputVal = Utils.arrayBufferToStr(output.data.result);
|
||||
} else {
|
||||
try {
|
||||
outputVal = cptable.utils.decode(this.outputChrEnc, new Uint8Array(output.data.result));
|
||||
} catch (err) {
|
||||
outputVal = err;
|
||||
}
|
||||
}
|
||||
|
||||
this.setOutput(outputVal);
|
||||
|
||||
// this.setFile(await this.getDishBuffer(output.data.dish), activeTab);
|
||||
break;
|
||||
}
|
||||
case "string":
|
||||
default:
|
||||
this.outputTextEl.style.display = "block";
|
||||
|
@ -1333,7 +1361,6 @@ class OutputWaiter {
|
|||
*/
|
||||
async switchClick() {
|
||||
const activeTab = this.manager.tabs.getActiveOutputTab();
|
||||
const transferable = [];
|
||||
|
||||
const switchButton = document.getElementById("switch");
|
||||
switchButton.classList.add("spin");
|
||||
|
@ -1341,82 +1368,15 @@ class OutputWaiter {
|
|||
switchButton.firstElementChild.innerHTML = "autorenew";
|
||||
$(switchButton).tooltip("hide");
|
||||
|
||||
let active = await this.getDishBuffer(this.getOutputDish(activeTab));
|
||||
const activeData = await this.getDishBuffer(this.getOutputDish(activeTab));
|
||||
|
||||
if (!this.outputExists(activeTab)) {
|
||||
this.resetSwitchButton();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.outputs[activeTab].data.type === "string" &&
|
||||
active.byteLength <= this.app.options.ioDisplayThreshold * 1024) {
|
||||
const dishString = await this.getDishStr(this.getOutputDish(activeTab));
|
||||
active = dishString;
|
||||
} else {
|
||||
transferable.push(active);
|
||||
}
|
||||
|
||||
this.manager.input.inputWorker.postMessage({
|
||||
action: "inputSwitch",
|
||||
data: {
|
||||
if (this.outputExists(activeTab)) {
|
||||
this.manager.input.set({
|
||||
inputNum: activeTab,
|
||||
outputData: active
|
||||
}
|
||||
}, transferable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for when the inputWorker has switched the inputs.
|
||||
* Stores the old input
|
||||
*
|
||||
* @param {object} switchData
|
||||
* @param {number} switchData.inputNum
|
||||
* @param {string | object} switchData.data
|
||||
* @param {ArrayBuffer} switchData.data.fileBuffer
|
||||
* @param {number} switchData.data.size
|
||||
* @param {string} switchData.data.type
|
||||
* @param {string} switchData.data.name
|
||||
*/
|
||||
inputSwitch(switchData) {
|
||||
this.switchOrigData = switchData;
|
||||
document.getElementById("undo-switch").disabled = false;
|
||||
|
||||
this.resetSwitchButton();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for undo switch click events.
|
||||
* Removes the output from the input and replaces the input that was removed.
|
||||
*/
|
||||
undoSwitchClick() {
|
||||
this.manager.input.updateInputObj(this.switchOrigData.inputNum, this.switchOrigData.data);
|
||||
|
||||
this.manager.input.fileLoaded(this.switchOrigData.inputNum);
|
||||
|
||||
this.resetSwitch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the switch data and resets the switch buttons
|
||||
*/
|
||||
resetSwitch() {
|
||||
if (this.switchOrigData !== undefined) {
|
||||
delete this.switchOrigData;
|
||||
input: activeData
|
||||
});
|
||||
}
|
||||
|
||||
const undoSwitch = document.getElementById("undo-switch");
|
||||
undoSwitch.disabled = true;
|
||||
$(undoSwitch).tooltip("hide");
|
||||
|
||||
this.resetSwitchButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the switch button to its usual state
|
||||
*/
|
||||
resetSwitchButton() {
|
||||
const switchButton = document.getElementById("switch");
|
||||
switchButton.classList.remove("spin");
|
||||
switchButton.disabled = false;
|
||||
switchButton.firstElementChild.innerHTML = "open_in_browser";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue