Merge branch 'v9' of github.com:gchq/CyberChef into node-lib

This commit is contained in:
d98762625 2019-07-05 11:07:31 +01:00
commit 368f508b17
122 changed files with 24317 additions and 4174 deletions

View file

@ -65,8 +65,8 @@ class AESEncrypt extends Operation {
* @throws {OperationError} if invalid key length
*/
run(input, args) {
const key = Utils.convertToByteArray(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteString(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];

View file

@ -0,0 +1,266 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";
/**
* Add Text To Image operation
*/
class AddTextToImage extends Operation {
/**
* AddTextToImage constructor
*/
constructor() {
super();
this.name = "Add Text To Image";
this.module = "Image";
this.description = "Adds text onto an image.<br><br>Text can be horizontally or vertically aligned, or the position can be manually specified.<br>Variants of the Roboto font face are available in any size or colour.";
this.infoURL = "";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Text",
type: "string",
value: ""
},
{
name: "Horizontal align",
type: "option",
value: ["None", "Left", "Center", "Right"]
},
{
name: "Vertical align",
type: "option",
value: ["None", "Top", "Middle", "Bottom"]
},
{
name: "X position",
type: "number",
value: 0
},
{
name: "Y position",
type: "number",
value: 0
},
{
name: "Size",
type: "number",
value: 32,
min: 8
},
{
name: "Font face",
type: "option",
value: [
"Roboto",
"Roboto Black",
"Roboto Mono",
"Roboto Slab"
]
},
{
name: "Red",
type: "number",
value: 255,
min: 0,
max: 255
},
{
name: "Green",
type: "number",
value: 255,
min: 0,
max: 255
},
{
name: "Blue",
type: "number",
value: 255,
min: 0,
max: 255
},
{
name: "Alpha",
type: "number",
value: 255,
min: 0,
max: 255
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const text = args[0],
hAlign = args[1],
vAlign = args[2],
size = args[5],
fontFace = args[6],
red = args[7],
green = args[8],
blue = args[9],
alpha = args[10];
let xPos = args[3],
yPos = args[4];
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Adding text to image...");
const fontsMap = {};
const fonts = [
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.fnt"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.fnt"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.fnt"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.fnt")
];
await Promise.all(fonts)
.then(fonts => {
fontsMap.Roboto = fonts[0];
fontsMap["Roboto Black"] = fonts[1];
fontsMap["Roboto Mono"] = fonts[2];
fontsMap["Roboto Slab"] = fonts[3];
});
// Make Webpack load the png font images
await Promise.all([
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.png"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.png"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.png"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.png")
]);
const font = fontsMap[fontFace];
// LoadFont needs an absolute url, so append the font name to self.docURL
const jimpFont = await jimp.loadFont(self.docURL + "/" + font.default);
jimpFont.pages.forEach(function(page) {
if (page.bitmap) {
// Adjust the RGB values of the image pages to change the font colour.
const pageWidth = page.bitmap.width;
const pageHeight = page.bitmap.height;
for (let ix = 0; ix < pageWidth; ix++) {
for (let iy = 0; iy < pageHeight; iy++) {
const idx = (iy * pageWidth + ix) << 2;
const newRed = page.bitmap.data[idx] - (255 - red);
const newGreen = page.bitmap.data[idx + 1] - (255 - green);
const newBlue = page.bitmap.data[idx + 2] - (255 - blue);
const newAlpha = page.bitmap.data[idx + 3] - (255 - alpha);
// Make sure the bitmap values don't go below 0 as that makes jimp very unhappy
page.bitmap.data[idx] = (newRed > 0) ? newRed : 0;
page.bitmap.data[idx + 1] = (newGreen > 0) ? newGreen : 0;
page.bitmap.data[idx + 2] = (newBlue > 0) ? newBlue : 0;
page.bitmap.data[idx + 3] = (newAlpha > 0) ? newAlpha : 0;
}
}
}
});
// Create a temporary image to hold the rendered text
const textImage = new jimp(jimp.measureText(jimpFont, text), jimp.measureTextHeight(jimpFont, text));
textImage.print(jimpFont, 0, 0, text);
// Scale the rendered text image to the correct size
const scaleFactor = size / 72;
if (size !== 1) {
// Use bicubic for decreasing size
if (size > 1) {
textImage.scale(scaleFactor, jimp.RESIZE_BICUBIC);
} else {
textImage.scale(scaleFactor, jimp.RESIZE_BILINEAR);
}
}
// If using the alignment options, calculate the pixel values AFTER the image has been scaled
switch (hAlign) {
case "Left":
xPos = 0;
break;
case "Center":
xPos = (image.getWidth() / 2) - (textImage.getWidth() / 2);
break;
case "Right":
xPos = image.getWidth() - textImage.getWidth();
break;
}
switch (vAlign) {
case "Top":
yPos = 0;
break;
case "Middle":
yPos = (image.getHeight() / 2) - (textImage.getHeight() / 2);
break;
case "Bottom":
yPos = image.getHeight() - textImage.getHeight();
break;
}
// Blit the rendered text image onto the original source image
image.blit(textImage, xPos, yPos);
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error adding text to image. (${err})`);
}
}
/**
* Displays the blurred image using HTML for web apps
*
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default AddTextToImage;

View file

@ -10,6 +10,7 @@ import { isWorkerEnvironment } from "../Utils";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";
import { gaussianBlur } from "../lib/ImageManipulation";
/**
* Blur Image operation
@ -26,8 +27,8 @@ class BlurImage extends Operation {
this.module = "Image";
this.description = "Applies a blur effect to the image.<br><br>Gaussian blur is much slower than fast blur, but produces better results.";
this.infoURL = "https://wikipedia.org/wiki/Gaussian_blur";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -45,37 +46,44 @@ class BlurImage extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [blurAmount, blurType] = args;
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
switch (blurType){
case "Fast":
if (isWorkerEnvironment())
self.sendStatusMessage("Fast blurring image...");
image.blur(blurAmount);
break;
case "Gaussian":
if (isWorkerEnvironment())
self.sendStatusMessage("Gaussian blurring image. This may take a while...");
image.gaussian(blurAmount);
self.sendStatusMessage("Gaussian blurring image...");
image = gaussianBlur(image, blurAmount);
break;
}
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error blurring image. (${err})`);
}
@ -84,18 +92,19 @@ class BlurImage extends Operation {
/**
* Displays the blurred image using HTML for web apps
*
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

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

View file

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

View file

@ -0,0 +1,157 @@
/**
* @author mshwed [m@ttshwed.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { toHexFast } from "../lib/Hex";
/**
* CRC-8 Checksum operation
*/
class CRC8Checksum extends Operation {
/**
* CRC8Checksum constructor
*/
constructor() {
super();
this.name = "CRC-8 Checksum";
this.module = "Crypto";
this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961.";
this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
"name": "Algorithm",
"type": "option",
"value": [
"CRC-8",
"CRC-8/CDMA2000",
"CRC-8/DARC",
"CRC-8/DVB-S2",
"CRC-8/EBU",
"CRC-8/I-CODE",
"CRC-8/ITU",
"CRC-8/MAXIM",
"CRC-8/ROHC",
"CRC-8/WCDMA"
]
}
];
}
/**
* Generates the pre-computed lookup table for byte division
*
* @param polynomial
*/
calculateCRC8LookupTable(polynomial) {
const crc8Table = new Uint8Array(256);
let currentByte;
for (let i = 0; i < 256; i++) {
currentByte = i;
for (let bit = 0; bit < 8; bit++) {
if ((currentByte & 0x80) !== 0) {
currentByte <<= 1;
currentByte ^= polynomial;
} else {
currentByte <<= 1;
}
}
crc8Table[i] = currentByte;
}
return crc8Table;
}
/**
* Calculates the CRC-8 Checksum from an input
*
* @param {ArrayBuffer} input
* @param {number} polynomial
* @param {number} initializationValue
* @param {boolean} inputReflection
* @param {boolean} outputReflection
* @param {number} xorOut
*/
calculateCRC8(input, polynomial, initializationValue, inputReflection, outputReflection, xorOut) {
const crcSize = 8;
const crcTable = this.calculateCRC8LookupTable(polynomial);
let crc = initializationValue !== 0 ? initializationValue : 0;
let currentByte, position;
input = new Uint8Array(input);
for (const inputByte of input) {
currentByte = inputReflection ? this.reverseBits(inputByte, crcSize) : inputByte;
position = (currentByte ^ crc) & 255;
crc = crcTable[position];
}
crc = outputReflection ? this.reverseBits(crc, crcSize) : crc;
if (xorOut !== 0) crc = crc ^ xorOut;
return toHexFast(new Uint8Array([crc]));
}
/**
* Reverse the bits for a given input byte.
*
* @param {number} input
*/
reverseBits(input, hashSize) {
let reversedByte = 0;
for (let i = hashSize - 1; i >= 0; i--) {
reversedByte |= ((input & 1) << i);
input >>= 1;
}
return reversedByte;
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const algorithm = args[0];
switch (algorithm) {
case "CRC-8":
return this.calculateCRC8(input, 0x7, 0x0, false, false, 0x0);
case "CRC-8/CDMA2000":
return this.calculateCRC8(input, 0x9B, 0xFF, false, false, 0x0);
case "CRC-8/DARC":
return this.calculateCRC8(input, 0x39, 0x0, true, true, 0x0);
case "CRC-8/DVB-S2":
return this.calculateCRC8(input, 0xD5, 0x0, false, false, 0x0);
case "CRC-8/EBU":
return this.calculateCRC8(input, 0x1D, 0xFF, true, true, 0x0);
case "CRC-8/I-CODE":
return this.calculateCRC8(input, 0x1D, 0xFD, false, false, 0x0);
case "CRC-8/ITU":
return this.calculateCRC8(input, 0x7, 0x0, false, false, 0x55);
case "CRC-8/MAXIM":
return this.calculateCRC8(input, 0x31, 0x0, true, true, 0x0);
case "CRC-8/ROHC":
return this.calculateCRC8(input, 0x7, 0xFF, true, true, 0x0);
case "CRC-8/WCDMA":
return this.calculateCRC8(input, 0x9B, 0x0, true, true, 0x0);
default:
throw new OperationError("Unknown checksum algorithm");
}
}
}
export default CRC8Checksum;

View file

@ -26,8 +26,8 @@ class ContainImage extends Operation {
this.module = "Image";
this.description = "Scales an image to the specified width and height, maintaining the aspect ratio. The image may be letterboxed.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -73,17 +73,22 @@ class ContainImage extends Operation {
"Bezier"
],
defaultIndex: 1
},
{
name: "Opaque background",
type: "boolean",
value: true
}
];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [width, height, hAlign, vAlign, alg] = args;
const [width, height, hAlign, vAlign, alg, opaqueBg] = args;
const resizeMap = {
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
@ -102,13 +107,13 @@ class ContainImage extends Operation {
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
};
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -116,8 +121,20 @@ class ContainImage extends Operation {
if (isWorkerEnvironment())
self.sendStatusMessage("Containing image...");
image.contain(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
if (opaqueBg) {
const newImage = await jimp.read(width, height, 0x000000FF);
newImage.blit(image, 0, 0);
image = newImage;
}
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error containing image. (${err})`);
}
@ -125,18 +142,19 @@ class ContainImage extends Operation {
/**
* Displays the contained image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -0,0 +1,143 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";
/**
* Convert Image Format operation
*/
class ConvertImageFormat extends Operation {
/**
* ConvertImageFormat constructor
*/
constructor() {
super();
this.name = "Convert Image Format";
this.module = "Image";
this.description = "Converts an image between different formats. Supported formats:<br><ul><li>Joint Photographic Experts Group (JPEG)</li><li>Portable Network Graphics (PNG)</li><li>Bitmap (BMP)</li><li>Tagged Image File Format (TIFF)</li></ul><br>Note: GIF files are supported for input, but cannot be outputted.";
this.infoURL = "https://wikipedia.org/wiki/Image_file_formats";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Output Format",
type: "option",
value: [
"JPEG",
"PNG",
"BMP",
"TIFF"
]
},
{
name: "JPEG Quality",
type: "number",
value: 80,
min: 1,
max: 100
},
{
name: "PNG Filter Type",
type: "option",
value: [
"Auto",
"None",
"Sub",
"Up",
"Average",
"Paeth"
]
},
{
name: "PNG Deflate Level",
type: "number",
value: 9,
min: 0,
max: 9
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;
const formatMap = {
"JPEG": jimp.MIME_JPEG,
"PNG": jimp.MIME_PNG,
"BMP": jimp.MIME_BMP,
"TIFF": jimp.MIME_TIFF
};
const pngFilterMap = {
"Auto": jimp.PNG_FILTER_AUTO,
"None": jimp.PNG_FILTER_NONE,
"Sub": jimp.PNG_FILTER_SUB,
"Up": jimp.PNG_FILTER_UP,
"Average": jimp.PNG_FILTER_AVERAGE,
"Paeth": jimp.PNG_FILTER_PATH // Incorrect spelling in Jimp library
};
const mime = formatMap[format];
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file format.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error opening image file. (${err})`);
}
try {
switch (format) {
case "JPEG":
image.quality(jpegQuality);
break;
case "PNG":
image.filterType(pngFilterMap[pngFilterType]);
image.deflateLevel(pngDeflateLevel);
break;
}
const imageBuffer = await image.getBufferAsync(mime);
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error converting image format. (${err})`);
}
}
/**
* Displays the converted image using HTML for web apps
*
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default ConvertImageFormat;

View file

@ -26,8 +26,8 @@ class CoverImage extends Operation {
this.module = "Image";
this.description = "Scales the image to the given width and height, keeping the aspect ratio. The image may be clipped.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -78,7 +78,7 @@ class CoverImage extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
@ -102,13 +102,13 @@ class CoverImage extends Operation {
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
};
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -116,8 +116,13 @@ class CoverImage extends Operation {
if (isWorkerEnvironment())
self.sendStatusMessage("Covering image...");
image.cover(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error covering image. (${err})`);
}
@ -125,18 +130,19 @@ class CoverImage extends Operation {
/**
* Displays the covered image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -26,8 +26,8 @@ class CropImage extends Operation {
this.module = "Image";
this.description = "Crops an image to the specified region, or automatically crops edges.<br><br><b><u>Autocrop</u></b><br>Automatically crops same-colour borders from the image.<br><br><u>Autocrop tolerance</u><br>A percentage value for the tolerance of colour difference between pixels.<br><br><u>Only autocrop frames</u><br>Only crop real frames (all sides must have the same border)<br><br><u>Symmetric autocrop</u><br>Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)<br><br><u>Autocrop keep border</u><br>The number of pixels of border to leave around the image.";
this.infoURL = "https://wikipedia.org/wiki/Cropping_(image)";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -87,19 +87,19 @@ class CropImage extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [xPos, yPos, width, height, autocrop, autoTolerance, autoFrames, autoSymmetric, autoBorder] = args;
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -117,8 +117,13 @@ class CropImage extends Operation {
image.crop(xPos, yPos, width, height);
}
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error cropping image. (${err})`);
}
@ -126,18 +131,19 @@ class CropImage extends Operation {
/**
* Displays the cropped image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -21,7 +21,12 @@ class DetectFileType extends Operation {
this.name = "Detect File Type";
this.module = "Default";
this.description = "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.<br><br>Currently supports the following file types: 7z, amr, avi, bmp, bz2, class, cr2, crx, dex, dmg, doc, elf, eot, epub, exe, flac, flv, gif, gz, ico, iso, jpg, jxr, m4a, m4v, mid, mkv, mov, mp3, mp4, mpg, ogg, otf, pdf, png, ppt, ps, psd, rar, rtf, sqlite, swf, tar, tar.z, tif, ttf, utf8, vmdk, wav, webm, webp, wmv, woff, woff2, xls, xz, zip.";
this.description = "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.<br><br>Currently supports the following file types: " +
Object.keys(FILE_SIGNATURES).map(cat =>
FILE_SIGNATURES[cat].map(sig =>
sig.extension.split(",")[0]
).join(", ")
).join(", ") + ".";
this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures";
this.inputType = "ArrayBuffer";
this.outputType = "string";
@ -52,18 +57,19 @@ class DetectFileType extends Operation {
if (!types.length) {
return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?";
} else {
let output = "";
types.forEach(type => {
output += "File extension: " + type.extension + "\n" +
"MIME type: " + type.mime + "\n";
const results = types.map(type => {
let output = `File type: ${type.name}
Extension: ${type.extension}
MIME type: ${type.mime}\n`;
if (type.description && type.description.length) {
output += "\nDescription: " + type.description + "\n";
output += `Description: ${type.description}\n`;
}
return output;
});
return output;
return results.join("\n");
}
}

View file

@ -26,25 +26,25 @@ class DitherImage extends Operation {
this.module = "Image";
this.description = "Apply a dither effect to an image.";
this.infoURL = "https://wikipedia.org/wiki/Dither";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -52,8 +52,14 @@ class DitherImage extends Operation {
if (isWorkerEnvironment())
self.sendStatusMessage("Applying dither to image...");
image.dither565();
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error applying dither to image. (${err})`);
}
@ -61,18 +67,19 @@ class DitherImage extends Operation {
/**
* Displays the dithered image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -4,8 +4,13 @@
* @license Apache-2.0
*/
import * as d3temp from "d3";
import * as nodomtemp from "nodom";
import Operation from "../Operation";
import Utils from "../Utils";
const d3 = d3temp.default ? d3temp.default : d3temp;
const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;
/**
* Entropy operation
@ -19,30 +24,45 @@ class Entropy extends Operation {
super();
this.name = "Entropy";
this.module = "Default";
this.module = "Charts";
this.description = "Shannon Entropy, in the context of information theory, is a measure of the rate at which information is produced by a source of data. It can be used, in a broad sense, to detect whether data is likely to be structured or unstructured. 8 is the maximum, representing highly unstructured, 'random' data. English language text usually falls somewhere between 3.5 and 5. Properly encrypted or compressed data should have an entropy of over 7.5.";
this.infoURL = "https://wikipedia.org/wiki/Entropy_(information_theory)";
this.inputType = "byteArray";
this.outputType = "number";
this.inputType = "ArrayBuffer";
this.outputType = "json";
this.presentType = "html";
this.args = [];
this.args = [
{
"name": "Visualisation",
"type": "option",
"value": ["Shannon scale", "Histogram (Bar)", "Histogram (Line)", "Curve", "Image"]
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* Calculates the frequency of bytes in the input.
*
* @param {Uint8Array} input
* @returns {number}
*/
run(input, args) {
calculateShannonEntropy(input) {
const prob = [],
uniques = input.unique(),
str = Utils.byteArrayToChars(input);
let i;
occurrences = new Array(256).fill(0);
for (i = 0; i < uniques.length; i++) {
prob.push(str.count(Utils.chr(uniques[i])) / input.length);
// Count occurrences of each byte in the input
let i;
for (i = 0; i < input.length; i++) {
occurrences[input[i]]++;
}
// Store probability list
for (i = 0; i < occurrences.length; i++) {
if (occurrences[i] > 0) {
prob.push(occurrences[i] / input.length);
}
}
// Calculate Shannon entropy
let entropy = 0,
p;
@ -54,44 +74,357 @@ class Entropy extends Operation {
return -entropy;
}
/**
* Calculates the scanning entropy of the input
*
* @param {Uint8Array} inputBytes
* @returns {Object}
*/
calculateScanningEntropy(inputBytes) {
const entropyData = [];
const binWidth = inputBytes.length < 256 ? 8 : 256;
for (let bytePos = 0; bytePos < inputBytes.length; bytePos += binWidth) {
const block = inputBytes.slice(bytePos, bytePos+binWidth);
entropyData.push(this.calculateShannonEntropy(block));
}
return { entropyData, binWidth };
}
/**
* Calculates the frequency of bytes in the input.
*
* @param {object} svg
* @param {function} xScale
* @param {function} yScale
* @param {integer} svgHeight
* @param {integer} svgWidth
* @param {object} margins
* @param {string} xTitle
* @param {string} yTitle
*/
createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, title, xTitle, yTitle) {
// Axes
const yAxis = d3.axisLeft()
.scale(yScale);
const xAxis = d3.axisBottom()
.scale(xScale);
svg.append("g")
.attr("transform", `translate(0, ${svgHeight - margins.bottom})`)
.call(xAxis);
svg.append("g")
.attr("transform", `translate(${margins.left},0)`)
.call(yAxis);
// Axes labels
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margins.left)
.attr("x", 0 - (svgHeight / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text(yTitle);
svg.append("text")
.attr("transform", `translate(${svgWidth / 2}, ${svgHeight - margins.bottom + 40})`)
.style("text-anchor", "middle")
.text(xTitle);
// Add title
svg.append("text")
.attr("transform", `translate(${svgWidth / 2}, ${margins.top - 10})`)
.style("text-anchor", "middle")
.text(title);
}
/**
* Calculates the frequency of bytes in the input.
*
* @param {Uint8Array} inputBytes
* @returns {number[]}
*/
calculateByteFrequency(inputBytes) {
const freq = new Array(256).fill(0);
if (inputBytes.length === 0) return freq;
// Count occurrences of each byte in the input
let i;
for (i = 0; i < inputBytes.length; i++) {
freq[inputBytes[i]]++;
}
for (i = 0; i < freq.length; i++) {
freq[i] = freq[i] / inputBytes.length;
}
return freq;
}
/**
* Calculates the frequency of bytes in the input.
*
* @param {number[]} byteFrequency
* @returns {HTML}
*/
createByteFrequencyLineHistogram(byteFrequency) {
const margins = { top: 30, right: 20, bottom: 50, left: 30 };
const svgWidth = 500,
svgHeight = 500;
const document = new nodom.Document();
let svg = document.createElement("svg");
svg = d3.select(svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
const yScale = d3.scaleLinear()
.domain([0, d3.max(byteFrequency, d => d)])
.range([svgHeight - margins.bottom, margins.top]);
const xScale = d3.scaleLinear()
.domain([0, byteFrequency.length - 1])
.range([margins.left, svgWidth - margins.right]);
const line = d3.line()
.x((_, i) => xScale(i))
.y(d => yScale(d))
.curve(d3.curveMonotoneX);
svg.append("path")
.datum(byteFrequency)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("d", line);
this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "", "Byte", "Byte Frequency");
return svg._groups[0][0].outerHTML;
}
/**
* Creates a byte frequency histogram
*
* @param {number[]} byteFrequency
* @returns {HTML}
*/
createByteFrequencyBarHistogram(byteFrequency) {
const margins = { top: 30, right: 20, bottom: 50, left: 30 };
const svgWidth = 500,
svgHeight = 500,
binWidth = 1;
const document = new nodom.Document();
let svg = document.createElement("svg");
svg = d3.select(svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
const yExtent = d3.extent(byteFrequency, d => d);
const yScale = d3.scaleLinear()
.domain(yExtent)
.range([svgHeight - margins.bottom, margins.top]);
const xScale = d3.scaleLinear()
.domain([0, byteFrequency.length - 1])
.range([margins.left - binWidth, svgWidth - margins.right]);
svg.selectAll("rect")
.data(byteFrequency)
.enter().append("rect")
.attr("x", (_, i) => xScale(i) + binWidth)
.attr("y", dataPoint => yScale(dataPoint))
.attr("width", binWidth)
.attr("height", dataPoint => yScale(yExtent[0]) - yScale(dataPoint))
.attr("fill", "blue");
this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "", "Byte", "Byte Frequency");
return svg._groups[0][0].outerHTML;
}
/**
* Creates a byte frequency histogram
*
* @param {number[]} entropyData
* @returns {HTML}
*/
createEntropyCurve(entropyData) {
const margins = { top: 30, right: 20, bottom: 50, left: 30 };
const svgWidth = 500,
svgHeight = 500;
const document = new nodom.Document();
let svg = document.createElement("svg");
svg = d3.select(svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
const yScale = d3.scaleLinear()
.domain([0, d3.max(entropyData, d => d)])
.range([svgHeight - margins.bottom, margins.top]);
const xScale = d3.scaleLinear()
.domain([0, entropyData.length])
.range([margins.left, svgWidth - margins.right]);
const line = d3.line()
.x((_, i) => xScale(i))
.y(d => yScale(d))
.curve(d3.curveMonotoneX);
if (entropyData.length > 0) {
svg.append("path")
.datum(entropyData)
.attr("d", line);
svg.selectAll("path").attr("fill", "none").attr("stroke", "steelblue");
}
this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "Scanning Entropy", "Block", "Entropy");
return svg._groups[0][0].outerHTML;
}
/**
* Creates an image representation of the entropy
*
* @param {number[]} entropyData
* @returns {HTML}
*/
createEntropyImage(entropyData) {
const svgHeight = 100,
svgWidth = 100,
cellSize = 1,
nodes = [];
for (let i = 0; i < entropyData.length; i++) {
nodes.push({
x: i % svgWidth,
y: Math.floor(i / svgWidth),
entropy: entropyData[i]
});
}
const document = new nodom.Document();
let svg = document.createElement("svg");
svg = d3.select(svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
const greyScale = d3.scaleLinear()
.domain([0, d3.max(entropyData, d => d)])
.range(["#000000", "#FFFFFF"])
.interpolate(d3.interpolateRgb);
svg
.selectAll("rect")
.data(nodes)
.enter().append("rect")
.attr("x", d => d.x * cellSize)
.attr("y", d => d.y * cellSize)
.attr("width", cellSize)
.attr("height", cellSize)
.style("fill", d => greyScale(d.entropy));
return svg._groups[0][0].outerHTML;
}
/**
* Displays the entropy as a scale bar for web apps.
*
* @param {number} entropy
* @returns {html}
* @returns {HTML}
*/
present(entropy) {
createShannonEntropyVisualization(entropy) {
return `Shannon entropy: ${entropy}
<br><canvas id='chart-area'></canvas><br>
- 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string.
- Standard English text usually falls somewhere between 3.5 and 5.
- Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5.
<br><canvas id='chart-area'></canvas><br>
- 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string.
- Standard English text usually falls somewhere between 3.5 and 5.
- Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5.
The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections.
The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections.
<br><script>
var canvas = document.getElementById("chart-area"),
parentRect = canvas.parentNode.getBoundingClientRect(),
entropy = ${entropy},
height = parentRect.height * 0.25;
<br><script>
var canvas = document.getElementById("chart-area"),
parentRect = canvas.parentNode.getBoundingClientRect(),
entropy = ${entropy},
height = parentRect.height * 0.25;
canvas.width = parentRect.width * 0.95;
canvas.height = height > 150 ? 150 : height;
canvas.width = parentRect.width * 0.95;
canvas.height = height > 150 ? 150 : height;
CanvasComponents.drawScaleBar(canvas, entropy, 8, [
{
label: "English text",
min: 3.5,
max: 5
},{
label: "Encrypted/compressed",
min: 7.5,
max: 8
}
]);
</script>`;
CanvasComponents.drawScaleBar(canvas, entropy, 8, [
{
label: "English text",
min: 3.5,
max: 5
},{
label: "Encrypted/compressed",
min: 7.5,
max: 8
}
]);
</script>`;
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {json}
*/
run(input, args) {
const visualizationType = args[0];
input = new Uint8Array(input);
switch (visualizationType) {
case "Histogram (Bar)":
case "Histogram (Line)":
return this.calculateByteFrequency(input);
case "Curve":
case "Image":
return this.calculateScanningEntropy(input).entropyData;
case "Shannon scale":
default:
return this.calculateShannonEntropy(input);
}
}
/**
* Displays the entropy in a visualisation for web apps.
*
* @param {json} entropyData
* @param {Object[]} args
* @returns {html}
*/
present(entropyData, args) {
const visualizationType = args[0];
switch (visualizationType) {
case "Histogram (Bar)":
return this.createByteFrequencyBarHistogram(entropyData);
case "Histogram (Line)":
return this.createByteFrequencyLineHistogram(entropyData);
case "Curve":
return this.createEntropyCurve(entropyData);
case "Image":
return this.createEntropyImage(entropyData);
case "Shannon scale":
default:
return this.createShannonEntropyVisualization(entropyData);
}
}
}
export default Entropy;

View file

@ -26,8 +26,8 @@ class FlipImage extends Operation {
this.module = "Image";
this.description = "Flips an image along its X or Y axis.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -39,19 +39,19 @@ class FlipImage extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [flipAxis] = args;
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid input file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -67,8 +67,13 @@ class FlipImage extends Operation {
break;
}
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error flipping image. (${err})`);
}
@ -76,18 +81,19 @@ class FlipImage extends Operation {
/**
* Displays the flipped image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import GostDigest from "../vendor/gost/gostDigest";
import {toHexFast} from "../lib/Hex";
/**
* GOST hash operation
*/
class GOSTHash extends Operation {
/**
* GOSTHash constructor
*/
constructor() {
super();
this.name = "GOST hash";
this.module = "Hashing";
this.description = "The GOST hash function, defined in the standards GOST R 34.11-94 and GOST 34.311-95 is a 256-bit cryptographic hash function. It was initially defined in the Russian national standard GOST R 34.11-94 <i>Information Technology Cryptographic Information Security Hash Function</i>. The equivalent standard used by other member-states of the CIS is GOST 34.311-95.<br><br>This function must not be confused with a different Streebog hash function, which is defined in the new revision of the standard GOST R 34.11-2012.<br><br>The GOST hash function is based on the GOST block cipher.";
this.infoURL = "https://wikipedia.org/wiki/GOST_(hash_function)";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
"name": "S-Box",
"type": "option",
"value": [
"D-A",
"D-SC",
"E-TEST",
"E-A",
"E-B",
"E-C",
"E-D",
"E-SC",
"E-Z",
"D-TEST"
]
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
try {
const sBox = args[1];
const gostDigest = new GostDigest({
name: "GOST R 34.11",
version: 1994,
sBox: sBox
});
return toHexFast(gostDigest.digest(input));
} catch (err) {
throw new OperationError(err);
}
}
}
export default GOSTHash;

View file

@ -26,10 +26,13 @@ import Fletcher16Checksum from "./Fletcher16Checksum";
import Fletcher32Checksum from "./Fletcher32Checksum";
import Fletcher64Checksum from "./Fletcher64Checksum";
import Adler32Checksum from "./Adler32Checksum";
import CRC8Checksum from "./CRC8Checksum";
import CRC16Checksum from "./CRC16Checksum";
import CRC32Checksum from "./CRC32Checksum";
import BLAKE2b from "./BLAKE2b";
import BLAKE2s from "./BLAKE2s";
import Streebog from "./Streebog";
import GOSTHash from "./GOSTHash";
/**
* Generate all hashes operation
@ -60,52 +63,56 @@ class GenerateAllHashes extends Operation {
const arrayBuffer = input,
str = Utils.arrayBufferToStr(arrayBuffer, false),
byteArray = new Uint8Array(arrayBuffer),
output = "MD2: " + (new MD2()).run(arrayBuffer, []) +
"\nMD4: " + (new MD4()).run(arrayBuffer, []) +
"\nMD5: " + (new MD5()).run(arrayBuffer, []) +
"\nMD6: " + (new MD6()).run(str, []) +
"\nSHA0: " + (new SHA0()).run(arrayBuffer, []) +
"\nSHA1: " + (new SHA1()).run(arrayBuffer, []) +
"\nSHA2 224: " + (new SHA2()).run(arrayBuffer, ["224"]) +
"\nSHA2 256: " + (new SHA2()).run(arrayBuffer, ["256"]) +
"\nSHA2 384: " + (new SHA2()).run(arrayBuffer, ["384"]) +
"\nSHA2 512: " + (new SHA2()).run(arrayBuffer, ["512"]) +
"\nSHA3 224: " + (new SHA3()).run(arrayBuffer, ["224"]) +
"\nSHA3 256: " + (new SHA3()).run(arrayBuffer, ["256"]) +
"\nSHA3 384: " + (new SHA3()).run(arrayBuffer, ["384"]) +
"\nSHA3 512: " + (new SHA3()).run(arrayBuffer, ["512"]) +
"\nKeccak 224: " + (new Keccak()).run(arrayBuffer, ["224"]) +
"\nKeccak 256: " + (new Keccak()).run(arrayBuffer, ["256"]) +
"\nKeccak 384: " + (new Keccak()).run(arrayBuffer, ["384"]) +
"\nKeccak 512: " + (new Keccak()).run(arrayBuffer, ["512"]) +
"\nShake 128: " + (new Shake()).run(arrayBuffer, ["128", 256]) +
"\nShake 256: " + (new Shake()).run(arrayBuffer, ["256", 512]) +
"\nRIPEMD-128: " + (new RIPEMD()).run(arrayBuffer, ["128"]) +
"\nRIPEMD-160: " + (new RIPEMD()).run(arrayBuffer, ["160"]) +
"\nRIPEMD-256: " + (new RIPEMD()).run(arrayBuffer, ["256"]) +
"\nRIPEMD-320: " + (new RIPEMD()).run(arrayBuffer, ["320"]) +
"\nHAS-160: " + (new HAS160()).run(arrayBuffer, []) +
"\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) +
"\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) +
"\nWhirlpool: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) +
"\nBLAKE2b-128: " + (new BLAKE2b).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-160: " + (new BLAKE2b).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-256: " + (new BLAKE2b).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-384: " + (new BLAKE2b).run(arrayBuffer, ["384", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-512: " + (new BLAKE2b).run(arrayBuffer, ["512", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-128: " + (new BLAKE2s).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-160: " + (new BLAKE2s).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-256: " + (new BLAKE2s).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
"\nSSDEEP: " + (new SSDEEP()).run(str) +
"\nCTPH: " + (new CTPH()).run(str) +
output = "MD2: " + (new MD2()).run(arrayBuffer, []) +
"\nMD4: " + (new MD4()).run(arrayBuffer, []) +
"\nMD5: " + (new MD5()).run(arrayBuffer, []) +
"\nMD6: " + (new MD6()).run(str, []) +
"\nSHA0: " + (new SHA0()).run(arrayBuffer, []) +
"\nSHA1: " + (new SHA1()).run(arrayBuffer, []) +
"\nSHA2 224: " + (new SHA2()).run(arrayBuffer, ["224"]) +
"\nSHA2 256: " + (new SHA2()).run(arrayBuffer, ["256"]) +
"\nSHA2 384: " + (new SHA2()).run(arrayBuffer, ["384"]) +
"\nSHA2 512: " + (new SHA2()).run(arrayBuffer, ["512"]) +
"\nSHA3 224: " + (new SHA3()).run(arrayBuffer, ["224"]) +
"\nSHA3 256: " + (new SHA3()).run(arrayBuffer, ["256"]) +
"\nSHA3 384: " + (new SHA3()).run(arrayBuffer, ["384"]) +
"\nSHA3 512: " + (new SHA3()).run(arrayBuffer, ["512"]) +
"\nKeccak 224: " + (new Keccak()).run(arrayBuffer, ["224"]) +
"\nKeccak 256: " + (new Keccak()).run(arrayBuffer, ["256"]) +
"\nKeccak 384: " + (new Keccak()).run(arrayBuffer, ["384"]) +
"\nKeccak 512: " + (new Keccak()).run(arrayBuffer, ["512"]) +
"\nShake 128: " + (new Shake()).run(arrayBuffer, ["128", 256]) +
"\nShake 256: " + (new Shake()).run(arrayBuffer, ["256", 512]) +
"\nRIPEMD-128: " + (new RIPEMD()).run(arrayBuffer, ["128"]) +
"\nRIPEMD-160: " + (new RIPEMD()).run(arrayBuffer, ["160"]) +
"\nRIPEMD-256: " + (new RIPEMD()).run(arrayBuffer, ["256"]) +
"\nRIPEMD-320: " + (new RIPEMD()).run(arrayBuffer, ["320"]) +
"\nHAS-160: " + (new HAS160()).run(arrayBuffer, []) +
"\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) +
"\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) +
"\nWhirlpool: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) +
"\nBLAKE2b-128: " + (new BLAKE2b).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-160: " + (new BLAKE2b).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-256: " + (new BLAKE2b).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-384: " + (new BLAKE2b).run(arrayBuffer, ["384", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-512: " + (new BLAKE2b).run(arrayBuffer, ["512", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-128: " + (new BLAKE2s).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-160: " + (new BLAKE2s).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-256: " + (new BLAKE2s).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
"\nStreebog-256: " + (new Streebog).run(arrayBuffer, ["256"]) +
"\nStreebog-512: " + (new Streebog).run(arrayBuffer, ["512"]) +
"\nGOST: " + (new GOSTHash).run(arrayBuffer, ["D-A"]) +
"\nSSDEEP: " + (new SSDEEP()).run(str) +
"\nCTPH: " + (new CTPH()).run(str) +
"\n\nChecksums:" +
"\nFletcher-8: " + (new Fletcher8Checksum).run(byteArray, []) +
"\nFletcher-16: " + (new Fletcher16Checksum).run(byteArray, []) +
"\nFletcher-32: " + (new Fletcher32Checksum).run(byteArray, []) +
"\nFletcher-64: " + (new Fletcher64Checksum).run(byteArray, []) +
"\nAdler-32: " + (new Adler32Checksum).run(byteArray, []) +
"\nCRC-16: " + (new CRC16Checksum).run(str, []) +
"\nCRC-32: " + (new CRC32Checksum).run(str, []);
"\nFletcher-8: " + (new Fletcher8Checksum).run(byteArray, []) +
"\nFletcher-16: " + (new Fletcher16Checksum).run(byteArray, []) +
"\nFletcher-32: " + (new Fletcher32Checksum).run(byteArray, []) +
"\nFletcher-64: " + (new Fletcher64Checksum).run(byteArray, []) +
"\nAdler-32: " + (new Adler32Checksum).run(byteArray, []) +
"\nCRC-8: " + (new CRC8Checksum).run(arrayBuffer, ["CRC-8"]) +
"\nCRC-16: " + (new CRC16Checksum).run(arrayBuffer, []) +
"\nCRC-32: " + (new CRC32Checksum).run(arrayBuffer, []);
return output;
}

View file

@ -6,7 +6,7 @@
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import qr from "qr-image";
import { generateQrCode } from "../lib/QRCode";
import { toBase64 } from "../lib/Base64";
import { isImage } from "../lib/FileType";
import Utils from "../Utils";
@ -27,7 +27,7 @@ class GenerateQRCode extends Operation {
this.description = "Generates a Quick Response (QR) code from the input text.<br><br>A QR code is a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan. A barcode is a machine-readable optical label that contains information about the item to which it is attached.";
this.infoURL = "https://wikipedia.org/wiki/QR_code";
this.inputType = "string";
this.outputType = "byteArray";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -38,12 +38,14 @@ class GenerateQRCode extends Operation {
{
"name": "Module size (px)",
"type": "number",
"value": 5
"value": 5,
"min": 1
},
{
"name": "Margin (num modules)",
"type": "number",
"value": 2
"value": 2,
"min": 0
},
{
"name": "Error correction",
@ -57,61 +59,34 @@ class GenerateQRCode extends Operation {
/**
* @param {string} input
* @param {Object[]} args
* @returns {byteArray}
* @returns {ArrayBuffer}
*/
run(input, args) {
const [format, size, margin, errorCorrection] = args;
// Create new QR image from the input data, and convert it to a buffer
const qrImage = qr.imageSync(input, {
type: format,
size: size,
margin: margin,
"ec_level": errorCorrection.charAt(0).toUpperCase()
});
if (qrImage == null) {
throw new OperationError("Error generating QR code.");
}
switch (format) {
case "SVG":
case "EPS":
case "PDF":
return [...Buffer.from(qrImage)];
case "PNG":
// Return the QR image buffer as a byte array
return [...qrImage];
default:
throw new OperationError("Unsupported QR code format.");
}
return generateQrCode(input, format, size, margin, errorCorrection);
}
/**
* Displays the QR image using HTML for web apps
*
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data, args) {
if (!data.length) return "";
const [format] = args;
if (!data.byteLength && !data.length) return "";
const dataArray = new Uint8Array(data),
[format] = args;
if (format === "PNG") {
let dataURI = "data:";
const mime = isImage(data);
if (mime){
dataURI += mime + ";";
} else {
throw new OperationError("Invalid PNG file generated by QR image");
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
dataURI += "base64," + toBase64(data);
return `<img src="${dataURI}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
return Utils.byteArrayToChars(data);
return Utils.arrayBufferToStr(data);
}
}

View file

@ -26,8 +26,8 @@ class ImageBrightnessContrast extends Operation {
this.module = "Image";
this.description = "Adjust the brightness or contrast of an image.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -48,19 +48,19 @@ class ImageBrightnessContrast extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [brightness, contrast] = args;
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -76,8 +76,13 @@ class ImageBrightnessContrast extends Operation {
image.contrast(contrast / 100);
}
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error adjusting image brightness or contrast. (${err})`);
}
@ -85,18 +90,19 @@ class ImageBrightnessContrast extends Operation {
/**
* Displays the image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -26,8 +26,8 @@ class ImageFilter extends Operation {
this.module = "Image";
this.description = "Applies a greyscale or sepia filter to an image.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -42,19 +42,19 @@ class ImageFilter extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [filterType] = args;
if (!isImage(input)){
if (!isImage(new Uint8Array(input))){
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -67,8 +67,13 @@ class ImageFilter extends Operation {
image.sepia();
}
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error applying filter to image. (${err})`);
}
@ -76,18 +81,19 @@ class ImageFilter extends Operation {
/**
* Displays the blurred image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -26,8 +26,8 @@ class ImageHueSaturationLightness extends Operation {
this.module = "Image";
this.description = "Adjusts the hue / saturation / lightness (HSL) values of an image.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -55,20 +55,20 @@ class ImageHueSaturationLightness extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [hue, saturation, lightness] = args;
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -103,8 +103,14 @@ class ImageHueSaturationLightness extends Operation {
}
]);
}
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error adjusting image hue / saturation / lightness. (${err})`);
}
@ -112,18 +118,19 @@ class ImageHueSaturationLightness extends Operation {
/**
* Displays the image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -26,8 +26,8 @@ class ImageOpacity extends Operation {
this.module = "Image";
this.description = "Adjust the opacity of an image.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -41,19 +41,19 @@ class ImageOpacity extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [opacity] = args;
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -62,8 +62,13 @@ class ImageOpacity extends Operation {
self.sendStatusMessage("Changing image opacity...");
image.opacity(opacity / 100);
const imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error changing image opacity. (${err})`);
}
@ -71,18 +76,19 @@ class ImageOpacity extends Operation {
/**
* Displays the image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

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

View file

@ -26,25 +26,25 @@ class InvertImage extends Operation {
this.module = "Image";
this.description = "Invert the colours of an image.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid input file format.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -52,8 +52,14 @@ class InvertImage extends Operation {
if (isWorkerEnvironment())
self.sendStatusMessage("Inverting image...");
image.invert();
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error inverting image. (${err})`);
}
@ -61,18 +67,19 @@ class InvertImage extends Operation {
/**
* Displays the inverted image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

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

View file

@ -25,44 +25,59 @@ class NormaliseImage extends Operation {
this.module = "Image";
this.description = "Normalise the image colours.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType= "html";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
const image = await jimp.read(Buffer.from(input));
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error opening image file. (${err})`);
}
image.normalize();
try {
image.normalize();
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error normalising image. (${err})`);
}
}
/**
* Displays the normalised image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -93,7 +93,7 @@ class PGPDecryptAndVerify extends Operation {
text += `${signer.username} `;
}
if (signer.comment) {
text += `${signer.comment} `;
text += `(${signer.comment}) `;
}
if (signer.email) {
text += `<${signer.email}>`;
@ -101,8 +101,9 @@ class PGPDecryptAndVerify extends Operation {
text += "\n";
}
text += [
`PGP key ID: ${km.get_pgp_short_key_id()}`,
`PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`,
`Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`,
`Signed on ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`,
"----------------------------------\n"
].join("\n");
text += unboxedLiterals.toString();

View file

@ -0,0 +1,111 @@
/**
* @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import kbpgp from "kbpgp";
import { ASP, importPublicKey } from "../lib/PGP";
import * as es6promisify from "es6-promisify";
const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
/**
* PGP Verify operation
*/
class PGPVerify extends Operation {
/**
* PGPVerify constructor
*/
constructor() {
super();
this.name = "PGP Verify";
this.module = "PGP";
this.description = [
"Input: the ASCII-armoured encrypted PGP message you want to verify.",
"<br><br>",
"Argument: the ASCII-armoured PGP public key of the signer",
"<br><br>",
"This operation uses PGP to decrypt a clearsigned message.",
"<br><br>",
"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
"<br><br>",
"This function uses the Keybase implementation of PGP.",
].join("\n");
this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Public key of signer",
"type": "text",
"value": ""
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
async run(input, args) {
const signedMessage = input,
[publicKey] = args,
keyring = new kbpgp.keyring.KeyRing();
let unboxedLiterals;
if (!publicKey) throw new OperationError("Enter the public key of the signer.");
const pubKey = await importPublicKey(publicKey);
keyring.add_key_manager(pubKey);
try {
unboxedLiterals = await promisify(kbpgp.unbox)({
armored: signedMessage,
keyfetch: keyring,
asp: ASP
});
const ds = unboxedLiterals[0].get_data_signer();
if (ds) {
const km = ds.get_key_manager();
if (km) {
const signer = km.get_userids_mark_primary()[0].components;
let text = "Signed by ";
if (signer.email || signer.username || signer.comment) {
if (signer.username) {
text += `${signer.username} `;
}
if (signer.comment) {
text += `(${signer.comment}) `;
}
if (signer.email) {
text += `<${signer.email}>`;
}
text += "\n";
}
text += [
`PGP key ID: ${km.get_pgp_short_key_id()}`,
`PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`,
`Signed on ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`,
"----------------------------------\n"
].join("\n");
text += unboxedLiterals.toString();
return text.trim();
} else {
throw new OperationError("Could not identify a key manager.");
}
} else {
throw new OperationError("The data does not appear to be signed.");
}
} catch (err) {
throw new OperationError(`Couldn't verify message: ${err}`);
}
}
}
export default PGPVerify;

View file

@ -6,9 +6,8 @@
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import jsqr from "jsqr";
import jimp from "jimp";
import { isImage } from "../lib/FileType.mjs";
import { parseQrCode } from "../lib/QRCode";
/**
* Parse QR Code operation
@ -25,7 +24,7 @@ class ParseQRCode extends Operation {
this.module = "Image";
this.description = "Reads an image file and attempts to detect and read a Quick Response (QR) code from the image.<br><br><u>Normalise Image</u><br>Attempts to normalise the image before parsing it to improve detection of a QR code.";
this.infoURL = "https://wikipedia.org/wiki/QR_code";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@ -34,69 +33,28 @@ class ParseQRCode extends Operation {
"value": false
}
];
this.patterns = [
{
"match": "^(?:\\xff\\xd8\\xff|\\x89\\x50\\x4e\\x47|\\x47\\x49\\x46|.{8}\\x57\\x45\\x42\\x50|\\x42\\x4d)",
"flags": "",
"args": [false],
"useful": true
}
];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
async run(input, args) {
const [normalise] = args;
// Make sure that the input is an image
if (!isImage(input)) throw new OperationError("Invalid file type.");
let image = input;
if (normalise) {
// Process the image to be easier to read by jsqr
// Disables the alpha channel
// Sets the image default background to white
// Normalises the image colours
// Makes the image greyscale
// Converts image to a JPEG
image = await new Promise((resolve, reject) => {
jimp.read(Buffer.from(input))
.then(image => {
image
.rgba(false)
.background(0xFFFFFFFF)
.normalize()
.greyscale()
.getBuffer(jimp.MIME_JPEG, (error, result) => {
resolve(result);
});
})
.catch(err => {
reject(new OperationError("Error reading the image file."));
});
});
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
if (image instanceof OperationError) {
throw image;
}
return new Promise((resolve, reject) => {
jimp.read(Buffer.from(image))
.then(image => {
if (image.bitmap != null) {
const qrData = jsqr(image.bitmap.data, image.getWidth(), image.getHeight());
if (qrData != null) {
resolve(qrData.data);
} else {
reject(new OperationError("Couldn't read a QR code from the image."));
}
} else {
reject(new OperationError("Error reading the image file."));
}
})
.catch(err => {
reject(new OperationError("Error reading the image file."));
});
});
return await parseQrCode(input, normalise);
}
}

View file

@ -26,8 +26,8 @@ class ResizeImage extends Operation {
this.module = "Image";
this.description = "Resizes an image to the specified width and height values.";
this.infoURL = "https://wikipedia.org/wiki/Image_scaling";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -68,7 +68,7 @@ class ResizeImage extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
@ -87,13 +87,13 @@ class ResizeImage extends Operation {
"Bezier": jimp.RESIZE_BEZIER
};
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -111,8 +111,13 @@ class ResizeImage extends Operation {
image.resize(width, height, resizeMap[resizeAlg]);
}
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error resizing image. (${err})`);
}
@ -120,18 +125,19 @@ class ResizeImage extends Operation {
/**
* Displays the resized image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -26,8 +26,8 @@ class RotateImage extends Operation {
this.module = "Image";
this.description = "Rotates an image by the specified number of degrees.";
this.infoURL = "";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
@ -39,20 +39,20 @@ class RotateImage extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [degrees] = args;
if (!isImage(input)) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(Buffer.from(input));
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
@ -60,8 +60,14 @@ class RotateImage extends Operation {
if (isWorkerEnvironment())
self.sendStatusMessage("Rotating image...");
image.rotate(degrees);
const imageBuffer = await image.getBufferAsync(jimp.AUTO);
return [...imageBuffer];
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error rotating image. (${err})`);
}
@ -69,18 +75,19 @@ class RotateImage extends Operation {
/**
* Displays the rotated image using HTML for web apps
* @param {byteArray} data
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(data)}">`;
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}

View file

@ -55,12 +55,13 @@ class ScanForEmbeddedFiles extends Operation {
if (types.length) {
types.forEach(type => {
numFound++;
output += "\nOffset " + type.offset + " (0x" + Utils.hex(type.offset) + "):\n" +
" File extension: " + type.fileDetails.extension + "\n" +
" MIME type: " + type.fileDetails.mime + "\n";
output += `\nOffset ${type.offset} (0x${Utils.hex(type.offset)}):
File type: ${type.fileDetails.name}
Extension: ${type.fileDetails.extension}
MIME type: ${type.fileDetails.mime}\n`;
if (type.fileDetails.description && type.fileDetails.description.length) {
output += " Description: " + type.fileDetails.description + "\n";
output += ` Description: ${type.fileDetails.description}\n`;
}
});
}

View file

@ -0,0 +1,168 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import { gaussianBlur } from "../lib/ImageManipulation";
import jimp from "jimp";
/**
* Sharpen Image operation
*/
class SharpenImage extends Operation {
/**
* SharpenImage constructor
*/
constructor() {
super();
this.name = "Sharpen Image";
this.module = "Image";
this.description = "Sharpens an image (Unsharp mask)";
this.infoURL = "https://wikipedia.org/wiki/Unsharp_masking";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Radius",
type: "number",
value: 2,
min: 1
},
{
name: "Amount",
type: "number",
value: 1,
min: 0,
step: 0.1
},
{
name: "Threshold",
type: "number",
value: 10,
min: 0,
max: 100
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [radius, amount, threshold] = args;
if (!isImage(new Uint8Array(input))){
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Sharpening image... (Cloning image)");
const blurMask = image.clone();
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Sharpening image... (Blurring cloned image)");
const blurImage = gaussianBlur(image.clone(), radius, 3);
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Sharpening image... (Creating unsharp mask)");
blurMask.scan(0, 0, blurMask.bitmap.width, blurMask.bitmap.height, function(x, y, idx) {
const blurRed = blurImage.bitmap.data[idx];
const blurGreen = blurImage.bitmap.data[idx + 1];
const blurBlue = blurImage.bitmap.data[idx + 2];
const normalRed = this.bitmap.data[idx];
const normalGreen = this.bitmap.data[idx + 1];
const normalBlue = this.bitmap.data[idx + 2];
// Subtract blurred pixel value from normal image
this.bitmap.data[idx] = (normalRed > blurRed) ? normalRed - blurRed : 0;
this.bitmap.data[idx + 1] = (normalGreen > blurGreen) ? normalGreen - blurGreen : 0;
this.bitmap.data[idx + 2] = (normalBlue > blurBlue) ? normalBlue - blurBlue : 0;
});
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Sharpening image... (Merging with unsharp mask)");
image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) {
let maskRed = blurMask.bitmap.data[idx];
let maskGreen = blurMask.bitmap.data[idx + 1];
let maskBlue = blurMask.bitmap.data[idx + 2];
const normalRed = this.bitmap.data[idx];
const normalGreen = this.bitmap.data[idx + 1];
const normalBlue = this.bitmap.data[idx + 2];
// Calculate luminance
const maskLuminance = (0.2126 * maskRed + 0.7152 * maskGreen + 0.0722 * maskBlue);
const normalLuminance = (0.2126 * normalRed + 0.7152 * normalGreen + 0.0722 * normalBlue);
let luminanceDiff;
if (maskLuminance > normalLuminance) {
luminanceDiff = maskLuminance - normalLuminance;
} else {
luminanceDiff = normalLuminance - maskLuminance;
}
// Scale mask colours by amount
maskRed = maskRed * amount;
maskGreen = maskGreen * amount;
maskBlue = maskBlue * amount;
// Only change pixel value if the difference is higher than threshold
if ((luminanceDiff / 255) * 100 >= threshold) {
this.bitmap.data[idx] = (normalRed + maskRed) <= 255 ? normalRed + maskRed : 255;
this.bitmap.data[idx + 1] = (normalGreen + maskGreen) <= 255 ? normalGreen + maskGreen : 255;
this.bitmap.data[idx + 2] = (normalBlue + maskBlue) <= 255 ? normalBlue + maskBlue : 255;
}
});
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error sharpening image. (${err})`);
}
}
/**
* Displays the sharpened image using HTML for web apps
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default SharpenImage;

View file

@ -0,0 +1,60 @@
/**
* @author mshwed [m@ttshwed.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import GostDigest from "../vendor/gost/gostDigest";
import {toHexFast} from "../lib/Hex";
/**
* Streebog operation
*/
class Streebog extends Operation {
/**
* Streebog constructor
*/
constructor() {
super();
this.name = "Streebog";
this.module = "Hashing";
this.description = "Streebog is a cryptographic hash function defined in the Russian national standard GOST R 34.11-2012 <i>Information Technology \u2013 Cryptographic Information Security \u2013 Hash Function</i>. It was created to replace an obsolete GOST hash function defined in the old standard GOST R 34.11-94, and as an asymmetric reply to SHA-3 competition by the US National Institute of Standards and Technology.";
this.infoURL = "https://wikipedia.org/wiki/Streebog";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
"name": "Size",
"type": "option",
"value": ["256", "512"]
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
try {
const length = parseInt(args[0], 10);
const gostDigest = new GostDigest({
name: "GOST R 34.11",
version: 2012,
length: length
});
return toHexFast(gostDigest.digest(input));
} catch (err) {
throw new OperationError(err);
}
}
}
export default Streebog;

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,7 @@ class UnescapeString extends Operation {
this.name = "Unescape string";
this.module = "Default";
this.description = "Unescapes characters in a string that have been escaped. For example, <code>Don\\'t stop me now</code> becomes <code>Don't stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\n</code> (Line feed/newline)</li><li><code>\\r</code> (Carriage return)</li><li><code>\\t</code> (Horizontal tab)</li><li><code>\\b</code> (Backspace)</li><li><code>\\f</code> (Form feed)</li><li><code>\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\</code> (Backslash)</li><li><code>\\'</code> (Single quote)</li><li><code>\\&quot;</code> (Double quote)</li><li><code>\\unnnn</code> (Unicode character)</li><li><code>\\u{nnnnnn}</code> (Unicode code point)</li></ul>";
this.description = "Unescapes characters in a string that have been escaped. For example, <code>Don\\'t stop me now</code> becomes <code>Don't stop me now</code>.<br><br>Supports the following escape sequences:<ul><li><code>\\n</code> (Line feed/newline)</li><li><code>\\r</code> (Carriage return)</li><li><code>\\t</code> (Horizontal tab)</li><li><code>\\b</code> (Backspace)</li><li><code>\\f</code> (Form feed)</li><li><code>\\nnn</code> (Octal, where n is 0-7)</li><li><code>\\xnn</code> (Hex, where n is 0-f)</li><li><code>\\\\</code> (Backslash)</li><li><code>\\'</code> (Single quote)</li><li><code>\\&quot;</code> (Double quote)</li><li><code>\\unnnn</code> (Unicode character)</li><li><code>\\u{nnnnnn}</code> (Unicode code point)</li></ul>";
this.infoURL = "https://wikipedia.org/wiki/Escape_sequence";
this.inputType = "string";
this.outputType = "string";