From 4e00ac930028390d19297225c689cfbac617fd5a Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 18 Dec 2017 20:39:55 +0000 Subject: [PATCH 01/15] Files are now uploaded in a worker and not displayed in the input by default. Added ArrayBuffer Dish type. --- src/core/Chef.js | 7 +- src/core/Dish.js | 46 ++++++--- src/web/InputWaiter.js | 122 +++++++++++++++-------- src/web/LoaderWorker.js | 50 ++++++++++ src/web/Manager.js | 7 +- src/web/html/index.html | 14 +++ src/web/stylesheets/components/_pane.css | 41 ++++++++ src/web/stylesheets/layout/_io.css | 12 ++- 8 files changed, 232 insertions(+), 67 deletions(-) create mode 100644 src/web/LoaderWorker.js diff --git a/src/core/Chef.js b/src/core/Chef.js index 4e7c042a..d176a36d 100755 --- a/src/core/Chef.js +++ b/src/core/Chef.js @@ -19,7 +19,7 @@ const Chef = function() { /** * 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} options - The options object storing various user choices * @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.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(), recipe = new Recipe(recipeConfig), containsFc = recipe.containsFlowControl(), @@ -62,7 +62,8 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress, // If starting from scratch, load data 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 { diff --git a/src/core/Dish.js b/src/core/Dish.js index 914188c1..3cd1c6f3 100755 --- a/src/core/Dish.js +++ b/src/core/Dish.js @@ -8,11 +8,11 @@ import Utils from "./Utils.js"; * @license Apache-2.0 * * @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. */ 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; }; @@ -41,6 +41,12 @@ Dish.NUMBER = 2; * @enum */ 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": return Dish.HTML; + case "arrayBuffer": + case "ArrayBuffer": + return Dish.ARRAY_BUFFER; default: throw "Invalid data type string. No matching enum."; } @@ -87,6 +96,8 @@ Dish.enumLookup = function(typeEnum) { return "number"; case Dish.HTML: return "html"; + case Dish.ARRAY_BUFFER: + return "ArrayBuffer"; default: 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. * - * @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. */ 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. * * @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) { if (this.type !== type) { @@ -134,20 +145,23 @@ Dish.prototype.translate = function(toType) { switch (this.type) { case Dish.STRING: this.value = this.value ? Utils.strToByteArray(this.value) : []; - this.type = Dish.BYTE_ARRAY; break; case Dish.NUMBER: this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : []; - this.type = Dish.BYTE_ARRAY; break; case Dish.HTML: 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; default: break; } + this.type = Dish.BYTE_ARRAY; + // Convert from byteArray to toType switch (toType) { case Dish.STRING: @@ -159,6 +173,10 @@ Dish.prototype.translate = function(toType) { this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0; this.type = Dish.NUMBER; break; + case Dish.ARRAY_BUFFER: + this.value = new Uint8Array(this.value).buffer; + this.type = Dish.ARRAY_BUFFER; + break; default: break; } @@ -180,7 +198,7 @@ Dish.prototype.valid = function() { // Check that every value is a number between 0 - 255 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] > 255) { return false; @@ -189,15 +207,11 @@ Dish.prototype.valid = function() { return true; case Dish.STRING: case Dish.HTML: - if (typeof this.value == "string") { - return true; - } - return false; + return typeof this.value === "string"; case Dish.NUMBER: - if (typeof this.value == "number") { - return true; - } - return false; + return typeof this.value === "number"; + case Dish.ARRAY_BUFFER: + return this.value instanceof ArrayBuffer; default: return false; } diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js index aba57334..b5748e3f 100755 --- a/src/web/InputWaiter.js +++ b/src/web/InputWaiter.js @@ -1,4 +1,5 @@ import Utils from "../core/Utils.js"; +import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js"; /** @@ -33,6 +34,9 @@ const InputWaiter = function(app, manager) { 144, //Num 145, //Scroll ]; + + this.loaderWorker = null; + this.fileBuffer = null; }; @@ -42,23 +46,49 @@ const InputWaiter = function(app, manager) { * @returns {string} */ 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 */ InputWaiter.prototype.set = function(input) { + if (input instanceof File) { + this.setFile(input); + input = ""; + } + document.getElementById("input-text").value = input; 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"), + fileUploaded = document.getElementById("input-file-uploaded"); + + fileOverlay.style.display = "block"; + fileName.textContent = file.name; + fileSize.textContent = file.size.toLocaleString() + " bytes"; + fileType.textContent = file.type; + fileUploaded.textContent = "0%"; +}; + + /** * Displays information about the input. * @@ -118,7 +148,7 @@ InputWaiter.prototype.inputDragover = function(e) { e.stopPropagation(); 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) { e.stopPropagation(); 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.preventDefault(); - const el = e.target; const file = e.dataTransfer.files[0]; const text = e.dataTransfer.getData("Text"); - const reader = new FileReader(); - let inputCharcode = ""; - let offset = 0; - const CHUNK_SIZE = 20480; // 20KB - const setInput = function() { - const recipeConfig = this.app.getRecipeConfig(); - if (!recipeConfig[0] || recipeConfig[0].op !== "From Hex") { - recipeConfig.unshift({op: "From Hex", args: ["Space"]}); - this.app.setRecipeConfig(recipeConfig); - } + document.getElementById("input-text").classList.remove("dropping-file"); + document.getElementById("input-file").classList.remove("dropping-file"); - this.set(inputCharcode); - - el.classList.remove("loadingFile"); - }.bind(this); - - const seek = function() { - if (offset >= file.size) { - setInput(); - 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 (text) { + this.closeFile(); + this.set(text); + return; + } if (file) { - el.classList.add("loadingFile"); - seek(); - } else if (text) { - this.set(text); + this.closeFile(); + this.loaderWorker = new LoaderWorker(); + this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this)); + 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 fileUploaded = document.getElementById("input-file-uploaded"); + fileUploaded.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. * Resets the input, output and info areas. @@ -205,6 +238,7 @@ InputWaiter.prototype.inputDrop = function(e) { * @fires Manager#statechange */ InputWaiter.prototype.clearIoClick = function() { + this.closeFile(); this.manager.highlighter.removeHighlights(); document.getElementById("input-text").value = ""; document.getElementById("output-text").value = ""; diff --git a/src/web/LoaderWorker.js b/src/web/LoaderWorker.js new file mode 100644 index 00000000..b841c096 --- /dev/null +++ b/src/web/LoaderWorker.js @@ -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(); +}; diff --git a/src/web/Manager.js b/src/web/Manager.js index 2b44e6d2..b3ef0158 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -135,13 +135,14 @@ Manager.prototype.initialiseEventListeners = function() { 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("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input)); - document.getElementById("input-text").addEventListener("dragover", this.input.inputDragover.bind(this.input)); - document.getElementById("input-text").addEventListener("dragleave", this.input.inputDragleave.bind(this.input)); - document.getElementById("input-text").addEventListener("drop", this.input.inputDrop.bind(this.input)); + this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); + this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, 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("mouseup", this.highlighter.inputMouseup.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); + document.querySelector("#input-file .close").addEventListener("click", this.input.closeFile.bind(this.input)); // Output document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); diff --git a/src/web/html/index.html b/src/web/html/index.html index 66dfcc80..3bfefa8b 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -181,6 +181,20 @@
+
+
+
+ +
+ + Name:
+ Size:
+ Type:
+ Uploaded: +
+
+
+
diff --git a/src/web/stylesheets/components/_pane.css b/src/web/stylesheets/components/_pane.css index 7246a24a..f69984b4 100644 --- a/src/web/stylesheets/components/_pane.css +++ b/src/web/stylesheets/components/_pane.css @@ -28,3 +28,44 @@ margin: 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: 150px; + height: 150px; +} + +.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; +} \ No newline at end of file diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 997af92e..855d4262 100644 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -39,7 +39,7 @@ } .textarea-wrapper textarea, -.textarea-wrapper div { +.textarea-wrapper>div { font-family: var(--fixed-width-font-family); font-size: var(--fixed-width-font-size); color: var(--fixed-width-font-colour); @@ -77,6 +77,16 @@ transition: all 0.5s ease; } +#input-file { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: var(--title-background-colour); + display: none; +} + .io-btn-group { float: right; margin-top: -4px; From 0e7989111f871ed0387fab7453f153125e0145e6 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 25 Dec 2017 23:11:52 +0000 Subject: [PATCH 02/15] Removed CryptoJS from Utils.js. UTF8 conversion is now achieved with the much smaller and actively maintained utf8 library. --- package-lock.json | 5 ++ package.json | 1 + src/core/Dish.js | 24 ++++++ src/core/Utils.js | 118 +++++++++-------------------- src/core/config/modules/Default.js | 1 - src/core/operations/BitwiseOp.js | 18 ++--- src/core/operations/Cipher.js | 95 ++++++++++++++++------- src/web/HighlighterWaiter.js | 2 +- src/web/OutputWaiter.js | 10 +++ 9 files changed, 151 insertions(+), 123 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f0fddf5..5bfed4eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8663,6 +8663,11 @@ } } }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", diff --git a/package.json b/package.json index f62b95e2..ac864d02 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "sladex-blowfish": "^0.8.1", "sortablejs": "^1.7.0", "split.js": "^1.3.5", + "utf8": "^3.0.0", "vkbeautify": "^0.99.3", "xmldom": "^0.1.27", "xpath": "0.0.24", diff --git a/src/core/Dish.js b/src/core/Dish.js index 3cd1c6f3..f1093966 100755 --- a/src/core/Dish.js +++ b/src/core/Dish.js @@ -217,4 +217,28 @@ Dish.prototype.valid = function() { } }; + +/** + * 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; diff --git a/src/core/Utils.js b/src/core/Utils.js index f2a992e5..a4a455a2 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -1,4 +1,4 @@ -import CryptoJS from "crypto-js"; +import utf8 from "utf8"; /** @@ -340,6 +340,32 @@ 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 [] + */ + 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. * Treats the string as UTF-8 if any values are over 255. @@ -381,17 +407,17 @@ const Utils = { * Utils.strToUtf8ByteArray("你好"); */ strToUtf8ByteArray: function(str) { - let wordArray = CryptoJS.enc.Utf8.parse(str), - byteArray = Utils.wordArrayToByteArray(wordArray); + const utf8Str = utf8.encode(str); - if (str.length !== wordArray.sigBytes) { + if (str.length !== utf8Str.length) { if (ENVIRONMENT_IS_WORKER()) { self.setOption("attemptHighlight", false); } else if (ENVIRONMENT_IS_WEB()) { window.app.options.attemptHighlight = false; } } - return byteArray; + + return Utils.strToByteArray(utf8Str); }, @@ -443,26 +469,21 @@ const Utils = { * Utils.byteArrayToUtf8([228,189,160,229,165,189]); */ byteArrayToUtf8: function(byteArray) { + const str = Utils.byteArrayToChars(byteArray); try { - // Try to output data as UTF-8 string - 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); + const utf8Str = utf8.decode(str); - if (str.length !== wordArray.sigBytes) { + if (str.length !== utf8Str.length) { if (ENVIRONMENT_IS_WORKER()) { self.setOption("attemptHighlight", false); } else if (ENVIRONMENT_IS_WEB()) { window.app.options.attemptHighlight = false; } } - return str; + return utf8Str; } catch (err) { // If it fails, treat it as ANSI - return Utils.byteArrayToChars(byteArray); + return str; } }, @@ -490,30 +511,6 @@ const Utils = { }, - /** - * Converts a CryptoJS.lib.WordArray to a byteArray. - * - * @param {CryptoJS.lib.WordArray} wordArray - * @returns {byteArray} - * - * @example - * // returns [84, 101, 115, 116] - * Utils.wordArrayToByteArray(CryptoJS.enc.Hex.parse("54657374")); - */ - wordArrayToByteArray: function(wordArray) { - if (wordArray.sigBytes <= 0) return []; - - 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. * @@ -1248,21 +1245,6 @@ const Utils = { "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; @@ -1374,31 +1356,3 @@ Array.prototype.equals = function(other) { String.prototype.count = function(chr) { return this.split(chr).length - 1; }; - - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Library overrides /////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * 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); -}; diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js index dec015a5..eea41164 100644 --- a/src/core/config/modules/Default.js +++ b/src/core/config/modules/Default.js @@ -37,7 +37,6 @@ import UUID from "../../operations/UUID.js"; * * Libraries: * - Utils.js - * - CryptoJS * - otp * * @author n1474335 [n1474335@gmail.com] diff --git a/src/core/operations/BitwiseOp.js b/src/core/operations/BitwiseOp.js index 7512aa32..f2551cba 100755 --- a/src/core/operations/BitwiseOp.js +++ b/src/core/operations/BitwiseOp.js @@ -67,7 +67,7 @@ const BitwiseOp = { * @constant * @default */ - KEY_FORMAT: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"], + KEY_FORMAT: ["Hex", "Base64", "UTF8", "Latin1"], /** * XOR operation. @@ -77,12 +77,10 @@ const BitwiseOp = { * @returns {byteArray} */ 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], nullPreserving = args[2]; - key = Utils.wordArrayToByteArray(key); - return BitwiseOp._bitOp(input, key, BitwiseOp._xor, nullPreserving, scheme); }, @@ -200,8 +198,7 @@ const BitwiseOp = { * @returns {byteArray} */ runAnd: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return BitwiseOp._bitOp(input, key, BitwiseOp._and); }, @@ -215,8 +212,7 @@ const BitwiseOp = { * @returns {byteArray} */ runOr: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return BitwiseOp._bitOp(input, key, BitwiseOp._or); }, @@ -230,8 +226,7 @@ const BitwiseOp = { * @returns {byteArray} */ runAdd: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return BitwiseOp._bitOp(input, key, BitwiseOp._add); }, @@ -245,8 +240,7 @@ const BitwiseOp = { * @returns {byteArray} */ runSub: function (input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""); - key = Utils.wordArrayToByteArray(key); + const key = Utils.convertToByteArray(args[0].string || "", args[0].option); return BitwiseOp._bitOp(input, key, BitwiseOp._sub); }, diff --git a/src/core/operations/Cipher.js b/src/core/operations/Cipher.js index 465c3e9e..227c2735 100755 --- a/src/core/operations/Cipher.js +++ b/src/core/operations/Cipher.js @@ -61,9 +61,9 @@ const Cipher = { * @returns {string} */ _enc: function (algo, input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""), - iv = Utils.format[args[1].option].parse(args[1].string || ""), - salt = Utils.format[args[2].option].parse(args[2].string || ""), + let key = Cipher._format[args[0].option].parse(args[0].string || ""), + iv = Cipher._format[args[1].option].parse(args[1].string || ""), + salt = Cipher._format[args[2].option].parse(args[2].string || ""), mode = CryptoJS.mode[args[3]], padding = CryptoJS.pad[args[4]], resultOption = args[5].toLowerCase(), @@ -83,12 +83,12 @@ const Cipher = { let result = ""; if (resultOption === "show all") { - result += "Key: " + encrypted.key.toString(Utils.format[outputFormat]); - result += "\nIV: " + encrypted.iv.toString(Utils.format[outputFormat]); - if (encrypted.salt) result += "\nSalt: " + encrypted.salt.toString(Utils.format[outputFormat]); - result += "\n\nCiphertext: " + encrypted.ciphertext.toString(Utils.format[outputFormat]); + result += "Key: " + encrypted.key.toString(Cipher._format[outputFormat]); + result += "\nIV: " + encrypted.iv.toString(Cipher._format[outputFormat]); + if (encrypted.salt) result += "\nSalt: " + encrypted.salt.toString(Cipher._format[outputFormat]); + result += "\n\nCiphertext: " + encrypted.ciphertext.toString(Cipher._format[outputFormat]); } else { - result = encrypted[resultOption].toString(Utils.format[outputFormat]); + result = encrypted[resultOption].toString(Cipher._format[outputFormat]); } return result; @@ -105,9 +105,9 @@ const Cipher = { * @returns {string} */ _dec: function (algo, input, args) { - let key = Utils.format[args[0].option].parse(args[0].string || ""), - iv = Utils.format[args[1].option].parse(args[1].string || ""), - salt = Utils.format[args[2].option].parse(args[2].string || ""), + let key = Cipher._format[args[0].option].parse(args[0].string || ""), + iv = Cipher._format[args[1].option].parse(args[1].string || ""), + salt = Cipher._format[args[2].option].parse(args[2].string || ""), mode = CryptoJS.mode[args[3]], padding = CryptoJS.pad[args[4]], inputFormat = args[5], @@ -118,7 +118,7 @@ const Cipher = { return "No input"; } - const ciphertext = Utils.format[inputFormat].parse(input); + const ciphertext = Cipher._format[inputFormat].parse(input); if (iv.sigBytes === 0) { // Use passphrase rather than key. Need to convert it to a string. @@ -136,7 +136,7 @@ const Cipher = { let result; try { - result = decrypted.toString(Utils.format[outputFormat]); + result = decrypted.toString(Cipher._format[outputFormat]); } catch (err) { result = "Decrypt error: " + err.message; } @@ -260,7 +260,7 @@ const Cipher = { * @returns {string} */ 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], outputFormat = args[2]; @@ -272,7 +272,7 @@ const Cipher = { }), 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} */ 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], inputFormat = args[2]; 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, { outputType: 0, // This actually means inputType. The library is weird. @@ -329,14 +329,14 @@ const Cipher = { salt = CryptoJS.enc.Hex.parse(args[3] || ""), inputFormat = args[4], outputFormat = args[5], - passphrase = Utils.format[inputFormat].parse(input), + passphrase = Cipher._format[inputFormat].parse(input), key = CryptoJS.PBKDF2(passphrase, salt, { keySize: keySize, hasher: CryptoJS.algo[hasher], 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] || ""), inputFormat = args[4], outputFormat = args[5], - passphrase = Utils.format[inputFormat].parse(input), + passphrase = Cipher._format[inputFormat].parse(input), key = CryptoJS.EvpKDF(passphrase, salt, { keySize: keySize, hasher: CryptoJS.algo[hasher], iterations: iterations, }); - return key.toString(Utils.format[outputFormat]); + return key.toString(Cipher._format[outputFormat]); }, @@ -373,11 +373,11 @@ const Cipher = { * @returns {string} */ runRc4: function (input, args) { - let message = Utils.format[args[1]].parse(input), - passphrase = Utils.format[args[0].option].parse(args[0].string), + let message = Cipher._format[args[1]].parse(input), + passphrase = Cipher._format[args[0].option].parse(args[0].string), 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} */ runRc4drop: function (input, args) { - let message = Utils.format[args[1]].parse(input), - passphrase = Utils.format[args[0].option].parse(args[0].string), + let message = Cipher._format[args[1]].parse(input), + passphrase = Cipher._format[args[0].option].parse(args[0].string), drop = args[3], 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; }, + + /** + * 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; @@ -827,3 +844,27 @@ CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) { // Return params 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); +}; diff --git a/src/web/HighlighterWaiter.js b/src/web/HighlighterWaiter.js index c650e6ba..d9980fec 100755 --- a/src/web/HighlighterWaiter.js +++ b/src/web/HighlighterWaiter.js @@ -402,7 +402,7 @@ HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) { // 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. - if (!this.app.dishStr || this.app.dishStr.indexOf("\r") >= 0) return false; + if (this.manager.output.containsCR()) return false; const startPlaceholder = "[startHighlight]"; const startPlaceholderRegex = /\[startHighlight\]/g; diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index a5576f31..076d62b2 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -282,4 +282,14 @@ OutputWaiter.prototype.setStatusMsg = function(msg) { el.textContent = msg; }; + +/** + * Returns true if the output contains carriage returns + * + * @returns {boolean} + */ +OutputWaiter.prototype.containsCR = function() { + return this.app.dishStr.indexOf("\r") >= 0; +}; + export default OutputWaiter; From af71ca6a25bfb8f1d4e449d84bdfaa677e3603d8 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Tue, 26 Dec 2017 00:44:40 +0000 Subject: [PATCH 03/15] Output over 1MiB is passed back as an ArrayBuffer and an output file card is displayed. --- src/core/Chef.js | 7 ++- src/core/Utils.js | 25 +++++++- src/web/InputWaiter.js | 9 +-- src/web/OutputWaiter.js | 92 +++++++++++++++++++++--------- src/web/WorkerWaiter.js | 14 ++++- src/web/html/index.html | 15 ++++- src/web/stylesheets/layout/_io.css | 3 +- 7 files changed, 130 insertions(+), 35 deletions(-) diff --git a/src/core/Chef.js b/src/core/Chef.js index d176a36d..06d08aa0 100755 --- a/src/core/Chef.js +++ b/src/core/Chef.js @@ -76,10 +76,15 @@ Chef.prototype.bake = async function(input, recipeConfig, options, progress, ste 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. + // 1048576 bytes = 1 MiB + const returnType = this.dish.size() > 1048576 ? Dish.ARRAY_BUFFER : Dish.STRING; + return { result: this.dish.type === Dish.HTML ? this.dish.get(Dish.HTML) : - this.dish.get(Dish.STRING), + this.dish.get(returnType), type: Dish.enumLookup(this.dish.type), progress: progress, duration: new Date().getTime() - startTime, diff --git a/src/core/Utils.js b/src/core/Utils.js index a4a455a2..4ab365c8 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -349,7 +349,14 @@ const Utils = { * @returns {byteArray} * * @example - * // returns [] + * // 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()) { @@ -511,6 +518,22 @@ const Utils = { }, + /** + * Converts an ArrayBuffer to a string. + * + * @param {ArrayBuffer} arrayBuffer + * @returns {string} + * + * @example + * // returns "hello" + * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); + */ + arrayBufferToStr: function(arrayBuffer) { + const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer)); + return Utils.byteArrayToUtf8(byteArray); + }, + + /** * Base64's the input byte array using the given alphabet, returning a string. * diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js index b5748e3f..e5a58fe1 100755 --- a/src/web/InputWaiter.js +++ b/src/web/InputWaiter.js @@ -79,13 +79,13 @@ InputWaiter.prototype.setFile = function(file) { fileName = document.getElementById("input-file-name"), fileSize = document.getElementById("input-file-size"), fileType = document.getElementById("input-file-type"), - fileUploaded = document.getElementById("input-file-uploaded"); + fileLoaded = document.getElementById("input-file-loaded"); fileOverlay.style.display = "block"; fileName.textContent = file.name; fileSize.textContent = file.size.toLocaleString() + " bytes"; fileType.textContent = file.type; - fileUploaded.textContent = "0%"; + fileLoaded.textContent = "0%"; }; @@ -210,8 +210,8 @@ InputWaiter.prototype.inputDrop = function(e) { InputWaiter.prototype.handleLoaderMessage = function(e) { const r = e.data; if (r.hasOwnProperty("progress")) { - const fileUploaded = document.getElementById("input-file-uploaded"); - fileUploaded.textContent = r.progress + "%"; + const fileLoaded = document.getElementById("input-file-loaded"); + fileLoaded.textContent = r.progress + "%"; } if (r.hasOwnProperty("fileBuffer")) { @@ -246,6 +246,7 @@ InputWaiter.prototype.clearIoClick = function() { document.getElementById("output-info").innerHTML = ""; document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = ""; + document.getElementById("output-file").style.display = "none"; window.dispatchEvent(this.manager.statechange); }; diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index 076d62b2..1c857074 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -31,47 +31,85 @@ OutputWaiter.prototype.get = function() { /** * 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 {number} duration - The length of time (ms) it took to generate the output */ -OutputWaiter.prototype.set = function(dataStr, type, duration) { +OutputWaiter.prototype.set = function(data, type, duration) { const outputText = document.getElementById("output-text"); const outputHtml = document.getElementById("output-html"); + const outputFile = document.getElementById("output-file"); const outputHighlighter = document.getElementById("output-highlighter"); const inputHighlighter = document.getElementById("input-highlighter"); + let scriptElements, lines, length; - if (type === "html") { - outputText.style.display = "none"; - outputHtml.style.display = "block"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; + switch (type) { + case "html": + outputText.style.display = "none"; + outputHtml.style.display = "block"; + outputFile.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; - outputText.value = ""; - outputHtml.innerHTML = dataStr; + outputText.value = ""; + outputHtml.innerHTML = data; + length = data.length; - // Execute script sections - const scriptElements = outputHtml.querySelectorAll("script"); - for (let i = 0; i < scriptElements.length; i++) { - try { - eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval - } catch (err) { - console.error(err); + // Execute script sections + scriptElements = outputHtml.querySelectorAll("script"); + for (let i = 0; i < scriptElements.length; i++) { + try { + eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval + } catch (err) { + console.error(err); + } } - } - } else { - outputText.style.display = "block"; - outputHtml.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; + break; + case "ArrayBuffer": + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputHighlighter.display = "none"; + inputHighlighter.display = "none"; - outputText.value = Utils.printable(dataStr, true); - outputHtml.innerHTML = ""; + outputText.value = ""; + outputHtml.innerHTML = ""; + length = data.byteLength; + + this.setFile(new File([data], "output.dat")); + break; + case "string": + default: + outputText.style.display = "block"; + outputHtml.style.display = "none"; + outputFile.style.display = "none"; + outputHighlighter.display = "block"; + inputHighlighter.display = "block"; + + outputText.value = Utils.printable(data, true); + outputHtml.innerHTML = ""; + + lines = data.count("\n") + 1; + length = data.length; + break; } this.manager.highlighter.removeHighlights(); - const lines = dataStr.count("\n") + 1; - this.setOutputInfo(dataStr.length, lines, duration); + this.setOutputInfo(length, lines, duration); +}; + + +/** + * Shows file details. + * + * @param {File} file + */ +OutputWaiter.prototype.setFile = function(file) { + // 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"; }; @@ -86,6 +124,8 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) { let width = length.toString().length; width = width < 4 ? 4 : width; + lines = typeof lines === "number" ? lines : ""; + const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, " "); diff --git a/src/web/WorkerWaiter.js b/src/web/WorkerWaiter.js index 6fc69e40..3ed9e7ea 100644 --- a/src/web/WorkerWaiter.js +++ b/src/web/WorkerWaiter.js @@ -111,7 +111,19 @@ WorkerWaiter.prototype.bakingComplete = function(response) { this.app.handleError(response.error); } - this.app.dishStr = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result; + switch (response.type) { + case "html": + this.app.dishStr = Utils.stripHtmlTags(response.result, true); + break; + case "ArrayBuffer": + this.app.dishStr = ""; + break; + case "string": + default: + this.app.dishStr = response.result; + break; + } + this.app.progress = response.progress; this.manager.recipe.updateBreakpointIndicator(response.progress); this.manager.output.set(response.result, response.type, response.duration); diff --git a/src/web/html/index.html b/src/web/html/index.html index 3bfefa8b..dd6260ea 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -190,7 +190,7 @@ Name:
Size:
Type:
- Uploaded: + Loaded: @@ -216,6 +216,19 @@
+
+
+
+ +
+ Size:
+ Download
+ Display in output
+ Options for how much to display +
+
+
+
diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 855d4262..519b81fc 100644 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -77,7 +77,8 @@ transition: all 0.5s ease; } -#input-file { +#input-file, +#output-file { position: absolute; left: 0; top: 0; From ff94172b3cf3b079c1a8eedf1a2af468d188ba85 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Tue, 26 Dec 2017 01:32:51 +0000 Subject: [PATCH 04/15] Output files can now be downloaded using FileSaver (supports large files ~500MB) --- package-lock.json | 917 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/web/InputWaiter.js | 2 +- src/web/Manager.js | 1 + src/web/OutputWaiter.js | 47 +- src/web/html/index.html | 2 +- 6 files changed, 952 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5bfed4eb..e4ba3947 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1407,6 +1407,7 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", + "fsevents": "1.1.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -3118,6 +3119,11 @@ "schema-utils": "0.3.0" } }, + "file-saver": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.3.tgz", + "integrity": "sha1-zdTETTqiZOrC9o7BZbx5HDSvEjI=" + }, "file-sync-cmp": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", @@ -3308,6 +3314,910 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.8.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + } + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -5524,6 +6434,13 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "dev": true, + "optional": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", diff --git a/package.json b/package.json index ac864d02..8c6fc2d8 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "esmangle": "^1.0.1", "esprima": "^4.0.0", "exif-parser": "^0.1.12", + "file-saver": "^1.3.3", "google-code-prettify": "^1.0.5", "jquery": "^3.2.1", "js-crc": "^0.2.0", diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js index e5a58fe1..62a579bb 100755 --- a/src/web/InputWaiter.js +++ b/src/web/InputWaiter.js @@ -239,6 +239,7 @@ InputWaiter.prototype.closeFile = function() { */ InputWaiter.prototype.clearIoClick = function() { this.closeFile(); + this.manager.output.closeFile(); this.manager.highlighter.removeHighlights(); document.getElementById("input-text").value = ""; document.getElementById("output-text").value = ""; @@ -246,7 +247,6 @@ InputWaiter.prototype.clearIoClick = function() { document.getElementById("output-info").innerHTML = ""; document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = ""; - document.getElementById("output-file").style.display = "none"; window.dispatchEvent(this.manager.statechange); }; diff --git a/src/web/Manager.js b/src/web/Manager.js index b3ef0158..270e68f8 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -158,6 +158,7 @@ Manager.prototype.initialiseEventListeners = function() { this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); this.addDynamicListener(".file-switch", "click", this.output.fileSwitch, this.output); + this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); // Options document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index 1c857074..d8d028d0 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -1,4 +1,5 @@ import Utils from "../core/Utils.js"; +import FileSaver from "file-saver"; /** @@ -15,6 +16,8 @@ import Utils from "../core/Utils.js"; const OutputWaiter = function(app, manager) { this.app = app; this.manager = manager; + + this.file = null; }; @@ -43,6 +46,8 @@ OutputWaiter.prototype.set = function(data, type, duration) { const inputHighlighter = document.getElementById("input-highlighter"); let scriptElements, lines, length; + this.closeFile(); + switch (type) { case "html": outputText.style.display = "none"; @@ -104,6 +109,8 @@ OutputWaiter.prototype.set = function(data, type, duration) { * @param {File} file */ OutputWaiter.prototype.setFile = function(file) { + this.file = file; + // Display file overlay in output area with details const fileOverlay = document.getElementById("output-file"), fileSize = document.getElementById("output-file-size"); @@ -113,6 +120,25 @@ OutputWaiter.prototype.setFile = function(file) { }; +/** + * Removes the output file and nulls its memory. + */ +OutputWaiter.prototype.closeFile = function() { + this.file = null; + document.getElementById("output-file").style.display = "none"; +}; + + +/** + * Handler for file download events. + */ +OutputWaiter.prototype.downloadFile = function() { + const filename = window.prompt("Please enter a filename:", "download.dat"); + + if (filename) FileSaver.saveAs(this.file, filename, false); +}; + + /** * Displays information about the output. * @@ -169,24 +195,13 @@ OutputWaiter.prototype.adjustWidth = function() { /** * 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() { - const data = Utils.toBase64(this.app.dishStr); - const filename = window.prompt("Please enter a filename:", "download.dat"); - - 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(); + if (!this.file) { + this.file = new File([new Uint8Array(Utils.strToCharcode(this.app.dishStr))], ""); } + this.downloadFile(); }; @@ -250,7 +265,7 @@ OutputWaiter.prototype.undoSwitchClick = function() { /** * 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) { e.preventDefault(); diff --git a/src/web/html/index.html b/src/web/html/index.html index dd6260ea..cd690869 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -222,7 +222,7 @@
Size:
- Download
+
Display in output
Options for how much to display
From 53a3f3d4528e3429f0a3de614ba1a18b892204e7 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Tue, 26 Dec 2017 22:05:10 +0000 Subject: [PATCH 05/15] Changed inputType for file magic byte operations to ArrayBuffer --- src/core/config/OperationConfig.js | 4 ++-- src/core/operations/FileType.js | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 43072558..d353328b 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3261,14 +3261,14 @@ const OperationConfig = { "Detect File Type": { module: "Default", description: "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.

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", args: [] }, "Scan for Embedded Files": { 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.

WARNING: Files over about 100KB in size will take a VERY long time to process.", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [ { diff --git a/src/core/operations/FileType.js b/src/core/operations/FileType.js index ad3e5ba7..715f8205 100755 --- a/src/core/operations/FileType.js +++ b/src/core/operations/FileType.js @@ -15,12 +15,13 @@ const FileType = { /** * Detect File Type operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runDetect: function(input, args) { - const type = FileType.magicType(input); + const data = new Uint8Array(input), + type = FileType.magicType(data); if (!type) { 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. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ 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", type, - ignoreCommon = args[0], - commonExts = ["ico", "ttf", ""], numFound = 0, numCommonFound = 0; + const ignoreCommon = args[0], + commonExts = ["ico", "ttf", ""], + data = new Uint8Array(input); - for (let i = 0; i < input.length; i++) { - type = FileType.magicType(input.slice(i)); + for (let i = 0; i < data.length; i++) { + type = FileType.magicType(data.slice(i)); if (type) { if (ignoreCommon && commonExts.indexOf(type.ext) > -1) { numCommonFound++; @@ -96,7 +98,7 @@ const FileType = { * Given a buffer, detects magic byte sequences at specific positions and returns the * extension and mime type. * - * @param {byteArray} buf + * @param {Uint8Array} buf * @returns {Object} type * @returns {string} type.ext - File extension * @returns {string} type.mime - Mime type From bad0816115ea73ef9459980d26bdca30bbb1473c Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 27 Dec 2017 01:52:41 +0000 Subject: [PATCH 06/15] Output files can be viewed in slices --- src/web/Manager.js | 4 +- src/web/OutputWaiter.js | 58 ++++++++++++++++++----- src/web/html/index.html | 17 +++++-- src/web/static/images/file-128x128.png | Bin 0 -> 19378 bytes src/web/static/images/file-32x32.png | Bin 0 -> 1946 bytes src/web/stylesheets/components/_pane.css | 18 +++++-- src/web/stylesheets/layout/_io.css | 8 ++++ src/web/stylesheets/utils/_overrides.css | 12 ++++- 8 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 src/web/static/images/file-128x128.png create mode 100644 src/web/static/images/file-32x32.png diff --git a/src/web/Manager.js b/src/web/Manager.js index 270e68f8..4b89942a 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -142,7 +142,7 @@ Manager.prototype.initialiseEventListeners = function() { document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.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); - document.querySelector("#input-file .close").addEventListener("click", this.input.closeFile.bind(this.input)); + document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); // Output document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output)); @@ -159,6 +159,8 @@ Manager.prototype.initialiseEventListeners = function() { this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); 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.displayFile, this.output); + document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output)); // Options document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index d8d028d0..93cc351b 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -17,7 +17,7 @@ const OutputWaiter = function(app, manager) { this.app = app; this.manager = manager; - this.file = null; + this.dishBuffer = null; }; @@ -37,8 +37,9 @@ OutputWaiter.prototype.get = function() { * @param {string|ArrayBuffer} data - The output string/HTML/ArrayBuffer * @param {string} type - The data type of 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(data, type, duration) { +OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { const outputText = document.getElementById("output-text"); const outputHtml = document.getElementById("output-html"); const outputFile = document.getElementById("output-file"); @@ -46,7 +47,7 @@ OutputWaiter.prototype.set = function(data, type, duration) { const inputHighlighter = document.getElementById("input-highlighter"); let scriptElements, lines, length; - this.closeFile(); + if (!preserveBuffer) this.closeFile(); switch (type) { case "html": @@ -80,7 +81,7 @@ OutputWaiter.prototype.set = function(data, type, duration) { outputHtml.innerHTML = ""; length = data.byteLength; - this.setFile(new File([data], "output.dat")); + this.setFile(data); break; case "string": default: @@ -106,10 +107,11 @@ OutputWaiter.prototype.set = function(data, type, duration) { /** * Shows file details. * - * @param {File} file + * @param {ArrayBuffer} buf */ -OutputWaiter.prototype.setFile = function(file) { - this.file = file; +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"), @@ -124,7 +126,7 @@ OutputWaiter.prototype.setFile = function(file) { * Removes the output file and nulls its memory. */ OutputWaiter.prototype.closeFile = function() { - this.file = null; + this.dishBuffer = null; document.getElementById("output-file").style.display = "none"; }; @@ -134,8 +136,40 @@ OutputWaiter.prototype.closeFile = function() { */ OutputWaiter.prototype.downloadFile = function() { const filename = window.prompt("Please enter a filename:", "download.dat"); + const file = new File([this.dishBuffer], filename); - if (filename) FileSaver.saveAs(this.file, filename, false); + if (filename) FileSaver.saveAs(file, filename, false); +}; + + +/** + * Handler for file display events. + */ +OutputWaiter.prototype.displayFile = 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"; }; @@ -198,8 +232,8 @@ OutputWaiter.prototype.adjustWidth = function() { * Saves the current output to a file. */ OutputWaiter.prototype.saveClick = function() { - if (!this.file) { - this.file = new File([new Uint8Array(Utils.strToCharcode(this.app.dishStr))], ""); + if (!this.dishBuffer) { + this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.app.dishStr)).buffer; } this.downloadFile(); }; @@ -227,7 +261,7 @@ OutputWaiter.prototype.copyClick = function() { let success = false; try { textarea.select(); - success = document.execCommand("copy"); + success = textarea.value && document.execCommand("copy"); } catch (err) { success = false; } diff --git a/src/web/html/index.html b/src/web/html/index.html index cd690869..a132aebd 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -184,7 +184,7 @@
- +
Name:
@@ -216,15 +216,22 @@
+
- +
Size:
-
- Display in output
- Options for how much to display + +
+ + + + +
to
+ +
diff --git a/src/web/static/images/file-128x128.png b/src/web/static/images/file-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..005c504dd363985045a80a4f76c3de22ea506998 GIT binary patch literal 19378 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 literal 0 HcmV?d00001 diff --git a/src/web/static/images/file-32x32.png b/src/web/static/images/file-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..884bf4851c1adef7191525b124c246db6ff48d80 GIT binary patch literal 1946 zcmV;L2W9w)P)OIt)aMW!1`|0{S6A!n)~$1_UAq=?$JEpm zgJIRGRd6^QAj-F88O~csO7^2ZAiE*u02zcfDgwS4`eQ)=qS$ItCyYE~ zv)Sbdi0sb#`g#%oQBqO@Vt#&}S(6my<};j5CkO^6f&4WG4jjPo!GY|vjz%_OmsLX>054au#6WG6h|9UCITX+;0gx9`(`^E+b2bDNnjgOD3NF<^r zCnwdTAnK@*kr8$9;6Zit=uwrAuC6Y%bLY-I^7v>4_*)ef6_}o$<})*<%y<;24h>5` zc)ecasZ13|wqna0LOni=76m}Xv* z0Oc;Hspx*1?FnF;aoe_S%$Nb$v{*tW!}82XpK!@mPcG#FvUQ2%_70H6cw8`0Yilc7 zT3WDq^JZ+{z8$r-wNw*#?%d&T8A>S&4}m=kPM$oe+S=L>3Pn4{A`)4-H+%cS7i)1iHJsReO6o39uK~`}t|{LHAoYfAP|PS~hLkaQ6KV z@%g{5E;$RPr0*9~Hg-Rf_m+{&E#&8`bW|W3xMz1{em%r`D@W6>BOR#tE z-V4&hU#9HbJ}XwNusfbeYG2FYKPzd1$c|-Qn*YV)<4hnrr-JN z>r$J$ixr@2H`1{*6tm~WdeYG(#WTTj`2CwFapd#sKwUjNTV8_0=|slL!c>5Ug>I{U z{;RDVU?ms`94UfjOLa)6GI8Z7$ZaHe%gh+r!;za@^v)F7@hxpT(vp90D4`^5q z|C%*$NfL3>frMrvDl=Uawy~iZM$-#60Z9K3v#qlJi9~|?ns;s` z-bu)wjs(l3={mk1x`xgRp8+p!MWALiG_McS84E85OVQWSg!+IFF=fbFxg;5#BfWmZ z&$P0NSM`;ve=v@xDHA6mvu4|5=2V4u5@AphW2v}9G)n*OM}XT8uN-FJ_d_diBC9mi zI!#oW8ph)ZZe?h04^%n@q>^wv-wHTgU77RmlYouNTeoh31Bpa<+~RyE!ulR0ILZT>dEm6cTf3{4!L6H#V-u-cTtL z$uyJ(k>3SP%FNzo1Vhlu%6IB{6HU>849+{{W)n5CXxCW*z~^$(Ah`p-_kpCkbY22c;4$2iFiz znjjJ9+68%`V33GX7)c~SI3ze>|K&q=9@cyY@C7RKOJFc)&U~!5eI}s%R3`rrLbPq# zgu0q4SR?=CUS9~HEbx(>?7%q^{77=Vtl1fM%?UkkUprWHnr~=mhyiIv5(B@}_4Eb5 zQH}fe?x_N=PyM=S128&*bk>5FNkhq6&ErB?d~iej@kMkNgl39|bPW&_W_jSfbrUK) z+Hbew&dyFHrxwNzxL3fL!&h{HS_`HX8`sD8WMT!n&n`L zs1Rb}k?PVC+!!6jyW=t7wO_y;s)oT{AZ1GGCfu^#OwA!PHHmCIiuAn*vj6CrP`#I$ z0gRD=AHY^QK>St)kRchzW$PJoDvn7xQi3@tPeS~WmF$b9{ysTg4by(PSyI5e0cck9 z=G~50b~l?fbqkIcg?J9hO7Qsy_hH@s7Wnto9`*I`ZvaH{2U39E!qekLk>{$mu2Jc2 z+m-(6Yf5Y1q0rc(P`OgUFunxPf&~~9pB}(dfF7|Qb7}+uya1pMKq!}36aa(w1DFF4 g1CX@O13xCd2d**bOul<@m;e9(07*qoM6N<$g6&+S2><{9 literal 0 HcmV?d00001 diff --git a/src/web/stylesheets/components/_pane.css b/src/web/stylesheets/components/_pane.css index f69984b4..fb8f309b 100644 --- a/src/web/stylesheets/components/_pane.css +++ b/src/web/stylesheets/components/_pane.css @@ -50,8 +50,10 @@ .card>img { float: left; - width: 150px; - height: 150px; + width: 128px; + height: 128px; + margin-left: 10px; + margin-top: 11px; } .card-body .close { @@ -68,4 +70,14 @@ overflow: hidden; text-overflow: ellipsis; user-select: text; -} \ No newline at end of file +} + +.card-body>.btn { + margin-bottom: 15px; + margin-top: 5px; +} + +.card input[type=number] { + padding-right: 6px; + padding-left: 6px; +} diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 519b81fc..370c2235 100644 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -88,6 +88,14 @@ display: none; } +#show-file-overlay { + position: absolute; + right: 15px; + top: 15px; + cursor: pointer; + display: none; +} + .io-btn-group { float: right; margin-top: -4px; diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css index 36069cd5..23f83bd8 100755 --- a/src/web/stylesheets/utils/_overrides.css +++ b/src/web/stylesheets/utils/_overrides.css @@ -64,7 +64,8 @@ a:focus { .alert, .modal-content, .tooltip-inner, -.dropdown-menu { +.dropdown-menu, +.input-group-addon { border-radius: 0 !important; } @@ -187,6 +188,15 @@ optgroup { 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 */ From a0aa363203a31bdb3a755156cef2b4447f086d2b Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 27 Dec 2017 02:01:17 +0000 Subject: [PATCH 07/15] Download filenames now persist --- src/web/OutputWaiter.js | 6 +++--- src/web/html/index.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index 93cc351b..ca1f3605 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -135,10 +135,10 @@ OutputWaiter.prototype.closeFile = function() { * Handler for file download events. */ OutputWaiter.prototype.downloadFile = function() { - const filename = window.prompt("Please enter a filename:", "download.dat"); - const file = new File([this.dishBuffer], filename); + this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat"); + const file = new File([this.dishBuffer], this.filename); - if (filename) FileSaver.saveAs(file, filename, false); + if (this.filename) FileSaver.saveAs(file, this.filename, false); }; diff --git a/src/web/html/index.html b/src/web/html/index.html index a132aebd..c8857ff9 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -226,7 +226,7 @@
- +
to
From e81122739bf4a134a00734208fccdef2e2058771 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 27 Dec 2017 02:26:24 +0000 Subject: [PATCH 08/15] Files can now be moved from the output to the input --- src/web/InputWaiter.js | 2 +- src/web/Manager.js | 2 +- src/web/OutputWaiter.js | 33 +++++++++++++++++++++++++-------- src/web/WorkerWaiter.js | 14 -------------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js index 62a579bb..f6818b11 100755 --- a/src/web/InputWaiter.js +++ b/src/web/InputWaiter.js @@ -84,7 +84,7 @@ InputWaiter.prototype.setFile = function(file) { fileOverlay.style.display = "block"; fileName.textContent = file.name; fileSize.textContent = file.size.toLocaleString() + " bytes"; - fileType.textContent = file.type; + fileType.textContent = file.type || "unknown"; fileLoaded.textContent = "0%"; }; diff --git a/src/web/Manager.js b/src/web/Manager.js index 4b89942a..39aaf409 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -159,7 +159,7 @@ Manager.prototype.initialiseEventListeners = function() { this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); 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.displayFile, 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 diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index ca1f3605..51def0b4 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -18,6 +18,7 @@ const OutputWaiter = function(app, manager) { this.manager = manager; this.dishBuffer = null; + this.dishStr = null; }; @@ -47,7 +48,10 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { const inputHighlighter = document.getElementById("input-highlighter"); let scriptElements, lines, length; - if (!preserveBuffer) this.closeFile(); + if (!preserveBuffer) { + this.closeFile(); + document.getElementById("show-file-overlay").style.display = "none"; + } switch (type) { case "html": @@ -60,6 +64,7 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { outputText.value = ""; outputHtml.innerHTML = data; length = data.length; + this.dishStr = Utils.stripHtmlTags(data, true); // Execute script sections scriptElements = outputHtml.querySelectorAll("script"); @@ -80,6 +85,7 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { outputText.value = ""; outputHtml.innerHTML = ""; length = data.byteLength; + this.dishStr = ""; this.setFile(data); break; @@ -96,6 +102,7 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { lines = data.count("\n") + 1; length = data.length; + this.dishStr = data; break; } @@ -143,9 +150,9 @@ OutputWaiter.prototype.downloadFile = function() { /** - * Handler for file display events. + * Handler for file slice display events. */ -OutputWaiter.prototype.displayFile = function() { +OutputWaiter.prototype.displayFileSlice = function() { const startTime = new Date().getTime(), showFileOverlay = document.getElementById("show-file-overlay"), sliceFromEl = document.getElementById("output-file-slice-from"), @@ -233,7 +240,7 @@ OutputWaiter.prototype.adjustWidth = function() { */ OutputWaiter.prototype.saveClick = function() { if (!this.dishBuffer) { - this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.app.dishStr)).buffer; + this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.dishStr)).buffer; } this.downloadFile(); }; @@ -254,14 +261,14 @@ OutputWaiter.prototype.copyClick = function() { textarea.style.height = 0; textarea.style.border = "none"; - textarea.value = this.app.dishStr; + textarea.value = this.dishStr; document.body.appendChild(textarea); // Select and copy the contents of this textarea let success = false; try { textarea.select(); - success = textarea.value && document.execCommand("copy"); + success = this.dishStr && document.execCommand("copy"); } catch (err) { success = false; } @@ -284,7 +291,17 @@ OutputWaiter.prototype.copyClick = function() { OutputWaiter.prototype.switchClick = function() { this.switchOrigData = this.manager.input.get(); 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); + } }; @@ -378,7 +395,7 @@ OutputWaiter.prototype.setStatusMsg = function(msg) { * @returns {boolean} */ OutputWaiter.prototype.containsCR = function() { - return this.app.dishStr.indexOf("\r") >= 0; + return this.dishStr.indexOf("\r") >= 0; }; export default OutputWaiter; diff --git a/src/web/WorkerWaiter.js b/src/web/WorkerWaiter.js index 3ed9e7ea..402b56b0 100644 --- a/src/web/WorkerWaiter.js +++ b/src/web/WorkerWaiter.js @@ -1,4 +1,3 @@ -import Utils from "../core/Utils.js"; import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.js"; /** @@ -111,19 +110,6 @@ WorkerWaiter.prototype.bakingComplete = function(response) { this.app.handleError(response.error); } - switch (response.type) { - case "html": - this.app.dishStr = Utils.stripHtmlTags(response.result, true); - break; - case "ArrayBuffer": - this.app.dishStr = ""; - break; - case "string": - default: - this.app.dishStr = response.result; - break; - } - this.app.progress = response.progress; this.manager.recipe.updateBreakpointIndicator(response.progress); this.manager.output.set(response.result, response.type, response.duration); From caf794b01d24aea3b16bafa9b478a3eaccb96f66 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 27 Dec 2017 12:29:10 +0000 Subject: [PATCH 09/15] Threshold for treating output as a file is now configurable --- src/core/Chef.js | 5 +++-- src/web/html/index.html | 18 +++++++++++------- src/web/index.js | 19 ++++++++++--------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/core/Chef.js b/src/core/Chef.js index 06d08aa0..8df6d523 100755 --- a/src/core/Chef.js +++ b/src/core/Chef.js @@ -78,8 +78,9 @@ Chef.prototype.bake = async function(input, recipeConfig, options, progress, ste // 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. - // 1048576 bytes = 1 MiB - const returnType = this.dish.size() > 1048576 ? Dish.ARRAY_BUFFER : Dish.STRING; + // The threshold is specified in KiB. + const threshold = (options.outputFileThreshold || 1024) * 1024; + const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; return { result: this.dish.type === Dish.HTML ? diff --git a/src/web/html/index.html b/src/web/html/index.html index c8857ff9..7f2c98d0 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -342,31 +342,35 @@
- +
- +
- +
- +
- +
- +
- + +
+
+ +
From f7f07f2cb55dad6dfa35bb889c24d46023eee484 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 27 Dec 2017 15:52:38 +0000 Subject: [PATCH 11/15] Loading a new file only causes a statechange event once the whole file has loaded --- src/web/InputWaiter.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js index f6818b11..af5ed81f 100755 --- a/src/web/InputWaiter.js +++ b/src/web/InputWaiter.js @@ -58,13 +58,14 @@ InputWaiter.prototype.get = function() { * @fires Manager#statechange */ InputWaiter.prototype.set = function(input) { + const inputText = document.getElementById("input-text"); if (input instanceof File) { this.setFile(input); - input = ""; + inputText.value = ""; + } else { + inputText.value = input; + window.dispatchEvent(this.manager.statechange); } - - document.getElementById("input-text").value = input; - window.dispatchEvent(this.manager.statechange); }; From 50e4daeaf228988bfb34c51ba17800975e4caef0 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 27 Dec 2017 23:05:32 +0000 Subject: [PATCH 12/15] Output info tidied up for file outputs --- src/web/OutputWaiter.js | 22 +++++++++++++--------- src/web/stylesheets/layout/_io.css | 2 ++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index 51def0b4..06379b94 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -63,8 +63,9 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { outputText.value = ""; outputHtml.innerHTML = data; - length = data.length; this.dishStr = Utils.stripHtmlTags(data, true); + length = data.length; + lines = this.dishStr.count("\n") + 1; // Execute script sections scriptElements = outputHtml.querySelectorAll("script"); @@ -84,8 +85,8 @@ OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) { outputText.value = ""; outputHtml.innerHTML = ""; - length = data.byteLength; this.dishStr = ""; + length = data.byteLength; this.setFile(data); break; @@ -177,6 +178,7 @@ OutputWaiter.prototype.showFileOverlayClick = function(e) { outputFile.style.display = "block"; showFileOverlay.style.display = "none"; + this.setOutputInfo(this.dishBuffer.byteLength, null, 0); }; @@ -191,15 +193,17 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) { let width = length.toString().length; width = width < 4 ? 4 : width; - lines = typeof lines === "number" ? lines : ""; - const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); - const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, " "); + const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, " "); - document.getElementById("output-info").innerHTML = "time: " + timeStr + - "
length: " + lengthStr + - "
lines: " + linesStr; + let msg = "time: " + timeStr + "
length: " + lengthStr; + + if (typeof lines === "number") { + const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); + msg += "
lines: " + linesStr; + } + + document.getElementById("output-info").innerHTML = msg; document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = ""; }; diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 370c2235..c93c800b 100644 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -111,6 +111,8 @@ font-family: var(--fixed-width-font-family); font-weight: normal; font-size: 8pt; + display: flex; + align-items: center; } #input-info { From e18ec5f2b2831053c64abca2930dccbd42c8e6a7 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Thu, 28 Dec 2017 00:24:16 +0000 Subject: [PATCH 13/15] Changed inputType to ArrayBuffer for 'Frequency distribution', 'Chi Square' and 'Extract EXIF' operations. --- src/core/config/OperationConfig.js | 6 +++--- src/core/operations/Entropy.js | 18 ++++++++++-------- src/core/operations/Image.js | 7 +++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 354ec12e..0a4a6161 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3287,7 +3287,7 @@ const OperationConfig = { "Frequency distribution": { module: "Default", description: "Displays the distribution of bytes in the data as a graph.", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "html", args: [ { @@ -3300,7 +3300,7 @@ const OperationConfig = { "Chi Square": { module: "Default", description: "Calculates the Chi Square distribution of values.", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "number", args: [] }, @@ -3740,7 +3740,7 @@ const OperationConfig = { "

", "EXIF data from photos usually contains information about the image file itself as well as the device used to create it.", ].join("\n"), - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [], }, diff --git a/src/core/operations/Entropy.js b/src/core/operations/Entropy.js index aa9ed0bc..db860720 100755 --- a/src/core/operations/Entropy.js +++ b/src/core/operations/Entropy.js @@ -81,22 +81,23 @@ const Entropy = { /** * Frequency distribution operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {html} */ 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), percentages = new Array(256), - len = input.length, + len = data.length, showZeroes = args[0], i; // Count bytes for (i = 0; i < len; i++) { - distrib[input[i]]++; + distrib[data[i]]++; } // Calculate percentages @@ -138,21 +139,22 @@ const Entropy = { /** * Chi Square operation. * - * @param {byteArray} data + * @param {ArrayBuffer} data * @param {Object[]} args * @returns {number} */ runChiSq: function(input, args) { + const data = new Uint8Array(input); let distArray = new Array(256).fill(0), total = 0; - for (let i = 0; i < input.length; i++) { - distArray[input[i]]++; + for (let i = 0; i < data.length; i++) { + distArray[data[i]]++; } for (let i = 0; i < distArray.length; i++) { 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); } } diff --git a/src/core/operations/Image.js b/src/core/operations/Image.js index 2dd72df5..afab21c1 100644 --- a/src/core/operations/Image.js +++ b/src/core/operations/Image.js @@ -20,14 +20,13 @@ const Image = { * * Extracts EXIF data from a byteArray, representing a JPG or a TIFF image. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runExtractEXIF(input, args) { try { - const bytes = Uint8Array.from(input); - const parser = ExifParser.create(bytes.buffer); + const parser = ExifParser.create(input); const result = parser.parse(); let lines = []; @@ -53,7 +52,7 @@ const Image = { * @author David Moodie [davidmoodie12@gmail.com] * @param {byteArray} input * @param {Object[]} args - * @returns {string} + * @returns {byteArray} */ runRemoveEXIF(input, args) { // Do nothing if input is empty From 849d41ee5600f1a389d1239d940256905b0e5c74 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Thu, 28 Dec 2017 14:38:57 +0000 Subject: [PATCH 14/15] Removed padLeft and padRight in favour of String.prototype.padStart and padEnd. 'To Hex' now supports ArrayBuffers. --- src/core/Utils.js | 110 ++++++++++++----------------- src/core/config/OperationConfig.js | 2 +- src/core/operations/BCD.js | 4 +- src/core/operations/ByteRepr.js | 6 +- src/core/operations/Compress.js | 6 +- src/core/operations/Entropy.js | 2 +- src/core/operations/HTML.js | 6 +- src/core/operations/Hexdump.js | 4 +- src/core/operations/PublicKey.js | 9 ++- src/core/operations/SeqUtils.js | 2 +- src/core/operations/Tidy.js | 7 +- src/core/operations/URL.js | 3 +- src/web/HighlighterWaiter.js | 9 +-- src/web/InputWaiter.js | 5 +- src/web/OutputWaiter.js | 6 +- 15 files changed, 77 insertions(+), 104 deletions(-) diff --git a/src/core/Utils.js b/src/core/Utils.js index 766bc118..a6ab48db 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -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. * @@ -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. * @@ -201,7 +141,7 @@ const Utils = { hex: function(c, length) { c = typeof c == "string" ? Utils.ord(c) : c; 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) { c = typeof c == "string" ? Utils.ord(c) : c; length = length || 8; - return Utils.pad(c.toString(2), length); + return c.toString(2).padStart(length, "0"); }, @@ -656,7 +596,7 @@ const Utils = { /** * Convert a byte array into a hex string. * - * @param {byteArray} data + * @param {Uint8Array|byteArray} data * @param {string} [delim=" "] * @param {number} [padding=2] * @returns {string} @@ -676,7 +616,7 @@ const Utils = { let output = ""; 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 @@ -1379,3 +1319,45 @@ Array.prototype.equals = function(other) { String.prototype.count = function(chr) { return this.split(chr).length - 1; }; + + +/* + * Polyfills + */ + +// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart +if (!String.prototype.padStart) { + String.prototype.padStart = function padStart(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 padString.slice(0, targetLength) + String(this); + } + }; +} + + +// 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); + } + }; +} diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 0a4a6161..15f00df8 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -631,7 +631,7 @@ const OperationConfig = { description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.

e.g. The UTF-8 encoded string Γειά σου becomes ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a", highlight: "func", highlightReverse: "func", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [ { diff --git a/src/core/operations/BCD.js b/src/core/operations/BCD.js index 63c0cda3..d3efbc71 100755 --- a/src/core/operations/BCD.js +++ b/src/core/operations/BCD.js @@ -134,11 +134,11 @@ const BCD = { switch (outputFormat) { case "Nibbles": return nibbles.map(n => { - return Utils.padLeft(n.toString(2), 4); + return n.toString(2).padStart(4, "0"); }).join(" "); case "Bytes": return bytes.map(b => { - return Utils.padLeft(b.toString(2), 8); + return b.toString(2).padStart(8, "0"); }).join(" "); case "Raw": default: diff --git a/src/core/operations/ByteRepr.js b/src/core/operations/ByteRepr.js index 87a543ff..986926ca 100755 --- a/src/core/operations/ByteRepr.js +++ b/src/core/operations/ByteRepr.js @@ -31,13 +31,13 @@ const ByteRepr = { /** * To Hex operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runToHex: function(input, args) { 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; 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) { diff --git a/src/core/operations/Compress.js b/src/core/operations/Compress.js index 639b89c6..57b01027 100755 --- a/src/core/operations/Compress.js +++ b/src/core/operations/Compress.js @@ -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 lastModTime = Utils.padLeft(currentUnixTimestamp.toString(8), 11, "0"); + const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0"); const file = { 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; const tarball = new Tarball(); diff --git a/src/core/operations/Entropy.js b/src/core/operations/Entropy.js index db860720..baf9edb3 100755 --- a/src/core/operations/Entropy.js +++ b/src/core/operations/Entropy.js @@ -127,7 +127,7 @@ const Entropy = { for (i = 0; i < 256; i++) { if (distrib[i] || showZeroes) { 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"; } } diff --git a/src/core/operations/HTML.js b/src/core/operations/HTML.js index 30eda63e..b1f7e065 100755 --- a/src/core/operations/HTML.js +++ b/src/core/operations/HTML.js @@ -215,9 +215,9 @@ const HTML = { k = k.toFixed(2); let hex = "#" + - Utils.padLeft(Math.round(r).toString(16), 2) + - Utils.padLeft(Math.round(g).toString(16), 2) + - Utils.padLeft(Math.round(b).toString(16), 2), + Math.round(r).toString(16).padStart(2, "0") + + Math.round(g).toString(16).padStart(2, "0") + + Math.round(b).toString(16).padStart(2, "0"), rgb = "rgb(" + r + ", " + g + ", " + b + ")", rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")", hsl = "hsl(" + h + ", " + s + "%, " + l + "%)", diff --git a/src/core/operations/Hexdump.js b/src/core/operations/Hexdump.js index a9ed7a10..6b322b1f 100755 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/Hexdump.js @@ -56,8 +56,8 @@ const Hexdump = { } output += lineNo + " " + - Utils.padRight(hexa, (length*(padding+1))) + - " |" + Utils.padRight(Utils.printable(Utils.byteArrayToChars(buff)), buff.length) + "|\n"; + hexa.padEnd(length*(padding+1), " ") + + " |" + Utils.printable(Utils.byteArrayToChars(buff)).padEnd(buff.length, " ") + "|\n"; if (includeFinalLength && i+buff.length === input.length) { output += Utils.hex(i+buff.length, 8) + "\n"; diff --git a/src/core/operations/PublicKey.js b/src/core/operations/PublicKey.js index 295a5bf4..66b177a5 100755 --- a/src/core/operations/PublicKey.js +++ b/src/core/operations/PublicKey.js @@ -121,8 +121,7 @@ const PublicKey = { // Format Public Key fields for (let i = 0; i < pkFields.length; i++) { pkStr += " " + pkFields[i].key + ":" + - Utils.padLeft( - pkFields[i].value + "\n", + (pkFields[i].value + "\n").padStart( 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, " " ); @@ -286,9 +285,9 @@ ${extensions}`; key = fields[i].split("=")[0]; 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); @@ -314,7 +313,7 @@ ${extensions}`; if (i === 0) { output += str; } else { - output += Utils.padLeft(str, indent + str.length, " "); + output += str.padStart(indent + str.length, " "); } } diff --git a/src/core/operations/SeqUtils.js b/src/core/operations/SeqUtils.js index 3272e706..fa900cf9 100755 --- a/src/core/operations/SeqUtils.js +++ b/src/core/operations/SeqUtils.js @@ -158,7 +158,7 @@ const SeqUtils = { width = lines.length.toString().length; 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); }, diff --git a/src/core/operations/Tidy.js b/src/core/operations/Tidy.js index 881508cf..41f0561d 100755 --- a/src/core/operations/Tidy.js +++ b/src/core/operations/Tidy.js @@ -1,6 +1,3 @@ -import Utils from "../Utils.js"; - - /** * Tidy operations. * @@ -229,11 +226,11 @@ const Tidy = { if (position === "Start") { 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") { 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"; } } diff --git a/src/core/operations/URL.js b/src/core/operations/URL.js index 25526f0e..2f30c952 100755 --- a/src/core/operations/URL.js +++ b/src/core/operations/URL.js @@ -1,5 +1,4 @@ /* globals unescape */ -import Utils from "../Utils.js"; import url from "url"; @@ -78,7 +77,7 @@ const URL_ = { output += "Arguments:\n"; for (let key in uri.query) { - output += "\t" + Utils.padRight(key, padding); + output += "\t" + key.padEnd(padding, " "); if (uri.query[key].length) { output += " = " + uri.query[key] + "\n"; } else { diff --git a/src/web/HighlighterWaiter.js b/src/web/HighlighterWaiter.js index d9980fec..6e4ca599 100755 --- a/src/web/HighlighterWaiter.js +++ b/src/web/HighlighterWaiter.js @@ -1,6 +1,3 @@ -import Utils from "../core/Utils.js"; - - /** * Waiter to handle events related to highlighting in CyberChef. * @@ -312,9 +309,9 @@ HighlighterWaiter.prototype.outputHtmlMousemove = function(e) { HighlighterWaiter.prototype.selectionInfo = function(start, end) { const len = end.toString().length; const width = len < 2 ? 2 : len; - const startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, " "); - const endStr = Utils.pad(end.toString(), width, " ").replace(/ /g, " "); - const lenStr = Utils.pad((end-start).toString(), width, " ").replace(/ /g, " "); + const startStr = start.toString().padStart(width, " ").replace(/ /g, " "); + const endStr = end.toString().padStart(width, " ").replace(/ /g, " "); + const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, " "); return "start: " + startStr + "
end: " + endStr + "
length: " + lenStr; }; diff --git a/src/web/InputWaiter.js b/src/web/InputWaiter.js index af5ed81f..af3f72ee 100755 --- a/src/web/InputWaiter.js +++ b/src/web/InputWaiter.js @@ -1,4 +1,3 @@ -import Utils from "../core/Utils.js"; import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js"; @@ -100,8 +99,8 @@ InputWaiter.prototype.setInputInfo = function(length, lines) { let width = length.toString().length; width = width < 2 ? 2 : width; - const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); + const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); + const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); document.getElementById("input-info").innerHTML = "length: " + lengthStr + "
lines: " + linesStr; }; diff --git a/src/web/OutputWaiter.js b/src/web/OutputWaiter.js index 06379b94..42742734 100755 --- a/src/web/OutputWaiter.js +++ b/src/web/OutputWaiter.js @@ -193,13 +193,13 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) { let width = length.toString().length; width = width < 4 ? 4 : width; - const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " "); - const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, " "); + const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); + const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " "); let msg = "time: " + timeStr + "
length: " + lengthStr; if (typeof lines === "number") { - const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " "); + const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); msg += "
lines: " + linesStr; } From 75a554e215ac16c3e9c8b13d064f6a3943f355fb Mon Sep 17 00:00:00 2001 From: n1474335 Date: Thu, 28 Dec 2017 15:59:58 +0000 Subject: [PATCH 15/15] 'To Base64' and 'To Hexdump' operations now support ArrayBuffers --- src/core/Utils.js | 4 ++-- src/core/config/OperationConfig.js | 4 ++-- src/core/operations/Base64.js | 4 ++-- src/core/operations/Hexdump.js | 9 +++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/core/Utils.js b/src/core/Utils.js index a6ab48db..f9e5a3b8 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -438,7 +438,7 @@ const Utils = { /** * Converts a charcode array to a string. * - * @param {byteArray} byteArray + * @param {byteArray|Uint8Array} byteArray * @returns {string} * * @example @@ -477,7 +477,7 @@ const Utils = { /** * 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] * @returns {string} * diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 15f00df8..dbac0bfe 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -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.

This operation encodes data in an ASCII Base64 string.

e.g. hello becomes aGVsbG8=", highlight: "func", highlightReverse: "func", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", 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.", highlight: "func", highlightReverse: "func", - inputType: "byteArray", + inputType: "ArrayBuffer", outputType: "string", args: [ { diff --git a/src/core/operations/Base64.js b/src/core/operations/Base64.js index c6d9ce6c..31c7e2a1 100755 --- a/src/core/operations/Base64.js +++ b/src/core/operations/Base64.js @@ -40,13 +40,13 @@ const Base64 = { /** * To Base64 operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runTo: function(input, args) { const alphabet = args[0] || Base64.ALPHABET; - return Utils.toBase64(input, alphabet); + return Utils.toBase64(new Uint8Array(input), alphabet); }, diff --git a/src/core/operations/Hexdump.js b/src/core/operations/Hexdump.js index 6b322b1f..fc907d9e 100755 --- a/src/core/operations/Hexdump.js +++ b/src/core/operations/Hexdump.js @@ -31,18 +31,19 @@ const Hexdump = { /** * To Hexdump operation. * - * @param {byteArray} input + * @param {ArrayBuffer} input * @param {Object[]} args * @returns {string} */ runTo: function(input, args) { + const data = new Uint8Array(input); const length = args[0] || Hexdump.WIDTH; const upperCase = args[1]; const includeFinalLength = args[2]; let output = "", padding = 2; - for (let i = 0; i < input.length; i += length) { - const buff = input.slice(i, i+length); + for (let i = 0; i < data.length; i += length) { + const buff = data.slice(i, i+length); let hexa = ""; for (let j = 0; j < buff.length; j++) { hexa += Utils.hex(buff[j], padding) + " "; @@ -59,7 +60,7 @@ const Hexdump = { hexa.padEnd(length*(padding+1), " ") + " |" + 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"; } }