mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-22 15:56:16 -04:00
Merge branch 'master' into module-charts
This commit is contained in:
commit
5bb8eb22ec
568 changed files with 68420 additions and 48547 deletions
126
src/core/Chef.js
126
src/core/Chef.js
|
@ -1,126 +0,0 @@
|
|||
import Dish from "./Dish.js";
|
||||
import Recipe from "./Recipe.js";
|
||||
|
||||
|
||||
/**
|
||||
* The main controller for CyberChef.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
const Chef = function() {
|
||||
this.dish = new Dish();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Runs the recipe over the input.
|
||||
*
|
||||
* @param {string} inputText - The input data as a string
|
||||
* @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
|
||||
* @param {number} progress - The position in the recipe to start from
|
||||
* @param {number} [step] - Whether to only execute one operation in the recipe
|
||||
*
|
||||
* @returns {Object} response
|
||||
* @returns {string} response.result - The output of the recipe
|
||||
* @returns {string} response.type - The data type of the result
|
||||
* @returns {number} response.progress - The position that we have got to in the recipe
|
||||
* @returns {number} response.options - The app options object (which may have been changed)
|
||||
* @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) {
|
||||
let startTime = new Date().getTime(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
containsFc = recipe.containsFlowControl(),
|
||||
error = false;
|
||||
|
||||
// Reset attemptHighlight flag
|
||||
if (options.hasOwnProperty("attemptHighlight")) {
|
||||
options.attemptHighlight = true;
|
||||
}
|
||||
|
||||
if (containsFc) options.attemptHighlight = false;
|
||||
|
||||
// Clean up progress
|
||||
if (progress >= recipeConfig.length) {
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
if (step) {
|
||||
// Unset breakpoint on this step
|
||||
recipe.setBreakpoint(progress, false);
|
||||
// Set breakpoint on next step
|
||||
recipe.setBreakpoint(progress + 1, true);
|
||||
}
|
||||
|
||||
// If stepping with flow control, we have to start from the beginning
|
||||
// but still want to skip all previous breakpoints
|
||||
if (progress > 0 && containsFc) {
|
||||
recipe.removeBreaksUpTo(progress);
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
// If starting from scratch, load data
|
||||
if (progress === 0) {
|
||||
this.dish.set(inputText, Dish.STRING);
|
||||
}
|
||||
|
||||
try {
|
||||
progress = await recipe.execute(this.dish, progress);
|
||||
} catch (err) {
|
||||
// Return the error in the result so that everything else gets correctly updated
|
||||
// rather than throwing it here and losing state info.
|
||||
error = err;
|
||||
progress = err.progress;
|
||||
}
|
||||
|
||||
return {
|
||||
result: this.dish.type === Dish.HTML ?
|
||||
this.dish.get(Dish.HTML) :
|
||||
this.dish.get(Dish.STRING),
|
||||
type: Dish.enumLookup(this.dish.type),
|
||||
progress: progress,
|
||||
options: options,
|
||||
duration: new Date().getTime() - startTime,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs,
|
||||
* it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a
|
||||
* minute, we run a silent bake which will force the browser to load and cache all the relevant
|
||||
* JavaScript code needed to do a real bake.
|
||||
*
|
||||
* This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a
|
||||
* long time and the browser has swapped out all its memory.
|
||||
*
|
||||
* The output will not be modified (hence "silent" bake).
|
||||
*
|
||||
* This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load
|
||||
* the recipe, ingredients and dish.
|
||||
*
|
||||
* @param {Object[]} recipeConfig - The recipe configuration object
|
||||
* @returns {number} The time it took to run the silent bake in milliseconds.
|
||||
*/
|
||||
Chef.prototype.silentBake = function(recipeConfig) {
|
||||
let startTime = new Date().getTime(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
dish = new Dish("", Dish.STRING);
|
||||
|
||||
try {
|
||||
recipe.execute(dish);
|
||||
} catch (err) {
|
||||
// Suppress all errors
|
||||
}
|
||||
return new Date().getTime() - startTime;
|
||||
};
|
||||
|
||||
export default Chef;
|
198
src/core/Chef.mjs
Executable file
198
src/core/Chef.mjs
Executable file
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Dish from "./Dish";
|
||||
import Recipe from "./Recipe";
|
||||
import log from "loglevel";
|
||||
|
||||
/**
|
||||
* The main controller for CyberChef.
|
||||
*/
|
||||
class Chef {
|
||||
|
||||
/**
|
||||
* Chef constructor
|
||||
*/
|
||||
constructor() {
|
||||
this.dish = new Dish();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs the recipe over the input.
|
||||
*
|
||||
* @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
|
||||
* @param {number} progress - The position in the recipe to start from
|
||||
* @param {number} [step] - Whether to only execute one operation in the recipe
|
||||
*
|
||||
* @returns {Object} response
|
||||
* @returns {string} response.result - The output of the recipe
|
||||
* @returns {string} response.type - The data type of the result
|
||||
* @returns {number} response.progress - The position that we have got to in the recipe
|
||||
* @returns {number} response.duration - The number of ms it took to execute the recipe
|
||||
* @returns {number} response.error - The error object thrown by a failed operation (false if no error)
|
||||
*/
|
||||
async bake(input, recipeConfig, options, progress, step) {
|
||||
log.debug("Chef baking");
|
||||
const startTime = new Date().getTime(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
containsFc = recipe.containsFlowControl(),
|
||||
notUTF8 = options && options.hasOwnProperty("treatAsUtf8") && !options.treatAsUtf8;
|
||||
let error = false;
|
||||
|
||||
if (containsFc && ENVIRONMENT_IS_WORKER()) self.setOption("attemptHighlight", false);
|
||||
|
||||
// Clean up progress
|
||||
if (progress >= recipeConfig.length) {
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
if (step) {
|
||||
// Unset breakpoint on this step
|
||||
recipe.setBreakpoint(progress, false);
|
||||
// Set breakpoint on next step
|
||||
recipe.setBreakpoint(progress + 1, true);
|
||||
}
|
||||
|
||||
// If the previously run operation presented a different value to its
|
||||
// normal output, we need to recalculate it.
|
||||
if (recipe.lastOpPresented(progress)) {
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
// If stepping with flow control, we have to start from the beginning
|
||||
// but still want to skip all previous breakpoints
|
||||
if (progress > 0 && containsFc) {
|
||||
recipe.removeBreaksUpTo(progress);
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
// If starting from scratch, load data
|
||||
if (progress === 0) {
|
||||
const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
|
||||
this.dish.set(input, type);
|
||||
}
|
||||
|
||||
try {
|
||||
progress = await recipe.execute(this.dish, progress);
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
error = {
|
||||
displayStr: err.displayStr,
|
||||
};
|
||||
progress = err.progress;
|
||||
}
|
||||
|
||||
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
|
||||
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
|
||||
// The threshold is specified in KiB.
|
||||
const threshold = (options.ioDisplayThreshold || 1024) * 1024;
|
||||
const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
|
||||
|
||||
// Create a raw version of the dish, unpresented
|
||||
const rawDish = this.dish.clone();
|
||||
|
||||
// Present the raw result
|
||||
await recipe.present(this.dish);
|
||||
|
||||
return {
|
||||
dish: rawDish,
|
||||
result: this.dish.type === Dish.HTML ?
|
||||
await this.dish.get(Dish.HTML, notUTF8) :
|
||||
await this.dish.get(returnType, notUTF8),
|
||||
type: Dish.enumLookup(this.dish.type),
|
||||
progress: progress,
|
||||
duration: new Date().getTime() - startTime,
|
||||
error: error
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When a browser tab is unfocused and the browser has to run lots of dynamic content in other tabs,
|
||||
* it swaps out the memory for that tab. If the CyberChef tab has been unfocused for more than a
|
||||
* minute, we run a silent bake which will force the browser to load and cache all the relevant
|
||||
* JavaScript code needed to do a real bake.
|
||||
*
|
||||
* This will stop baking taking a long time when the CyberChef browser tab has been unfocused for a
|
||||
* long time and the browser has swapped out all its memory.
|
||||
*
|
||||
* The output will not be modified (hence "silent" bake).
|
||||
*
|
||||
* This will only actually execute the recipe if auto-bake is enabled, otherwise it will just load
|
||||
* the recipe, ingredients and dish.
|
||||
*
|
||||
* @param {Object[]} recipeConfig - The recipe configuration object
|
||||
* @returns {number} The time it took to run the silent bake in milliseconds.
|
||||
*/
|
||||
silentBake(recipeConfig) {
|
||||
log.debug("Running silent bake");
|
||||
|
||||
const startTime = new Date().getTime(),
|
||||
recipe = new Recipe(recipeConfig),
|
||||
dish = new Dish();
|
||||
|
||||
try {
|
||||
recipe.execute(dish);
|
||||
} catch (err) {
|
||||
// Suppress all errors
|
||||
}
|
||||
return new Date().getTime() - startTime;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates highlight offsets if possible.
|
||||
*
|
||||
* @param {Object[]} recipeConfig
|
||||
* @param {string} direction
|
||||
* @param {Object} pos - The position object for the highlight.
|
||||
* @param {number} pos.start - The start offset.
|
||||
* @param {number} pos.end - The end offset.
|
||||
* @returns {Object}
|
||||
*/
|
||||
async calculateHighlights(recipeConfig, direction, pos) {
|
||||
const recipe = new Recipe(recipeConfig);
|
||||
const highlights = await recipe.generateHighlightList();
|
||||
|
||||
if (!highlights) return false;
|
||||
|
||||
for (let i = 0; i < highlights.length; i++) {
|
||||
// Remove multiple highlights before processing again
|
||||
pos = [pos[0]];
|
||||
|
||||
const func = direction === "forward" ? highlights[i].f : highlights[i].b;
|
||||
|
||||
if (typeof func == "function") {
|
||||
pos = func(pos, highlights[i].args);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pos: pos,
|
||||
direction: direction
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translates the dish to a specified type and returns it.
|
||||
*
|
||||
* @param {Dish} dish
|
||||
* @param {string} type
|
||||
* @returns {Dish}
|
||||
*/
|
||||
async getDishAs(dish, type) {
|
||||
const newDish = new Dish(dish);
|
||||
return await newDish.get(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Chef;
|
235
src/core/ChefWorker.js
Normal file
235
src/core/ChefWorker.js
Normal file
|
@ -0,0 +1,235 @@
|
|||
/**
|
||||
* Web Worker to handle communications between the front-end and the core.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import "babel-polyfill";
|
||||
import Chef from "./Chef";
|
||||
import OperationConfig from "./config/OperationConfig.json";
|
||||
import OpModules from "./config/modules/OpModules";
|
||||
|
||||
// Add ">" to the start of all log messages in the Chef Worker
|
||||
import loglevelMessagePrefix from "loglevel-message-prefix";
|
||||
|
||||
loglevelMessagePrefix(log, {
|
||||
prefixes: [],
|
||||
staticPrefixes: [">"],
|
||||
prefixFormat: "%p"
|
||||
});
|
||||
|
||||
|
||||
// Set up Chef instance
|
||||
self.chef = new Chef();
|
||||
|
||||
self.OpModules = OpModules;
|
||||
self.OperationConfig = OperationConfig;
|
||||
|
||||
// Tell the app that the worker has loaded and is ready to operate
|
||||
self.postMessage({
|
||||
action: "workerLoaded",
|
||||
data: {}
|
||||
});
|
||||
|
||||
/**
|
||||
* Respond to message from parent thread.
|
||||
*
|
||||
* Messages should have the following format:
|
||||
* {
|
||||
* action: "bake" | "silentBake",
|
||||
* data: {
|
||||
* input: {string},
|
||||
* recipeConfig: {[Object]},
|
||||
* options: {Object},
|
||||
* progress: {number},
|
||||
* step: {boolean}
|
||||
* } | undefined
|
||||
* }
|
||||
*/
|
||||
self.addEventListener("message", function(e) {
|
||||
// Handle message
|
||||
const r = e.data;
|
||||
log.debug("ChefWorker receiving command '" + r.action + "'");
|
||||
|
||||
switch (r.action) {
|
||||
case "bake":
|
||||
bake(r.data);
|
||||
break;
|
||||
case "silentBake":
|
||||
silentBake(r.data);
|
||||
break;
|
||||
case "getDishAs":
|
||||
getDishAs(r.data);
|
||||
break;
|
||||
case "docURL":
|
||||
// Used to set the URL of the current document so that scripts can be
|
||||
// imported into an inline worker.
|
||||
self.docURL = r.data;
|
||||
break;
|
||||
case "highlight":
|
||||
calculateHighlights(
|
||||
r.data.recipeConfig,
|
||||
r.data.direction,
|
||||
r.data.pos
|
||||
);
|
||||
break;
|
||||
case "setLogLevel":
|
||||
log.setLevel(r.data, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Baking handler
|
||||
*
|
||||
* @param {Object} data
|
||||
*/
|
||||
async function bake(data) {
|
||||
// Ensure the relevant modules are loaded
|
||||
self.loadRequiredModules(data.recipeConfig);
|
||||
|
||||
try {
|
||||
const response = await self.chef.bake(
|
||||
data.input, // The user's input
|
||||
data.recipeConfig, // The configuration of the recipe
|
||||
data.options, // Options set by the user
|
||||
data.progress, // The current position in the recipe
|
||||
data.step // Whether or not to take one step or execute the whole recipe
|
||||
);
|
||||
|
||||
self.postMessage({
|
||||
action: "bakeComplete",
|
||||
data: Object.assign(response, {
|
||||
id: data.id
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
self.postMessage({
|
||||
action: "bakeError",
|
||||
data: Object.assign(err, {
|
||||
id: data.id
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Silent baking handler
|
||||
*/
|
||||
function silentBake(data) {
|
||||
const duration = self.chef.silentBake(data.recipeConfig);
|
||||
|
||||
self.postMessage({
|
||||
action: "silentBakeComplete",
|
||||
data: duration
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translates the dish to a given type.
|
||||
*/
|
||||
async function getDishAs(data) {
|
||||
const value = await self.chef.getDishAs(data.dish, data.type);
|
||||
|
||||
self.postMessage({
|
||||
action: "dishReturned",
|
||||
data: {
|
||||
value: value,
|
||||
id: data.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates highlight offsets if possible.
|
||||
*
|
||||
* @param {Object[]} recipeConfig
|
||||
* @param {string} direction
|
||||
* @param {Object} pos - The position object for the highlight.
|
||||
* @param {number} pos.start - The start offset.
|
||||
* @param {number} pos.end - The end offset.
|
||||
*/
|
||||
async function calculateHighlights(recipeConfig, direction, pos) {
|
||||
pos = await self.chef.calculateHighlights(recipeConfig, direction, pos);
|
||||
|
||||
self.postMessage({
|
||||
action: "highlightsCalculated",
|
||||
data: pos
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks that all required modules are loaded and loads them if not.
|
||||
*
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
self.loadRequiredModules = function(recipeConfig) {
|
||||
recipeConfig.forEach(op => {
|
||||
const module = self.OperationConfig[op.op].module;
|
||||
|
||||
if (!OpModules.hasOwnProperty(module)) {
|
||||
log.info(`Loading ${module} module`);
|
||||
self.sendStatusMessage(`Loading ${module} module`);
|
||||
self.importScripts(`${self.docURL}/${module}.js`);
|
||||
self.sendStatusMessage("");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send status update to the app.
|
||||
*
|
||||
* @param {string} msg
|
||||
*/
|
||||
self.sendStatusMessage = function(msg) {
|
||||
self.postMessage({
|
||||
action: "statusMessage",
|
||||
data: msg
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send an option value update to the app.
|
||||
*
|
||||
* @param {string} option
|
||||
* @param {*} value
|
||||
*/
|
||||
self.setOption = function(option, value) {
|
||||
self.postMessage({
|
||||
action: "optionUpdate",
|
||||
data: {
|
||||
option: option,
|
||||
value: value
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send register values back to the app.
|
||||
*
|
||||
* @param {number} opIndex
|
||||
* @param {number} numPrevRegisters
|
||||
* @param {string[]} registers
|
||||
*/
|
||||
self.setRegisters = function(opIndex, numPrevRegisters, registers) {
|
||||
self.postMessage({
|
||||
action: "setRegisters",
|
||||
data: {
|
||||
opIndex: opIndex,
|
||||
numPrevRegisters: numPrevRegisters,
|
||||
registers: registers
|
||||
}
|
||||
});
|
||||
};
|
206
src/core/Dish.js
206
src/core/Dish.js
|
@ -1,206 +0,0 @@
|
|||
import Utils from "./Utils.js";
|
||||
|
||||
/**
|
||||
* The data being operated on by each operation.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
* @param {byteArray|string|number} 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.type = type || Dish.BYTE_ARRAY;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dish data type enum for byte arrays.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.BYTE_ARRAY = 0;
|
||||
/**
|
||||
* Dish data type enum for strings.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.STRING = 1;
|
||||
/**
|
||||
* Dish data type enum for numbers.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.NUMBER = 2;
|
||||
/**
|
||||
* Dish data type enum for HTML.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.HTML = 3;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data type enum for the given type string.
|
||||
*
|
||||
* @static
|
||||
* @param {string} typeStr - The name of the data type.
|
||||
* @returns {number} The data type enum value.
|
||||
*/
|
||||
Dish.typeEnum = function(typeStr) {
|
||||
switch (typeStr) {
|
||||
case "byteArray":
|
||||
case "Byte array":
|
||||
return Dish.BYTE_ARRAY;
|
||||
case "string":
|
||||
case "String":
|
||||
return Dish.STRING;
|
||||
case "number":
|
||||
case "Number":
|
||||
return Dish.NUMBER;
|
||||
case "html":
|
||||
case "HTML":
|
||||
return Dish.HTML;
|
||||
default:
|
||||
throw "Invalid data type string. No matching enum.";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data type string for the given type enum.
|
||||
*
|
||||
* @static
|
||||
* @param {string} typeEnum - The enum value of the data type.
|
||||
* @returns {number} The data type as a string.
|
||||
*/
|
||||
Dish.enumLookup = function(typeEnum) {
|
||||
switch (typeEnum) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
return "byteArray";
|
||||
case Dish.STRING:
|
||||
return "string";
|
||||
case Dish.NUMBER:
|
||||
return "number";
|
||||
case Dish.HTML:
|
||||
return "html";
|
||||
default:
|
||||
throw "Invalid data type enum. No matching type.";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the data value and type and then validates them.
|
||||
*
|
||||
* @param {byteArray|string|number} value - The value of the input data.
|
||||
* @param {number} type - The data type of value, see Dish enums.
|
||||
*/
|
||||
Dish.prototype.set = function(value, type) {
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
|
||||
if (!this.valid()) {
|
||||
const sample = Utils.truncate(JSON.stringify(this.value), 13);
|
||||
throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
Dish.prototype.get = function(type) {
|
||||
if (this.type !== type) {
|
||||
this.translate(type);
|
||||
}
|
||||
return this.value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Translates the data to the given type format.
|
||||
*
|
||||
* @param {number} toType - The data type of value, see Dish enums.
|
||||
*/
|
||||
Dish.prototype.translate = function(toType) {
|
||||
// Convert data to intermediate byteArray type
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert from byteArray to toType
|
||||
switch (toType) {
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
this.value = this.value ? Utils.byteArrayToUtf8(this.value) : "";
|
||||
this.type = Dish.STRING;
|
||||
break;
|
||||
case Dish.NUMBER:
|
||||
this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0;
|
||||
this.type = Dish.NUMBER;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the value is the type that has been specified.
|
||||
* May have to disable parts of BYTE_ARRAY validation if it effects performance.
|
||||
*
|
||||
* @returns {boolean} Whether the data is valid or not.
|
||||
*/
|
||||
Dish.prototype.valid = function() {
|
||||
switch (this.type) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
if (!(this.value instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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" ||
|
||||
this.value[i] < 0 ||
|
||||
this.value[i] > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
if (typeof this.value == "string") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case Dish.NUMBER:
|
||||
if (typeof this.value == "number") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default Dish;
|
434
src/core/Dish.mjs
Executable file
434
src/core/Dish.mjs
Executable file
|
@ -0,0 +1,434 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "./Utils";
|
||||
import DishError from "./errors/DishError";
|
||||
import BigNumber from "bignumber.js";
|
||||
import log from "loglevel";
|
||||
|
||||
/**
|
||||
* The data being operated on by each operation.
|
||||
*/
|
||||
class Dish {
|
||||
|
||||
/**
|
||||
* Dish constructor
|
||||
*
|
||||
* @param {Dish} [dish=null] - A dish to clone
|
||||
*/
|
||||
constructor(dish=null) {
|
||||
this.value = [];
|
||||
this.type = Dish.BYTE_ARRAY;
|
||||
|
||||
if (dish &&
|
||||
dish.hasOwnProperty("value") &&
|
||||
dish.hasOwnProperty("type")) {
|
||||
this.set(dish.value, dish.type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data type enum for the given type string.
|
||||
*
|
||||
* @param {string} typeStr - The name of the data type.
|
||||
* @returns {number} The data type enum value.
|
||||
*/
|
||||
static typeEnum(typeStr) {
|
||||
switch (typeStr.toLowerCase()) {
|
||||
case "bytearray":
|
||||
case "byte array":
|
||||
return Dish.BYTE_ARRAY;
|
||||
case "string":
|
||||
return Dish.STRING;
|
||||
case "number":
|
||||
return Dish.NUMBER;
|
||||
case "html":
|
||||
return Dish.HTML;
|
||||
case "arraybuffer":
|
||||
case "array buffer":
|
||||
return Dish.ARRAY_BUFFER;
|
||||
case "bignumber":
|
||||
case "big number":
|
||||
return Dish.BIG_NUMBER;
|
||||
case "json":
|
||||
return Dish.JSON;
|
||||
case "file":
|
||||
return Dish.FILE;
|
||||
case "list<file>":
|
||||
return Dish.LIST_FILE;
|
||||
default:
|
||||
throw new DishError("Invalid data type string. No matching enum.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data type string for the given type enum.
|
||||
*
|
||||
* @param {number} typeEnum - The enum value of the data type.
|
||||
* @returns {string} The data type as a string.
|
||||
*/
|
||||
static enumLookup(typeEnum) {
|
||||
switch (typeEnum) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
return "byteArray";
|
||||
case Dish.STRING:
|
||||
return "string";
|
||||
case Dish.NUMBER:
|
||||
return "number";
|
||||
case Dish.HTML:
|
||||
return "html";
|
||||
case Dish.ARRAY_BUFFER:
|
||||
return "ArrayBuffer";
|
||||
case Dish.BIG_NUMBER:
|
||||
return "BigNumber";
|
||||
case Dish.JSON:
|
||||
return "JSON";
|
||||
case Dish.FILE:
|
||||
return "File";
|
||||
case Dish.LIST_FILE:
|
||||
return "List<File>";
|
||||
default:
|
||||
throw new DishError("Invalid data type enum. No matching type.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the data value and type and then validates them.
|
||||
*
|
||||
* @param {*} value
|
||||
* - The value of the input data.
|
||||
* @param {number} type
|
||||
* - The data type of value, see Dish enums.
|
||||
*/
|
||||
set(value, type) {
|
||||
if (typeof type === "string") {
|
||||
type = Dish.typeEnum(type);
|
||||
}
|
||||
|
||||
log.debug("Dish type: " + Dish.enumLookup(type));
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
|
||||
if (!this.valid()) {
|
||||
const sample = Utils.truncate(JSON.stringify(this.value), 13);
|
||||
throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the data in the type format specified.
|
||||
*
|
||||
* @param {number} type - The data type of value, see Dish enums.
|
||||
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
|
||||
* @returns {*} - The value of the output data.
|
||||
*/
|
||||
async get(type, notUTF8=false) {
|
||||
if (typeof type === "string") {
|
||||
type = Dish.typeEnum(type);
|
||||
}
|
||||
if (this.type !== type) {
|
||||
await this._translate(type, notUTF8);
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translates the data to the given type format.
|
||||
*
|
||||
* @param {number} toType - The data type of value, see Dish enums.
|
||||
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
|
||||
*/
|
||||
async _translate(toType, notUTF8=false) {
|
||||
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
|
||||
const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8;
|
||||
|
||||
// Convert data to intermediate byteArray type
|
||||
try {
|
||||
switch (this.type) {
|
||||
case Dish.STRING:
|
||||
this.value = this.value ? Utils.strToByteArray(this.value) : [];
|
||||
break;
|
||||
case Dish.NUMBER:
|
||||
this.value = typeof this.value === "number" ? Utils.strToByteArray(this.value.toString()) : [];
|
||||
break;
|
||||
case Dish.HTML:
|
||||
this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : [];
|
||||
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;
|
||||
case Dish.BIG_NUMBER:
|
||||
this.value = BigNumber.isBigNumber(this.value) ? Utils.strToByteArray(this.value.toFixed()) : [];
|
||||
break;
|
||||
case Dish.JSON:
|
||||
this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value, null, 4)) : [];
|
||||
break;
|
||||
case Dish.FILE:
|
||||
this.value = await Utils.readFile(this.value);
|
||||
this.value = Array.prototype.slice.call(this.value);
|
||||
break;
|
||||
case Dish.LIST_FILE:
|
||||
this.value = await Promise.all(this.value.map(async f => Utils.readFile(f)));
|
||||
this.value = this.value.map(b => Array.prototype.slice.call(b));
|
||||
this.value = [].concat.apply([], this.value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to byteArray: ${err}`);
|
||||
}
|
||||
|
||||
this.type = Dish.BYTE_ARRAY;
|
||||
|
||||
// Convert from byteArray to toType
|
||||
try {
|
||||
switch (toType) {
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
this.value = this.value ? byteArrayToStr(this.value) : "";
|
||||
this.type = Dish.STRING;
|
||||
break;
|
||||
case Dish.NUMBER:
|
||||
this.value = this.value ? parseFloat(byteArrayToStr(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;
|
||||
case Dish.BIG_NUMBER:
|
||||
try {
|
||||
this.value = new BigNumber(byteArrayToStr(this.value));
|
||||
} catch (err) {
|
||||
this.value = new BigNumber(NaN);
|
||||
}
|
||||
this.type = Dish.BIG_NUMBER;
|
||||
break;
|
||||
case Dish.JSON:
|
||||
this.value = JSON.parse(byteArrayToStr(this.value));
|
||||
this.type = Dish.JSON;
|
||||
break;
|
||||
case Dish.FILE:
|
||||
this.value = new File(this.value, "unknown");
|
||||
break;
|
||||
case Dish.LIST_FILE:
|
||||
this.value = [new File(this.value, "unknown")];
|
||||
this.type = Dish.LIST_FILE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new DishError(`Error translating from byteArray to ${Dish.enumLookup(toType)}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the value is the type that has been specified.
|
||||
* May have to disable parts of BYTE_ARRAY validation if it effects performance.
|
||||
*
|
||||
* @returns {boolean} Whether the data is valid or not.
|
||||
*/
|
||||
valid() {
|
||||
switch (this.type) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
if (!(this.value instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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" ||
|
||||
this.value[i] < 0 ||
|
||||
this.value[i] > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
return typeof this.value === "string";
|
||||
case Dish.NUMBER:
|
||||
return typeof this.value === "number";
|
||||
case Dish.ARRAY_BUFFER:
|
||||
return this.value instanceof ArrayBuffer;
|
||||
case Dish.BIG_NUMBER:
|
||||
return BigNumber.isBigNumber(this.value);
|
||||
case Dish.JSON:
|
||||
// All values can be serialised in some manner, so we return true in all cases
|
||||
return true;
|
||||
case Dish.FILE:
|
||||
return this.value instanceof File;
|
||||
case Dish.LIST_FILE:
|
||||
return this.value instanceof Array &&
|
||||
this.value.reduce((acc, curr) => acc && curr instanceof File, true);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines how much space the Dish takes up.
|
||||
* Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish,
|
||||
* we measure how many bytes are taken up when the number is written as a string.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
get size() {
|
||||
switch (this.type) {
|
||||
case Dish.BYTE_ARRAY:
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
return this.value.length;
|
||||
case Dish.NUMBER:
|
||||
case Dish.BIG_NUMBER:
|
||||
return this.value.toString().length;
|
||||
case Dish.ARRAY_BUFFER:
|
||||
return this.value.byteLength;
|
||||
case Dish.JSON:
|
||||
return JSON.stringify(this.value).length;
|
||||
case Dish.FILE:
|
||||
return this.value.size;
|
||||
case Dish.LIST_FILE:
|
||||
return this.value.reduce((acc, curr) => acc + curr.size, 0);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a deep clone of the current Dish.
|
||||
*
|
||||
* @returns {Dish}
|
||||
*/
|
||||
clone() {
|
||||
const newDish = new Dish();
|
||||
|
||||
switch (this.type) {
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
case Dish.NUMBER:
|
||||
case Dish.BIG_NUMBER:
|
||||
// These data types are immutable so it is acceptable to copy them by reference
|
||||
newDish.set(
|
||||
this.value,
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
case Dish.BYTE_ARRAY:
|
||||
case Dish.JSON:
|
||||
// These data types are mutable so they need to be copied by value
|
||||
newDish.set(
|
||||
JSON.parse(JSON.stringify(this.value)),
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
case Dish.ARRAY_BUFFER:
|
||||
// Slicing an ArrayBuffer returns a new ArrayBuffer with a copy its contents
|
||||
newDish.set(
|
||||
this.value.slice(0),
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
case Dish.FILE:
|
||||
// A new file can be created by copying over all the values from the original
|
||||
newDish.set(
|
||||
new File([this.value], this.value.name, {
|
||||
"type": this.value.type,
|
||||
"lastModified": this.value.lastModified
|
||||
}),
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
case Dish.LIST_FILE:
|
||||
newDish.set(
|
||||
this.value.map(f =>
|
||||
new File([f], f.name, {
|
||||
"type": f.type,
|
||||
"lastModified": f.lastModified
|
||||
})
|
||||
),
|
||||
this.type
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new DishError("Cannot clone Dish, unknown type");
|
||||
}
|
||||
|
||||
return newDish;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dish data type enum for byte arrays.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.BYTE_ARRAY = 0;
|
||||
/**
|
||||
* Dish data type enum for strings.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.STRING = 1;
|
||||
/**
|
||||
* Dish data type enum for numbers.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.NUMBER = 2;
|
||||
/**
|
||||
* Dish data type enum for HTML.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.HTML = 3;
|
||||
/**
|
||||
* Dish data type enum for ArrayBuffers.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.ARRAY_BUFFER = 4;
|
||||
/**
|
||||
* Dish data type enum for BigNumbers.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.BIG_NUMBER = 5;
|
||||
/**
|
||||
* Dish data type enum for JSON.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.JSON = 6;
|
||||
/**
|
||||
* Dish data type enum for lists of files.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.FILE = 7;
|
||||
/**
|
||||
* Dish data type enum for lists of files.
|
||||
* @readonly
|
||||
* @enum
|
||||
*/
|
||||
Dish.LIST_FILE = 8;
|
||||
|
||||
|
||||
export default Dish;
|
|
@ -1,213 +0,0 @@
|
|||
import Recipe from "./Recipe.js";
|
||||
import Dish from "./Dish.js";
|
||||
|
||||
|
||||
/**
|
||||
* Flow Control operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const FlowControl = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
FORK_DELIM: "\\n",
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
MERGE_DELIM: "\\n",
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
FORK_IGNORE_ERRORS: false,
|
||||
|
||||
/**
|
||||
* Fork operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runFork: async function(state) {
|
||||
let opList = state.opList,
|
||||
inputType = opList[state.progress].inputType,
|
||||
outputType = opList[state.progress].outputType,
|
||||
input = state.dish.get(inputType),
|
||||
ings = opList[state.progress].getIngValues(),
|
||||
splitDelim = ings[0],
|
||||
mergeDelim = ings[1],
|
||||
ignoreErrors = ings[2],
|
||||
subOpList = [],
|
||||
inputs = [],
|
||||
i;
|
||||
|
||||
if (input)
|
||||
inputs = input.split(splitDelim);
|
||||
|
||||
// Create subOpList for each tranche to operate on
|
||||
// (all remaining operations unless we encounter a Merge)
|
||||
for (i = state.progress + 1; i < opList.length; i++) {
|
||||
if (opList[i].name === "Merge" && !opList[i].isDisabled()) {
|
||||
break;
|
||||
} else {
|
||||
subOpList.push(opList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let recipe = new Recipe(),
|
||||
output = "",
|
||||
progress = 0;
|
||||
|
||||
recipe.addOperations(subOpList);
|
||||
|
||||
// Run recipe over each tranche
|
||||
for (i = 0; i < inputs.length; i++) {
|
||||
const dish = new Dish(inputs[i], inputType);
|
||||
try {
|
||||
progress = await recipe.execute(dish, 0);
|
||||
} catch (err) {
|
||||
if (!ignoreErrors) {
|
||||
throw err;
|
||||
}
|
||||
progress = err.progress + 1;
|
||||
}
|
||||
output += dish.get(outputType) + mergeDelim;
|
||||
}
|
||||
|
||||
state.dish.set(output, outputType);
|
||||
state.progress += progress;
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Merge operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runMerge: function(state) {
|
||||
// No need to actually do anything here. The fork operation will
|
||||
// merge when it sees this operation.
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
JUMP_NUM: 0,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
MAX_JUMPS: 10,
|
||||
|
||||
/**
|
||||
* Jump operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @param {number} state.numJumps - The number of jumps taken so far.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runJump: function(state) {
|
||||
let ings = state.opList[state.progress].getIngValues(),
|
||||
jumpNum = ings[0],
|
||||
maxJumps = ings[1];
|
||||
|
||||
if (jumpNum < 0) {
|
||||
jumpNum--;
|
||||
}
|
||||
|
||||
if (state.numJumps >= maxJumps) {
|
||||
return state;
|
||||
}
|
||||
|
||||
state.progress += jumpNum;
|
||||
state.numJumps++;
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Conditional Jump operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @param {number} state.numJumps - The number of jumps taken so far.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runCondJump: function(state) {
|
||||
let ings = state.opList[state.progress].getIngValues(),
|
||||
dish = state.dish,
|
||||
regexStr = ings[0],
|
||||
jumpNum = ings[1],
|
||||
maxJumps = ings[2];
|
||||
|
||||
if (jumpNum < 0) {
|
||||
jumpNum--;
|
||||
}
|
||||
|
||||
if (state.numJumps >= maxJumps) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (regexStr !== "" && dish.get(Dish.STRING).search(regexStr) > -1) {
|
||||
state.progress += jumpNum;
|
||||
state.numJumps++;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Return operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runReturn: function(state) {
|
||||
state.progress = state.opList.length;
|
||||
return state;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Comment operation.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
runComment: function(state) {
|
||||
return state;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default FlowControl;
|
|
@ -1,92 +0,0 @@
|
|||
import Utils from "./Utils.js";
|
||||
|
||||
|
||||
/**
|
||||
* The arguments to operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
* @param {Object} ingredientConfig
|
||||
*/
|
||||
const Ingredient = function(ingredientConfig) {
|
||||
this.name = "";
|
||||
this.type = "";
|
||||
this.value = null;
|
||||
|
||||
if (ingredientConfig) {
|
||||
this._parseConfig(ingredientConfig);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} ingredientConfig
|
||||
*/
|
||||
Ingredient.prototype._parseConfig = function(ingredientConfig) {
|
||||
this.name = ingredientConfig.name;
|
||||
this.type = ingredientConfig.type;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Ingredient as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
Ingredient.prototype.getConfig = function() {
|
||||
return this.value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of the Ingredient.
|
||||
*
|
||||
* @param {*} value
|
||||
*/
|
||||
Ingredient.prototype.setValue = function(value) {
|
||||
this.value = Ingredient.prepare(value, this.type);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Most values will be strings when they are entered. This function converts them to the correct
|
||||
* type.
|
||||
*
|
||||
* @static
|
||||
* @param {*} data
|
||||
* @param {string} type - The name of the data type.
|
||||
*/
|
||||
Ingredient.prepare = function(data, type) {
|
||||
let number;
|
||||
|
||||
switch (type) {
|
||||
case "binaryString":
|
||||
case "binaryShortString":
|
||||
case "editableOption":
|
||||
return Utils.parseEscapedChars(data);
|
||||
case "byteArray":
|
||||
if (typeof data == "string") {
|
||||
data = data.replace(/\s+/g, "");
|
||||
return Utils.hexToByteArray(data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
case "number":
|
||||
number = parseFloat(data);
|
||||
if (isNaN(number)) {
|
||||
const sample = Utils.truncate(data.toString(), 10);
|
||||
throw "Invalid ingredient value. Not a number: " + sample;
|
||||
}
|
||||
return number;
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export default Ingredient;
|
123
src/core/Ingredient.mjs
Executable file
123
src/core/Ingredient.mjs
Executable file
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "./Utils";
|
||||
import {fromHex} from "./lib/Hex";
|
||||
|
||||
/**
|
||||
* The arguments to operations.
|
||||
*/
|
||||
class Ingredient {
|
||||
|
||||
/**
|
||||
* Ingredient constructor
|
||||
*
|
||||
* @param {Object} ingredientConfig
|
||||
*/
|
||||
constructor(ingredientConfig) {
|
||||
this.name = "";
|
||||
this.type = "";
|
||||
this._value = null;
|
||||
this.disabled = false;
|
||||
this.hint = "";
|
||||
this.rows = 0;
|
||||
this.toggleValues = [];
|
||||
this.target = null;
|
||||
this.defaultIndex = 0;
|
||||
|
||||
if (ingredientConfig) {
|
||||
this._parseConfig(ingredientConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} ingredientConfig
|
||||
*/
|
||||
_parseConfig(ingredientConfig) {
|
||||
this.name = ingredientConfig.name;
|
||||
this.type = ingredientConfig.type;
|
||||
this.defaultValue = ingredientConfig.value;
|
||||
this.disabled = !!ingredientConfig.disabled;
|
||||
this.hint = ingredientConfig.hint || false;
|
||||
this.rows = ingredientConfig.rows || false;
|
||||
this.toggleValues = ingredientConfig.toggleValues;
|
||||
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
|
||||
this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Ingredient as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
get config() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of the Ingredient.
|
||||
*
|
||||
* @param {*} value
|
||||
*/
|
||||
set value(value) {
|
||||
this._value = Ingredient.prepare(value, this.type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of the Ingredient.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Most values will be strings when they are entered. This function converts them to the correct
|
||||
* type.
|
||||
*
|
||||
* @param {*} data
|
||||
* @param {string} type - The name of the data type.
|
||||
*/
|
||||
static prepare(data, type) {
|
||||
let number;
|
||||
|
||||
switch (type) {
|
||||
case "binaryString":
|
||||
case "binaryShortString":
|
||||
case "editableOption":
|
||||
case "editableOptionShort":
|
||||
return Utils.parseEscapedChars(data);
|
||||
case "byteArray":
|
||||
if (typeof data == "string") {
|
||||
data = data.replace(/\s+/g, "");
|
||||
return fromHex(data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
case "number":
|
||||
number = parseFloat(data);
|
||||
if (isNaN(number)) {
|
||||
const sample = Utils.truncate(data.toString(), 10);
|
||||
throw "Invalid ingredient value. Not a number: " + sample;
|
||||
}
|
||||
return number;
|
||||
default:
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Ingredient;
|
|
@ -1,163 +0,0 @@
|
|||
import Dish from "./Dish.js";
|
||||
import Ingredient from "./Ingredient.js";
|
||||
|
||||
|
||||
/**
|
||||
* The Operation specified by the user to be run.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
* @param {string} operationName
|
||||
* @param {Object} operationConfig
|
||||
*/
|
||||
const Operation = function(operationName, operationConfig) {
|
||||
this.name = operationName;
|
||||
this.description = "";
|
||||
this.inputType = -1;
|
||||
this.outputType = -1;
|
||||
this.run = null;
|
||||
this.highlight = null;
|
||||
this.highlightReverse = null;
|
||||
this.breakpoint = false;
|
||||
this.disabled = false;
|
||||
this.ingList = [];
|
||||
|
||||
if (operationConfig) {
|
||||
this._parseConfig(operationConfig);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} operationConfig
|
||||
*/
|
||||
Operation.prototype._parseConfig = function(operationConfig) {
|
||||
this.description = operationConfig.description;
|
||||
this.inputType = Dish.typeEnum(operationConfig.inputType);
|
||||
this.outputType = Dish.typeEnum(operationConfig.outputType);
|
||||
this.run = operationConfig.run;
|
||||
this.highlight = operationConfig.highlight;
|
||||
this.highlightReverse = operationConfig.highlightReverse;
|
||||
this.flowControl = operationConfig.flowControl;
|
||||
|
||||
for (let a = 0; a < operationConfig.args.length; a++) {
|
||||
const ingredientConfig = operationConfig.args[a];
|
||||
const ingredient = new Ingredient(ingredientConfig);
|
||||
this.addIngredient(ingredient);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Operation as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
Operation.prototype.getConfig = function() {
|
||||
const ingredientConfig = [];
|
||||
|
||||
for (let o = 0; o < this.ingList.length; o++) {
|
||||
ingredientConfig.push(this.ingList[o].getConfig());
|
||||
}
|
||||
|
||||
const operationConfig = {
|
||||
"op": this.name,
|
||||
"args": ingredientConfig
|
||||
};
|
||||
|
||||
return operationConfig;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new Ingredient to this Operation.
|
||||
*
|
||||
* @param {Ingredient} ingredient
|
||||
*/
|
||||
Operation.prototype.addIngredient = function(ingredient) {
|
||||
this.ingList.push(ingredient);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the Ingredient values for this Operation.
|
||||
*
|
||||
* @param {Object[]} ingValues
|
||||
*/
|
||||
Operation.prototype.setIngValues = function(ingValues) {
|
||||
for (let i = 0; i < ingValues.length; i++) {
|
||||
this.ingList[i].setValue(ingValues[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the Ingredient values for this Operation.
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
Operation.prototype.getIngValues = function() {
|
||||
const ingValues = [];
|
||||
for (let i = 0; i < this.ingList.length; i++) {
|
||||
ingValues.push(this.ingList[i].value);
|
||||
}
|
||||
return ingValues;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation has a breakpoint.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
Operation.prototype.setBreakpoint = function(value) {
|
||||
this.breakpoint = !!value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation has a breakpoint set.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Operation.prototype.isBreakpoint = function() {
|
||||
return this.breakpoint;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation is disabled.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
Operation.prototype.setDisabled = function(value) {
|
||||
this.disabled = !!value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation is disabled.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Operation.prototype.isDisabled = function() {
|
||||
return this.disabled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation is a flow control.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Operation.prototype.isFlowControl = function() {
|
||||
return this.flowControl;
|
||||
};
|
||||
|
||||
export default Operation;
|
318
src/core/Operation.mjs
Executable file
318
src/core/Operation.mjs
Executable file
|
@ -0,0 +1,318 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Dish from "./Dish";
|
||||
import Ingredient from "./Ingredient";
|
||||
|
||||
/**
|
||||
* The Operation specified by the user to be run.
|
||||
*/
|
||||
class Operation {
|
||||
|
||||
/**
|
||||
* Operation constructor
|
||||
*/
|
||||
constructor() {
|
||||
// Private fields
|
||||
this._inputType = -1;
|
||||
this._outputType = -1;
|
||||
this._presentType = -1;
|
||||
this._breakpoint = false;
|
||||
this._disabled = false;
|
||||
this._flowControl = false;
|
||||
this._manualBake = false;
|
||||
this._ingList = [];
|
||||
|
||||
// Public fields
|
||||
this.name = "";
|
||||
this.module = "";
|
||||
this.description = "";
|
||||
this.infoURL = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface for operation runner
|
||||
*
|
||||
* @param {*} input
|
||||
* @param {Object[]} args
|
||||
* @returns {*}
|
||||
*/
|
||||
run(input, args) {
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface for forward highlighter
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface for reverse highlighter
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method to be called when displaying the result of an operation in a human-readable
|
||||
* format. This allows operations to return usable data from their run() method and
|
||||
* only format them when this method is called.
|
||||
*
|
||||
* The default action is to return the data unchanged, but child classes can override
|
||||
* this behaviour.
|
||||
*
|
||||
* @param {*} data - The result of the run() function
|
||||
* @param {Object[]} args - The operation's arguments
|
||||
* @returns {*} - A human-readable version of the data
|
||||
*/
|
||||
present(data, args) {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the input type as a Dish enum.
|
||||
*
|
||||
* @param {string} typeStr
|
||||
*/
|
||||
set inputType(typeStr) {
|
||||
this._inputType = Dish.typeEnum(typeStr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the input type as a readable string.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get inputType() {
|
||||
return Dish.enumLookup(this._inputType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the output type as a Dish enum.
|
||||
*
|
||||
* @param {string} typeStr
|
||||
*/
|
||||
set outputType(typeStr) {
|
||||
this._outputType = Dish.typeEnum(typeStr);
|
||||
if (this._presentType < 0) this._presentType = this._outputType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the output type as a readable string.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get outputType() {
|
||||
return Dish.enumLookup(this._outputType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the presentation type as a Dish enum.
|
||||
*
|
||||
* @param {string} typeStr
|
||||
*/
|
||||
set presentType(typeStr) {
|
||||
this._presentType = Dish.typeEnum(typeStr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the presentation type as a readable string.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get presentType() {
|
||||
return Dish.enumLookup(this._presentType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the args for the current operation.
|
||||
*
|
||||
* @param {Object[]} conf
|
||||
*/
|
||||
set args(conf) {
|
||||
conf.forEach(arg => {
|
||||
const ingredient = new Ingredient(arg);
|
||||
this.addIngredient(ingredient);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the args for the current operation.
|
||||
*
|
||||
* @param {Object[]} conf
|
||||
*/
|
||||
get args() {
|
||||
return this._ingList.map(ing => {
|
||||
const conf = {
|
||||
name: ing.name,
|
||||
type: ing.type,
|
||||
value: ing.defaultValue
|
||||
};
|
||||
|
||||
if (ing.toggleValues) conf.toggleValues = ing.toggleValues;
|
||||
if (ing.hint) conf.hint = ing.hint;
|
||||
if (ing.rows) conf.rows = ing.rows;
|
||||
if (ing.disabled) conf.disabled = ing.disabled;
|
||||
if (ing.target) conf.target = ing.target;
|
||||
if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;
|
||||
return conf;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Operation as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
get config() {
|
||||
return {
|
||||
"op": this.name,
|
||||
"args": this._ingList.map(ing => ing.config)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new Ingredient to this Operation.
|
||||
*
|
||||
* @param {Ingredient} ingredient
|
||||
*/
|
||||
addIngredient(ingredient) {
|
||||
this._ingList.push(ingredient);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the Ingredient values for this Operation.
|
||||
*
|
||||
* @param {Object[]} ingValues
|
||||
*/
|
||||
set ingValues(ingValues) {
|
||||
ingValues.forEach((val, i) => {
|
||||
this._ingList[i].value = val;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Ingredient values for this Operation.
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
get ingValues() {
|
||||
return this._ingList.map(ing => ing.value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation has a breakpoint.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set breakpoint(value) {
|
||||
this._breakpoint = !!value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation has a breakpoint set.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get breakpoint() {
|
||||
return this._breakpoint;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation is disabled.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set disabled(value) {
|
||||
this._disabled = !!value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation is disabled.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation is a flow control.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get flowControl() {
|
||||
return this._flowControl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation is a flowcontrol op.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set flowControl(value) {
|
||||
this._flowControl = !!value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this Operation should not trigger AutoBake.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get manualBake() {
|
||||
return this._manualBake;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this Operation should trigger AutoBake.
|
||||
*
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set manualBake(value) {
|
||||
this._manualBake = !!value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Operation;
|
|
@ -1,220 +0,0 @@
|
|||
import Operation from "./Operation.js";
|
||||
import OperationConfig from "./config/OperationConfig.js";
|
||||
|
||||
|
||||
/**
|
||||
* The Recipe controls a list of Operations and the Dish they operate on.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @class
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
const Recipe = function(recipeConfig) {
|
||||
this.opList = [];
|
||||
|
||||
if (recipeConfig) {
|
||||
this._parseConfig(recipeConfig);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
Recipe.prototype._parseConfig = function(recipeConfig) {
|
||||
for (let c = 0; c < recipeConfig.length; c++) {
|
||||
const operationName = recipeConfig[c].op;
|
||||
const operationConfig = OperationConfig[operationName];
|
||||
const operation = new Operation(operationName, operationConfig);
|
||||
operation.setIngValues(recipeConfig[c].args);
|
||||
operation.setBreakpoint(recipeConfig[c].breakpoint);
|
||||
operation.setDisabled(recipeConfig[c].disabled);
|
||||
this.addOperation(operation);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Recipe as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
Recipe.prototype.getConfig = function() {
|
||||
const recipeConfig = [];
|
||||
|
||||
for (let o = 0; o < this.opList.length; o++) {
|
||||
recipeConfig.push(this.opList[o].getConfig());
|
||||
}
|
||||
|
||||
return recipeConfig;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new Operation to this Recipe.
|
||||
*
|
||||
* @param {Operation} operation
|
||||
*/
|
||||
Recipe.prototype.addOperation = function(operation) {
|
||||
this.opList.push(operation);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a list of Operations to this Recipe.
|
||||
*
|
||||
* @param {Operation[]} operations
|
||||
*/
|
||||
Recipe.prototype.addOperations = function(operations) {
|
||||
this.opList = this.opList.concat(operations);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set a breakpoint on a specified Operation.
|
||||
*
|
||||
* @param {number} position - The index of the Operation
|
||||
* @param {boolean} value
|
||||
*/
|
||||
Recipe.prototype.setBreakpoint = function(position, value) {
|
||||
try {
|
||||
this.opList[position].setBreakpoint(value);
|
||||
} catch (err) {
|
||||
// Ignore index error
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow
|
||||
* Control Fork operation.
|
||||
*
|
||||
* @param {number} pos
|
||||
*/
|
||||
Recipe.prototype.removeBreaksUpTo = function(pos) {
|
||||
for (let i = 0; i < pos; i++) {
|
||||
this.opList[i].setBreakpoint(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if there is an Flow Control Operation in this Recipe.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Recipe.prototype.containsFlowControl = function() {
|
||||
for (let i = 0; i < this.opList.length; i++) {
|
||||
if (this.opList[i].isFlowControl()) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index of the last Operation index that will be executed, taking into account disabled
|
||||
* Operations and breakpoints.
|
||||
*
|
||||
* @param {number} [startIndex=0] - The index to start searching from
|
||||
* @returns (number}
|
||||
*/
|
||||
Recipe.prototype.lastOpIndex = function(startIndex) {
|
||||
let i = startIndex + 1 || 0,
|
||||
op;
|
||||
|
||||
for (; i < this.opList.length; i++) {
|
||||
op = this.opList[i];
|
||||
if (op.isDisabled()) return i-1;
|
||||
if (op.isBreakpoint()) return i-1;
|
||||
}
|
||||
|
||||
return i-1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Executes each operation in the recipe over the given Dish.
|
||||
*
|
||||
* @param {Dish} dish
|
||||
* @param {number} [startFrom=0] - The index of the Operation to start executing from
|
||||
* @returns {number} - The final progress through the recipe
|
||||
*/
|
||||
Recipe.prototype.execute = async function(dish, startFrom) {
|
||||
startFrom = startFrom || 0;
|
||||
let op, input, output, numJumps = 0;
|
||||
|
||||
for (let i = startFrom; i < this.opList.length; i++) {
|
||||
op = this.opList[i];
|
||||
if (op.isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
if (op.isBreakpoint()) {
|
||||
return i;
|
||||
}
|
||||
|
||||
try {
|
||||
input = dish.get(op.inputType);
|
||||
|
||||
if (op.isFlowControl()) {
|
||||
// Package up the current state
|
||||
let state = {
|
||||
"progress" : i,
|
||||
"dish" : dish,
|
||||
"opList" : this.opList,
|
||||
"numJumps" : numJumps
|
||||
};
|
||||
|
||||
state = await op.run(state);
|
||||
i = state.progress;
|
||||
numJumps = state.numJumps;
|
||||
} else {
|
||||
output = await op.run(input, op.getIngValues());
|
||||
dish.set(output, op.outputType);
|
||||
}
|
||||
} catch (err) {
|
||||
const e = typeof err == "string" ? { message: err } : err;
|
||||
|
||||
e.progress = i;
|
||||
if (e.fileName) {
|
||||
e.displayStr = op.name + " - " + e.name + " in " +
|
||||
e.fileName + " on line " + e.lineNumber +
|
||||
".<br><br>Message: " + (e.displayStr || e.message);
|
||||
} else {
|
||||
e.displayStr = op.name + " - " + (e.displayStr || e.message);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return this.opList.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the recipe configuration in string format.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
Recipe.prototype.toString = function() {
|
||||
return JSON.stringify(this.getConfig());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Recipe from a given configuration string.
|
||||
*
|
||||
* @param {string} recipeStr
|
||||
*/
|
||||
Recipe.prototype.fromString = function(recipeStr) {
|
||||
const recipeConfig = JSON.parse(recipeStr);
|
||||
this._parseConfig(recipeConfig);
|
||||
};
|
||||
|
||||
export default Recipe;
|
342
src/core/Recipe.mjs
Executable file
342
src/core/Recipe.mjs
Executable file
|
@ -0,0 +1,342 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationConfig from "./config/OperationConfig.json";
|
||||
import OperationError from "./errors/OperationError";
|
||||
import Operation from "./Operation";
|
||||
import DishError from "./errors/DishError";
|
||||
import log from "loglevel";
|
||||
|
||||
// Cache container for modules
|
||||
let modules = null;
|
||||
|
||||
/**
|
||||
* The Recipe controls a list of Operations and the Dish they operate on.
|
||||
*/
|
||||
class Recipe {
|
||||
|
||||
/**
|
||||
* Recipe constructor
|
||||
*
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
constructor(recipeConfig) {
|
||||
this.opList = [];
|
||||
|
||||
if (recipeConfig) {
|
||||
this._parseConfig(recipeConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads and parses the given config.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} recipeConfig
|
||||
*/
|
||||
_parseConfig(recipeConfig) {
|
||||
recipeConfig.forEach(c => {
|
||||
this.opList.push({
|
||||
name: c.op,
|
||||
module: OperationConfig[c.op].module,
|
||||
ingValues: c.args,
|
||||
breakpoint: c.breakpoint,
|
||||
disabled: c.disabled,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populate elements of opList with operation instances.
|
||||
* Dynamic import here removes top-level cyclic dependency issue.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
async _hydrateOpList() {
|
||||
if (!modules) {
|
||||
// Using Webpack Magic Comments to force the dynamic import to be included in the main chunk
|
||||
// https://webpack.js.org/api/module-methods/
|
||||
modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules");
|
||||
modules = modules.default;
|
||||
}
|
||||
|
||||
this.opList = this.opList.map(o => {
|
||||
if (o instanceof Operation) {
|
||||
return o;
|
||||
} else {
|
||||
const op = new modules[o.module][o.name]();
|
||||
op.ingValues = o.ingValues;
|
||||
op.breakpoint = o.breakpoint;
|
||||
op.disabled = o.disabled;
|
||||
return op;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the Recipe as it should be displayed in a recipe config.
|
||||
*
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
get config() {
|
||||
return this.opList.map(op => ({
|
||||
op: op.name,
|
||||
args: op.ingValues,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new Operation to this Recipe.
|
||||
*
|
||||
* @param {Operation} operation
|
||||
*/
|
||||
addOperation(operation) {
|
||||
this.opList.push(operation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a list of Operations to this Recipe.
|
||||
*
|
||||
* @param {Operation[]} operations
|
||||
*/
|
||||
addOperations(operations) {
|
||||
operations.forEach(o => {
|
||||
if (o instanceof Operation) {
|
||||
this.opList.push(o);
|
||||
} else {
|
||||
this.opList.push({
|
||||
name: o.name,
|
||||
module: o.module,
|
||||
ingValues: o.args,
|
||||
breakpoint: o.breakpoint,
|
||||
disabled: o.disabled,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a breakpoint on a specified Operation.
|
||||
*
|
||||
* @param {number} position - The index of the Operation
|
||||
* @param {boolean} value
|
||||
*/
|
||||
setBreakpoint(position, value) {
|
||||
try {
|
||||
this.opList[position].breakpoint = value;
|
||||
} catch (err) {
|
||||
// Ignore index error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove breakpoints on all Operations in the Recipe up to the specified position. Used by Flow
|
||||
* Control Fork operation.
|
||||
*
|
||||
* @param {number} pos
|
||||
*/
|
||||
removeBreaksUpTo(pos) {
|
||||
for (let i = 0; i < pos; i++) {
|
||||
this.opList[i].breakpoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if there is a Flow Control Operation in this Recipe.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
containsFlowControl() {
|
||||
return this.opList.reduce((acc, curr) => {
|
||||
return acc || curr.flowControl;
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes each operation in the recipe over the given Dish.
|
||||
*
|
||||
* @param {Dish} dish
|
||||
* @param {number} [startFrom=0]
|
||||
* - The index of the Operation to start executing from
|
||||
* @param {number} [forkState={}]
|
||||
* - If this is a forked recipe, the state of the recipe up to this point
|
||||
* @returns {number}
|
||||
* - The final progress through the recipe
|
||||
*/
|
||||
async execute(dish, startFrom=0, forkState={}) {
|
||||
let op, input, output,
|
||||
numJumps = 0,
|
||||
numRegisters = forkState.numRegisters || 0;
|
||||
|
||||
if (startFrom === 0) this.lastRunOp = null;
|
||||
|
||||
await this._hydrateOpList();
|
||||
|
||||
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
|
||||
|
||||
for (let i = startFrom; i < this.opList.length; i++) {
|
||||
op = this.opList[i];
|
||||
log.debug(`[${i}] ${op.name} ${JSON.stringify(op.ingValues)}`);
|
||||
if (op.disabled) {
|
||||
log.debug("Operation is disabled, skipping");
|
||||
continue;
|
||||
}
|
||||
if (op.breakpoint) {
|
||||
log.debug("Pausing at breakpoint");
|
||||
return i;
|
||||
}
|
||||
|
||||
try {
|
||||
input = await dish.get(op.inputType);
|
||||
log.debug("Executing operation");
|
||||
|
||||
if (op.flowControl) {
|
||||
// Package up the current state
|
||||
let state = {
|
||||
"progress": i,
|
||||
"dish": dish,
|
||||
"opList": this.opList,
|
||||
"numJumps": numJumps,
|
||||
"numRegisters": numRegisters,
|
||||
"forkOffset": forkState.forkOffset || 0
|
||||
};
|
||||
|
||||
state = await op.run(state);
|
||||
i = state.progress;
|
||||
numJumps = state.numJumps;
|
||||
numRegisters = state.numRegisters;
|
||||
} else {
|
||||
output = await op.run(input, op.ingValues);
|
||||
dish.set(output, op.outputType);
|
||||
}
|
||||
this.lastRunOp = op;
|
||||
} catch (err) {
|
||||
// Return expected errors as output
|
||||
if (err instanceof OperationError ||
|
||||
(err.type && err.type === "OperationError")) {
|
||||
// Cannot rely on `err instanceof OperationError` here as extending
|
||||
// native types is not fully supported yet.
|
||||
dish.set(err.message, "string");
|
||||
return i;
|
||||
} else if (err instanceof DishError ||
|
||||
(err.type && err.type === "DishError")) {
|
||||
dish.set(err.message, "string");
|
||||
return i;
|
||||
} else {
|
||||
const e = typeof err == "string" ? { message: err } : err;
|
||||
|
||||
e.progress = i;
|
||||
if (e.fileName) {
|
||||
e.displayStr = `${op.name} - ${e.name} in ${e.fileName} on line ` +
|
||||
`${e.lineNumber}.<br><br>Message: ${e.displayStr || e.message}`;
|
||||
} else {
|
||||
e.displayStr = `${op.name} - ${e.displayStr || e.message}`;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Recipe complete");
|
||||
return this.opList.length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Present the results of the final operation.
|
||||
*
|
||||
* @param {Dish} dish
|
||||
*/
|
||||
async present(dish) {
|
||||
if (!this.lastRunOp) return;
|
||||
|
||||
const output = await this.lastRunOp.present(
|
||||
await dish.get(this.lastRunOp.outputType),
|
||||
this.lastRunOp.ingValues
|
||||
);
|
||||
dish.set(output, this.lastRunOp.presentType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the recipe configuration in string format.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
toString() {
|
||||
return JSON.stringify(this.config);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Recipe from a given configuration string.
|
||||
*
|
||||
* @param {string} recipeStr
|
||||
*/
|
||||
fromString(recipeStr) {
|
||||
const recipeConfig = JSON.parse(recipeStr);
|
||||
this._parseConfig(recipeConfig);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a list of all the highlight functions assigned to operations in the recipe, if the
|
||||
* entire recipe supports highlighting.
|
||||
*
|
||||
* @returns {Object[]} highlights
|
||||
* @returns {function} highlights[].f
|
||||
* @returns {function} highlights[].b
|
||||
* @returns {Object[]} highlights[].args
|
||||
*/
|
||||
async generateHighlightList() {
|
||||
await this._hydrateOpList();
|
||||
const highlights = [];
|
||||
|
||||
for (let i = 0; i < this.opList.length; i++) {
|
||||
const op = this.opList[i];
|
||||
if (op.disabled) continue;
|
||||
|
||||
// If any breakpoints are set, do not attempt to highlight
|
||||
if (op.breakpoint) return false;
|
||||
|
||||
// If any of the operations do not support highlighting, fail immediately.
|
||||
if (op.highlight === false || op.highlight === undefined) return false;
|
||||
|
||||
highlights.push({
|
||||
f: op.highlight,
|
||||
b: op.highlightReverse,
|
||||
args: op.ingValues
|
||||
});
|
||||
}
|
||||
|
||||
return highlights;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the previous operation has a different presentation type to its normal output.
|
||||
*
|
||||
* @param {number} progress
|
||||
* @returns {boolean}
|
||||
*/
|
||||
lastOpPresented(progress) {
|
||||
if (progress < 1) return false;
|
||||
return this.opList[progress-1].presentType !== this.opList[progress-1].outputType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Recipe;
|
1206
src/core/Utils.js
1206
src/core/Utils.js
File diff suppressed because it is too large
Load diff
1206
src/core/Utils.mjs
Executable file
1206
src/core/Utils.mjs
Executable file
File diff suppressed because it is too large
Load diff
|
@ -1,30 +1,11 @@
|
|||
/**
|
||||
* Type definition for a CatConf.
|
||||
*
|
||||
* @typedef {Object} CatConf
|
||||
* @property {string} name - The display name for the category
|
||||
* @property {string[]} ops - A list of the operations to be included in this category
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Categories of operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constant
|
||||
* @type {CatConf[]}
|
||||
*/
|
||||
const Categories = [
|
||||
[
|
||||
{
|
||||
name: "Favourites",
|
||||
ops: []
|
||||
"name": "Favourites",
|
||||
"ops": []
|
||||
},
|
||||
{
|
||||
name: "Data format",
|
||||
ops: [
|
||||
"name": "Data format",
|
||||
"ops": [
|
||||
"To Hexdump",
|
||||
"From Hexdump",
|
||||
"To Hex",
|
||||
|
@ -44,12 +25,19 @@ const Categories = [
|
|||
"From Base32",
|
||||
"To Base58",
|
||||
"From Base58",
|
||||
"To Base62",
|
||||
"From Base62",
|
||||
"To Base85",
|
||||
"From Base85",
|
||||
"To Base",
|
||||
"From Base",
|
||||
"To BCD",
|
||||
"From BCD",
|
||||
"To HTML Entity",
|
||||
"From HTML Entity",
|
||||
"URL Encode",
|
||||
"URL Decode",
|
||||
"Escape Unicode Characters",
|
||||
"Unescape Unicode Characters",
|
||||
"To Quoted Printable",
|
||||
"From Quoted Printable",
|
||||
|
@ -63,12 +51,20 @@ const Categories = [
|
|||
"Change IP format",
|
||||
"Encode text",
|
||||
"Decode text",
|
||||
"Text Encoding Brute Force",
|
||||
"Swap endianness",
|
||||
"To MessagePack",
|
||||
"From MessagePack",
|
||||
"To Braille",
|
||||
"From Braille",
|
||||
"Parse TLV",
|
||||
"CSV to JSON",
|
||||
"JSON to CSV"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Encryption / Encoding",
|
||||
ops: [
|
||||
"name": "Encryption / Encoding",
|
||||
"ops": [
|
||||
"AES Encrypt",
|
||||
"AES Decrypt",
|
||||
"Blowfish Encrypt",
|
||||
|
@ -77,8 +73,8 @@ const Categories = [
|
|||
"DES Decrypt",
|
||||
"Triple DES Encrypt",
|
||||
"Triple DES Decrypt",
|
||||
"Rabbit Encrypt",
|
||||
"Rabbit Decrypt",
|
||||
"RC2 Encrypt",
|
||||
"RC2 Decrypt",
|
||||
"RC4",
|
||||
"RC4 Drop",
|
||||
"ROT13",
|
||||
|
@ -89,28 +85,51 @@ const Categories = [
|
|||
"Vigenère Decode",
|
||||
"To Morse Code",
|
||||
"From Morse Code",
|
||||
"Bifid Cipher Encode",
|
||||
"Bifid Cipher Decode",
|
||||
"Affine Cipher Encode",
|
||||
"Affine Cipher Decode",
|
||||
"A1Z26 Cipher Encode",
|
||||
"A1Z26 Cipher Decode",
|
||||
"Atbash Cipher",
|
||||
"Substitute",
|
||||
"Derive PBKDF2 key",
|
||||
"Derive EVP key",
|
||||
"Bcrypt",
|
||||
"Scrypt",
|
||||
"JWT Sign",
|
||||
"JWT Verify",
|
||||
"JWT Decode",
|
||||
"Citrix CTX1 Encode",
|
||||
"Citrix CTX1 Decode",
|
||||
"Pseudo-Random Number Generator"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Public Key",
|
||||
ops: [
|
||||
"name": "Public Key",
|
||||
"ops": [
|
||||
"Parse X.509 certificate",
|
||||
"Parse ASN.1 hex string",
|
||||
"PEM to Hex",
|
||||
"Hex to PEM",
|
||||
"Hex to Object Identifier",
|
||||
"Object Identifier to Hex",
|
||||
"Generate PGP Key Pair",
|
||||
"PGP Encrypt",
|
||||
"PGP Decrypt",
|
||||
"PGP Encrypt and Sign",
|
||||
"PGP Decrypt and Verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Logical operations",
|
||||
ops: [
|
||||
"name": "Arithmetic / Logic",
|
||||
"ops": [
|
||||
"Set Union",
|
||||
"Set Intersection",
|
||||
"Set Difference",
|
||||
"Symmetric Difference",
|
||||
"Cartesian Product",
|
||||
"Power Set",
|
||||
"XOR",
|
||||
"XOR Brute Force",
|
||||
"OR",
|
||||
|
@ -118,15 +137,27 @@ const Categories = [
|
|||
"AND",
|
||||
"ADD",
|
||||
"SUB",
|
||||
"Sum",
|
||||
"Subtract",
|
||||
"Multiply",
|
||||
"Divide",
|
||||
"Mean",
|
||||
"Median",
|
||||
"Standard Deviation",
|
||||
"Bit shift left",
|
||||
"Bit shift right",
|
||||
"Rotate left",
|
||||
"Rotate right",
|
||||
"ROT13",
|
||||
"ROT13"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Networking",
|
||||
ops: [
|
||||
"name": "Networking",
|
||||
"ops": [
|
||||
"HTTP request",
|
||||
"DNS over HTTPS",
|
||||
"Strip HTTP headers",
|
||||
"Dechunk HTTP response",
|
||||
"Parse User Agent",
|
||||
"Parse IP range",
|
||||
"Parse IPv6 address",
|
||||
|
@ -139,26 +170,31 @@ const Categories = [
|
|||
"Group IP addresses",
|
||||
"Encode NetBIOS Name",
|
||||
"Decode NetBIOS Name",
|
||||
"Defang URL"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Language",
|
||||
ops: [
|
||||
"name": "Language",
|
||||
"ops": [
|
||||
"Encode text",
|
||||
"Decode text",
|
||||
"Unescape Unicode Characters",
|
||||
"Remove Diacritics",
|
||||
"Unescape Unicode Characters"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Utils",
|
||||
ops: [
|
||||
"name": "Utils",
|
||||
"ops": [
|
||||
"Diff",
|
||||
"Remove whitespace",
|
||||
"Remove null bytes",
|
||||
"To Upper case",
|
||||
"To Lower case",
|
||||
"To Case Insensitive Regex",
|
||||
"From Case Insensitive Regex",
|
||||
"Add line numbers",
|
||||
"Remove line numbers",
|
||||
"To Table",
|
||||
"Reverse",
|
||||
"Sort",
|
||||
"Unique",
|
||||
|
@ -168,36 +204,44 @@ const Categories = [
|
|||
"Tail",
|
||||
"Count occurrences",
|
||||
"Expand alphabet range",
|
||||
"Parse escaped string",
|
||||
"Drop bytes",
|
||||
"Take bytes",
|
||||
"Pad lines",
|
||||
"Find / Replace",
|
||||
"Regular expression",
|
||||
"Offset checker",
|
||||
"Hamming Distance",
|
||||
"Convert distance",
|
||||
"Convert area",
|
||||
"Convert mass",
|
||||
"Convert speed",
|
||||
"Convert data units",
|
||||
"Convert co-ordinate format",
|
||||
"Parse UNIX file permissions",
|
||||
"Swap endianness",
|
||||
"Parse colour code",
|
||||
"Escape string",
|
||||
"Unescape string",
|
||||
"Pseudo-Random Number Generator",
|
||||
"Sleep"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Date / Time",
|
||||
ops: [
|
||||
"name": "Date / Time",
|
||||
"ops": [
|
||||
"Parse DateTime",
|
||||
"Translate DateTime Format",
|
||||
"From UNIX Timestamp",
|
||||
"To UNIX Timestamp",
|
||||
"Windows Filetime to UNIX Timestamp",
|
||||
"UNIX Timestamp to Windows Filetime",
|
||||
"Extract dates",
|
||||
"Sleep"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Extractors",
|
||||
ops: [
|
||||
"name": "Extractors",
|
||||
"ops": [
|
||||
"Strings",
|
||||
"Extract IP addresses",
|
||||
"Extract email addresses",
|
||||
|
@ -208,13 +252,14 @@ const Categories = [
|
|||
"Extract dates",
|
||||
"Regular expression",
|
||||
"XPath expression",
|
||||
"JPath expression",
|
||||
"CSS selector",
|
||||
"Extract EXIF",
|
||||
"Extract EXIF"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Compression",
|
||||
ops: [
|
||||
"name": "Compression",
|
||||
"ops": [
|
||||
"Raw Deflate",
|
||||
"Raw Inflate",
|
||||
"Zlib Deflate",
|
||||
|
@ -225,38 +270,50 @@ const Categories = [
|
|||
"Unzip",
|
||||
"Bzip2 Decompress",
|
||||
"Tar",
|
||||
"Untar",
|
||||
"Untar"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Hashing",
|
||||
ops: [
|
||||
"name": "Hashing",
|
||||
"ops": [
|
||||
"Analyse hash",
|
||||
"Generate all hashes",
|
||||
"MD2",
|
||||
"MD4",
|
||||
"MD5",
|
||||
"MD6",
|
||||
"SHA0",
|
||||
"SHA1",
|
||||
"SHA224",
|
||||
"SHA256",
|
||||
"SHA384",
|
||||
"SHA512",
|
||||
"SHA2",
|
||||
"SHA3",
|
||||
"RIPEMD-160",
|
||||
"Keccak",
|
||||
"Shake",
|
||||
"RIPEMD",
|
||||
"HAS-160",
|
||||
"Whirlpool",
|
||||
"Snefru",
|
||||
"SSDEEP",
|
||||
"CTPH",
|
||||
"Compare SSDEEP hashes",
|
||||
"Compare CTPH hashes",
|
||||
"HMAC",
|
||||
"Bcrypt",
|
||||
"Bcrypt compare",
|
||||
"Bcrypt parse",
|
||||
"Scrypt",
|
||||
"Fletcher-8 Checksum",
|
||||
"Fletcher-16 Checksum",
|
||||
"Fletcher-32 Checksum",
|
||||
"Fletcher-64 Checksum",
|
||||
"Adler-32 Checksum",
|
||||
"CRC-16 Checksum",
|
||||
"CRC-32 Checksum",
|
||||
"TCP/IP Checksum",
|
||||
"TCP/IP Checksum"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Code tidy",
|
||||
ops: [
|
||||
"name": "Code tidy",
|
||||
"ops": [
|
||||
"Syntax highlighter",
|
||||
"Generic Code Beautify",
|
||||
"JavaScript Parser",
|
||||
|
@ -271,37 +328,72 @@ const Categories = [
|
|||
"CSS Beautify",
|
||||
"CSS Minify",
|
||||
"XPath expression",
|
||||
"JPath expression",
|
||||
"CSS selector",
|
||||
"PHP Deserialize",
|
||||
"Microsoft Script Decoder",
|
||||
"Strip HTML tags",
|
||||
"Diff",
|
||||
"To Snake case",
|
||||
"To Camel case",
|
||||
"To Kebab case",
|
||||
"BSON serialise",
|
||||
"BSON deserialise",
|
||||
"To MessagePack",
|
||||
"From MessagePack"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Other",
|
||||
ops: [
|
||||
"Entropy",
|
||||
"Frequency distribution",
|
||||
"name": "Forensics",
|
||||
"ops": [
|
||||
"Detect File Type",
|
||||
"Scan for Embedded Files",
|
||||
"Generate UUID",
|
||||
"Render Image",
|
||||
"Numberwang",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Flow control",
|
||||
ops: [
|
||||
"name": "Multimedia",
|
||||
"ops": [
|
||||
"Render Image",
|
||||
"Play Media",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF",
|
||||
"Split Colour Channels"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Other",
|
||||
"ops": [
|
||||
"Entropy",
|
||||
"Frequency distribution",
|
||||
"Chi Square",
|
||||
"Disassemble x86",
|
||||
"Pseudo-Random Number Generator",
|
||||
"Generate UUID",
|
||||
"Generate TOTP",
|
||||
"Generate HOTP",
|
||||
"Generate QR Code",
|
||||
"Parse QR Code",
|
||||
"Haversine distance",
|
||||
"Generate Lorem Ipsum",
|
||||
"Numberwang",
|
||||
"XKCD Random Number"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Flow control",
|
||||
"ops": [
|
||||
"Magic",
|
||||
"Fork",
|
||||
"Subsection",
|
||||
"Merge",
|
||||
"Register",
|
||||
"Label",
|
||||
"Jump",
|
||||
"Conditional Jump",
|
||||
"Return",
|
||||
"Comment"
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
export default Categories;
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load diff
150
src/core/config/scripts/generateConfig.mjs
Normal file
150
src/core/config/scripts/generateConfig.mjs
Normal file
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* This script automatically generates OperationConfig.json, containing metadata
|
||||
* for each operation in the src/core/operations directory.
|
||||
* It also generates modules in the src/core/config/modules directory to separate
|
||||
* out operations into logical collections.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/*eslint no-console: ["off"] */
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import process from "process";
|
||||
import * as Ops from "../../operations/index";
|
||||
|
||||
const dir = path.join(process.cwd() + "/src/core/config/");
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log("\nCWD: " + process.cwd());
|
||||
console.log("Error: generateConfig.mjs should be run from the project root");
|
||||
console.log("Example> node --experimental-modules src/core/config/scripts/generateConfig.mjs");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
const operationConfig = {},
|
||||
modules = {};
|
||||
|
||||
/**
|
||||
* Generate operation config and module lists.
|
||||
*/
|
||||
for (const opObj in Ops) {
|
||||
const op = new Ops[opObj]();
|
||||
|
||||
operationConfig[op.name] = {
|
||||
module: op.module,
|
||||
description: op.description,
|
||||
infoURL: op.infoURL,
|
||||
inputType: op.inputType,
|
||||
outputType: op.presentType,
|
||||
flowControl: op.flowControl,
|
||||
manualBake: op.manualBake,
|
||||
args: op.args
|
||||
};
|
||||
|
||||
if (op.hasOwnProperty("patterns")) {
|
||||
operationConfig[op.name].patterns = op.patterns;
|
||||
}
|
||||
|
||||
if (!modules.hasOwnProperty(op.module))
|
||||
modules[op.module] = {};
|
||||
modules[op.module][op.name] = opObj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write OperationConfig.
|
||||
*/
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "OperationConfig.json"),
|
||||
JSON.stringify(operationConfig, null, 4)
|
||||
);
|
||||
console.log("Written OperationConfig.json");
|
||||
|
||||
|
||||
/**
|
||||
* Write modules.
|
||||
*/
|
||||
if (!fs.existsSync(path.join(dir, "modules/"))) {
|
||||
fs.mkdirSync(path.join(dir, "modules/"));
|
||||
}
|
||||
|
||||
for (const module in modules) {
|
||||
let code = `/**
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateConfig.mjs
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
`;
|
||||
|
||||
for (const opName in modules[module]) {
|
||||
const objName = modules[module][opName];
|
||||
code += `import ${objName} from "../../operations/${objName}";\n`;
|
||||
}
|
||||
|
||||
code += `
|
||||
const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
|
||||
|
||||
OpModules.${module} = {
|
||||
`;
|
||||
for (const opName in modules[module]) {
|
||||
const objName = modules[module][opName];
|
||||
code += ` "${opName}": ${objName},\n`;
|
||||
}
|
||||
|
||||
code += `};
|
||||
|
||||
export default OpModules;
|
||||
`;
|
||||
fs.writeFileSync(
|
||||
path.join(dir, `modules/${module}.mjs`),
|
||||
code
|
||||
);
|
||||
console.log(`Written ${module} module`);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write OpModules wrapper.
|
||||
*/
|
||||
let opModulesCode = `/**
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateConfig.mjs
|
||||
*
|
||||
* Imports all modules for builds which do not load modules separately.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
`;
|
||||
|
||||
for (const module in modules) {
|
||||
opModulesCode += `import ${module}Module from "./${module}";\n`;
|
||||
}
|
||||
|
||||
opModulesCode += `
|
||||
const OpModules = {};
|
||||
|
||||
Object.assign(
|
||||
OpModules,
|
||||
`;
|
||||
|
||||
for (const module in modules) {
|
||||
opModulesCode += ` ${module}Module,\n`;
|
||||
}
|
||||
|
||||
opModulesCode += `);
|
||||
|
||||
export default OpModules;
|
||||
`;
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "modules/OpModules.mjs"),
|
||||
opModulesCode
|
||||
);
|
||||
console.log("Written OpModules.mjs");
|
60
src/core/config/scripts/generateOpsIndex.mjs
Normal file
60
src/core/config/scripts/generateOpsIndex.mjs
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* This script automatically generates src/core/operations/index.mjs, containing
|
||||
* imports for all operations in src/core/operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/*eslint no-console: ["off"] */
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import process from "process";
|
||||
|
||||
const dir = path.join(process.cwd() + "/src/core/config/");
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log("\nCWD: " + process.cwd());
|
||||
console.log("Error: generateOpsIndex.mjs should be run from the project root");
|
||||
console.log("Example> node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Find all operation files
|
||||
const opObjs = [];
|
||||
fs.readdirSync(path.join(dir, "../operations")).forEach(file => {
|
||||
if (!file.endsWith(".mjs") || file === "index.mjs") return;
|
||||
opObjs.push(file.split(".mjs")[0]);
|
||||
});
|
||||
|
||||
// Construct index file
|
||||
let code = `/**
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
`;
|
||||
|
||||
opObjs.forEach(obj => {
|
||||
code += `import ${obj} from "./${obj}";\n`;
|
||||
});
|
||||
|
||||
code += `
|
||||
export {
|
||||
`;
|
||||
|
||||
opObjs.forEach(obj => {
|
||||
code += ` ${obj},\n`;
|
||||
});
|
||||
|
||||
code += "};\n";
|
||||
|
||||
// Write file
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "../operations/index.mjs"),
|
||||
code
|
||||
);
|
||||
console.log("Written operation index.");
|
230
src/core/config/scripts/newOperation.mjs
Normal file
230
src/core/config/scripts/newOperation.mjs
Normal file
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* Interactive script for generating a new operation template.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/*eslint no-console: ["off"] */
|
||||
|
||||
import prompt from "prompt";
|
||||
import colors from "colors";
|
||||
import process from "process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import EscapeString from "../../operations/EscapeString";
|
||||
|
||||
|
||||
const dir = path.join(process.cwd() + "/src/core/operations/");
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log("\nCWD: " + process.cwd());
|
||||
console.log("Error: newOperation.mjs should be run from the project root");
|
||||
console.log("Example> node --experimental-modules src/core/config/scripts/newOperation.mjs");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const ioTypes = ["string", "byteArray", "number", "html", "ArrayBuffer", "BigNumber", "JSON", "File", "List<File>"];
|
||||
|
||||
const schema = {
|
||||
properties: {
|
||||
opName: {
|
||||
description: "The operation name should be short but descriptive.",
|
||||
example: "URL Decode",
|
||||
prompt: "Operation name",
|
||||
type: "string",
|
||||
pattern: /^[\w\s-/().]+$/,
|
||||
required: true,
|
||||
message: "Operation names should consist of letters, numbers or the following symbols: _-/()."
|
||||
},
|
||||
module: {
|
||||
description: `Modules are used to group operations that rely on large libraries. Any operation that is not in the Default module will be loaded in dynamically when it is first called. All operations in the same module will also be loaded at this time. This system prevents the CyberChef web app from getting too bloated and taking a long time to load initially.
|
||||
If your operation does not rely on a library, just leave this blank and it will be added to the Default module. If it relies on the same library as other operations, enter the name of the module those operations are in. If it relies on a new large library, enter a new module name (capitalise the first letter).`,
|
||||
example: "Crypto",
|
||||
prompt: "Module",
|
||||
type: "string",
|
||||
pattern: /^[A-Z][A-Za-z\d]+$/,
|
||||
message: "Module names should start with a capital letter and not contain any spaces or symbols.",
|
||||
default: "Default"
|
||||
},
|
||||
description: {
|
||||
description: "The description should explain what the operation is and how it works. It can describe how the arguments should be entered and give examples of expected input and output. HTML markup is supported. Use <code> tags for examples. The description is scanned during searches, so include terms that are likely to be searched for when someone is looking for your operation.",
|
||||
example: "Converts URI/URL percent-encoded characters back to their raw values.<br><br>e.g. <code>%3d</code> becomes <code>=</code>",
|
||||
prompt: "Description",
|
||||
type: "string"
|
||||
},
|
||||
infoURL: {
|
||||
description: "An optional URL for an external site can be added to give more information about the operation. Wikipedia links are often suitable. If linking to Wikipedia, use an international link (e.g. https://wikipedia.org/...) rather than a localised link (e.g. https://en.wikipedia.org/...).",
|
||||
example: "https://wikipedia.org/wiki/Percent-encoding",
|
||||
prompt: "Information URL",
|
||||
type: "string",
|
||||
},
|
||||
inputType: {
|
||||
description: `The input type defines how the input data will be presented to your operation. Check the project wiki for a full description of each type. The options are: ${ioTypes.join(", ")}.`,
|
||||
example: "string",
|
||||
prompt: "Input type",
|
||||
type: "string",
|
||||
pattern: new RegExp(`^(${ioTypes.join("|")})$`),
|
||||
required: true,
|
||||
message: `The input type should be one of: ${ioTypes.join(", ")}.`
|
||||
},
|
||||
outputType: {
|
||||
description: `The output type tells CyberChef what sort of data you are returning from your operation. Check the project wiki for a full description of each type. The options are: ${ioTypes.join(", ")}.`,
|
||||
example: "string",
|
||||
prompt: "Output type",
|
||||
type: "string",
|
||||
pattern: new RegExp(`^(${ioTypes.join("|")})$`),
|
||||
required: true,
|
||||
message: `The output type should be one of: ${ioTypes.join(", ")}.`
|
||||
},
|
||||
highlight: {
|
||||
description: "If your operation does not change the length of the input in any way, we can enable highlighting. If it does change the length in a predictable way, we may still be able to enable highlighting and calculate the correct offsets. If this is not possible, we will disable highlighting for this operation.",
|
||||
example: "true/false",
|
||||
prompt: "Enable highlighting",
|
||||
type: "boolean",
|
||||
default: "false",
|
||||
message: "Enter true or false to specify if highlighting should be enabled."
|
||||
},
|
||||
authorName: {
|
||||
description: "Your name or username will be added to the @author tag for this operation.",
|
||||
example: "n1474335",
|
||||
prompt: "Username",
|
||||
type: "string"
|
||||
},
|
||||
authorEmail: {
|
||||
description: "Your email address will also be added to the @author tag for this operation.",
|
||||
example: "n1474335@gmail.com",
|
||||
prompt: "Email",
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Build schema
|
||||
for (const prop in schema.properties) {
|
||||
const p = schema.properties[prop];
|
||||
p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
|
||||
}
|
||||
|
||||
console.log("\n\nThis script will generate a new operation template based on the information you provide. These values can be changed manually later.".yellow);
|
||||
|
||||
prompt.message = "";
|
||||
prompt.delimiter = ":".green;
|
||||
|
||||
prompt.start();
|
||||
|
||||
prompt.get(schema, (err, result) => {
|
||||
if (err) {
|
||||
console.log("\nExiting build script.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const moduleName = result.opName.replace(/\w\S*/g, txt => {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1);
|
||||
}).replace(/[\s-()/./]/g, "");
|
||||
|
||||
|
||||
const template = `/**
|
||||
* @author ${result.authorName} [${result.authorEmail}]
|
||||
* @copyright Crown Copyright ${(new Date()).getFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* ${result.opName} operation
|
||||
*/
|
||||
class ${moduleName} extends Operation {
|
||||
|
||||
/**
|
||||
* ${moduleName} constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "${result.opName}";
|
||||
this.module = "${result.module}";
|
||||
this.description = "${(new EscapeString).run(result.description, ["Special chars", "Double"])}";
|
||||
this.infoURL = "${result.infoURL}";
|
||||
this.inputType = "${result.inputType}";
|
||||
this.outputType = "${result.outputType}";
|
||||
this.args = [
|
||||
/* Example arguments. See the project wiki for full details.
|
||||
{
|
||||
name: "First arg",
|
||||
type: "string",
|
||||
value: "Don't Panic"
|
||||
},
|
||||
{
|
||||
name: "Second arg",
|
||||
type: "number",
|
||||
value: 42
|
||||
}
|
||||
*/
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {${result.inputType}} input
|
||||
* @param {Object[]} args
|
||||
* @returns {${result.outputType}}
|
||||
*/
|
||||
run(input, args) {
|
||||
// const [firstArg, secondArg] = args;
|
||||
|
||||
throw new OperationError("Test");
|
||||
}
|
||||
${result.highlight ? `
|
||||
/**
|
||||
* Highlight ${result.opName}
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ${result.opName} in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
` : ""}
|
||||
}
|
||||
|
||||
export default ${moduleName};
|
||||
`;
|
||||
|
||||
//console.log(template);
|
||||
|
||||
const filename = path.join(dir, `./${moduleName}.mjs`);
|
||||
if (fs.existsSync(filename)) {
|
||||
console.log(`${filename} already exists. It has NOT been overwritten.`.red);
|
||||
console.log("Choose a different operation name to avoid conflicts.");
|
||||
process.exit(0);
|
||||
}
|
||||
fs.writeFileSync(filename, template);
|
||||
|
||||
console.log(`\nOperation template written to ${colors.green(filename)}`);
|
||||
console.log(`\nNext steps:
|
||||
1. Add your operation to ${colors.green("src/core/config/Categories.json")}
|
||||
2. Write your operation code.
|
||||
3. Write tests in ${colors.green("tests/operations/tests/")}
|
||||
4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")}
|
||||
5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);
|
||||
|
||||
});
|
||||
|
26
src/core/errors/DishError.mjs
Normal file
26
src/core/errors/DishError.mjs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Custom error type for handling Dish type errors.
|
||||
* i.e. where the Dish cannot be successfully translated between types
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
class DishError extends Error {
|
||||
/**
|
||||
* Standard error constructor. Adds no new behaviour.
|
||||
*
|
||||
* @param args - Standard error args
|
||||
*/
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.type = "DishError";
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, DishError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DishError;
|
26
src/core/errors/OperationError.mjs
Normal file
26
src/core/errors/OperationError.mjs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Custom error type for handling operation input errors.
|
||||
* i.e. where the operation can handle the error and print a message to the screen.
|
||||
*
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
class OperationError extends Error {
|
||||
/**
|
||||
* Standard error constructor. Adds no new behaviour.
|
||||
*
|
||||
* @param args - Standard error args
|
||||
*/
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.type = "OperationError";
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, OperationError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default OperationError;
|
139
src/core/lib/Arithmetic.mjs
Normal file
139
src/core/lib/Arithmetic.mjs
Normal file
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* @author bwhitn [brian.m.whitney@outlook.com]
|
||||
* @author d98762625 [d98762625@gmailcom]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
import BigNumber from "bignumber.js";
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string array to a number array.
|
||||
*
|
||||
* @param {string[]} input
|
||||
* @param {string} delim
|
||||
* @returns {BigNumber[]}
|
||||
*/
|
||||
export function createNumArray(input, delim) {
|
||||
delim = Utils.charRep(delim || "Space");
|
||||
const splitNumbers = input.split(delim);
|
||||
const numbers = [];
|
||||
let num;
|
||||
|
||||
splitNumbers.map((number) => {
|
||||
try {
|
||||
num = BigNumber(number.trim());
|
||||
if (!num.isNaN()) {
|
||||
numbers.push(num);
|
||||
}
|
||||
} catch (err) {
|
||||
// This line is not a valid number
|
||||
}
|
||||
});
|
||||
return numbers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an array of numbers and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function sum(data) {
|
||||
if (data.length > 0) {
|
||||
return data.reduce((acc, curr) => acc.plus(curr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subtracts an array of numbers and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function sub(data) {
|
||||
if (data.length > 0) {
|
||||
return data.reduce((acc, curr) => acc.minus(curr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Multiplies an array of numbers and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function multi(data) {
|
||||
if (data.length > 0) {
|
||||
return data.reduce((acc, curr) => acc.times(curr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Divides an array of numbers and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function div(data) {
|
||||
if (data.length > 0) {
|
||||
return data.reduce((acc, curr) => acc.div(curr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes mean of a number array and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function mean(data) {
|
||||
if (data.length > 0) {
|
||||
return sum(data).div(data.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes median of a number array and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function median(data) {
|
||||
if ((data.length % 2) === 0 && data.length > 0) {
|
||||
data.sort(function(a, b){
|
||||
return a.minus(b);
|
||||
});
|
||||
const first = data[Math.floor(data.length / 2)];
|
||||
const second = data[Math.floor(data.length / 2) - 1];
|
||||
return mean([first, second]);
|
||||
} else {
|
||||
return data[Math.floor(data.length / 2)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes standard deviation of a number array and returns the value.
|
||||
*
|
||||
* @param {BigNumber[]} data
|
||||
* @returns {BigNumber}
|
||||
*/
|
||||
export function stdDev(data) {
|
||||
if (data.length > 0) {
|
||||
const avg = mean(data);
|
||||
let devSum = new BigNumber(0);
|
||||
data.map((datum) => {
|
||||
devSum = devSum.plus(datum.minus(avg).pow(2));
|
||||
});
|
||||
return devSum.div(data.length).sqrt();
|
||||
}
|
||||
}
|
48
src/core/lib/BCD.mjs
Executable file
48
src/core/lib/BCD.mjs
Executable file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Binary Code Decimal resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* BCD encoding schemes.
|
||||
*/
|
||||
export const ENCODING_SCHEME = [
|
||||
"8 4 2 1",
|
||||
"7 4 2 1",
|
||||
"4 2 2 1",
|
||||
"2 4 2 1",
|
||||
"8 4 -2 -1",
|
||||
"Excess-3",
|
||||
"IBM 8 4 2 1",
|
||||
];
|
||||
|
||||
/**
|
||||
* Lookup table for the binary value of each digit representation.
|
||||
*
|
||||
* I wrote a very nice algorithm to generate 8 4 2 1 encoding programatically,
|
||||
* but unfortunately it's much easier (if less elegant) to use lookup tables
|
||||
* when supporting multiple encoding schemes.
|
||||
*
|
||||
* "Practicality beats purity" - PEP 20
|
||||
*
|
||||
* In some schemes it is possible to represent the same value in multiple ways.
|
||||
* For instance, in 4 2 2 1 encoding, 0100 and 0010 both represent 2. Support
|
||||
* has not yet been added for this.
|
||||
*/
|
||||
export const ENCODING_LOOKUP = {
|
||||
"8 4 2 1": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
"7 4 2 1": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10],
|
||||
"4 2 2 1": [0, 1, 4, 5, 8, 9, 12, 13, 14, 15],
|
||||
"2 4 2 1": [0, 1, 2, 3, 4, 11, 12, 13, 14, 15],
|
||||
"8 4 -2 -1": [0, 7, 6, 5, 4, 11, 10, 9, 8, 15],
|
||||
"Excess-3": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
"IBM 8 4 2 1": [10, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
};
|
||||
|
||||
/**
|
||||
* BCD formats.
|
||||
*/
|
||||
export const FORMAT = ["Nibbles", "Bytes", "Raw"];
|
22
src/core/lib/Base58.mjs
Executable file
22
src/core/lib/Base58.mjs
Executable file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Base58 resources.
|
||||
*
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base58 alphabet options.
|
||||
*/
|
||||
export const ALPHABET_OPTIONS = [
|
||||
{
|
||||
name: "Bitcoin",
|
||||
value: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
|
||||
},
|
||||
{
|
||||
name: "Ripple",
|
||||
value: "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
|
||||
},
|
||||
];
|
142
src/core/lib/Base64.mjs
Executable file
142
src/core/lib/Base64.mjs
Executable file
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* Base64 functions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
|
||||
|
||||
/**
|
||||
* Base64's the input byte array using the given alphabet, returning a string.
|
||||
*
|
||||
* @param {byteArray|Uint8Array|string} data
|
||||
* @param {string} [alphabet="A-Za-z0-9+/="]
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "SGVsbG8="
|
||||
* toBase64([72, 101, 108, 108, 111]);
|
||||
*
|
||||
* // returns "SGVsbG8="
|
||||
* toBase64("Hello");
|
||||
*/
|
||||
export function toBase64(data, alphabet="A-Za-z0-9+/=") {
|
||||
if (!data) return "";
|
||||
if (typeof data == "string") {
|
||||
data = Utils.strToByteArray(data);
|
||||
}
|
||||
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
|
||||
let output = "",
|
||||
chr1, chr2, chr3,
|
||||
enc1, enc2, enc3, enc4,
|
||||
i = 0;
|
||||
|
||||
while (i < data.length) {
|
||||
chr1 = data[i++];
|
||||
chr2 = data[i++];
|
||||
chr3 = data[i++];
|
||||
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
|
||||
output += alphabet.charAt(enc1) + alphabet.charAt(enc2) +
|
||||
alphabet.charAt(enc3) + alphabet.charAt(enc4);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* UnBase64's the input string using the given alphabet, returning a byte array.
|
||||
*
|
||||
* @param {byteArray} data
|
||||
* @param {string} [alphabet="A-Za-z0-9+/="]
|
||||
* @param {string} [returnType="string"] - Either "string" or "byteArray"
|
||||
* @param {boolean} [removeNonAlphChars=true]
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns "Hello"
|
||||
* fromBase64("SGVsbG8=");
|
||||
*
|
||||
* // returns [72, 101, 108, 108, 111]
|
||||
* fromBase64("SGVsbG8=", null, "byteArray");
|
||||
*/
|
||||
export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) {
|
||||
if (!data) {
|
||||
return returnType === "string" ? "" : [];
|
||||
}
|
||||
|
||||
alphabet = alphabet || "A-Za-z0-9+/=";
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
|
||||
const output = [];
|
||||
let chr1, chr2, chr3,
|
||||
enc1, enc2, enc3, enc4,
|
||||
i = 0;
|
||||
|
||||
if (removeNonAlphChars) {
|
||||
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||
data = data.replace(re, "");
|
||||
}
|
||||
|
||||
while (i < data.length) {
|
||||
enc1 = alphabet.indexOf(data.charAt(i++));
|
||||
enc2 = alphabet.indexOf(data.charAt(i++) || "=");
|
||||
enc3 = alphabet.indexOf(data.charAt(i++) || "=");
|
||||
enc4 = alphabet.indexOf(data.charAt(i++) || "=");
|
||||
|
||||
enc2 = enc2 === -1 ? 64 : enc2;
|
||||
enc3 = enc3 === -1 ? 64 : enc3;
|
||||
enc4 = enc4 === -1 ? 64 : enc4;
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
output.push(chr1);
|
||||
|
||||
if (enc3 !== 64) {
|
||||
output.push(chr2);
|
||||
}
|
||||
if (enc4 !== 64) {
|
||||
output.push(chr3);
|
||||
}
|
||||
}
|
||||
|
||||
return returnType === "string" ? Utils.byteArrayToUtf8(output) : output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base64 alphabets.
|
||||
*/
|
||||
export const ALPHABET_OPTIONS = [
|
||||
{name: "Standard (RFC 4648): A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
|
||||
{name: "URL safe (RFC 4648 \u00A75): A-Za-z0-9-_", value: "A-Za-z0-9-_"},
|
||||
{name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
|
||||
{name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
|
||||
{name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
|
||||
{name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
|
||||
{name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
|
||||
{name: "Radix-64 (RFC 4880): 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
|
||||
{name: "Uuencoding: [space]-_", value: " -_"},
|
||||
{name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
|
||||
{name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
|
||||
{name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
|
||||
{name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
|
||||
];
|
45
src/core/lib/Base85.mjs
Normal file
45
src/core/lib/Base85.mjs
Normal file
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Base85 resources.
|
||||
*
|
||||
* @author PenguinGeorge [george@penguingeorge.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base85 alphabet options.
|
||||
*/
|
||||
export const ALPHABET_OPTIONS = [
|
||||
{
|
||||
name: "Standard",
|
||||
value: "!-u",
|
||||
},
|
||||
{
|
||||
name: "Z85 (ZeroMQ)",
|
||||
value: "0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#",
|
||||
},
|
||||
{
|
||||
name: "IPv6",
|
||||
value: "0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|~}",
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of the alphabet, when given the alphabet.
|
||||
*
|
||||
* @param {string} alphabet
|
||||
* @returns {string}
|
||||
*/
|
||||
export function alphabetName(alphabet) {
|
||||
alphabet = alphabet.replace("'", "'");
|
||||
alphabet = alphabet.replace("\"", """);
|
||||
alphabet = alphabet.replace("\\", "\");
|
||||
let name;
|
||||
|
||||
ALPHABET_OPTIONS.forEach(function(a) {
|
||||
if (escape(alphabet) === escape(a.value)) name = a.name;
|
||||
});
|
||||
|
||||
return name;
|
||||
}
|
70
src/core/lib/Binary.mjs
Normal file
70
src/core/lib/Binary.mjs
Normal file
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Binary functions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
|
||||
|
||||
/**
|
||||
* Convert a byte array into a binary string.
|
||||
*
|
||||
* @param {Uint8Array|byteArray} data
|
||||
* @param {string} [delim="Space"]
|
||||
* @param {number} [padding=8]
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "00010000 00100000 00110000"
|
||||
* toBinary([10,20,30]);
|
||||
*
|
||||
* // returns "00010000 00100000 00110000"
|
||||
* toBinary([10,20,30], ":");
|
||||
*/
|
||||
export function toBinary(data, delim="Space", padding=8) {
|
||||
if (!data) return "";
|
||||
|
||||
delim = Utils.charRep(delim);
|
||||
let output = "";
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
output += data[i].toString(2).padStart(padding, "0") + delim;
|
||||
}
|
||||
|
||||
if (delim.length) {
|
||||
return output.slice(0, -delim.length);
|
||||
} else {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a binary string into a byte array.
|
||||
*
|
||||
* @param {string} data
|
||||
* @param {string} [delim]
|
||||
* @param {number} [byteLen=8]
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns [10,20,30]
|
||||
* fromBinary("00010000 00100000 00110000");
|
||||
*
|
||||
* // returns [10,20,30]
|
||||
* fromBinary("00010000:00100000:00110000", "Colon");
|
||||
*/
|
||||
export function fromBinary(data, delim="Space", byteLen=8) {
|
||||
const delimRegex = Utils.regexRep(delim);
|
||||
data = data.replace(delimRegex, "");
|
||||
|
||||
const output = [];
|
||||
for (let i = 0; i < data.length; i += byteLen) {
|
||||
output.push(parseInt(data.substr(i, byteLen), 2));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
124
src/core/lib/BitwiseOp.mjs
Normal file
124
src/core/lib/BitwiseOp.mjs
Normal file
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Bitwise operation resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runs bitwise operations across the input data.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {byteArray} key
|
||||
* @param {function} func - The bitwise calculation to carry out
|
||||
* @param {boolean} nullPreserving
|
||||
* @param {string} scheme
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function bitOp (input, key, func, nullPreserving, scheme) {
|
||||
if (!key || !key.length) key = [0];
|
||||
const result = [];
|
||||
let x = null,
|
||||
k = null,
|
||||
o = null;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
k = key[i % key.length];
|
||||
if (scheme === "Cascade") k = input[i + 1] || 0;
|
||||
o = input[i];
|
||||
x = nullPreserving && (o === 0 || o === k) ? o : func(o, k);
|
||||
result.push(x);
|
||||
if (scheme &&
|
||||
scheme !== "Standard" &&
|
||||
!(nullPreserving && (o === 0 || o === k))) {
|
||||
switch (scheme) {
|
||||
case "Input differential":
|
||||
key[i % key.length] = x;
|
||||
break;
|
||||
case "Output differential":
|
||||
key[i % key.length] = o;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* XOR bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function xor(operand, key) {
|
||||
return operand ^ key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOT bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @returns {number}
|
||||
*/
|
||||
export function not(operand, _) {
|
||||
return ~operand & 0xff;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AND bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function and(operand, key) {
|
||||
return operand & key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* OR bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function or(operand, key) {
|
||||
return operand | key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ADD bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function add(operand, key) {
|
||||
return (operand + key) % 256;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SUB bitwise calculation.
|
||||
*
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
export function sub(operand, key) {
|
||||
const result = operand - key;
|
||||
return (result < 0) ? 256 + result : result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delimiter options for bitwise operations
|
||||
*/
|
||||
export const BITWISE_OP_DELIMS = ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"];
|
15
src/core/lib/Braille.mjs
Normal file
15
src/core/lib/Braille.mjs
Normal file
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Braille resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Braille lookup table.
|
||||
*/
|
||||
export const BRAILLE_LOOKUP = {
|
||||
ascii: " A1B'K2L@CIF/MSP\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\0Z7(_?W]#Y)=",
|
||||
dot6: "⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿"
|
||||
};
|
204
src/core/lib/CanvasComponents.mjs
Executable file
204
src/core/lib/CanvasComponents.mjs
Executable file
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* Various components for drawing diagrams on an HTML5 canvas.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Draws a line from one point to another
|
||||
*
|
||||
* @param ctx
|
||||
* @param startX
|
||||
* @param startY
|
||||
* @param endX
|
||||
* @param endY
|
||||
*/
|
||||
export function drawLine(ctx, startX, startY, endX, endY) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX, startY);
|
||||
ctx.lineTo(endX, endY);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a bar chart on the canvas.
|
||||
*
|
||||
* @param canvas
|
||||
* @param scores
|
||||
* @param xAxisLabel
|
||||
* @param yAxisLabel
|
||||
* @param numXLabels
|
||||
* @param numYLabels
|
||||
* @param fontSize
|
||||
*/
|
||||
export function drawBarChart(canvas, scores, xAxisLabel, yAxisLabel, numXLabels, numYLabels, fontSize) {
|
||||
fontSize = fontSize || 15;
|
||||
if (!numXLabels || numXLabels > Math.round(canvas.width / 50)) {
|
||||
numXLabels = Math.round(canvas.width / 50);
|
||||
}
|
||||
if (!numYLabels || numYLabels > Math.round(canvas.width / 50)) {
|
||||
numYLabels = Math.round(canvas.height / 50);
|
||||
}
|
||||
|
||||
// Graph properties
|
||||
const ctx = canvas.getContext("2d"),
|
||||
leftPadding = canvas.width * 0.08,
|
||||
rightPadding = canvas.width * 0.03,
|
||||
topPadding = canvas.height * 0.08,
|
||||
bottomPadding = canvas.height * 0.2,
|
||||
graphHeight = canvas.height - topPadding - bottomPadding,
|
||||
graphWidth = canvas.width - leftPadding - rightPadding,
|
||||
base = topPadding + graphHeight,
|
||||
ceil = topPadding;
|
||||
|
||||
ctx.font = fontSize + "px Arial";
|
||||
|
||||
// Draw axis
|
||||
ctx.lineWidth = "1.0";
|
||||
ctx.strokeStyle = "#444";
|
||||
drawLine(ctx, leftPadding, base, graphWidth + leftPadding, base); // x
|
||||
drawLine(ctx, leftPadding, base, leftPadding, ceil); // y
|
||||
|
||||
// Bar properties
|
||||
const barPadding = graphWidth * 0.003,
|
||||
barWidth = (graphWidth - (barPadding * scores.length)) / scores.length,
|
||||
max = Math.max.apply(Math, scores);
|
||||
let currX = leftPadding + barPadding;
|
||||
|
||||
// Draw bars
|
||||
ctx.fillStyle = "green";
|
||||
for (let i = 0; i < scores.length; i++) {
|
||||
const h = scores[i] / max * graphHeight;
|
||||
ctx.fillRect(currX, base - h, barWidth, h);
|
||||
currX += barWidth + barPadding;
|
||||
}
|
||||
|
||||
// Mark x axis
|
||||
ctx.fillStyle = "black";
|
||||
ctx.textAlign = "center";
|
||||
currX = leftPadding + barPadding;
|
||||
if (numXLabels >= scores.length) {
|
||||
// Mark every score
|
||||
for (let i = 0; i <= scores.length; i++) {
|
||||
ctx.fillText(i, currX, base + (bottomPadding * 0.3));
|
||||
currX += barWidth + barPadding;
|
||||
}
|
||||
} else {
|
||||
// Mark some scores
|
||||
for (let i = 0; i <= numXLabels; i++) {
|
||||
const val = Math.ceil((scores.length / numXLabels) * i);
|
||||
currX = (graphWidth / numXLabels) * i + leftPadding;
|
||||
ctx.fillText(val, currX, base + (bottomPadding * 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark y axis
|
||||
ctx.textAlign = "right";
|
||||
let currY;
|
||||
if (numYLabels >= max) {
|
||||
// Mark every increment
|
||||
for (let i = 0; i <= max; i++) {
|
||||
currY = base - (i / max * graphHeight) + fontSize / 3;
|
||||
ctx.fillText(i, leftPadding * 0.8, currY);
|
||||
}
|
||||
} else {
|
||||
// Mark some increments
|
||||
for (let i = 0; i <= numYLabels; i++) {
|
||||
const val = Math.ceil((max / numYLabels) * i);
|
||||
currY = base - (val / max * graphHeight) + fontSize / 3;
|
||||
ctx.fillText(val, leftPadding * 0.8, currY);
|
||||
}
|
||||
}
|
||||
|
||||
// Label x axis
|
||||
if (xAxisLabel) {
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(xAxisLabel, graphWidth / 2 + leftPadding, base + bottomPadding * 0.8);
|
||||
}
|
||||
|
||||
// Label y axis
|
||||
if (yAxisLabel) {
|
||||
ctx.save();
|
||||
const x = leftPadding * 0.3,
|
||||
y = graphHeight / 2 + topPadding;
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(yAxisLabel, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a scale bar on the canvas.
|
||||
*
|
||||
* @param canvas
|
||||
* @param score
|
||||
* @param max
|
||||
* @param markings
|
||||
*/
|
||||
export function drawScaleBar(canvas, score, max, markings) {
|
||||
// Bar properties
|
||||
const ctx = canvas.getContext("2d"),
|
||||
leftPadding = canvas.width * 0.01,
|
||||
rightPadding = canvas.width * 0.01,
|
||||
topPadding = canvas.height * 0.1,
|
||||
bottomPadding = canvas.height * 0.35,
|
||||
barHeight = canvas.height - topPadding - bottomPadding,
|
||||
barWidth = canvas.width - leftPadding - rightPadding;
|
||||
|
||||
// Scale properties
|
||||
const proportion = score / max;
|
||||
|
||||
// Draw bar outline
|
||||
ctx.strokeRect(leftPadding, topPadding, barWidth, barHeight);
|
||||
|
||||
// Shade in up to proportion
|
||||
const grad = ctx.createLinearGradient(leftPadding, 0, barWidth + leftPadding, 0);
|
||||
grad.addColorStop(0, "green");
|
||||
grad.addColorStop(0.5, "gold");
|
||||
grad.addColorStop(1, "red");
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(leftPadding, topPadding, barWidth * proportion, barHeight);
|
||||
|
||||
// Add markings
|
||||
let x0, y0, x1, y1;
|
||||
ctx.fillStyle = "black";
|
||||
ctx.textAlign = "center";
|
||||
ctx.font = "13px Arial";
|
||||
for (let i = 0; i < markings.length; i++) {
|
||||
// Draw min line down
|
||||
x0 = barWidth / max * markings[i].min + leftPadding;
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.1);
|
||||
x1 = x0;
|
||||
y1 = topPadding + barHeight + (bottomPadding * 0.3);
|
||||
drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Draw max line down
|
||||
x0 = barWidth / max * markings[i].max + leftPadding;
|
||||
x1 = x0;
|
||||
drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Join min and max lines
|
||||
x0 = barWidth / max * markings[i].min + leftPadding;
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.3);
|
||||
x1 = barWidth / max * markings[i].max + leftPadding;
|
||||
y1 = y0;
|
||||
drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Add label
|
||||
if (markings[i].max >= max * 0.9) {
|
||||
ctx.textAlign = "right";
|
||||
x0 = x1;
|
||||
} else if (markings[i].max <= max * 0.1) {
|
||||
ctx.textAlign = "left";
|
||||
} else {
|
||||
x0 = x0 + (x1 - x0) / 2;
|
||||
}
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.8);
|
||||
ctx.fillText(markings[i].label, x0, y0);
|
||||
}
|
||||
}
|
58
src/core/lib/ChrEnc.mjs
Normal file
58
src/core/lib/ChrEnc.mjs
Normal file
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Character encoding resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Character encoding format mappings.
|
||||
*/
|
||||
export const IO_FORMAT = {
|
||||
"UTF-8 (65001)": 65001,
|
||||
"UTF-7 (65000)": 65000,
|
||||
"UTF16LE (1200)": 1200,
|
||||
"UTF16BE (1201)": 1201,
|
||||
"UTF16 (1201)": 1201,
|
||||
"IBM EBCDIC International (500)": 500,
|
||||
"IBM EBCDIC US-Canada (37)": 37,
|
||||
"Windows-874 Thai (874)": 874,
|
||||
"Japanese Shift-JIS (932)": 932,
|
||||
"Simplified Chinese GBK (936)": 936,
|
||||
"Korean (949)": 949,
|
||||
"Traditional Chinese Big5 (950)": 950,
|
||||
"Windows-1250 Central European (1250)": 1250,
|
||||
"Windows-1251 Cyrillic (1251)": 1251,
|
||||
"Windows-1252 Latin (1252)": 1252,
|
||||
"Windows-1253 Greek (1253)": 1253,
|
||||
"Windows-1254 Turkish (1254)": 1254,
|
||||
"Windows-1255 Hebrew (1255)": 1255,
|
||||
"Windows-1256 Arabic (1256)": 1256,
|
||||
"Windows-1257 Baltic (1257)": 1257,
|
||||
"Windows-1258 Vietnam (1258)": 1258,
|
||||
"US-ASCII (20127)": 20127,
|
||||
"Simplified Chinese GB2312 (20936)": 20936,
|
||||
"KOI8-R Russian Cyrillic (20866)": 20866,
|
||||
"KOI8-U Ukrainian Cyrillic (21866)": 21866,
|
||||
"ISO-8859-1 Latin 1 Western European (28591)": 28591,
|
||||
"ISO-8859-2 Latin 2 Central European (28592)": 28592,
|
||||
"ISO-8859-3 Latin 3 South European (28593)": 28593,
|
||||
"ISO-8859-4 Latin 4 North European (28594)": 28594,
|
||||
"ISO-8859-5 Latin/Cyrillic (28595)": 28595,
|
||||
"ISO-8859-6 Latin/Arabic (28596)": 28596,
|
||||
"ISO-8859-7 Latin/Greek (28597)": 28597,
|
||||
"ISO-8859-8 Latin/Hebrew (28598)": 28598,
|
||||
"ISO-8859-9 Latin 5 Turkish (28599)": 28599,
|
||||
"ISO-8859-10 Latin 6 Nordic (28600)": 28600,
|
||||
"ISO-8859-11 Latin/Thai (28601)": 28601,
|
||||
"ISO-8859-13 Latin 7 Baltic Rim (28603)": 28603,
|
||||
"ISO-8859-14 Latin 8 Celtic (28604)": 28604,
|
||||
"ISO-8859-15 Latin 9 (28605)": 28605,
|
||||
"ISO-8859-16 Latin 10 (28606)": 28606,
|
||||
"ISO-2022 JIS Japanese (50222)": 50222,
|
||||
"EUC Japanese (51932)": 51932,
|
||||
"EUC Korean (51949)": 51949,
|
||||
"Simplified Chinese GB18030 (54936)": 54936,
|
||||
};
|
||||
|
82
src/core/lib/Ciphers.mjs
Normal file
82
src/core/lib/Ciphers.mjs
Normal file
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* Cipher functions.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
*
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError";
|
||||
import CryptoJS from "crypto-js";
|
||||
|
||||
/**
|
||||
* Affine Cipher Encode operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
export function affineEncode(input, args) {
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyz",
|
||||
a = args[0],
|
||||
b = args[1];
|
||||
let output = "";
|
||||
|
||||
if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
|
||||
throw new OperationError("The values of a and b can only be integers.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (alphabet.indexOf(input[i]) >= 0) {
|
||||
// Uses the affine function ax+b % m = y (where m is length of the alphabet)
|
||||
output += alphabet[((a * alphabet.indexOf(input[i])) + b) % 26];
|
||||
} else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
|
||||
// Same as above, accounting for uppercase
|
||||
output += alphabet[((a * alphabet.indexOf(input[i].toLowerCase())) + b) % 26].toUpperCase();
|
||||
} else {
|
||||
// Non-alphabetic characters
|
||||
output += input[i];
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a polybius square for the given keyword
|
||||
*
|
||||
* @private
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @param {string} keyword - Must be upper case
|
||||
* @returns {string}
|
||||
*/
|
||||
export function genPolybiusSquare (keyword) {
|
||||
const alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ",
|
||||
polArray = `${keyword}${alpha}`.split("").unique(),
|
||||
polybius = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
polybius[i] = polArray.slice(i*5, i*5 + 5);
|
||||
}
|
||||
|
||||
return polybius;
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping of string formats to their classes in the CryptoJS library.
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
*/
|
||||
export const 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,
|
||||
};
|
29
src/core/lib/Code.mjs
Normal file
29
src/core/lib/Code.mjs
Normal file
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Code resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* This tries to rename variable names in a code snippet according to a function.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {function} replacer - This function will be fed the token which should be renamed.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function replaceVariableNames(input, replacer) {
|
||||
const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
|
||||
|
||||
return input.replace(tokenRegex, (...args) => {
|
||||
const match = args[0],
|
||||
quotes = args[1];
|
||||
|
||||
if (!quotes) {
|
||||
return match;
|
||||
} else {
|
||||
return replacer(match);
|
||||
}
|
||||
});
|
||||
}
|
655
src/core/lib/ConvertCoordinates.mjs
Normal file
655
src/core/lib/ConvertCoordinates.mjs
Normal file
|
@ -0,0 +1,655 @@
|
|||
/**
|
||||
* Co-ordinate conversion resources.
|
||||
*
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import geohash from "ngeohash";
|
||||
import geodesy from "geodesy";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Co-ordinate formats
|
||||
*/
|
||||
export const FORMATS = [
|
||||
"Degrees Minutes Seconds",
|
||||
"Degrees Decimal Minutes",
|
||||
"Decimal Degrees",
|
||||
"Geohash",
|
||||
"Military Grid Reference System",
|
||||
"Ordnance Survey National Grid",
|
||||
"Universal Transverse Mercator"
|
||||
];
|
||||
|
||||
/**
|
||||
* Formats that should be passed to the conversion module as-is
|
||||
*/
|
||||
const NO_CHANGE = [
|
||||
"Geohash",
|
||||
"Military Grid Reference System",
|
||||
"Ordnance Survey National Grid",
|
||||
"Universal Transverse Mercator",
|
||||
];
|
||||
|
||||
/**
|
||||
* Convert a given latitude and longitude into a different format.
|
||||
*
|
||||
* @param {string} input - Input string to be converted
|
||||
* @param {string} inFormat - Format of the input coordinates
|
||||
* @param {string} inDelim - The delimiter splitting the lat/long of the input
|
||||
* @param {string} outFormat - Format to convert to
|
||||
* @param {string} outDelim - The delimiter to separate the output with
|
||||
* @param {string} includeDir - Whether or not to include the compass direction in the output
|
||||
* @param {number} precision - Precision of the result
|
||||
* @returns {string} A formatted string of the converted co-ordinates
|
||||
*/
|
||||
export function convertCoordinates (input, inFormat, inDelim, outFormat, outDelim, includeDir, precision) {
|
||||
let isPair = false,
|
||||
split,
|
||||
latlon,
|
||||
convLat,
|
||||
convLon,
|
||||
conv,
|
||||
hash,
|
||||
utm,
|
||||
mgrs,
|
||||
osng,
|
||||
splitLat,
|
||||
splitLong,
|
||||
lat,
|
||||
lon;
|
||||
|
||||
// Can't have a precision less than 0!
|
||||
if (precision < 0) {
|
||||
precision = 0;
|
||||
}
|
||||
|
||||
if (inDelim === "Auto") {
|
||||
// Try to detect a delimiter in the input.
|
||||
inDelim = findDelim(input);
|
||||
if (inDelim === null) {
|
||||
throw new OperationError("Unable to detect the input delimiter automatically.");
|
||||
}
|
||||
} else if (!inDelim.includes("Direction")) {
|
||||
// Convert the delimiter argument value to the actual character
|
||||
inDelim = realDelim(inDelim);
|
||||
}
|
||||
|
||||
if (inFormat === "Auto") {
|
||||
// Try to detect the format of the input data
|
||||
inFormat = findFormat(input, inDelim);
|
||||
if (inFormat === null) {
|
||||
throw new OperationError("Unable to detect the input format automatically.");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the output delimiter argument to the real character
|
||||
outDelim = realDelim(outDelim);
|
||||
|
||||
if (!NO_CHANGE.includes(inFormat)) {
|
||||
if (inDelim.includes("Direction")) {
|
||||
// Split on directions
|
||||
split = input.split(/[NnEeSsWw]/g);
|
||||
if (split[0] === "") {
|
||||
// Remove first element if direction preceding
|
||||
split = split.slice(1);
|
||||
}
|
||||
} else {
|
||||
split = input.split(inDelim);
|
||||
}
|
||||
// Replace any co-ordinate symbols with spaces so we can split on them later
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
split[i] = split[i].replace(/[°˝´'"]/g, " ");
|
||||
}
|
||||
if (split.length > 1) {
|
||||
isPair = true;
|
||||
}
|
||||
} else {
|
||||
// Remove any delimiters from the input
|
||||
input = input.replace(inDelim, "");
|
||||
isPair = true;
|
||||
}
|
||||
|
||||
// Conversions from the input format into a geodesy latlon object
|
||||
switch (inFormat) {
|
||||
case "Geohash":
|
||||
hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = new geodesy.LatLonEllipsoidal(hash.latitude, hash.longitude);
|
||||
break;
|
||||
case "Military Grid Reference System":
|
||||
utm = geodesy.Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm();
|
||||
latlon = utm.toLatLonE();
|
||||
break;
|
||||
case "Ordnance Survey National Grid":
|
||||
osng = geodesy.OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = geodesy.OsGridRef.osGridToLatLon(osng);
|
||||
break;
|
||||
case "Universal Transverse Mercator":
|
||||
// Geodesy needs a space between the first 2 digits and the next letter
|
||||
if (/^[\d]{2}[A-Za-z]/.test(input)) {
|
||||
input = input.slice(0, 2) + " " + input.slice(2);
|
||||
}
|
||||
utm = geodesy.Utm.parse(input);
|
||||
latlon = utm.toLatLonE();
|
||||
break;
|
||||
case "Degrees Minutes Seconds":
|
||||
if (isPair) {
|
||||
// Split up the lat/long into degrees / minutes / seconds values
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
|
||||
if (splitLat.length >= 3 && splitLong.length >= 3) {
|
||||
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10);
|
||||
lon = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
} else {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
|
||||
}
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(split[0]);
|
||||
if (splitLat.length >= 3) {
|
||||
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
} else {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Degrees Decimal Minutes":
|
||||
if (isPair) {
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
if (splitLat.length !== 2 || splitLong.length !== 2) {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
|
||||
}
|
||||
// Convert to decimal degrees, and then convert to a geodesy object
|
||||
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
|
||||
lon = convDDMToDD(splitLong[0], splitLong[1], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(input);
|
||||
if (splitLat.length !== 2) {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
|
||||
}
|
||||
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
}
|
||||
break;
|
||||
case "Decimal Degrees":
|
||||
if (isPair) {
|
||||
splitLat = splitInput(split[0]);
|
||||
splitLong = splitInput(split[1]);
|
||||
if (splitLat.length !== 1 || splitLong.length !== 1) {
|
||||
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
|
||||
}
|
||||
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLong[0]);
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(split[0]);
|
||||
if (splitLat.length !== 1) {
|
||||
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
|
||||
}
|
||||
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLat[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown input format '${inFormat}'`);
|
||||
}
|
||||
|
||||
// Everything is now a geodesy latlon object
|
||||
// These store the latitude and longitude as decimal
|
||||
if (inFormat.includes("Degrees")) {
|
||||
// If the input string contains directions, we need to check if they're S or W.
|
||||
// If either of the directions are, we should make the decimal value negative
|
||||
const dirs = input.toUpperCase().match(/[NESW]/g);
|
||||
if (dirs && dirs.length >= 1) {
|
||||
// Make positive lat/lon values with S/W directions into negative values
|
||||
if (dirs[0] === "S" || dirs[0] === "W" && latlon.lat > 0) {
|
||||
latlon.lat = -latlon.lat;
|
||||
}
|
||||
if (dirs.length >= 2) {
|
||||
if (dirs[1] === "S" || dirs[1] === "W" && latlon.lon > 0) {
|
||||
latlon.lon = -latlon.lon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find the compass directions of the lat and long
|
||||
const [latDir, longDir] = findDirs(latlon.lat + "," + latlon.lon, ",");
|
||||
|
||||
// Output conversions for each output format
|
||||
switch (outFormat) {
|
||||
case "Decimal Degrees":
|
||||
// We could use the built in latlon.toString(),
|
||||
// but this makes adjusting the output harder
|
||||
lat = convDDToDD(latlon.lat, precision);
|
||||
lon = convDDToDD(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Degrees Decimal Minutes":
|
||||
lat = convDDToDDM(latlon.lat, precision);
|
||||
lon = convDDToDDM(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Degrees Minutes Seconds":
|
||||
lat = convDDToDMS(latlon.lat, precision);
|
||||
lon = convDDToDMS(latlon.lon, precision);
|
||||
convLat = lat.string;
|
||||
convLon = lon.string;
|
||||
break;
|
||||
case "Geohash":
|
||||
convLat = geohash.encode(latlon.lat, latlon.lon, precision);
|
||||
break;
|
||||
case "Military Grid Reference System":
|
||||
utm = latlon.toUtm();
|
||||
mgrs = utm.toMgrs();
|
||||
// MGRS wants a precision that's an even number between 2 and 10
|
||||
if (precision % 2 !== 0) {
|
||||
precision = precision + 1;
|
||||
}
|
||||
if (precision > 10) {
|
||||
precision = 10;
|
||||
}
|
||||
convLat = mgrs.toString(precision);
|
||||
break;
|
||||
case "Ordnance Survey National Grid":
|
||||
osng = geodesy.OsGridRef.latLonToOsGrid(latlon);
|
||||
if (osng.toString() === "") {
|
||||
throw new OperationError("Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?");
|
||||
}
|
||||
// OSNG wants a precision that's an even number between 2 and 10
|
||||
if (precision % 2 !== 0) {
|
||||
precision = precision + 1;
|
||||
}
|
||||
if (precision > 10) {
|
||||
precision = 10;
|
||||
}
|
||||
convLat = osng.toString(precision);
|
||||
break;
|
||||
case "Universal Transverse Mercator":
|
||||
utm = latlon.toUtm();
|
||||
convLat = utm.toString(precision);
|
||||
break;
|
||||
}
|
||||
|
||||
if (convLat === undefined) {
|
||||
throw new OperationError("Error converting co-ordinates.");
|
||||
}
|
||||
|
||||
if (outFormat.includes("Degrees")) {
|
||||
// Format DD/DDM/DMS for output
|
||||
// If we're outputting a compass direction, remove the negative sign
|
||||
if (latDir === "S" && includeDir !== "None") {
|
||||
convLat = convLat.replace("-", "");
|
||||
}
|
||||
if (longDir === "W" && includeDir !== "None") {
|
||||
convLon = convLon.replace("-", "");
|
||||
}
|
||||
|
||||
let outConv = "";
|
||||
if (includeDir === "Before") {
|
||||
outConv += latDir + " ";
|
||||
}
|
||||
|
||||
outConv += convLat;
|
||||
if (includeDir === "After") {
|
||||
outConv += " " + latDir;
|
||||
}
|
||||
outConv += outDelim;
|
||||
if (isPair) {
|
||||
if (includeDir === "Before") {
|
||||
outConv += longDir + " ";
|
||||
}
|
||||
outConv += convLon;
|
||||
if (includeDir === "After") {
|
||||
outConv += " " + longDir;
|
||||
}
|
||||
outConv += outDelim;
|
||||
}
|
||||
conv = outConv;
|
||||
} else {
|
||||
conv = convLat + outDelim;
|
||||
}
|
||||
|
||||
return conv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split up the input using a space or degrees signs, and sanitise the result
|
||||
*
|
||||
* @param {string} input - The input data to be split
|
||||
* @returns {number[]} An array of the different items in the string, stored as floats
|
||||
*/
|
||||
function splitInput (input){
|
||||
const split = [];
|
||||
|
||||
input.split(/\s+/).forEach(item => {
|
||||
// Remove any character that isn't a digit, decimal point or negative sign
|
||||
item = item.replace(/[^0-9.-]/g, "");
|
||||
if (item.length > 0){
|
||||
// Turn the item into a float
|
||||
split.push(parseFloat(item));
|
||||
}
|
||||
});
|
||||
return split;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Degrees Minutes Seconds to Decimal Degrees
|
||||
*
|
||||
* @param {number} degrees - The degrees of the input co-ordinates
|
||||
* @param {number} minutes - The minutes of the input co-ordinates
|
||||
* @param {number} seconds - The seconds of the input co-ordinates
|
||||
* @param {number} precision - The precision the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDMSToDD (degrees, minutes, seconds, precision){
|
||||
const absDegrees = Math.abs(degrees);
|
||||
let conv = absDegrees + (minutes / 60) + (seconds / 3600);
|
||||
let outString = round(conv, precision) + "°";
|
||||
if (isNegativeZero(degrees) || degrees < 0) {
|
||||
conv = -conv;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": conv,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees Minutes to Decimal Degrees
|
||||
*
|
||||
* @param {number} degrees - The input degrees to be converted
|
||||
* @param {number} minutes - The input minutes to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDMToDD (degrees, minutes, precision) {
|
||||
const absDegrees = Math.abs(degrees);
|
||||
let conv = absDegrees + minutes / 60;
|
||||
let outString = round(conv, precision) + "°";
|
||||
if (isNegativeZero(degrees) || degrees < 0) {
|
||||
conv = -conv;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": conv,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Decimal Degrees
|
||||
*
|
||||
* Doesn't affect the input, just puts it into an object
|
||||
* @param {number} degrees - The input degrees to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDD (degrees, precision) {
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"string": round(degrees, precision) + "°"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Degrees Minutes Seconds
|
||||
*
|
||||
* @param {number} decDegrees - The input data to be converted
|
||||
* @param {number} precision - The precision which the result should be rounded to
|
||||
* @returns {{string: string, degrees: number, minutes: number, seconds: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes, .seconds), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDMS (decDegrees, precision) {
|
||||
const absDegrees = Math.abs(decDegrees);
|
||||
let degrees = Math.floor(absDegrees);
|
||||
const minutes = Math.floor(60 * (absDegrees - degrees)),
|
||||
seconds = round(3600 * (absDegrees - degrees) - 60 * minutes, precision);
|
||||
let outString = degrees + "° " + minutes + "' " + seconds + "\"";
|
||||
if (isNegativeZero(decDegrees) || decDegrees < 0) {
|
||||
degrees = -degrees;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"minutes": minutes,
|
||||
"seconds": seconds,
|
||||
"string": outString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Decimal Degrees to Degrees Decimal Minutes
|
||||
*
|
||||
* @param {number} decDegrees - The input degrees to be converted
|
||||
* @param {number} precision - The precision the input data should be rounded to
|
||||
* @returns {{string: string, degrees: number, minutes: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDDToDDM (decDegrees, precision) {
|
||||
const absDegrees = Math.abs(decDegrees);
|
||||
let degrees = Math.floor(absDegrees);
|
||||
const minutes = absDegrees - degrees,
|
||||
decMinutes = round(minutes * 60, precision);
|
||||
let outString = degrees + "° " + decMinutes + "'";
|
||||
if (decDegrees < 0 || isNegativeZero(decDegrees)) {
|
||||
degrees = -degrees;
|
||||
outString = "-" + outString;
|
||||
}
|
||||
|
||||
return {
|
||||
"degrees": degrees,
|
||||
"minutes": decMinutes,
|
||||
"string": outString,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the compass directions in an input string
|
||||
*
|
||||
* @param {string} input - The input co-ordinates containing the direction
|
||||
* @param {string} delim - The delimiter separating latitide and longitude
|
||||
* @returns {string[]} String array containing the latitude and longitude directions
|
||||
*/
|
||||
export function findDirs(input, delim) {
|
||||
const upperInput = input.toUpperCase();
|
||||
const dirExp = new RegExp(/[NESW]/g);
|
||||
|
||||
const dirs = upperInput.match(dirExp);
|
||||
|
||||
if (dirs) {
|
||||
// If there's actually compass directions
|
||||
// in the input, use these to work out the direction
|
||||
if (dirs.length <= 2 && dirs.length >= 1) {
|
||||
return dirs.length === 2 ? [dirs[0], dirs[1]] : [dirs[0], ""];
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing was returned, so guess the directions
|
||||
let lat = upperInput,
|
||||
long,
|
||||
latDir = "",
|
||||
longDir = "";
|
||||
if (!delim.includes("Direction")) {
|
||||
if (upperInput.includes(delim)) {
|
||||
const split = upperInput.split(delim);
|
||||
if (split.length >= 1) {
|
||||
if (split[0] !== "") {
|
||||
lat = split[0];
|
||||
}
|
||||
if (split.length >= 2 && split[1] !== "") {
|
||||
long = split[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const split = upperInput.split(dirExp);
|
||||
if (split.length > 1) {
|
||||
lat = split[0] === "" ? split[1] : split[0];
|
||||
if (split.length > 2 && split[2] !== "") {
|
||||
long = split[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lat) {
|
||||
lat = parseFloat(lat);
|
||||
latDir = lat < 0 ? "S" : "N";
|
||||
}
|
||||
|
||||
if (long) {
|
||||
long = parseFloat(long);
|
||||
longDir = long < 0 ? "W" : "E";
|
||||
}
|
||||
|
||||
return [latDir, longDir];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the co-ordinate format of the input data
|
||||
*
|
||||
* @param {string} input - The input data whose format we need to detect
|
||||
* @param {string} delim - The delimiter separating the data in input
|
||||
* @returns {string} The input format
|
||||
*/
|
||||
export function findFormat (input, delim) {
|
||||
let testData;
|
||||
const mgrsPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]{1}\s?[A-HJ-NP-Z][A-HJ-NP-V]\s?[0-9\s]+/),
|
||||
osngPattern = new RegExp(/^[A-HJ-Z]{2}\s+[0-9\s]+$/),
|
||||
geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/),
|
||||
utmPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]\s[0-9.]+\s?[0-9.]+$/),
|
||||
degPattern = new RegExp(/[°'"]/g);
|
||||
|
||||
input = input.trim();
|
||||
|
||||
if (delim !== null && delim.includes("Direction")) {
|
||||
const split = input.split(/[NnEeSsWw]/);
|
||||
if (split.length > 1) {
|
||||
testData = split[0] === "" ? split[1] : split[0];
|
||||
}
|
||||
} else if (delim !== null && delim !== "") {
|
||||
if (input.includes(delim)) {
|
||||
const split = input.split(delim);
|
||||
if (split.length > 1) {
|
||||
testData = split[0] === "" ? split[1] : split[0];
|
||||
}
|
||||
} else {
|
||||
testData = input;
|
||||
}
|
||||
}
|
||||
|
||||
// Test non-degrees formats
|
||||
if (!degPattern.test(input)) {
|
||||
const filteredInput = input.toUpperCase().replace(delim, "");
|
||||
|
||||
if (utmPattern.test(filteredInput)) {
|
||||
return "Universal Transverse Mercator";
|
||||
}
|
||||
if (mgrsPattern.test(filteredInput)) {
|
||||
return "Military Grid Reference System";
|
||||
}
|
||||
if (osngPattern.test(filteredInput)) {
|
||||
return "Ordnance Survey National Grid";
|
||||
}
|
||||
if (geohashPattern.test(filteredInput)) {
|
||||
return "Geohash";
|
||||
}
|
||||
}
|
||||
|
||||
// Test DMS/DDM/DD formats
|
||||
if (testData !== undefined) {
|
||||
const split = splitInput(testData);
|
||||
switch (split.length){
|
||||
case 3:
|
||||
return "Degrees Minutes Seconds";
|
||||
case 2:
|
||||
return "Degrees Decimal Minutes";
|
||||
case 1:
|
||||
return "Decimal Degrees";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically find the delimeter type from the given input
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string} Delimiter type
|
||||
*/
|
||||
export function findDelim (input) {
|
||||
input = input.trim();
|
||||
const delims = [",", ";", ":"];
|
||||
const testDir = input.match(/[NnEeSsWw]/g);
|
||||
if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
|
||||
// Possibly contains a direction
|
||||
const splitInput = input.split(/[NnEeSsWw]/);
|
||||
if (splitInput.length <= 3 && splitInput.length > 0) {
|
||||
// If there's 3 splits (one should be empty), then assume we have directions
|
||||
if (splitInput[0] === "") {
|
||||
return "Direction Preceding";
|
||||
} else if (splitInput[splitInput.length - 1] === "") {
|
||||
return "Direction Following";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through the standard delimiters, and try to find them in the input
|
||||
for (let i = 0; i < delims.length; i++) {
|
||||
const delim = delims[i];
|
||||
if (input.includes(delim)) {
|
||||
const splitInput = input.split(delim);
|
||||
if (splitInput.length <= 3 && splitInput.length > 0) {
|
||||
// Don't want to try and convert more than 2 co-ordinates
|
||||
return delim;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real string for a delimiter name.
|
||||
*
|
||||
* @param {string} delim The delimiter to be matched
|
||||
* @returns {string}
|
||||
*/
|
||||
export function realDelim (delim) {
|
||||
return {
|
||||
"Auto": "Auto",
|
||||
"Space": " ",
|
||||
"\\n": "\n",
|
||||
"Comma": ",",
|
||||
"Semi-colon": ";",
|
||||
"Colon": ":"
|
||||
}[delim];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a zero is negative
|
||||
*
|
||||
* @param {number} zero
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNegativeZero(zero) {
|
||||
return zero === 0 && (1/zero < 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds a number to a specified number of decimal places
|
||||
*
|
||||
* @param {number} input - The number to be rounded
|
||||
* @param {precision} precision - The number of decimal places the number should be rounded to
|
||||
* @returns {number}
|
||||
*/
|
||||
function round(input, precision) {
|
||||
precision = Math.pow(10, precision);
|
||||
return Math.round(input * precision) / precision;
|
||||
}
|
313
src/core/lib/DateTime.mjs
Normal file
313
src/core/lib/DateTime.mjs
Normal file
|
@ -0,0 +1,313 @@
|
|||
/**
|
||||
* DateTime resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* DateTime units.
|
||||
*/
|
||||
export const UNITS = ["Seconds (s)", "Milliseconds (ms)", "Microseconds (μs)", "Nanoseconds (ns)"];
|
||||
|
||||
/**
|
||||
* DateTime formats.
|
||||
*/
|
||||
export const DATETIME_FORMATS = [
|
||||
{
|
||||
name: "Standard date and time",
|
||||
value: "DD/MM/YYYY HH:mm:ss"
|
||||
},
|
||||
{
|
||||
name: "American-style date and time",
|
||||
value: "MM/DD/YYYY HH:mm:ss"
|
||||
},
|
||||
{
|
||||
name: "International date and time",
|
||||
value: "YYYY-MM-DD HH:mm:ss"
|
||||
},
|
||||
{
|
||||
name: "Verbose date and time",
|
||||
value: "dddd Do MMMM YYYY HH:mm:ss Z z"
|
||||
},
|
||||
{
|
||||
name: "UNIX timestamp (seconds)",
|
||||
value: "X"
|
||||
},
|
||||
{
|
||||
name: "UNIX timestamp offset (milliseconds)",
|
||||
value: "x"
|
||||
},
|
||||
{
|
||||
name: "Automatic",
|
||||
value: ""
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* MomentJS DateTime formatting examples.
|
||||
*/
|
||||
export const FORMAT_EXAMPLES = `Format string tokens:
|
||||
<table class="table table-striped table-hover table-sm table-bordered" style="font-family: sans-serif">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>Token</th>
|
||||
<th>Output</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>Month</b></td>
|
||||
<td>M</td>
|
||||
<td>1 2 ... 11 12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Mo</td>
|
||||
<td>1st 2nd ... 11th 12th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>MM</td>
|
||||
<td>01 02 ... 11 12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>MMM</td>
|
||||
<td>Jan Feb ... Nov Dec</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>MMMM</td>
|
||||
<td>January February ... November December</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Quarter</b></td>
|
||||
<td>Q</td>
|
||||
<td>1 2 3 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Month</b></td>
|
||||
<td>D</td>
|
||||
<td>1 2 ... 30 31</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Do</td>
|
||||
<td>1st 2nd ... 30th 31st</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>DD</td>
|
||||
<td>01 02 ... 30 31</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Year</b></td>
|
||||
<td>DDD</td>
|
||||
<td>1 2 ... 364 365</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>DDDo</td>
|
||||
<td>1st 2nd ... 364th 365th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>DDDD</td>
|
||||
<td>001 002 ... 364 365</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Week</b></td>
|
||||
<td>d</td>
|
||||
<td>0 1 ... 5 6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>do</td>
|
||||
<td>0th 1st ... 5th 6th</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>dd</td>
|
||||
<td>Su Mo ... Fr Sa</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ddd</td>
|
||||
<td>Sun Mon ... Fri Sat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>dddd</td>
|
||||
<td>Sunday Monday ... Friday Saturday</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Week (Locale)</b></td>
|
||||
<td>e</td>
|
||||
<td>0 1 ... 5 6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Day of Week (ISO)</b></td>
|
||||
<td>E</td>
|
||||
<td>1 2 ... 6 7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Week of Year</b></td>
|
||||
<td>w</td>
|
||||
<td>1 2 ... 52 53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>wo</td>
|
||||
<td>1st 2nd ... 52nd 53rd</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ww</td>
|
||||
<td>01 02 ... 52 53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Week of Year (ISO)</b></td>
|
||||
<td>W</td>
|
||||
<td>1 2 ... 52 53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Wo</td>
|
||||
<td>1st 2nd ... 52nd 53rd</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>WW</td>
|
||||
<td>01 02 ... 52 53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Year</b></td>
|
||||
<td>YY</td>
|
||||
<td>70 71 ... 29 30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>YYYY</td>
|
||||
<td>1970 1971 ... 2029 2030</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Week Year</b></td>
|
||||
<td>gg</td>
|
||||
<td>70 71 ... 29 30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>gggg</td>
|
||||
<td>1970 1971 ... 2029 2030</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Week Year (ISO)</b></td>
|
||||
<td>GG</td>
|
||||
<td>70 71 ... 29 30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>GGGG</td>
|
||||
<td>1970 1971 ... 2029 2030</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>AM/PM</b></td>
|
||||
<td>A</td>
|
||||
<td>AM PM</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>a</td>
|
||||
<td>am pm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Hour</b></td>
|
||||
<td>H</td>
|
||||
<td>0 1 ... 22 23</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>HH</td>
|
||||
<td>00 01 ... 22 23</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>h</td>
|
||||
<td>1 2 ... 11 12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>hh</td>
|
||||
<td>01 02 ... 11 12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Minute</b></td>
|
||||
<td>m</td>
|
||||
<td>0 1 ... 58 59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>mm</td>
|
||||
<td>00 01 ... 58 59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Second</b></td>
|
||||
<td>s</td>
|
||||
<td>0 1 ... 58 59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ss</td>
|
||||
<td>00 01 ... 58 59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Fractional Second</b></td>
|
||||
<td>S</td>
|
||||
<td>0 1 ... 8 9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>SS</td>
|
||||
<td>00 01 ... 98 99</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>SSS</td>
|
||||
<td>000 001 ... 998 999</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>SSSS ... SSSSSSSSS</td>
|
||||
<td>000[0..] 001[0..] ... 998[0..] 999[0..]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Timezone</b></td>
|
||||
<td>z or zz</td>
|
||||
<td>EST CST ... MST PST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Z</td>
|
||||
<td>-07:00 -06:00 ... +06:00 +07:00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ZZ</td>
|
||||
<td>-0700 -0600 ... +0600 +0700</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Unix Timestamp</b></td>
|
||||
<td>X</td>
|
||||
<td>1360013296</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Unix Millisecond Timestamp</b></td>
|
||||
<td>x</td>
|
||||
<td>1360013296123</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>`;
|
||||
|
37
src/core/lib/Decimal.mjs
Normal file
37
src/core/lib/Decimal.mjs
Normal file
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Decimal functions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
|
||||
|
||||
/**
|
||||
* Convert a string of decimal values into a byte array.
|
||||
*
|
||||
* @param {string} data
|
||||
* @param {string} [delim]
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns [10,20,30]
|
||||
* fromDecimal("10 20 30");
|
||||
*
|
||||
* // returns [10,20,30]
|
||||
* fromDecimal("10:20:30", "Colon");
|
||||
*/
|
||||
export function fromDecimal(data, delim="Auto") {
|
||||
delim = Utils.charRep(delim);
|
||||
const output = [];
|
||||
let byteStr = data.split(delim);
|
||||
if (byteStr[byteStr.length-1] === "")
|
||||
byteStr = byteStr.slice(0, byteStr.length-1);
|
||||
|
||||
for (let i = 0; i < byteStr.length; i++) {
|
||||
output[i] = parseInt(byteStr[i], 10);
|
||||
}
|
||||
return output;
|
||||
}
|
74
src/core/lib/Delim.mjs
Normal file
74
src/core/lib/Delim.mjs
Normal file
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Various delimiters
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generic sequence delimiters.
|
||||
*/
|
||||
export const DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"];
|
||||
|
||||
/**
|
||||
* Binary sequence delimiters.
|
||||
*/
|
||||
export const BIN_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"];
|
||||
|
||||
/**
|
||||
* Letter sequence delimiters.
|
||||
*/
|
||||
export const LETTER_DELIM_OPTIONS = ["Space", "Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"];
|
||||
|
||||
/**
|
||||
* Word sequence delimiters.
|
||||
*/
|
||||
export const WORD_DELIM_OPTIONS = ["Line feed", "CRLF", "Forward slash", "Backslash", "Comma", "Semi-colon", "Colon"];
|
||||
|
||||
/**
|
||||
* Input sequence delimiters.
|
||||
*/
|
||||
export const INPUT_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"];
|
||||
|
||||
/**
|
||||
* Armithmetic sequence delimiters
|
||||
*/
|
||||
export const ARITHMETIC_DELIM_OPTIONS = ["Line feed", "Space", "Comma", "Semi-colon", "Colon", "CRLF"];
|
||||
|
||||
/**
|
||||
* Hash delimiters
|
||||
*/
|
||||
export const HASH_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma"];
|
||||
|
||||
/**
|
||||
* IP delimiters
|
||||
*/
|
||||
export const IP_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma", "Semi-colon"];
|
||||
|
||||
/**
|
||||
* Split delimiters.
|
||||
*/
|
||||
export const SPLIT_DELIM_OPTIONS = [
|
||||
{name: "Comma", value: ","},
|
||||
{name: "Space", value: " "},
|
||||
{name: "Line feed", value: "\\n"},
|
||||
{name: "CRLF", value: "\\r\\n"},
|
||||
{name: "Semi-colon", value: ";"},
|
||||
{name: "Colon", value: ":"},
|
||||
{name: "Nothing (separate chars)", value: ""}
|
||||
];
|
||||
|
||||
/**
|
||||
* Join delimiters.
|
||||
*/
|
||||
export const JOIN_DELIM_OPTIONS = [
|
||||
{name: "Line feed", value: "\\n"},
|
||||
{name: "CRLF", value: "\\r\\n"},
|
||||
{name: "Space", value: " "},
|
||||
{name: "Comma", value: ","},
|
||||
{name: "Semi-colon", value: ";"},
|
||||
{name: "Colon", value: ":"},
|
||||
{name: "Nothing (join chars)", value: ""}
|
||||
];
|
||||
|
59
src/core/lib/Extract.mjs
Normal file
59
src/core/lib/Extract.mjs
Normal file
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Identifier extraction functions
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runs search operations across the input data using regular expressions.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {RegExp} searchRegex
|
||||
* @param {RegExp} removeRegex - A regular expression defining results to remove from the
|
||||
* final list
|
||||
* @param {boolean} includeTotal - Whether or not to include the total number of results
|
||||
* @returns {string}
|
||||
*/
|
||||
export function search (input, searchRegex, removeRegex, includeTotal) {
|
||||
let output = "",
|
||||
total = 0,
|
||||
match;
|
||||
|
||||
while ((match = searchRegex.exec(input))) {
|
||||
// Moves pointer when an empty string is matched (prevents infinite loop)
|
||||
if (match.index === searchRegex.lastIndex) {
|
||||
searchRegex.lastIndex++;
|
||||
}
|
||||
|
||||
if (removeRegex && removeRegex.test(match[0]))
|
||||
continue;
|
||||
total++;
|
||||
output += match[0] + "\n";
|
||||
}
|
||||
|
||||
if (includeTotal)
|
||||
output = "Total found: " + total + "\n\n" + output;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* URL regular expression
|
||||
*/
|
||||
const protocol = "[A-Z]+://",
|
||||
hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+",
|
||||
port = ":\\d+",
|
||||
path = "/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*" +
|
||||
"(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*";
|
||||
|
||||
export const URL_REGEX = new RegExp(protocol + hostname + "(?:" + port + ")?(?:" + path + ")?", "ig");
|
||||
|
||||
|
||||
/**
|
||||
* Domain name regular expression
|
||||
*/
|
||||
export const DOMAIN_REGEX = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig;
|
20
src/core/lib/FlowControl.mjs
Normal file
20
src/core/lib/FlowControl.mjs
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Flow control functions
|
||||
*
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the index of a label.
|
||||
*
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {string} name - The label name to look for.
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getLabelIndex(name, state) {
|
||||
return state.opList.findIndex((operation) => {
|
||||
return (operation.name === "Label") && (name === operation.ingValues[0]);
|
||||
});
|
||||
}
|
28
src/core/lib/Hash.mjs
Normal file
28
src/core/lib/Hash.mjs
Normal file
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Hashing resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
*
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
import CryptoApi from "crypto-api/src/crypto-api";
|
||||
|
||||
|
||||
/**
|
||||
* Generic hash function.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object} [options={}]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function runHash(name, input, options={}) {
|
||||
const msg = Utils.arrayBufferToStr(input, false),
|
||||
hasher = CryptoApi.getHasher(name, options);
|
||||
hasher.update(msg);
|
||||
return CryptoApi.encoder.toHex(hasher.finalize());
|
||||
}
|
||||
|
109
src/core/lib/Hex.mjs
Normal file
109
src/core/lib/Hex.mjs
Normal file
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* Hexadecimal functions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
|
||||
|
||||
/**
|
||||
* Convert a byte array into a hex string.
|
||||
*
|
||||
* @param {Uint8Array|byteArray} data
|
||||
* @param {string} [delim=" "]
|
||||
* @param {number} [padding=2]
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "0a 14 1e"
|
||||
* toHex([10,20,30]);
|
||||
*
|
||||
* // returns "0a:14:1e"
|
||||
* toHex([10,20,30], ":");
|
||||
*/
|
||||
export function toHex(data, delim=" ", padding=2) {
|
||||
if (!data) return "";
|
||||
|
||||
let output = "";
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
output += data[i].toString(16).padStart(padding, "0") + delim;
|
||||
}
|
||||
|
||||
// Add \x or 0x to beginning
|
||||
if (delim === "0x") output = "0x" + output;
|
||||
if (delim === "\\x") output = "\\x" + output;
|
||||
|
||||
if (delim.length)
|
||||
return output.slice(0, -delim.length);
|
||||
else
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a byte array into a hex string as efficiently as possible with no options.
|
||||
*
|
||||
* @param {byteArray} data
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "0a141e"
|
||||
* toHex([10,20,30]);
|
||||
*/
|
||||
export function toHexFast(data) {
|
||||
if (!data) return "";
|
||||
|
||||
const output = [];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
output.push((data[i] >>> 4).toString(16));
|
||||
output.push((data[i] & 0x0f).toString(16));
|
||||
}
|
||||
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a hex string into a byte array.
|
||||
*
|
||||
* @param {string} data
|
||||
* @param {string} [delim]
|
||||
* @param {number} [byteLen=2]
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns [10,20,30]
|
||||
* fromHex("0a 14 1e");
|
||||
*
|
||||
* // returns [10,20,30]
|
||||
* fromHex("0a:14:1e", "Colon");
|
||||
*/
|
||||
export function fromHex(data, delim="Auto", byteLen=2) {
|
||||
if (delim !== "None") {
|
||||
const delimRegex = delim === "Auto" ? /[^a-f\d]/gi : Utils.regexRep(delim);
|
||||
data = data.replace(delimRegex, "");
|
||||
}
|
||||
|
||||
const output = [];
|
||||
for (let i = 0; i < data.length; i += byteLen) {
|
||||
output.push(parseInt(data.substr(i, byteLen), 16));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To Hexadecimal delimiters.
|
||||
*/
|
||||
export const TO_HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
|
||||
|
||||
|
||||
/**
|
||||
* From Hexadecimal delimiters.
|
||||
*/
|
||||
export const FROM_HEX_DELIM_OPTIONS = ["Auto"].concat(TO_HEX_DELIM_OPTIONS);
|
676
src/core/lib/IP.mjs
Normal file
676
src/core/lib/IP.mjs
Normal file
|
@ -0,0 +1,676 @@
|
|||
/**
|
||||
* IP resources.
|
||||
*
|
||||
* @author picapi
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @author Klaxon [klaxon@veyr.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Parses an IPv4 CIDR range (e.g. 192.168.0.0/24) and displays information about it.
|
||||
*
|
||||
* @param {RegExp} cidr
|
||||
* @param {boolean} includeNetworkInfo
|
||||
* @param {boolean} enumerateAddresses
|
||||
* @param {boolean} allowLargeList
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ipv4CidrRange(cidr, includeNetworkInfo, enumerateAddresses, allowLargeList) {
|
||||
const network = strToIpv4(cidr[1]),
|
||||
cidrRange = parseInt(cidr[2], 10);
|
||||
let output = "";
|
||||
|
||||
if (cidrRange < 0 || cidrRange > 31) {
|
||||
return "IPv4 CIDR must be less than 32";
|
||||
}
|
||||
|
||||
const mask = ~(0xFFFFFFFF >>> cidrRange),
|
||||
ip1 = network & mask,
|
||||
ip2 = ip1 | ~mask;
|
||||
|
||||
if (includeNetworkInfo) {
|
||||
output += "Network: " + ipv4ToStr(network) + "\n";
|
||||
output += "CIDR: " + cidrRange + "\n";
|
||||
output += "Mask: " + ipv4ToStr(mask) + "\n";
|
||||
output += "Range: " + ipv4ToStr(ip1) + " - " + ipv4ToStr(ip2) + "\n";
|
||||
output += "Total addresses in range: " + (((ip2 - ip1) >>> 0) + 1) + "\n\n";
|
||||
}
|
||||
|
||||
if (enumerateAddresses) {
|
||||
if (cidrRange >= 16 || allowLargeList) {
|
||||
output += generateIpv4Range(ip1, ip2).join("\n");
|
||||
} else {
|
||||
output += _LARGE_RANGE_ERROR;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an IPv6 CIDR range (e.g. ff00::/48) and displays information about it.
|
||||
*
|
||||
* @param {RegExp} cidr
|
||||
* @param {boolean} includeNetworkInfo
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ipv6CidrRange(cidr, includeNetworkInfo) {
|
||||
let output = "";
|
||||
const network = strToIpv6(cidr[1]),
|
||||
cidrRange = parseInt(cidr[cidr.length-1], 10);
|
||||
|
||||
if (cidrRange < 0 || cidrRange > 127) {
|
||||
return "IPv6 CIDR must be less than 128";
|
||||
}
|
||||
|
||||
const ip1 = new Array(8),
|
||||
ip2 = new Array(8),
|
||||
total = new Array(128);
|
||||
|
||||
const mask = genIpv6Mask(cidrRange);
|
||||
let totalDiff = "";
|
||||
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
ip1[i] = network[i] & mask[i];
|
||||
ip2[i] = ip1[i] | (~mask[i] & 0x0000FFFF);
|
||||
totalDiff = (ip2[i] - ip1[i]).toString(2);
|
||||
|
||||
if (totalDiff !== "0") {
|
||||
for (let n = 0; n < totalDiff.length; n++) {
|
||||
total[i*16 + 16-(totalDiff.length-n)] = totalDiff[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeNetworkInfo) {
|
||||
output += "Network: " + ipv6ToStr(network) + "\n";
|
||||
output += "Shorthand: " + ipv6ToStr(network, true) + "\n";
|
||||
output += "CIDR: " + cidrRange + "\n";
|
||||
output += "Mask: " + ipv6ToStr(mask) + "\n";
|
||||
output += "Range: " + ipv6ToStr(ip1) + " - " + ipv6ToStr(ip2) + "\n";
|
||||
output += "Total addresses in range: " + (parseInt(total.join(""), 2) + 1) + "\n\n";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an IPv4 hyphenated range (e.g. 192.168.0.0 - 192.168.0.255) and displays information
|
||||
* about it.
|
||||
*
|
||||
* @param {RegExp} range
|
||||
* @param {boolean} includeNetworkInfo
|
||||
* @param {boolean} enumerateAddresses
|
||||
* @param {boolean} allowLargeList
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ipv4HyphenatedRange(range, includeNetworkInfo, enumerateAddresses, allowLargeList) {
|
||||
const ip1 = strToIpv4(range[0].split("-")[0].trim()),
|
||||
ip2 = strToIpv4(range[0].split("-")[1].trim());
|
||||
|
||||
let output = "";
|
||||
|
||||
// Calculate mask
|
||||
let diff = ip1 ^ ip2,
|
||||
cidr = 32,
|
||||
mask = 0;
|
||||
|
||||
while (diff !== 0) {
|
||||
diff >>= 1;
|
||||
cidr--;
|
||||
mask = (mask << 1) | 1;
|
||||
}
|
||||
|
||||
mask = ~mask >>> 0;
|
||||
const network = ip1 & mask,
|
||||
subIp1 = network & mask,
|
||||
subIp2 = subIp1 | ~mask;
|
||||
|
||||
if (includeNetworkInfo) {
|
||||
output += `Minimum subnet required to hold this range:
|
||||
\tNetwork: ${ipv4ToStr(network)}
|
||||
\tCIDR: ${cidr}
|
||||
\tMask: ${ipv4ToStr(mask)}
|
||||
\tSubnet range: ${ipv4ToStr(subIp1)} - ${ipv4ToStr(subIp2)}
|
||||
\tTotal addresses in subnet: ${(((subIp2 - subIp1) >>> 0) + 1)}
|
||||
|
||||
Range: ${ipv4ToStr(ip1)} - ${ipv4ToStr(ip2)}
|
||||
Total addresses in range: ${(((ip2 - ip1) >>> 0) + 1)}
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
if (enumerateAddresses) {
|
||||
if (((ip2 - ip1) >>> 0) <= 65536 || allowLargeList) {
|
||||
output += generateIpv4Range(ip1, ip2).join("\n");
|
||||
} else {
|
||||
output += _LARGE_RANGE_ERROR;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an IPv6 hyphenated range (e.g. ff00:: - ffff::) and displays information about it.
|
||||
*
|
||||
* @param {RegExp} range
|
||||
* @param {boolean} includeNetworkInfo
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ipv6HyphenatedRange(range, includeNetworkInfo) {
|
||||
const ip1 = strToIpv6(range[0].split("-")[0].trim()),
|
||||
ip2 = strToIpv6(range[0].split("-")[1].trim()),
|
||||
total = new Array(128).fill();
|
||||
|
||||
let output = "",
|
||||
t = "",
|
||||
i;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
t = (ip2[i] - ip1[i]).toString(2);
|
||||
if (t !== "0") {
|
||||
for (let n = 0; n < t.length; n++) {
|
||||
total[i*16 + 16-(t.length-n)] = t[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeNetworkInfo) {
|
||||
output += "Range: " + ipv6ToStr(ip1) + " - " + ipv6ToStr(ip2) + "\n";
|
||||
output += "Shorthand range: " + ipv6ToStr(ip1, true) + " - " + ipv6ToStr(ip2, true) + "\n";
|
||||
output += "Total addresses in range: " + (parseInt(total.join(""), 2) + 1) + "\n\n";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of IPv4 addresses separated by a new line (\n) and displays information
|
||||
* about it.
|
||||
*
|
||||
* @param {RegExp} list
|
||||
* @param {boolean} includeNetworkInfo
|
||||
* @param {boolean} enumerateAddresses
|
||||
* @param {boolean} allowLargeList
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ipv4ListedRange(match, includeNetworkInfo, enumerateAddresses, allowLargeList) {
|
||||
|
||||
let ipv4List = match[0].split("\n");
|
||||
ipv4List = ipv4List.filter(Boolean);
|
||||
|
||||
const ipv4CidrList = ipv4List.filter(function(a) {
|
||||
return a.includes("/");
|
||||
});
|
||||
for (let i = 0; i < ipv4CidrList.length; i++) {
|
||||
const network = strToIpv4(ipv4CidrList[i].split("/")[0]);
|
||||
const cidrRange = parseInt(ipv4CidrList[i].split("/")[1], 10);
|
||||
if (cidrRange < 0 || cidrRange > 31) {
|
||||
return "IPv4 CIDR must be less than 32";
|
||||
}
|
||||
const mask = ~(0xFFFFFFFF >>> cidrRange),
|
||||
cidrIp1 = network & mask,
|
||||
cidrIp2 = cidrIp1 | ~mask;
|
||||
ipv4List.splice(ipv4List.indexOf(ipv4CidrList[i]), 1);
|
||||
ipv4List.push(ipv4ToStr(cidrIp1), ipv4ToStr(cidrIp2));
|
||||
}
|
||||
|
||||
ipv4List = ipv4List.sort(ipv4Compare);
|
||||
const ip1 = ipv4List[0];
|
||||
const ip2 = ipv4List[ipv4List.length - 1];
|
||||
const range = [ip1 + " - " + ip2];
|
||||
return ipv4HyphenatedRange(range, includeNetworkInfo, enumerateAddresses, allowLargeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of IPv6 addresses separated by a new line (\n) and displays information
|
||||
* about it.
|
||||
*
|
||||
* @param {RegExp} list
|
||||
* @param {boolean} includeNetworkInfo
|
||||
* @returns {string}
|
||||
*/
|
||||
export function ipv6ListedRange(match, includeNetworkInfo) {
|
||||
|
||||
let ipv6List = match[0].split("\n");
|
||||
ipv6List = ipv6List.filter(function(str) {
|
||||
return str.trim();
|
||||
});
|
||||
for (let i =0; i < ipv6List.length; i++){
|
||||
ipv6List[i] = ipv6List[i].trim();
|
||||
}
|
||||
const ipv6CidrList = ipv6List.filter(function(a) {
|
||||
return a.includes("/");
|
||||
});
|
||||
|
||||
for (let i = 0; i < ipv6CidrList.length; i++) {
|
||||
|
||||
const network = strToIpv6(ipv6CidrList[i].split("/")[0]);
|
||||
const cidrRange = parseInt(ipv6CidrList[i].split("/")[1], 10);
|
||||
|
||||
if (cidrRange < 0 || cidrRange > 127) {
|
||||
return "IPv6 CIDR must be less than 128";
|
||||
}
|
||||
|
||||
const cidrIp1 = new Array(8),
|
||||
cidrIp2 = new Array(8);
|
||||
|
||||
const mask = genIpv6Mask(cidrRange);
|
||||
|
||||
for (let j = 0; j < 8; j++) {
|
||||
cidrIp1[j] = network[j] & mask[j];
|
||||
cidrIp2[j] = cidrIp1[j] | (~mask[j] & 0x0000FFFF);
|
||||
}
|
||||
ipv6List.splice(ipv6List.indexOf(ipv6CidrList[i]), 1);
|
||||
ipv6List.push(ipv6ToStr(cidrIp1), ipv6ToStr(cidrIp2));
|
||||
}
|
||||
ipv6List = ipv6List.sort(ipv6Compare);
|
||||
const ip1 = ipv6List[0];
|
||||
const ip2 = ipv6List[ipv6List.length - 1];
|
||||
const range = [ip1 + " - " + ip2];
|
||||
return ipv6HyphenatedRange(range, includeNetworkInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an IPv4 address from string format to numerical format.
|
||||
*
|
||||
* @param {string} ipStr
|
||||
* @returns {number}
|
||||
*
|
||||
* @example
|
||||
* // returns 168427520
|
||||
* strToIpv4("10.10.0.0");
|
||||
*/
|
||||
export function strToIpv4(ipStr) {
|
||||
const blocks = ipStr.split("."),
|
||||
numBlocks = parseBlocks(blocks);
|
||||
let result = 0;
|
||||
|
||||
result += numBlocks[0] << 24;
|
||||
result += numBlocks[1] << 16;
|
||||
result += numBlocks[2] << 8;
|
||||
result += numBlocks[3];
|
||||
|
||||
return result;
|
||||
|
||||
/**
|
||||
* Converts a list of 4 numeric strings in the range 0-255 to a list of numbers.
|
||||
*/
|
||||
function parseBlocks(blocks) {
|
||||
if (blocks.length !== 4)
|
||||
throw new OperationError("More than 4 blocks.");
|
||||
|
||||
const numBlocks = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
numBlocks[i] = parseInt(blocks[i], 10);
|
||||
if (numBlocks[i] < 0 || numBlocks[i] > 255)
|
||||
throw new OperationError("Block out of range.");
|
||||
}
|
||||
return numBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an IPv4 address from numerical format to string format.
|
||||
*
|
||||
* @param {number} ipInt
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "10.10.0.0"
|
||||
* ipv4ToStr(168427520);
|
||||
*/
|
||||
export function ipv4ToStr(ipInt) {
|
||||
const blockA = (ipInt >> 24) & 255,
|
||||
blockB = (ipInt >> 16) & 255,
|
||||
blockC = (ipInt >> 8) & 255,
|
||||
blockD = ipInt & 255;
|
||||
|
||||
return blockA + "." + blockB + "." + blockC + "." + blockD;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts an IPv6 address from string format to numerical array format.
|
||||
*
|
||||
* @param {string} ipStr
|
||||
* @returns {number[]}
|
||||
*
|
||||
* @example
|
||||
* // returns [65280, 0, 0, 0, 0, 0, 4369, 8738]
|
||||
* strToIpv6("ff00::1111:2222");
|
||||
*/
|
||||
export function strToIpv6(ipStr) {
|
||||
let j = 0;
|
||||
const blocks = ipStr.split(":"),
|
||||
numBlocks = parseBlocks(blocks),
|
||||
ipv6 = new Array(8);
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if (isNaN(numBlocks[j])) {
|
||||
ipv6[i] = 0;
|
||||
if (i === (8-numBlocks.slice(j).length)) j++;
|
||||
} else {
|
||||
ipv6[i] = numBlocks[j];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
return ipv6;
|
||||
|
||||
/**
|
||||
* Converts a list of 3-8 numeric hex strings in the range 0-65535 to a list of numbers.
|
||||
*/
|
||||
function parseBlocks(blocks) {
|
||||
if (blocks.length < 3 || blocks.length > 8)
|
||||
throw new OperationError("Badly formatted IPv6 address.");
|
||||
const numBlocks = [];
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
numBlocks[i] = parseInt(blocks[i], 16);
|
||||
if (numBlocks[i] < 0 || numBlocks[i] > 65535)
|
||||
throw new OperationError("Block out of range.");
|
||||
}
|
||||
return numBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an IPv6 address from numerical array format to string format.
|
||||
*
|
||||
* @param {number[]} ipv6
|
||||
* @param {boolean} compact - Whether or not to return the address in shorthand or not
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "ff00::1111:2222"
|
||||
* ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], true);
|
||||
*
|
||||
* // returns "ff00:0000:0000:0000:0000:0000:1111:2222"
|
||||
* ipv6ToStr([65280, 0, 0, 0, 0, 0, 4369, 8738], false);
|
||||
*/
|
||||
export function ipv6ToStr(ipv6, compact) {
|
||||
let output = "",
|
||||
i = 0;
|
||||
|
||||
if (compact) {
|
||||
let start = -1,
|
||||
end = -1,
|
||||
s = 0,
|
||||
e = -1;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (ipv6[i] === 0 && e === (i-1)) {
|
||||
e = i;
|
||||
} else if (ipv6[i] === 0) {
|
||||
s = i; e = i;
|
||||
}
|
||||
if (e >= 0 && (e-s) > (end - start)) {
|
||||
start = s;
|
||||
end = e;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (i !== start) {
|
||||
output += Utils.hex(ipv6[i], 1) + ":";
|
||||
} else {
|
||||
output += ":";
|
||||
i = end;
|
||||
if (end === 7) output += ":";
|
||||
}
|
||||
}
|
||||
if (output[0] === ":")
|
||||
output = ":" + output;
|
||||
} else {
|
||||
for (i = 0; i < 8; i++) {
|
||||
output += Utils.hex(ipv6[i], 4) + ":";
|
||||
}
|
||||
}
|
||||
return output.slice(0, output.length-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of IPv4 addresses in string format between two given numerical values.
|
||||
*
|
||||
* @param {number} ip
|
||||
* @param {number} endIp
|
||||
* @returns {string[]}
|
||||
*
|
||||
* @example
|
||||
* // returns ["0.0.0.1", "0.0.0.2", "0.0.0.3"]
|
||||
* IP.generateIpv4Range(1, 3);
|
||||
*/
|
||||
export function generateIpv4Range(ip, endIp) {
|
||||
const range = [];
|
||||
if (endIp >= ip) {
|
||||
for (; ip <= endIp; ip++) {
|
||||
range.push(ipv4ToStr(ip));
|
||||
}
|
||||
} else {
|
||||
range[0] = "Second IP address smaller than first.";
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an IPv6 subnet mask given a CIDR value.
|
||||
*
|
||||
* @param {number} cidr
|
||||
* @returns {number[]}
|
||||
*/
|
||||
export function genIpv6Mask(cidr) {
|
||||
const mask = new Array(8);
|
||||
let shift;
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if (cidr > ((i+1)*16)) {
|
||||
mask[i] = 0x0000FFFF;
|
||||
} else {
|
||||
shift = cidr-(i*16);
|
||||
if (shift < 0) shift = 0;
|
||||
mask[i] = ~((0x0000FFFF >>> shift) | 0xFFFF0000);
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison operation for sorting of IPv4 addresses.
|
||||
*
|
||||
* @param {string} a
|
||||
* @param {string} b
|
||||
* @returns {number}
|
||||
*/
|
||||
export function ipv4Compare(a, b) {
|
||||
return strToIpv4(a) - strToIpv4(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison operation for sorting of IPv6 addresses.
|
||||
*
|
||||
* @param {string} a
|
||||
* @param {string} b
|
||||
* @returns {number}
|
||||
*/
|
||||
export function ipv6Compare(a, b) {
|
||||
|
||||
const a_ = strToIpv6(a),
|
||||
b_ = strToIpv6(b);
|
||||
|
||||
for (let i = 0; i < a_.length; i++){
|
||||
if (a_[i] !== b_[i]){
|
||||
return a_[i] - b_[i];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const _LARGE_RANGE_ERROR = "The specified range contains more than 65,536 addresses. Running this query could crash your browser. If you want to run it, select the \"Allow large queries\" option. You are advised to turn off \"Auto Bake\" whilst editing large ranges.";
|
||||
|
||||
/**
|
||||
* A regular expression that matches an IPv4 address
|
||||
*/
|
||||
export const IPV4_REGEX = /^\s*((?:\d{1,3}\.){3}\d{1,3})\s*$/;
|
||||
|
||||
/**
|
||||
* A regular expression that matches an IPv6 address
|
||||
*/
|
||||
export const IPV6_REGEX = /^\s*(((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\4)::|:\b|(?![\dA-F])))|(?!\3\4)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4}))\s*$/i;
|
||||
|
||||
/**
|
||||
* Lookup table for Internet Protocols.
|
||||
* Taken from https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
|
||||
*/
|
||||
export const protocolLookup = {
|
||||
0: {keyword: "HOPOPT", protocol: "IPv6 Hop-by-Hop Option"},
|
||||
1: {keyword: "ICMP", protocol: "Internet Control Message"},
|
||||
2: {keyword: "IGMP", protocol: "Internet Group Management"},
|
||||
3: {keyword: "GGP", protocol: "Gateway-to-Gateway"},
|
||||
4: {keyword: "IPv4", protocol: "IPv4 encapsulation"},
|
||||
5: {keyword: "ST", protocol: "Stream"},
|
||||
6: {keyword: "TCP", protocol: "Transmission Control"},
|
||||
7: {keyword: "CBT", protocol: "CBT"},
|
||||
8: {keyword: "EGP", protocol: "Exterior Gateway Protocol"},
|
||||
9: {keyword: "IGP", protocol: "any private interior gateway (used by Cisco for their IGRP)"},
|
||||
10: {keyword: "BBN-RCC-MON", protocol: "BBN RCC Monitoring"},
|
||||
11: {keyword: "NVP-II", protocol: "Network Voice Protocol"},
|
||||
12: {keyword: "PUP", protocol: "PUP"},
|
||||
13: {keyword: "ARGUS (deprecated)", protocol: "ARGUS"},
|
||||
14: {keyword: "EMCON", protocol: "EMCON"},
|
||||
15: {keyword: "XNET", protocol: "Cross Net Debugger"},
|
||||
16: {keyword: "CHAOS", protocol: "Chaos"},
|
||||
17: {keyword: "UDP", protocol: "User Datagram"},
|
||||
18: {keyword: "MUX", protocol: "Multiplexing"},
|
||||
19: {keyword: "DCN-MEAS", protocol: "DCN Measurement Subsystems"},
|
||||
20: {keyword: "HMP", protocol: "Host Monitoring"},
|
||||
21: {keyword: "PRM", protocol: "Packet Radio Measurement"},
|
||||
22: {keyword: "XNS-IDP", protocol: "XEROX NS IDP"},
|
||||
23: {keyword: "TRUNK-1", protocol: "Trunk-1"},
|
||||
24: {keyword: "TRUNK-2", protocol: "Trunk-2"},
|
||||
25: {keyword: "LEAF-1", protocol: "Leaf-1"},
|
||||
26: {keyword: "LEAF-2", protocol: "Leaf-2"},
|
||||
27: {keyword: "RDP", protocol: "Reliable Data Protocol"},
|
||||
28: {keyword: "IRTP", protocol: "Internet Reliable Transaction"},
|
||||
29: {keyword: "ISO-TP4", protocol: "ISO Transport Protocol Class 4"},
|
||||
30: {keyword: "NETBLT", protocol: "Bulk Data Transfer Protocol"},
|
||||
31: {keyword: "MFE-NSP", protocol: "MFE Network Services Protocol"},
|
||||
32: {keyword: "MERIT-INP", protocol: "MERIT Internodal Protocol"},
|
||||
33: {keyword: "DCCP", protocol: "Datagram Congestion Control Protocol"},
|
||||
34: {keyword: "3PC", protocol: "Third Party Connect Protocol"},
|
||||
35: {keyword: "IDPR", protocol: "Inter-Domain Policy Routing Protocol"},
|
||||
36: {keyword: "XTP", protocol: "XTP"},
|
||||
37: {keyword: "DDP", protocol: "Datagram Delivery Protocol"},
|
||||
38: {keyword: "IDPR-CMTP", protocol: "IDPR Control Message Transport Proto"},
|
||||
39: {keyword: "TP++", protocol: "TP++ Transport Protocol"},
|
||||
40: {keyword: "IL", protocol: "IL Transport Protocol"},
|
||||
41: {keyword: "IPv6", protocol: "IPv6 encapsulation"},
|
||||
42: {keyword: "SDRP", protocol: "Source Demand Routing Protocol"},
|
||||
43: {keyword: "IPv6-Route", protocol: "Routing Header for IPv6"},
|
||||
44: {keyword: "IPv6-Frag", protocol: "Fragment Header for IPv6"},
|
||||
45: {keyword: "IDRP", protocol: "Inter-Domain Routing Protocol"},
|
||||
46: {keyword: "RSVP", protocol: "Reservation Protocol"},
|
||||
47: {keyword: "GRE", protocol: "Generic Routing Encapsulation"},
|
||||
48: {keyword: "DSR", protocol: "Dynamic Source Routing Protocol"},
|
||||
49: {keyword: "BNA", protocol: "BNA"},
|
||||
50: {keyword: "ESP", protocol: "Encap Security Payload"},
|
||||
51: {keyword: "AH", protocol: "Authentication Header"},
|
||||
52: {keyword: "I-NLSP", protocol: "Integrated Net Layer Security TUBA"},
|
||||
53: {keyword: "SWIPE (deprecated)", protocol: "IP with Encryption"},
|
||||
54: {keyword: "NARP", protocol: "NBMA Address Resolution Protocol"},
|
||||
55: {keyword: "MOBILE", protocol: "IP Mobility"},
|
||||
56: {keyword: "TLSP", protocol: "Transport Layer Security Protocol using Kryptonet key management"},
|
||||
57: {keyword: "SKIP", protocol: "SKIP"},
|
||||
58: {keyword: "IPv6-ICMP", protocol: "ICMP for IPv6"},
|
||||
59: {keyword: "IPv6-NoNxt", protocol: "No Next Header for IPv6"},
|
||||
60: {keyword: "IPv6-Opts", protocol: "Destination Options for IPv6"},
|
||||
61: {keyword: "", protocol: "any host internal protocol"},
|
||||
62: {keyword: "CFTP", protocol: "CFTP"},
|
||||
63: {keyword: "", protocol: "any local network"},
|
||||
64: {keyword: "SAT-EXPAK", protocol: "SATNET and Backroom EXPAK"},
|
||||
65: {keyword: "KRYPTOLAN", protocol: "Kryptolan"},
|
||||
66: {keyword: "RVD", protocol: "MIT Remote Virtual Disk Protocol"},
|
||||
67: {keyword: "IPPC", protocol: "Internet Pluribus Packet Core"},
|
||||
68: {keyword: "", protocol: "any distributed file system"},
|
||||
69: {keyword: "SAT-MON", protocol: "SATNET Monitoring"},
|
||||
70: {keyword: "VISA", protocol: "VISA Protocol"},
|
||||
71: {keyword: "IPCV", protocol: "Internet Packet Core Utility"},
|
||||
72: {keyword: "CPNX", protocol: "Computer Protocol Network Executive"},
|
||||
73: {keyword: "CPHB", protocol: "Computer Protocol Heart Beat"},
|
||||
74: {keyword: "WSN", protocol: "Wang Span Network"},
|
||||
75: {keyword: "PVP", protocol: "Packet Video Protocol"},
|
||||
76: {keyword: "BR-SAT-MON", protocol: "Backroom SATNET Monitoring"},
|
||||
77: {keyword: "SUN-ND", protocol: "SUN ND PROTOCOL-Temporary"},
|
||||
78: {keyword: "WB-MON", protocol: "WIDEBAND Monitoring"},
|
||||
79: {keyword: "WB-EXPAK", protocol: "WIDEBAND EXPAK"},
|
||||
80: {keyword: "ISO-IP", protocol: "ISO Internet Protocol"},
|
||||
81: {keyword: "VMTP", protocol: "VMTP"},
|
||||
82: {keyword: "SECURE-VMTP", protocol: "SECURE-VMTP"},
|
||||
83: {keyword: "VINES", protocol: "VINES"},
|
||||
84: {keyword: "TTP", protocol: "Transaction Transport Protocol"},
|
||||
85: {keyword: "NSFNET-IGP", protocol: "NSFNET-IGP"},
|
||||
86: {keyword: "DGP", protocol: "Dissimilar Gateway Protocol"},
|
||||
87: {keyword: "TCF", protocol: "TCF"},
|
||||
88: {keyword: "EIGRP", protocol: "EIGRP"},
|
||||
89: {keyword: "OSPFIGP", protocol: "OSPFIGP"},
|
||||
90: {keyword: "Sprite-RPC", protocol: "Sprite RPC Protocol"},
|
||||
91: {keyword: "LARP", protocol: "Locus Address Resolution Protocol"},
|
||||
92: {keyword: "MTP", protocol: "Multicast Transport Protocol"},
|
||||
93: {keyword: "AX.25", protocol: "AX.25 Frames"},
|
||||
94: {keyword: "IPIP", protocol: "IP-within-IP Encapsulation Protocol"},
|
||||
95: {keyword: "MICP (deprecated)", protocol: "Mobile Internetworking Control Pro."},
|
||||
96: {keyword: "SCC-SP", protocol: "Semaphore Communications Sec. Pro."},
|
||||
97: {keyword: "ETHERIP", protocol: "Ethernet-within-IP Encapsulation"},
|
||||
98: {keyword: "ENCAP", protocol: "Encapsulation Header"},
|
||||
99: {keyword: "", protocol: "any private encryption scheme"},
|
||||
100: {keyword: "GMTP", protocol: "GMTP"},
|
||||
101: {keyword: "IFMP", protocol: "Ipsilon Flow Management Protocol"},
|
||||
102: {keyword: "PNNI", protocol: "PNNI over IP"},
|
||||
103: {keyword: "PIM", protocol: "Protocol Independent Multicast"},
|
||||
104: {keyword: "ARIS", protocol: "ARIS"},
|
||||
105: {keyword: "SCPS", protocol: "SCPS"},
|
||||
106: {keyword: "QNX", protocol: "QNX"},
|
||||
107: {keyword: "A/N", protocol: "Active Networks"},
|
||||
108: {keyword: "IPComp", protocol: "IP Payload Compression Protocol"},
|
||||
109: {keyword: "SNP", protocol: "Sitara Networks Protocol"},
|
||||
110: {keyword: "Compaq-Peer", protocol: "Compaq Peer Protocol"},
|
||||
111: {keyword: "IPX-in-IP", protocol: "IPX in IP"},
|
||||
112: {keyword: "VRRP", protocol: "Virtual Router Redundancy Protocol"},
|
||||
113: {keyword: "PGM", protocol: "PGM Reliable Transport Protocol"},
|
||||
114: {keyword: "", protocol: "any 0-hop protocol"},
|
||||
115: {keyword: "L2TP", protocol: "Layer Two Tunneling Protocol"},
|
||||
116: {keyword: "DDX", protocol: "D-II Data Exchange (DDX)"},
|
||||
117: {keyword: "IATP", protocol: "Interactive Agent Transfer Protocol"},
|
||||
118: {keyword: "STP", protocol: "Schedule Transfer Protocol"},
|
||||
119: {keyword: "SRP", protocol: "SpectraLink Radio Protocol"},
|
||||
120: {keyword: "UTI", protocol: "UTI"},
|
||||
121: {keyword: "SMP", protocol: "Simple Message Protocol"},
|
||||
122: {keyword: "SM (deprecated)", protocol: "Simple Multicast Protocol"},
|
||||
123: {keyword: "PTP", protocol: "Performance Transparency Protocol"},
|
||||
124: {keyword: "ISIS over IPv4", protocol: ""},
|
||||
125: {keyword: "FIRE", protocol: ""},
|
||||
126: {keyword: "CRTP", protocol: "Combat Radio Transport Protocol"},
|
||||
127: {keyword: "CRUDP", protocol: "Combat Radio User Datagram"},
|
||||
128: {keyword: "SSCOPMCE", protocol: ""},
|
||||
129: {keyword: "IPLT", protocol: ""},
|
||||
130: {keyword: "SPS", protocol: "Secure Packet Shield"},
|
||||
131: {keyword: "PIPE", protocol: "Private IP Encapsulation within IP"},
|
||||
132: {keyword: "SCTP", protocol: "Stream Control Transmission Protocol"},
|
||||
133: {keyword: "FC", protocol: "Fibre Channel"},
|
||||
134: {keyword: "RSVP-E2E-IGNORE", protocol: ""},
|
||||
135: {keyword: "Mobility Header", protocol: ""},
|
||||
136: {keyword: "UDPLite", protocol: ""},
|
||||
137: {keyword: "MPLS-in-IP", protocol: ""},
|
||||
138: {keyword: "manet", protocol: "MANET Protocols"},
|
||||
139: {keyword: "HIP", protocol: "Host Identity Protocol"},
|
||||
140: {keyword: "Shim6", protocol: "Shim6 Protocol"},
|
||||
141: {keyword: "WESP", protocol: "Wrapped Encapsulating Security Payload"},
|
||||
142: {keyword: "ROHC", protocol: "Robust Header Compression"},
|
||||
253: {keyword: "", protocol: "Use for experimentation and testing"},
|
||||
254: {keyword: "", protocol: "Use for experimentation and testing"},
|
||||
255: {keyword: "Reserved", protocol: ""}
|
||||
};
|
230
src/core/lib/LoremIpsum.mjs
Normal file
230
src/core/lib/LoremIpsum.mjs
Normal file
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* Lorem Ipsum generator.
|
||||
*
|
||||
* @author Klaxon [klaxon@veyr.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate lorem ipsum paragraphs.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function GenerateParagraphs(length=3) {
|
||||
const paragraphs = [];
|
||||
while (paragraphs.length < length) {
|
||||
const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);
|
||||
const sentences = [];
|
||||
while (sentences.length < paragraphLength) {
|
||||
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
|
||||
const sentence = getWords(sentenceLength);
|
||||
sentences.push(formatSentence(sentence));
|
||||
}
|
||||
paragraphs.push(formatParagraph(sentences));
|
||||
}
|
||||
paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -2);
|
||||
paragraphs[0] = replaceStart(paragraphs[0]);
|
||||
return paragraphs.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate lorem ipsum sentences.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function GenerateSentences(length=3) {
|
||||
const sentences = [];
|
||||
while (sentences.length < length) {
|
||||
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
|
||||
const sentence = getWords(sentenceLength);
|
||||
sentences.push(formatSentence(sentence));
|
||||
}
|
||||
const paragraphs = sentencesToParagraphs(sentences);
|
||||
return paragraphs.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate lorem ipsum words.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function GenerateWords(length=3) {
|
||||
const words = getWords(length);
|
||||
const sentences = wordsToSentences(words);
|
||||
const paragraphs = sentencesToParagraphs(sentences);
|
||||
return paragraphs.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate lorem ipsum bytes.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string}
|
||||
*/
|
||||
export function GenerateBytes(length=3) {
|
||||
const str = GenerateWords(length/3);
|
||||
return str.slice(0, length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get array of randomly selected words from the lorem ipsum wordList.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
function getWords(length=3) {
|
||||
const words = [];
|
||||
let word;
|
||||
let previousWord;
|
||||
while (words.length < length){
|
||||
do {
|
||||
word = wordList[Math.floor(Math.random() * wordList.length)];
|
||||
} while (previousWord === word);
|
||||
words.push(word);
|
||||
previousWord = word;
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert an array of words into an array of sentences
|
||||
*
|
||||
* @param {string[]} words
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
function wordsToSentences(words) {
|
||||
const sentences = [];
|
||||
while (words.length > 0) {
|
||||
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
|
||||
if (sentenceLength <= words.length) {
|
||||
sentences.push(formatSentence(words.splice(0, sentenceLength)));
|
||||
} else {
|
||||
sentences.push(formatSentence(words.splice(0, words.length)));
|
||||
}
|
||||
}
|
||||
return sentences;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert an array of sentences into an array of paragraphs
|
||||
*
|
||||
* @param {string[]} sentences
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
function sentencesToParagraphs(sentences) {
|
||||
const paragraphs = [];
|
||||
while (sentences.length > 0) {
|
||||
const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);
|
||||
paragraphs.push(formatParagraph(sentences.splice(0, paragraphLength)));
|
||||
}
|
||||
paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -1);
|
||||
paragraphs[0] = replaceStart(paragraphs[0]);
|
||||
return paragraphs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format an array of words into a sentence.
|
||||
*
|
||||
* @param {string[]} words
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
function formatSentence(words) {
|
||||
// 0.35 chance of a comma being added randomly to the sentence.
|
||||
if (Math.random() < PROBABILITY_OF_A_COMMA) {
|
||||
const pos = Math.round(Math.random()*(words.length-1));
|
||||
words[pos] +=",";
|
||||
}
|
||||
let sentence = words.join(" ");
|
||||
sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1);
|
||||
sentence += ".";
|
||||
return sentence;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format an array of sentences into a paragraph.
|
||||
*
|
||||
* @param {string[]} sentences
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
function formatParagraph(sentences) {
|
||||
let paragraph = sentences.join(" ");
|
||||
paragraph += "\n\n";
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a random number based on a mean and standard deviation.
|
||||
*
|
||||
* @param {number} mean
|
||||
* @param {number} stdDev
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
function getRandomLength(mean, stdDev) {
|
||||
let length;
|
||||
do {
|
||||
length = Math.round((Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1)*stdDev+mean);
|
||||
} while (length <= 0);
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace first 5 words with "Lorem ipsum dolor sit amet"
|
||||
*
|
||||
* @param {string[]} str
|
||||
* @returns {string[]}
|
||||
* @private
|
||||
*/
|
||||
function replaceStart(str) {
|
||||
let words = str.split(" ");
|
||||
if (words.length > 5) {
|
||||
words.splice(0, 5, "Lorem", "ipsum", "dolor", "sit", "amet");
|
||||
return words.join(" ");
|
||||
} else {
|
||||
const lorem = ["Lorem", "ipsum", "dolor", "sit", "amet"];
|
||||
words = lorem.slice(0, words.length);
|
||||
str = words.join(" ");
|
||||
str += ".";
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const SENTENCE_LENGTH_MEAN = 15;
|
||||
const SENTENCE_LENGTH_STD_DEV = 9;
|
||||
const PARAGRAPH_LENGTH_MEAN = 5;
|
||||
const PARAGRAPH_LENGTH_STD_DEV = 2;
|
||||
const PROBABILITY_OF_A_COMMA = 0.35;
|
||||
|
||||
const wordList = [
|
||||
"ad", "adipisicing", "aliqua", "aliquip", "amet", "anim",
|
||||
"aute", "cillum", "commodo", "consectetur", "consequat", "culpa",
|
||||
"cupidatat", "deserunt", "do", "dolor", "dolore", "duis",
|
||||
"ea", "eiusmod", "elit", "enim", "esse", "est",
|
||||
"et", "eu", "ex", "excepteur", "exercitation", "fugiat",
|
||||
"id", "in", "incididunt", "ipsum", "irure", "labore",
|
||||
"laboris", "laborum", "Lorem", "magna", "minim", "mollit",
|
||||
"nisi", "non", "nostrud", "nulla", "occaecat", "officia",
|
||||
"pariatur", "proident", "qui", "quis", "reprehenderit", "sint",
|
||||
"sit", "sunt", "tempor", "ullamco", "ut", "velit",
|
||||
"veniam", "voluptate",
|
||||
];
|
1561
src/core/lib/Magic.mjs
Normal file
1561
src/core/lib/Magic.mjs
Normal file
File diff suppressed because it is too large
Load diff
117
src/core/lib/PGP.mjs
Normal file
117
src/core/lib/PGP.mjs
Normal file
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* PGP functions.
|
||||
*
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
*
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError";
|
||||
import kbpgp from "kbpgp";
|
||||
import * as es6promisify from "es6-promisify";
|
||||
const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
|
||||
|
||||
/**
|
||||
* Progress callback
|
||||
*/
|
||||
export const ASP = kbpgp.ASP({
|
||||
"progress_hook": info => {
|
||||
let msg = "";
|
||||
|
||||
switch (info.what) {
|
||||
case "guess":
|
||||
msg = "Guessing a prime";
|
||||
break;
|
||||
case "fermat":
|
||||
msg = "Factoring prime using Fermat's factorization method";
|
||||
break;
|
||||
case "mr":
|
||||
msg = "Performing Miller-Rabin primality test";
|
||||
break;
|
||||
case "passed_mr":
|
||||
msg = "Passed Miller-Rabin primality test";
|
||||
break;
|
||||
case "failed_mr":
|
||||
msg = "Failed Miller-Rabin primality test";
|
||||
break;
|
||||
case "found":
|
||||
msg = "Prime found";
|
||||
break;
|
||||
default:
|
||||
msg = `Stage: ${info.what}`;
|
||||
}
|
||||
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage(msg);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get size of subkey
|
||||
*
|
||||
* @param {number} keySize
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getSubkeySize(keySize) {
|
||||
return {
|
||||
1024: 1024,
|
||||
2048: 1024,
|
||||
4096: 2048,
|
||||
256: 256,
|
||||
384: 256,
|
||||
}[keySize];
|
||||
}
|
||||
|
||||
/**
|
||||
* Import private key and unlock if necessary
|
||||
*
|
||||
* @param {string} privateKey
|
||||
* @param {string} [passphrase]
|
||||
* @returns {Object}
|
||||
*/
|
||||
export async function importPrivateKey(privateKey, passphrase) {
|
||||
try {
|
||||
const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
|
||||
armored: privateKey,
|
||||
opts: {
|
||||
"no_check_keys": true
|
||||
}
|
||||
});
|
||||
if (key.is_pgp_locked()) {
|
||||
if (passphrase) {
|
||||
await promisify(key.unlock_pgp.bind(key))({
|
||||
passphrase
|
||||
});
|
||||
} else {
|
||||
throw new OperationError("Did not provide passphrase with locked private key.");
|
||||
}
|
||||
}
|
||||
return key;
|
||||
} catch (err) {
|
||||
throw new OperationError(`Could not import private key: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import public key
|
||||
*
|
||||
* @param {string} publicKey
|
||||
* @returns {Object}
|
||||
*/
|
||||
export async function importPublicKey (publicKey) {
|
||||
try {
|
||||
const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
|
||||
armored: publicKey,
|
||||
opts: {
|
||||
"no_check_keys": true
|
||||
}
|
||||
});
|
||||
return key;
|
||||
} catch (err) {
|
||||
throw new OperationError(`Could not import public key: ${err}`);
|
||||
}
|
||||
}
|
72
src/core/lib/PublicKey.mjs
Normal file
72
src/core/lib/PublicKey.mjs
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Public key resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import { toHex, fromHex } from "./Hex";
|
||||
|
||||
/**
|
||||
* Formats Distinguished Name (DN) strings.
|
||||
*
|
||||
* @param {string} dnStr
|
||||
* @param {number} indent
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatDnStr (dnStr, indent) {
|
||||
const fields = dnStr.substr(1).replace(/([^\\])\//g, "$1$1/").split(/[^\\]\//);
|
||||
let output = "",
|
||||
maxKeyLen = 0,
|
||||
key,
|
||||
value,
|
||||
i,
|
||||
str;
|
||||
|
||||
for (i = 0; i < fields.length; i++) {
|
||||
if (!fields[i].length) continue;
|
||||
|
||||
key = fields[i].split("=")[0];
|
||||
|
||||
maxKeyLen = key.length > maxKeyLen ? key.length : maxKeyLen;
|
||||
}
|
||||
|
||||
for (i = 0; i < fields.length; i++) {
|
||||
if (!fields[i].length) continue;
|
||||
|
||||
key = fields[i].split("=")[0];
|
||||
value = fields[i].split("=")[1];
|
||||
str = key.padEnd(maxKeyLen, " ") + " = " + value + "\n";
|
||||
|
||||
output += str.padStart(indent + str.length, " ");
|
||||
}
|
||||
|
||||
return output.slice(0, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats byte strings by adding line breaks and delimiters.
|
||||
*
|
||||
* @param {string} byteStr
|
||||
* @param {number} length - Line width
|
||||
* @param {number} indent
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatByteStr (byteStr, length, indent) {
|
||||
byteStr = toHex(fromHex(byteStr), ":");
|
||||
length = length * 3;
|
||||
let output = "";
|
||||
|
||||
for (let i = 0; i < byteStr.length; i += length) {
|
||||
const str = byteStr.slice(i, i + length) + "\n";
|
||||
if (i === 0) {
|
||||
output += str;
|
||||
} else {
|
||||
output += str.padStart(indent + str.length, " ");
|
||||
}
|
||||
}
|
||||
|
||||
return output.slice(0, output.length-1);
|
||||
}
|
103
src/core/lib/Rotate.mjs
Normal file
103
src/core/lib/Rotate.mjs
Normal file
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Bit rotation functions.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @todo Support for UTF16
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Runs rotation operations across the input data.
|
||||
*
|
||||
* @param {byteArray} data
|
||||
* @param {number} amount
|
||||
* @param {function} algo - The rotation operation to carry out
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function rot(data, amount, algo) {
|
||||
const result = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let b = data[i];
|
||||
for (let j = 0; j < amount; j++) {
|
||||
b = algo(b);
|
||||
}
|
||||
result.push(b);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rotate right bitwise op.
|
||||
*
|
||||
* @param {byte} b
|
||||
* @returns {byte}
|
||||
*/
|
||||
export function rotr(b) {
|
||||
const bit = (b & 1) << 7;
|
||||
return (b >> 1) | bit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate left bitwise op.
|
||||
*
|
||||
* @param {byte} b
|
||||
* @returns {byte}
|
||||
*/
|
||||
export function rotl(b) {
|
||||
const bit = (b >> 7) & 1;
|
||||
return ((b << 1) | bit) & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rotates a byte array to the right by a specific amount as a whole, so that bits are wrapped
|
||||
* from the end of the array to the beginning.
|
||||
*
|
||||
* @param {byteArray} data
|
||||
* @param {number} amount
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function rotrCarry(data, amount) {
|
||||
const result = [];
|
||||
let carryBits = 0,
|
||||
newByte;
|
||||
|
||||
amount = amount % 8;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const oldByte = data[i] >>> 0;
|
||||
newByte = (oldByte >> amount) | carryBits;
|
||||
carryBits = (oldByte & (Math.pow(2, amount)-1)) << (8-amount);
|
||||
result.push(newByte);
|
||||
}
|
||||
result[0] |= carryBits;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rotates a byte array to the left by a specific amount as a whole, so that bits are wrapped
|
||||
* from the beginning of the array to the end.
|
||||
*
|
||||
* @param {byteArray} data
|
||||
* @param {number} amount
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function rotlCarry(data, amount) {
|
||||
const result = [];
|
||||
let carryBits = 0,
|
||||
newByte;
|
||||
|
||||
amount = amount % 8;
|
||||
for (let i = data.length-1; i >= 0; i--) {
|
||||
const oldByte = data[i];
|
||||
newByte = ((oldByte << amount) | carryBits) & 0xFF;
|
||||
carryBits = (oldByte >> (8-amount)) & (Math.pow(2, amount)-1);
|
||||
result[i] = (newByte);
|
||||
}
|
||||
result[data.length-1] = result[data.length-1] | carryBits;
|
||||
return result;
|
||||
}
|
78
src/core/lib/TLVParser.mjs
Normal file
78
src/core/lib/TLVParser.mjs
Normal file
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Parser for Type-length-value data.
|
||||
*
|
||||
* @author gchq77703 []
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
const defaults = {
|
||||
location: 0,
|
||||
bytesInLength: 1,
|
||||
basicEncodingRules: false
|
||||
};
|
||||
|
||||
/**
|
||||
* TLVParser library
|
||||
*/
|
||||
export default class TLVParser {
|
||||
|
||||
/**
|
||||
* TLVParser constructor
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(input, options) {
|
||||
this.input = input;
|
||||
Object.assign(this, defaults, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
getLength() {
|
||||
if (this.basicEncodingRules) {
|
||||
const bit = this.input[this.location];
|
||||
if (bit & 0x80) {
|
||||
this.bytesInLength = bit & ~0x80;
|
||||
} else {
|
||||
this.location++;
|
||||
return bit & ~0x80;
|
||||
}
|
||||
}
|
||||
|
||||
let length = 0;
|
||||
|
||||
for (let i = 0; i < this.bytesInLength; i++) {
|
||||
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
|
||||
this.location++;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} length
|
||||
* @returns {number[]}
|
||||
*/
|
||||
getValue(length) {
|
||||
const value = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (this.location > this.input.length) return value;
|
||||
value.push(this.input[this.location]);
|
||||
this.location++;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
atEnd() {
|
||||
return this.input.length <= this.location;
|
||||
}
|
||||
}
|
19
src/core/lib/Zlib.mjs
Normal file
19
src/core/lib/Zlib.mjs
Normal file
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Zlib exports.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min";
|
||||
|
||||
const Zlib = zlibAndGzip.Zlib;
|
||||
|
||||
export const COMPRESSION_TYPE = ["Dynamic Huffman Coding", "Fixed Huffman Coding", "None (Store)"];
|
||||
export const INFLATE_BUFFER_TYPE = ["Adaptive", "Block"];
|
||||
export const ZLIB_COMPRESSION_TYPE_LOOKUP = {
|
||||
"Fixed Huffman Coding": Zlib.Deflate.CompressionType.FIXED,
|
||||
"Dynamic Huffman Coding": Zlib.Deflate.CompressionType.DYNAMIC,
|
||||
"None (Store)": Zlib.Deflate.CompressionType.NONE,
|
||||
};
|
|
@ -1,186 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Various components for drawing diagrams on an HTML5 canvas.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @constant
|
||||
* @namespace
|
||||
*/
|
||||
const CanvasComponents = {
|
||||
|
||||
drawLine: function(ctx, startX, startY, endX, endY) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX, startY);
|
||||
ctx.lineTo(endX, endY);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
drawBarChart: function(canvas, scores, xAxisLabel, yAxisLabel, numXLabels, numYLabels, fontSize) {
|
||||
fontSize = fontSize || 15;
|
||||
if (!numXLabels || numXLabels > Math.round(canvas.width / 50)) {
|
||||
numXLabels = Math.round(canvas.width / 50);
|
||||
}
|
||||
if (!numYLabels || numYLabels > Math.round(canvas.width / 50)) {
|
||||
numYLabels = Math.round(canvas.height / 50);
|
||||
}
|
||||
|
||||
// Graph properties
|
||||
var ctx = canvas.getContext("2d"),
|
||||
leftPadding = canvas.width * 0.08,
|
||||
rightPadding = canvas.width * 0.03,
|
||||
topPadding = canvas.height * 0.08,
|
||||
bottomPadding = canvas.height * 0.15,
|
||||
graphHeight = canvas.height - topPadding - bottomPadding,
|
||||
graphWidth = canvas.width - leftPadding - rightPadding,
|
||||
base = topPadding + graphHeight,
|
||||
ceil = topPadding;
|
||||
|
||||
ctx.font = fontSize + "px Arial";
|
||||
|
||||
// Draw axis
|
||||
ctx.lineWidth = "1.0";
|
||||
ctx.strokeStyle = "#444";
|
||||
CanvasComponents.drawLine(ctx, leftPadding, base, graphWidth + leftPadding, base); // x
|
||||
CanvasComponents.drawLine(ctx, leftPadding, base, leftPadding, ceil); // y
|
||||
|
||||
// Bar properties
|
||||
var barPadding = graphWidth * 0.003,
|
||||
barWidth = (graphWidth - (barPadding * scores.length)) / scores.length,
|
||||
currX = leftPadding + barPadding,
|
||||
max = Math.max.apply(Math, scores);
|
||||
|
||||
// Draw bars
|
||||
ctx.fillStyle = "green";
|
||||
for (var i = 0; i < scores.length; i++) {
|
||||
var h = scores[i] / max * graphHeight;
|
||||
ctx.fillRect(currX, base - h, barWidth, h);
|
||||
currX += barWidth + barPadding;
|
||||
}
|
||||
|
||||
// Mark x axis
|
||||
ctx.fillStyle = "black";
|
||||
ctx.textAlign = "center";
|
||||
currX = leftPadding + barPadding;
|
||||
if (numXLabels >= scores.length) {
|
||||
// Mark every score
|
||||
for (i = 0; i <= scores.length; i++) {
|
||||
ctx.fillText(i, currX, base + (bottomPadding * 0.3));
|
||||
currX += barWidth + barPadding;
|
||||
}
|
||||
} else {
|
||||
// Mark some scores
|
||||
for (i = 0; i <= numXLabels; i++) {
|
||||
var val = Math.ceil((scores.length / numXLabels) * i);
|
||||
currX = (graphWidth / numXLabels) * i + leftPadding;
|
||||
ctx.fillText(val, currX, base + (bottomPadding * 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark y axis
|
||||
ctx.textAlign = "right";
|
||||
var currY;
|
||||
if (numYLabels >= max) {
|
||||
// Mark every increment
|
||||
for (i = 0; i <= max; i++) {
|
||||
currY = base - (i / max * graphHeight) + fontSize / 3;
|
||||
ctx.fillText(i, leftPadding * 0.8, currY);
|
||||
}
|
||||
} else {
|
||||
// Mark some increments
|
||||
for (i = 0; i <= numYLabels; i++) {
|
||||
val = Math.ceil((max / numYLabels) * i);
|
||||
currY = base - (val / max * graphHeight) + fontSize / 3;
|
||||
ctx.fillText(val, leftPadding * 0.8, currY);
|
||||
}
|
||||
}
|
||||
|
||||
// Label x axis
|
||||
if (xAxisLabel) {
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(xAxisLabel, graphWidth / 2 + leftPadding, base + bottomPadding * 0.8);
|
||||
}
|
||||
|
||||
// Label y axis
|
||||
if (yAxisLabel) {
|
||||
ctx.save();
|
||||
var x = leftPadding * 0.3,
|
||||
y = graphHeight / 2 + topPadding;
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(yAxisLabel, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
|
||||
drawScaleBar: function(canvas, score, max, markings) {
|
||||
// Bar properties
|
||||
var ctx = canvas.getContext("2d"),
|
||||
leftPadding = canvas.width * 0.01,
|
||||
rightPadding = canvas.width * 0.01,
|
||||
topPadding = canvas.height * 0.1,
|
||||
bottomPadding = canvas.height * 0.3,
|
||||
barHeight = canvas.height - topPadding - bottomPadding,
|
||||
barWidth = canvas.width - leftPadding - rightPadding;
|
||||
|
||||
// Scale properties
|
||||
var proportion = score / max;
|
||||
|
||||
// Draw bar outline
|
||||
ctx.strokeRect(leftPadding, topPadding, barWidth, barHeight);
|
||||
|
||||
// Shade in up to proportion
|
||||
var grad = ctx.createLinearGradient(leftPadding, 0, barWidth + leftPadding, 0);
|
||||
grad.addColorStop(0, "green");
|
||||
grad.addColorStop(0.5, "gold");
|
||||
grad.addColorStop(1, "red");
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(leftPadding, topPadding, barWidth * proportion, barHeight);
|
||||
|
||||
// Add markings
|
||||
var x0, y0, x1, y1;
|
||||
ctx.fillStyle = "black";
|
||||
ctx.textAlign = "center";
|
||||
ctx.font = "13px Arial";
|
||||
for (var i = 0; i < markings.length; i++) {
|
||||
// Draw min line down
|
||||
x0 = barWidth / max * markings[i].min + leftPadding;
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.1);
|
||||
x1 = x0;
|
||||
y1 = topPadding + barHeight + (bottomPadding * 0.3);
|
||||
CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Draw max line down
|
||||
x0 = barWidth / max * markings[i].max + leftPadding;
|
||||
x1 = x0;
|
||||
CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Join min and max lines
|
||||
x0 = barWidth / max * markings[i].min + leftPadding;
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.3);
|
||||
x1 = barWidth / max * markings[i].max + leftPadding;
|
||||
y1 = y0;
|
||||
CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
|
||||
|
||||
// Add label
|
||||
if (markings[i].max >= max * 0.9) {
|
||||
ctx.textAlign = "right";
|
||||
x0 = x1;
|
||||
} else if (markings[i].max <= max * 0.1) {
|
||||
ctx.textAlign = "left";
|
||||
} else {
|
||||
x0 = x0 + (x1 - x0) / 2;
|
||||
}
|
||||
y0 = topPadding + barHeight + (bottomPadding * 0.8);
|
||||
ctx.fillText(markings[i].label, x0, y0);
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default CanvasComponents;
|
File diff suppressed because it is too large
Load diff
63
src/core/operations/A1Z26CipherDecode.mjs
Normal file
63
src/core/operations/A1Z26CipherDecode.mjs
Normal file
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @author Jarmo van Lenthe [github.com/jarmovanlenthe]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import {DELIM_OPTIONS} from "../lib/Delim";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* A1Z26 Cipher Decode operation
|
||||
*/
|
||||
class A1Z26CipherDecode extends Operation {
|
||||
|
||||
/**
|
||||
* A1Z26CipherDecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "A1Z26 Cipher Decode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Converts alphabet order numbers into their corresponding alphabet character.<br><br>e.g. <code>1</code> becomes <code>a</code> and <code>2</code> becomes <code>b</code>.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Delimiter",
|
||||
type: "option",
|
||||
value: DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const delim = Utils.charRep(args[0] || "Space");
|
||||
|
||||
if (input.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const bites = input.split(delim);
|
||||
let latin1 = "";
|
||||
for (let i = 0; i < bites.length; i++) {
|
||||
if (bites[i] < 1 || bites[i] > 26) {
|
||||
throw new OperationError("Error: all numbers must be between 1 and 26.");
|
||||
}
|
||||
latin1 += Utils.chr(parseInt(bites[i], 10) + 96);
|
||||
}
|
||||
return latin1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default A1Z26CipherDecode;
|
61
src/core/operations/A1Z26CipherEncode.mjs
Normal file
61
src/core/operations/A1Z26CipherEncode.mjs
Normal file
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* @author Jarmo van Lenthe [github.com/jarmovanlenthe]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import {DELIM_OPTIONS} from "../lib/Delim";
|
||||
|
||||
/**
|
||||
* A1Z26 Cipher Encode operation
|
||||
*/
|
||||
class A1Z26CipherEncode extends Operation {
|
||||
|
||||
/**
|
||||
* A1Z26CipherEncode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "A1Z26 Cipher Encode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Converts alphabet characters into their corresponding alphabet order number.<br><br>e.g. <code>a</code> becomes <code>1</code> and <code>b</code> becomes <code>2</code>.<br><br>Non-alphabet characters are dropped.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Delimiter",
|
||||
type: "option",
|
||||
value: DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const delim = Utils.charRep(args[0] || "Space");
|
||||
let output = "";
|
||||
|
||||
const sanitizedinput = input.toLowerCase(),
|
||||
charcode = Utils.strToCharcode(sanitizedinput);
|
||||
|
||||
for (let i = 0; i < charcode.length; i++) {
|
||||
const ordinal = charcode[i] - 96;
|
||||
|
||||
if (ordinal > 0 && ordinal <= 26) {
|
||||
output += ordinal.toString(10) + delim;
|
||||
}
|
||||
}
|
||||
return output.slice(0, -delim.length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default A1Z26CipherEncode;
|
77
src/core/operations/ADD.mjs
Normal file
77
src/core/operations/ADD.mjs
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import { bitOp, add, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
|
||||
|
||||
/**
|
||||
* ADD operation
|
||||
*/
|
||||
class ADD extends Operation {
|
||||
|
||||
/**
|
||||
* ADD constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ADD";
|
||||
this.module = "Default";
|
||||
this.description = "ADD the input with the given key (e.g. <code>fe023da5</code>), MOD 255";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bitwise_operators";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": BITWISE_OP_DELIMS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
|
||||
|
||||
return bitOp(input, key, add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ADD
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ADD in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ADD;
|
109
src/core/operations/AESDecrypt.mjs
Normal file
109
src/core/operations/AESDecrypt.mjs
Normal file
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import forge from "node-forge/dist/forge.min.js";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* AES Decrypt operation
|
||||
*/
|
||||
class AESDecrypt extends Operation {
|
||||
|
||||
/**
|
||||
* AESDecrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AES Decrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Mode",
|
||||
"type": "option",
|
||||
"value": ["CBC", "CFB", "OFB", "CTR", "GCM", "ECB"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
"name": "GCM Tag",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
* @throws {OperationError} if cannot decrypt input or 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),
|
||||
mode = args[2],
|
||||
inputType = args[3],
|
||||
outputType = args[4],
|
||||
gcmTag = Utils.convertToByteString(args[5].string, args[5].option);
|
||||
|
||||
if ([16, 24, 32].indexOf(key.length) < 0) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
The following algorithms will be used based on the size of the key:
|
||||
16 bytes = AES-128
|
||||
24 bytes = AES-192
|
||||
32 bytes = AES-256`);
|
||||
}
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
|
||||
decipher.start({
|
||||
iv: iv,
|
||||
tag: gcmTag
|
||||
});
|
||||
decipher.update(forge.util.createBuffer(input));
|
||||
const result = decipher.finish();
|
||||
|
||||
if (result) {
|
||||
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
|
||||
} else {
|
||||
throw new OperationError("Unable to decrypt input with these parameters.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AESDecrypt;
|
107
src/core/operations/AESEncrypt.mjs
Normal file
107
src/core/operations/AESEncrypt.mjs
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import forge from "node-forge/dist/forge.min.js";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* AES Encrypt operation
|
||||
*/
|
||||
class AESEncrypt extends Operation {
|
||||
|
||||
/**
|
||||
* AESEncrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AES Encrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Mode",
|
||||
"type": "option",
|
||||
"value": ["CBC", "CFB", "OFB", "CTR", "GCM", "ECB"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
* @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),
|
||||
mode = args[2],
|
||||
inputType = args[3],
|
||||
outputType = args[4];
|
||||
|
||||
if ([16, 24, 32].indexOf(key.length) < 0) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
The following algorithms will be used based on the size of the key:
|
||||
16 bytes = AES-128
|
||||
24 bytes = AES-192
|
||||
32 bytes = AES-256`);
|
||||
}
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
const cipher = forge.cipher.createCipher("AES-" + mode, key);
|
||||
cipher.start({iv: iv});
|
||||
cipher.update(forge.util.createBuffer(input));
|
||||
cipher.finish();
|
||||
|
||||
if (outputType === "Hex") {
|
||||
if (mode === "GCM") {
|
||||
return cipher.output.toHex() + "\n\n" +
|
||||
"Tag: " + cipher.mode.tag.toHex();
|
||||
}
|
||||
return cipher.output.toHex();
|
||||
} else {
|
||||
if (mode === "GCM") {
|
||||
return cipher.output.getBytes() + "\n\n" +
|
||||
"Tag: " + cipher.mode.tag.getBytes();
|
||||
}
|
||||
return cipher.output.getBytes();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AESEncrypt;
|
77
src/core/operations/AND.mjs
Normal file
77
src/core/operations/AND.mjs
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import { bitOp, and, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
|
||||
|
||||
/**
|
||||
* AND operation
|
||||
*/
|
||||
class AND extends Operation {
|
||||
|
||||
/**
|
||||
* AND constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AND";
|
||||
this.module = "Default";
|
||||
this.description = "AND the input with the given key.<br>e.g. <code>fe023da5</code>";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#AND";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": BITWISE_OP_DELIMS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
|
||||
|
||||
return bitOp(input, key, and);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight AND
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight AND in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AND;
|
46
src/core/operations/AddLineNumbers.mjs
Normal file
46
src/core/operations/AddLineNumbers.mjs
Normal file
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* Add line numbers operation
|
||||
*/
|
||||
class AddLineNumbers extends Operation {
|
||||
|
||||
/**
|
||||
* AddLineNumbers constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Add line numbers";
|
||||
this.module = "Default";
|
||||
this.description = "Adds line numbers to the output.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const lines = input.split("\n"),
|
||||
width = lines.length.toString().length;
|
||||
let output = "";
|
||||
|
||||
for (let n = 0; n < lines.length; n++) {
|
||||
output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
|
||||
}
|
||||
return output.slice(0, output.length-1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AddLineNumbers;
|
53
src/core/operations/Adler32Checksum.mjs
Normal file
53
src/core/operations/Adler32Checksum.mjs
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
|
||||
/**
|
||||
* Adler-32 Checksum operation
|
||||
*/
|
||||
class Adler32Checksum extends Operation {
|
||||
|
||||
/**
|
||||
* Adler32Checksum constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Adler-32 Checksum";
|
||||
this.module = "Crypto";
|
||||
this.description = "Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995, and is a modification of the Fletcher checksum. Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter).<br><br>Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Adler-32";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const MOD_ADLER = 65521;
|
||||
let a = 1,
|
||||
b = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a += input[i];
|
||||
b += a;
|
||||
}
|
||||
|
||||
a %= MOD_ADLER;
|
||||
b %= MOD_ADLER;
|
||||
|
||||
return Utils.hex(((b << 16) | a) >>> 0, 8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Adler32Checksum;
|
106
src/core/operations/AffineCipherDecode.mjs
Normal file
106
src/core/operations/AffineCipherDecode.mjs
Normal file
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Affine Cipher Decode operation
|
||||
*/
|
||||
class AffineCipherDecode extends Operation {
|
||||
|
||||
/**
|
||||
* AffineCipherDecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Affine Cipher Decode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "The Affine cipher is a type of monoalphabetic substitution cipher. To decrypt, each letter in an alphabet is mapped to its numeric equivalent, decrypted by a mathematical function, and converted back to a letter.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Affine_cipher";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "a",
|
||||
"type": "number",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "b",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
* @throws {OperationError} if a or b values are invalid
|
||||
*/
|
||||
run(input, args) {
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyz",
|
||||
[a, b] = args,
|
||||
aModInv = Utils.modInv(a, 26); // Calculates modular inverse of a
|
||||
let output = "";
|
||||
|
||||
if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
|
||||
throw new OperationError("The values of a and b can only be integers.");
|
||||
}
|
||||
|
||||
if (Utils.gcd(a, 26) !== 1) {
|
||||
throw new OperationError("The value of `a` must be coprime to 26.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (alphabet.indexOf(input[i]) >= 0) {
|
||||
// Uses the affine decode function (y-b * A') % m = x (where m is length of the alphabet and A' is modular inverse)
|
||||
output += alphabet[Utils.mod((alphabet.indexOf(input[i]) - b) * aModInv, 26)];
|
||||
} else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
|
||||
// Same as above, accounting for uppercase
|
||||
output += alphabet[Utils.mod((alphabet.indexOf(input[i].toLowerCase()) - b) * aModInv, 26)].toUpperCase();
|
||||
} else {
|
||||
// Non-alphabetic characters
|
||||
output += input[i];
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Affine Cipher Decode
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Affine Cipher Decode in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AffineCipherDecode;
|
78
src/core/operations/AffineCipherEncode.mjs
Normal file
78
src/core/operations/AffineCipherEncode.mjs
Normal file
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import { affineEncode } from "../lib/Ciphers";
|
||||
|
||||
/**
|
||||
* Affine Cipher Encode operation
|
||||
*/
|
||||
class AffineCipherEncode extends Operation {
|
||||
|
||||
/**
|
||||
* AffineCipherEncode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Affine Cipher Encode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "The Affine cipher is a type of monoalphabetic substitution cipher, wherein each letter in an alphabet is mapped to its numeric equivalent, encrypted using simple mathematical function, <code>(ax + b) % 26</code>, and converted back to a letter.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Affine_cipher";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "a",
|
||||
"type": "number",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "b",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return affineEncode(input, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Affine Cipher Encode
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Affine Cipher Encode in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AffineCipherEncode;
|
184
src/core/operations/AnalyseHash.mjs
Normal file
184
src/core/operations/AnalyseHash.mjs
Normal file
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Analyse hash operation
|
||||
*/
|
||||
class AnalyseHash extends Operation {
|
||||
|
||||
/**
|
||||
* AnalyseHash constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Analyse hash";
|
||||
this.module = "Crypto";
|
||||
this.description = "Tries to determine information about a given hash and suggests which algorithm may have been used to generate it based on its length.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
input = input.replace(/\s/g, "");
|
||||
|
||||
let output = "",
|
||||
possibleHashFunctions = [];
|
||||
const byteLength = input.length / 2,
|
||||
bitLength = byteLength * 8;
|
||||
|
||||
if (!/^[a-f0-9]+$/i.test(input)) {
|
||||
throw new OperationError("Invalid hash");
|
||||
}
|
||||
|
||||
output += "Hash length: " + input.length + "\n" +
|
||||
"Byte length: " + byteLength + "\n" +
|
||||
"Bit length: " + bitLength + "\n\n" +
|
||||
"Based on the length, this hash could have been generated by one of the following hashing functions:\n";
|
||||
|
||||
switch (bitLength) {
|
||||
case 4:
|
||||
possibleHashFunctions = [
|
||||
"Fletcher-4",
|
||||
"Luhn algorithm",
|
||||
"Verhoeff algorithm",
|
||||
];
|
||||
break;
|
||||
case 8:
|
||||
possibleHashFunctions = [
|
||||
"Fletcher-8",
|
||||
];
|
||||
break;
|
||||
case 16:
|
||||
possibleHashFunctions = [
|
||||
"BSD checksum",
|
||||
"CRC-16",
|
||||
"SYSV checksum",
|
||||
"Fletcher-16"
|
||||
];
|
||||
break;
|
||||
case 32:
|
||||
possibleHashFunctions = [
|
||||
"CRC-32",
|
||||
"Fletcher-32",
|
||||
"Adler-32",
|
||||
];
|
||||
break;
|
||||
case 64:
|
||||
possibleHashFunctions = [
|
||||
"CRC-64",
|
||||
"RIPEMD-64",
|
||||
"SipHash",
|
||||
];
|
||||
break;
|
||||
case 128:
|
||||
possibleHashFunctions = [
|
||||
"MD5",
|
||||
"MD4",
|
||||
"MD2",
|
||||
"HAVAL-128",
|
||||
"RIPEMD-128",
|
||||
"Snefru",
|
||||
"Tiger-128",
|
||||
];
|
||||
break;
|
||||
case 160:
|
||||
possibleHashFunctions = [
|
||||
"SHA-1",
|
||||
"SHA-0",
|
||||
"FSB-160",
|
||||
"HAS-160",
|
||||
"HAVAL-160",
|
||||
"RIPEMD-160",
|
||||
"Tiger-160",
|
||||
];
|
||||
break;
|
||||
case 192:
|
||||
possibleHashFunctions = [
|
||||
"Tiger",
|
||||
"HAVAL-192",
|
||||
];
|
||||
break;
|
||||
case 224:
|
||||
possibleHashFunctions = [
|
||||
"SHA-224",
|
||||
"SHA3-224",
|
||||
"ECOH-224",
|
||||
"FSB-224",
|
||||
"HAVAL-224",
|
||||
];
|
||||
break;
|
||||
case 256:
|
||||
possibleHashFunctions = [
|
||||
"SHA-256",
|
||||
"SHA3-256",
|
||||
"BLAKE-256",
|
||||
"ECOH-256",
|
||||
"FSB-256",
|
||||
"GOST",
|
||||
"Grøstl-256",
|
||||
"HAVAL-256",
|
||||
"PANAMA",
|
||||
"RIPEMD-256",
|
||||
"Snefru",
|
||||
];
|
||||
break;
|
||||
case 320:
|
||||
possibleHashFunctions = [
|
||||
"RIPEMD-320",
|
||||
];
|
||||
break;
|
||||
case 384:
|
||||
possibleHashFunctions = [
|
||||
"SHA-384",
|
||||
"SHA3-384",
|
||||
"ECOH-384",
|
||||
"FSB-384",
|
||||
];
|
||||
break;
|
||||
case 512:
|
||||
possibleHashFunctions = [
|
||||
"SHA-512",
|
||||
"SHA3-512",
|
||||
"BLAKE-512",
|
||||
"ECOH-512",
|
||||
"FSB-512",
|
||||
"Grøstl-512",
|
||||
"JH",
|
||||
"MD6",
|
||||
"Spectral Hash",
|
||||
"SWIFFT",
|
||||
"Whirlpool",
|
||||
];
|
||||
break;
|
||||
case 1024:
|
||||
possibleHashFunctions = [
|
||||
"Fowler-Noll-Vo",
|
||||
];
|
||||
break;
|
||||
default:
|
||||
possibleHashFunctions = [
|
||||
"Unknown"
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return output + possibleHashFunctions.join("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AnalyseHash;
|
67
src/core/operations/AtbashCipher.mjs
Normal file
67
src/core/operations/AtbashCipher.mjs
Normal file
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import { affineEncode } from "../lib/Ciphers";
|
||||
|
||||
/**
|
||||
* Atbash Cipher operation
|
||||
*/
|
||||
class AtbashCipher extends Operation {
|
||||
|
||||
/**
|
||||
* AtbashCipher constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Atbash Cipher";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Atbash is a mono-alphabetic substitution cipher originally used to encode the Hebrew alphabet. It has been modified here for use with the Latin alphabet.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Atbash";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return affineEncode(input, [25, 25]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Atbash Cipher
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Atbash Cipher in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AtbashCipher;
|
49
src/core/operations/BSONDeserialise.mjs
Normal file
49
src/core/operations/BSONDeserialise.mjs
Normal file
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import bson from "bson";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* BSON deserialise operation
|
||||
*/
|
||||
class BSONDeserialise extends Operation {
|
||||
|
||||
/**
|
||||
* BSONDeserialise constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "BSON deserialise";
|
||||
this.module = "BSON";
|
||||
this.description = "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.<br><br>Input data should be in a raw bytes format.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/BSON";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (!input.byteLength) return "";
|
||||
|
||||
try {
|
||||
const data = bson.deserialize(new Buffer(input));
|
||||
return JSON.stringify(data, null, 2);
|
||||
} catch (err) {
|
||||
throw new OperationError(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BSONDeserialise;
|
49
src/core/operations/BSONSerialise.mjs
Normal file
49
src/core/operations/BSONSerialise.mjs
Normal file
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import bson from "bson";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* BSON serialise operation
|
||||
*/
|
||||
class BSONSerialise extends Operation {
|
||||
|
||||
/**
|
||||
* BSONSerialise constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "BSON serialise";
|
||||
this.module = "BSON";
|
||||
this.description = "BSON is a computer data interchange format used mainly as a data storage and network transfer format in the MongoDB database. It is a binary form for representing simple data structures, associative arrays (called objects or documents in MongoDB), and various data types of specific interest to MongoDB. The name 'BSON' is based on the term JSON and stands for 'Binary JSON'.<br><br>Input data should be valid JSON.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/BSON";
|
||||
this.inputType = "string";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (!input) return new ArrayBuffer();
|
||||
|
||||
try {
|
||||
const data = JSON.parse(input);
|
||||
return bson.serialize(data).buffer;
|
||||
} catch (err) {
|
||||
throw new OperationError(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BSONSerialise;
|
|
@ -1,66 +0,0 @@
|
|||
/**
|
||||
* Numerical base operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Base = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
DEFAULT_RADIX: 36,
|
||||
|
||||
/**
|
||||
* To Base operation.
|
||||
*
|
||||
* @param {number} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runTo: function(input, args) {
|
||||
if (!input) {
|
||||
throw ("Error: Input must be a number");
|
||||
}
|
||||
const radix = args[0] || Base.DEFAULT_RADIX;
|
||||
if (radix < 2 || radix > 36) {
|
||||
throw "Error: Radix argument must be between 2 and 36";
|
||||
}
|
||||
return input.toString(radix);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Base operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {number}
|
||||
*/
|
||||
runFrom: function(input, args) {
|
||||
const radix = args[0] || Base.DEFAULT_RADIX;
|
||||
if (radix < 2 || radix > 36) {
|
||||
throw "Error: Radix argument must be between 2 and 36";
|
||||
}
|
||||
|
||||
let number = input.replace(/\s/g, "").split("."),
|
||||
result = parseInt(number[0], radix) || 0;
|
||||
|
||||
if (number.length === 1) return result;
|
||||
|
||||
// Fractional part
|
||||
for (let i = 0; i < number[1].length; i++) {
|
||||
const digit = parseInt(number[1][i], radix);
|
||||
result += digit / Math.pow(radix, i+1);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default Base;
|
|
@ -1,137 +0,0 @@
|
|||
import Utils from "../Utils.js";
|
||||
|
||||
|
||||
/**
|
||||
* Base58 operations.
|
||||
*
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Base58 = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
ALPHABET_OPTIONS: [
|
||||
{
|
||||
name: "Bitcoin",
|
||||
value: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
|
||||
},
|
||||
{
|
||||
name: "Ripple",
|
||||
value: "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
|
||||
},
|
||||
],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
REMOVE_NON_ALPH_CHARS: true,
|
||||
|
||||
/**
|
||||
* To Base58 operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runTo: function(input, args) {
|
||||
let alphabet = args[0] || Base58.ALPHABET_OPTIONS[0].value,
|
||||
result = [0];
|
||||
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
|
||||
if (alphabet.length !== 58 ||
|
||||
[].unique.call(alphabet).length !== 58) {
|
||||
throw ("Error: alphabet must be of length 58");
|
||||
}
|
||||
|
||||
if (input.length === 0) return "";
|
||||
|
||||
input.forEach(function(b) {
|
||||
let carry = (result[0] << 8) + b;
|
||||
result[0] = carry % 58;
|
||||
carry = (carry / 58) | 0;
|
||||
|
||||
for (let i = 1; i < result.length; i++) {
|
||||
carry += result[i] << 8;
|
||||
result[i] = carry % 58;
|
||||
carry = (carry / 58) | 0;
|
||||
}
|
||||
|
||||
while (carry > 0) {
|
||||
result.push(carry % 58);
|
||||
carry = (carry / 58) | 0;
|
||||
}
|
||||
});
|
||||
|
||||
result = result.map(function(b) {
|
||||
return alphabet[b];
|
||||
}).reverse().join("");
|
||||
|
||||
while (result.length < input.length) {
|
||||
result = alphabet[0] + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Base58 operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFrom: function(input, args) {
|
||||
let alphabet = args[0] || Base58.ALPHABET_OPTIONS[0].value,
|
||||
removeNonAlphaChars = args[1] === undefined ? true : args[1],
|
||||
result = [0];
|
||||
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
|
||||
if (alphabet.length !== 58 ||
|
||||
[].unique.call(alphabet).length !== 58) {
|
||||
throw ("Alphabet must be of length 58");
|
||||
}
|
||||
|
||||
if (input.length === 0) return [];
|
||||
|
||||
[].forEach.call(input, function(c, charIndex) {
|
||||
const index = alphabet.indexOf(c);
|
||||
|
||||
if (index === -1) {
|
||||
if (removeNonAlphaChars) {
|
||||
return;
|
||||
} else {
|
||||
throw ("Char '" + c + "' at position " + charIndex + " not in alphabet");
|
||||
}
|
||||
}
|
||||
|
||||
let carry = result[0] * 58 + index;
|
||||
result[0] = carry & 0xFF;
|
||||
carry = carry >> 8;
|
||||
|
||||
for (let i = 1; i < result.length; i++) {
|
||||
carry += result[i] * 58;
|
||||
result[i] = carry & 0xFF;
|
||||
carry = carry >> 8;
|
||||
}
|
||||
|
||||
while (carry > 0) {
|
||||
result.push(carry & 0xFF);
|
||||
carry = carry >> 8;
|
||||
}
|
||||
});
|
||||
|
||||
return result.reverse();
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default Base58;
|
|
@ -1,347 +0,0 @@
|
|||
import Utils from "../Utils.js";
|
||||
|
||||
|
||||
/**
|
||||
* Base64 operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Base64 = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
ALPHABET: "A-Za-z0-9+/=",
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
ALPHABET_OPTIONS: [
|
||||
{name: "Standard: A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
|
||||
{name: "URL safe: A-Za-z0-9-_", value: "A-Za-z0-9-_"},
|
||||
{name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
|
||||
{name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
|
||||
{name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
|
||||
{name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
|
||||
{name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
|
||||
{name: "Radix-64: 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
|
||||
{name: "Uuencoding: [space]-_", value: " -_"},
|
||||
{name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
|
||||
{name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
|
||||
{name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
|
||||
{name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
|
||||
],
|
||||
|
||||
/**
|
||||
* To Base64 operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runTo: function(input, args) {
|
||||
const alphabet = args[0] || Base64.ALPHABET;
|
||||
return Utils.toBase64(input, alphabet);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
REMOVE_NON_ALPH_CHARS: true,
|
||||
|
||||
/**
|
||||
* From Base64 operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFrom: function(input, args) {
|
||||
let alphabet = args[0] || Base64.ALPHABET,
|
||||
removeNonAlphChars = args[1];
|
||||
|
||||
return Utils.fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
BASE32_ALPHABET: "A-Z2-7=",
|
||||
|
||||
/**
|
||||
* To Base32 operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runTo32: function(input, args) {
|
||||
if (!input) return "";
|
||||
|
||||
let alphabet = args[0] ?
|
||||
Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
|
||||
output = "",
|
||||
chr1, chr2, chr3, chr4, chr5,
|
||||
enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
|
||||
i = 0;
|
||||
|
||||
while (i < input.length) {
|
||||
chr1 = input[i++];
|
||||
chr2 = input[i++];
|
||||
chr3 = input[i++];
|
||||
chr4 = input[i++];
|
||||
chr5 = input[i++];
|
||||
|
||||
enc1 = chr1 >> 3;
|
||||
enc2 = ((chr1 & 7) << 2) | (chr2 >> 6);
|
||||
enc3 = (chr2 >> 1) & 31;
|
||||
enc4 = ((chr2 & 1) << 4) | (chr3 >> 4);
|
||||
enc5 = ((chr3 & 15) << 1) | (chr4 >> 7);
|
||||
enc6 = (chr4 >> 2) & 31;
|
||||
enc7 = ((chr4 & 3) << 3) | (chr5 >> 5);
|
||||
enc8 = chr5 & 31;
|
||||
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc5 = enc6 = enc7 = enc8 = 32;
|
||||
} else if (isNaN(chr4)) {
|
||||
enc6 = enc7 = enc8 = 32;
|
||||
} else if (isNaN(chr5)) {
|
||||
enc8 = 32;
|
||||
}
|
||||
|
||||
output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) +
|
||||
alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) +
|
||||
alphabet.charAt(enc7) + alphabet.charAt(enc8);
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Base32 operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFrom32: function(input, args) {
|
||||
if (!input) return [];
|
||||
|
||||
let alphabet = args[0] ?
|
||||
Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
|
||||
removeNonAlphChars = args[0];
|
||||
|
||||
let output = [],
|
||||
chr1, chr2, chr3, chr4, chr5,
|
||||
enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
|
||||
i = 0;
|
||||
|
||||
if (removeNonAlphChars) {
|
||||
const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g");
|
||||
input = input.replace(re, "");
|
||||
}
|
||||
|
||||
while (i < input.length) {
|
||||
enc1 = alphabet.indexOf(input.charAt(i++));
|
||||
enc2 = alphabet.indexOf(input.charAt(i++) || "=");
|
||||
enc3 = alphabet.indexOf(input.charAt(i++) || "=");
|
||||
enc4 = alphabet.indexOf(input.charAt(i++) || "=");
|
||||
enc5 = alphabet.indexOf(input.charAt(i++) || "=");
|
||||
enc6 = alphabet.indexOf(input.charAt(i++) || "=");
|
||||
enc7 = alphabet.indexOf(input.charAt(i++) || "=");
|
||||
enc8 = alphabet.indexOf(input.charAt(i++) || "=");
|
||||
|
||||
chr1 = (enc1 << 3) | (enc2 >> 2);
|
||||
chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4);
|
||||
chr3 = ((enc4 & 15) << 4) | (enc5 >> 1);
|
||||
chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3);
|
||||
chr5 = ((enc7 & 7) << 5) | enc8;
|
||||
|
||||
output.push(chr1);
|
||||
if (enc2 & 3 !== 0 || enc3 !== 32) output.push(chr2);
|
||||
if (enc4 & 15 !== 0 || enc5 !== 32) output.push(chr3);
|
||||
if (enc5 & 1 !== 0 || enc6 !== 32) output.push(chr4);
|
||||
if (enc7 & 7 !== 0 || enc8 !== 32) output.push(chr5);
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
SHOW_IN_BINARY: false,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
OFFSETS_SHOW_VARIABLE: true,
|
||||
|
||||
/**
|
||||
* Show Base64 offsets operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runOffsets: function(input, args) {
|
||||
let alphabet = args[0] || Base64.ALPHABET,
|
||||
showVariable = args[1],
|
||||
offset0 = Utils.toBase64(input, alphabet),
|
||||
offset1 = Utils.toBase64([0].concat(input), alphabet),
|
||||
offset2 = Utils.toBase64([0, 0].concat(input), alphabet),
|
||||
len0 = offset0.indexOf("="),
|
||||
len1 = offset1.indexOf("="),
|
||||
len2 = offset2.indexOf("="),
|
||||
script = "<script type='application/javascript'>$('[data-toggle=\"tooltip\"]').tooltip()</script>",
|
||||
staticSection = "",
|
||||
padding = "";
|
||||
|
||||
if (input.length < 1) {
|
||||
return "Please enter a string.";
|
||||
}
|
||||
|
||||
// Highlight offset 0
|
||||
if (len0 % 4 === 2) {
|
||||
staticSection = offset0.slice(0, -3);
|
||||
offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet).slice(0, -2)) + "'>" +
|
||||
staticSection + "</span>" +
|
||||
"<span class='hlgreen'>" + offset0.substr(offset0.length - 3, 1) + "</span>" +
|
||||
"<span class='hlred'>" + offset0.substr(offset0.length - 2) + "</span>";
|
||||
} else if (len0 % 4 === 3) {
|
||||
staticSection = offset0.slice(0, -2);
|
||||
offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet).slice(0, -1)) + "'>" +
|
||||
staticSection + "</span>" +
|
||||
"<span class='hlgreen'>" + offset0.substr(offset0.length - 2, 1) + "</span>" +
|
||||
"<span class='hlred'>" + offset0.substr(offset0.length - 1) + "</span>";
|
||||
} else {
|
||||
staticSection = offset0;
|
||||
offset0 = "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64(staticSection, alphabet)) + "'>" +
|
||||
staticSection + "</span>";
|
||||
}
|
||||
|
||||
if (!showVariable) {
|
||||
offset0 = staticSection;
|
||||
}
|
||||
|
||||
|
||||
// Highlight offset 1
|
||||
padding = "<span class='hlred'>" + offset1.substr(0, 1) + "</span>" +
|
||||
"<span class='hlgreen'>" + offset1.substr(1, 1) + "</span>";
|
||||
offset1 = offset1.substr(2);
|
||||
if (len1 % 4 === 2) {
|
||||
staticSection = offset1.slice(0, -3);
|
||||
offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1, -2)) + "'>" +
|
||||
staticSection + "</span>" +
|
||||
"<span class='hlgreen'>" + offset1.substr(offset1.length - 3, 1) + "</span>" +
|
||||
"<span class='hlred'>" + offset1.substr(offset1.length - 2) + "</span>";
|
||||
} else if (len1 % 4 === 3) {
|
||||
staticSection = offset1.slice(0, -2);
|
||||
offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1, -1)) + "'>" +
|
||||
staticSection + "</span>" +
|
||||
"<span class='hlgreen'>" + offset1.substr(offset1.length - 2, 1) + "</span>" +
|
||||
"<span class='hlred'>" + offset1.substr(offset1.length - 1) + "</span>";
|
||||
} else {
|
||||
staticSection = offset1;
|
||||
offset1 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64("AA" + staticSection, alphabet).slice(1)) + "'>" +
|
||||
staticSection + "</span>";
|
||||
}
|
||||
|
||||
if (!showVariable) {
|
||||
offset1 = staticSection;
|
||||
}
|
||||
|
||||
// Highlight offset 2
|
||||
padding = "<span class='hlred'>" + offset2.substr(0, 2) + "</span>" +
|
||||
"<span class='hlgreen'>" + offset2.substr(2, 1) + "</span>";
|
||||
offset2 = offset2.substr(3);
|
||||
if (len2 % 4 === 2) {
|
||||
staticSection = offset2.slice(0, -3);
|
||||
offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
|
||||
staticSection + "</span>" +
|
||||
"<span class='hlgreen'>" + offset2.substr(offset2.length - 3, 1) + "</span>" +
|
||||
"<span class='hlred'>" + offset2.substr(offset2.length - 2) + "</span>";
|
||||
} else if (len2 % 4 === 3) {
|
||||
staticSection = offset2.slice(0, -2);
|
||||
offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2, -2)) + "'>" +
|
||||
staticSection + "</span>" +
|
||||
"<span class='hlgreen'>" + offset2.substr(offset2.length - 2, 1) + "</span>" +
|
||||
"<span class='hlred'>" + offset2.substr(offset2.length - 1) + "</span>";
|
||||
} else {
|
||||
staticSection = offset2;
|
||||
offset2 = padding + "<span data-toggle='tooltip' data-placement='top' title='" +
|
||||
Utils.escapeHtml(Utils.fromBase64("AAA" + staticSection, alphabet).slice(2)) + "'>" +
|
||||
staticSection + "</span>";
|
||||
}
|
||||
|
||||
if (!showVariable) {
|
||||
offset2 = staticSection;
|
||||
}
|
||||
|
||||
return (showVariable ? "Characters highlighted in <span class='hlgreen'>green</span> could change if the input is surrounded by more data." +
|
||||
"\nCharacters highlighted in <span class='hlred'>red</span> are for padding purposes only." +
|
||||
"\nUnhighlighted characters are <span data-toggle='tooltip' data-placement='top' title='Tooltip on left'>static</span>." +
|
||||
"\nHover over the static sections to see what they decode to on their own.\n" +
|
||||
"\nOffset 0: " + offset0 +
|
||||
"\nOffset 1: " + offset1 +
|
||||
"\nOffset 2: " + offset2 +
|
||||
script :
|
||||
offset0 + "\n" + offset1 + "\n" + offset2);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Highlight to Base64
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightTo: function(pos, args) {
|
||||
pos[0].start = Math.floor(pos[0].start / 3 * 4);
|
||||
pos[0].end = Math.ceil(pos[0].end / 3 * 4);
|
||||
return pos;
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight from Base64
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightFrom: function(pos, args) {
|
||||
pos[0].start = Math.ceil(pos[0].start / 4 * 3);
|
||||
pos[0].end = Math.floor(pos[0].end / 4 * 3);
|
||||
return pos;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default Base64;
|
55
src/core/operations/Bcrypt.mjs
Normal file
55
src/core/operations/Bcrypt.mjs
Normal file
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
/**
|
||||
* Bcrypt operation
|
||||
*/
|
||||
class Bcrypt extends Operation {
|
||||
|
||||
/**
|
||||
* Bcrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bcrypt";
|
||||
this.module = "Crypto";
|
||||
this.description = "bcrypt is a password hashing function designed by Niels Provos and David Mazi\xe8res, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count (rounds) can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.<br><br>Enter the password in the input to generate its hash.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bcrypt";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Rounds",
|
||||
"type": "number",
|
||||
"value": 10
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const rounds = args[0];
|
||||
const salt = await bcrypt.genSalt(rounds);
|
||||
|
||||
return await bcrypt.hash(input, salt, null, p => {
|
||||
// Progress callback
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Bcrypt;
|
56
src/core/operations/BcryptCompare.mjs
Normal file
56
src/core/operations/BcryptCompare.mjs
Normal file
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
/**
|
||||
* Bcrypt compare operation
|
||||
*/
|
||||
class BcryptCompare extends Operation {
|
||||
|
||||
/**
|
||||
* BcryptCompare constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bcrypt compare";
|
||||
this.module = "Crypto";
|
||||
this.description = "Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bcrypt";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Hash",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const hash = args[0];
|
||||
|
||||
const match = await bcrypt.compare(input, hash, null, p => {
|
||||
// Progress callback
|
||||
if (ENVIRONMENT_IS_WORKER())
|
||||
self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);
|
||||
});
|
||||
|
||||
return match ? "Match: " + input : "No match";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BcryptCompare;
|
49
src/core/operations/BcryptParse.mjs
Normal file
49
src/core/operations/BcryptParse.mjs
Normal file
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
/**
|
||||
* Bcrypt parse operation
|
||||
*/
|
||||
class BcryptParse extends Operation {
|
||||
|
||||
/**
|
||||
* BcryptParse constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bcrypt parse";
|
||||
this.module = "Crypto";
|
||||
this.description = "Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bcrypt";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
try {
|
||||
return `Rounds: ${bcrypt.getRounds(input)}
|
||||
Salt: ${bcrypt.getSalt(input)}
|
||||
Password hash: ${input.split(bcrypt.getSalt(input))[1]}
|
||||
Full hash: ${input}`;
|
||||
} catch (err) {
|
||||
throw new OperationError("Error: " + err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BcryptParse;
|
125
src/core/operations/BifidCipherDecode.mjs
Normal file
125
src/core/operations/BifidCipherDecode.mjs
Normal file
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import { genPolybiusSquare } from "../lib/Ciphers";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Bifid Cipher Decode operation
|
||||
*/
|
||||
class BifidCipherDecode extends Operation {
|
||||
|
||||
/**
|
||||
* BifidCipherDecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bifid Cipher Decode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bifid_cipher";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Keyword",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
* @throws {OperationError} if invalid key
|
||||
*/
|
||||
run(input, args) {
|
||||
const keywordStr = args[0].toUpperCase().replace("J", "I"),
|
||||
keyword = keywordStr.split("").unique(),
|
||||
alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ",
|
||||
structure = [];
|
||||
|
||||
let output = "",
|
||||
count = 0,
|
||||
trans = "";
|
||||
|
||||
if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0)
|
||||
throw new OperationError("The key must consist only of letters in the English alphabet");
|
||||
|
||||
const polybius = genPolybiusSquare(keywordStr);
|
||||
|
||||
input.replace("J", "I").split("").forEach((letter) => {
|
||||
const alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0;
|
||||
let polInd;
|
||||
|
||||
if (alpInd) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
polInd = polybius[i].indexOf(letter.toLocaleUpperCase());
|
||||
if (polInd >= 0) {
|
||||
trans += `${i}${polInd}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (alpha.split("").indexOf(letter) >= 0) {
|
||||
structure.push(true);
|
||||
} else if (alpInd) {
|
||||
structure.push(false);
|
||||
}
|
||||
} else {
|
||||
structure.push(letter);
|
||||
}
|
||||
});
|
||||
|
||||
structure.forEach(pos => {
|
||||
if (typeof pos === "boolean") {
|
||||
const coords = [trans[count], trans[count+trans.length/2]];
|
||||
|
||||
output += pos ?
|
||||
polybius[coords[0]][coords[1]] :
|
||||
polybius[coords[0]][coords[1]].toLocaleLowerCase();
|
||||
count++;
|
||||
} else {
|
||||
output += pos;
|
||||
}
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Bifid Cipher Decode
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Bifid Cipher Decode in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BifidCipherDecode;
|
130
src/core/operations/BifidCipherEncode.mjs
Normal file
130
src/core/operations/BifidCipherEncode.mjs
Normal file
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { genPolybiusSquare } from "../lib/Ciphers";
|
||||
|
||||
/**
|
||||
* Bifid Cipher Encode operation
|
||||
*/
|
||||
class BifidCipherEncode extends Operation {
|
||||
|
||||
/**
|
||||
* BifidCipherEncode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bifid Cipher Encode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "The Bifid cipher is a cipher which uses a Polybius square in conjunction with transposition, which can be fairly difficult to decipher without knowing the alphabet keyword.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bifid_cipher";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Keyword",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
* @throws {OperationError} if key is invalid
|
||||
*/
|
||||
run(input, args) {
|
||||
const keywordStr = args[0].toUpperCase().replace("J", "I"),
|
||||
keyword = keywordStr.split("").unique(),
|
||||
alpha = "ABCDEFGHIKLMNOPQRSTUVWXYZ",
|
||||
xCo = [],
|
||||
yCo = [],
|
||||
structure = [];
|
||||
|
||||
let output = "",
|
||||
count = 0;
|
||||
|
||||
|
||||
if (!/^[A-Z]+$/.test(keywordStr) && keyword.length > 0)
|
||||
throw new OperationError("The key must consist only of letters in the English alphabet");
|
||||
|
||||
const polybius = genPolybiusSquare(keywordStr);
|
||||
|
||||
input.replace("J", "I").split("").forEach(letter => {
|
||||
const alpInd = alpha.split("").indexOf(letter.toLocaleUpperCase()) >= 0;
|
||||
let polInd;
|
||||
|
||||
if (alpInd) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
polInd = polybius[i].indexOf(letter.toLocaleUpperCase());
|
||||
if (polInd >= 0) {
|
||||
xCo.push(polInd);
|
||||
yCo.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (alpha.split("").indexOf(letter) >= 0) {
|
||||
structure.push(true);
|
||||
} else if (alpInd) {
|
||||
structure.push(false);
|
||||
}
|
||||
} else {
|
||||
structure.push(letter);
|
||||
}
|
||||
});
|
||||
|
||||
const trans = `${yCo.join("")}${xCo.join("")}`;
|
||||
|
||||
structure.forEach(pos => {
|
||||
if (typeof pos === "boolean") {
|
||||
const coords = trans.substr(2*count, 2).split("");
|
||||
|
||||
output += pos ?
|
||||
polybius[coords[0]][coords[1]] :
|
||||
polybius[coords[0]][coords[1]].toLocaleLowerCase();
|
||||
count++;
|
||||
} else {
|
||||
output += pos;
|
||||
}
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Bifid Cipher Encode
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Bifid Cipher Encode in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BifidCipherEncode;
|
76
src/core/operations/BitShiftLeft.mjs
Normal file
76
src/core/operations/BitShiftLeft.mjs
Normal file
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* Bit shift left operation
|
||||
*/
|
||||
class BitShiftLeft extends Operation {
|
||||
|
||||
/**
|
||||
* BitShiftLeft constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bit shift left";
|
||||
this.module = "Default";
|
||||
this.description = "Shifts the bits in each byte towards the left by the specified amount.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Amount",
|
||||
"type": "number",
|
||||
"value": 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const amount = args[0];
|
||||
|
||||
return input.map(b => {
|
||||
return (b << amount) & 0xff;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Bit shift left
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Bit shift left in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BitShiftLeft;
|
83
src/core/operations/BitShiftRight.mjs
Normal file
83
src/core/operations/BitShiftRight.mjs
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* Bit shift right operation
|
||||
*/
|
||||
class BitShiftRight extends Operation {
|
||||
|
||||
/**
|
||||
* BitShiftRight constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bit shift right";
|
||||
this.module = "Default";
|
||||
this.description = "Shifts the bits in each byte towards the right by the specified amount.<br><br><i>Logical shifts</i> replace the leftmost bits with zeros.<br><i>Arithmetic shifts</i> preserve the most significant bit (MSB) of the original byte keeping the sign the same (positive or negative).";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Amount",
|
||||
"type": "number",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "Type",
|
||||
"type": "option",
|
||||
"value": ["Logical shift", "Arithmetic shift"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const amount = args[0],
|
||||
type = args[1],
|
||||
mask = type === "Logical shift" ? 0 : 0x80;
|
||||
|
||||
return input.map(b => {
|
||||
return (b >>> amount) ^ (b & mask);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Bit shift right
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Bit shift right in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BitShiftRight;
|
|
@ -1,310 +0,0 @@
|
|||
import Utils from "../Utils.js";
|
||||
import CryptoJS from "crypto-js";
|
||||
|
||||
|
||||
/**
|
||||
* Bitwise operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const BitwiseOp = {
|
||||
|
||||
/**
|
||||
* Runs bitwise operations across the input data.
|
||||
*
|
||||
* @private
|
||||
* @param {byteArray} input
|
||||
* @param {byteArray} key
|
||||
* @param {function} func - The bitwise calculation to carry out
|
||||
* @param {boolean} nullPreserving
|
||||
* @param {string} scheme
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
_bitOp: function (input, key, func, nullPreserving, scheme) {
|
||||
if (!key || !key.length) key = [0];
|
||||
let result = [],
|
||||
x = null,
|
||||
k = null,
|
||||
o = null;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
k = key[i % key.length];
|
||||
o = input[i];
|
||||
x = nullPreserving && (o === 0 || o === k) ? o : func(o, k);
|
||||
result.push(x);
|
||||
if (scheme !== "Standard" && !(nullPreserving && (o === 0 || o === k))) {
|
||||
switch (scheme) {
|
||||
case "Input differential":
|
||||
key[i % key.length] = x;
|
||||
break;
|
||||
case "Output differential":
|
||||
key[i % key.length] = o;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XOR_PRESERVE_NULLS: false,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XOR_SCHEME: ["Standard", "Input differential", "Output differential"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
KEY_FORMAT: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"],
|
||||
|
||||
/**
|
||||
* XOR operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runXor: function (input, args) {
|
||||
let key = Utils.format[args[0].option].parse(args[0].string || ""),
|
||||
scheme = args[1],
|
||||
nullPreserving = args[2];
|
||||
|
||||
key = Utils.wordArrayToByteArray(key);
|
||||
|
||||
return BitwiseOp._bitOp(input, key, BitwiseOp._xor, nullPreserving, scheme);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XOR_BRUTE_KEY_LENGTH: ["1", "2"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XOR_BRUTE_SAMPLE_LENGTH: 100,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XOR_BRUTE_SAMPLE_OFFSET: 0,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XOR_BRUTE_PRINT_KEY: true,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XOR_BRUTE_OUTPUT_HEX: false,
|
||||
|
||||
/**
|
||||
* XOR Brute Force operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runXorBrute: function (input, args) {
|
||||
let keyLength = parseInt(args[0], 10),
|
||||
sampleLength = args[1],
|
||||
sampleOffset = args[2],
|
||||
nullPreserving = args[3],
|
||||
differential = args[4],
|
||||
crib = args[5],
|
||||
printKey = args[6],
|
||||
outputHex = args[7],
|
||||
regex;
|
||||
|
||||
let output = "",
|
||||
result,
|
||||
resultUtf8;
|
||||
|
||||
input = input.slice(sampleOffset, sampleOffset + sampleLength);
|
||||
|
||||
if (crib !== "") {
|
||||
regex = new RegExp(crib, "im");
|
||||
}
|
||||
|
||||
|
||||
for (let key = 1, l = Math.pow(256, keyLength); key < l; key++) {
|
||||
result = BitwiseOp._bitOp(input, Utils.hexToByteArray(key.toString(16)), BitwiseOp._xor, nullPreserving, differential);
|
||||
resultUtf8 = Utils.byteArrayToUtf8(result);
|
||||
if (crib !== "" && resultUtf8.search(regex) === -1) continue;
|
||||
if (printKey) output += "Key = " + Utils.hex(key, (2*keyLength)) + ": ";
|
||||
if (outputHex)
|
||||
output += Utils.byteArrayToHex(result) + "\n";
|
||||
else
|
||||
output += Utils.printable(resultUtf8, false) + "\n";
|
||||
if (printKey) output += "\n";
|
||||
}
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* NOT operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runNot: function (input, args) {
|
||||
return BitwiseOp._bitOp(input, null, BitwiseOp._not);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* AND operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runAnd: function (input, args) {
|
||||
let key = Utils.format[args[0].option].parse(args[0].string || "");
|
||||
key = Utils.wordArrayToByteArray(key);
|
||||
|
||||
return BitwiseOp._bitOp(input, key, BitwiseOp._and);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* OR operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runOr: function (input, args) {
|
||||
let key = Utils.format[args[0].option].parse(args[0].string || "");
|
||||
key = Utils.wordArrayToByteArray(key);
|
||||
|
||||
return BitwiseOp._bitOp(input, key, BitwiseOp._or);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* ADD operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runAdd: function (input, args) {
|
||||
let key = Utils.format[args[0].option].parse(args[0].string || "");
|
||||
key = Utils.wordArrayToByteArray(key);
|
||||
|
||||
return BitwiseOp._bitOp(input, key, BitwiseOp._add);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* SUB operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runSub: function (input, args) {
|
||||
let key = Utils.format[args[0].option].parse(args[0].string || "");
|
||||
key = Utils.wordArrayToByteArray(key);
|
||||
|
||||
return BitwiseOp._bitOp(input, key, BitwiseOp._sub);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* XOR bitwise calculation.
|
||||
*
|
||||
* @private
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
_xor: function (operand, key) {
|
||||
return operand ^ key;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* NOT bitwise calculation.
|
||||
*
|
||||
* @private
|
||||
* @param {number} operand
|
||||
* @returns {number}
|
||||
*/
|
||||
_not: function (operand, _) {
|
||||
return ~operand & 0xff;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* AND bitwise calculation.
|
||||
*
|
||||
* @private
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
_and: function (operand, key) {
|
||||
return operand & key;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* OR bitwise calculation.
|
||||
*
|
||||
* @private
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
_or: function (operand, key) {
|
||||
return operand | key;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* ADD bitwise calculation.
|
||||
*
|
||||
* @private
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
_add: function (operand, key) {
|
||||
return (operand + key) % 256;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* SUB bitwise calculation.
|
||||
*
|
||||
* @private
|
||||
* @param {number} operand
|
||||
* @param {number} key
|
||||
* @returns {number}
|
||||
*/
|
||||
_sub: function (operand, key) {
|
||||
const result = operand - key;
|
||||
return (result < 0) ? 256 + result : result;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default BitwiseOp;
|
101
src/core/operations/BlowfishDecrypt.mjs
Normal file
101
src/core/operations/BlowfishDecrypt.mjs
Normal file
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { Blowfish } from "../vendor/Blowfish";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
import { toHexFast } from "../lib/Hex";
|
||||
|
||||
/**
|
||||
* Lookup table for Blowfish output types.
|
||||
*/
|
||||
const BLOWFISH_OUTPUT_TYPE_LOOKUP = {
|
||||
Base64: 0, Hex: 1, String: 2, Raw: 3
|
||||
};
|
||||
/**
|
||||
* Lookup table for Blowfish modes.
|
||||
*/
|
||||
const BLOWFISH_MODE_LOOKUP = {
|
||||
ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5
|
||||
};
|
||||
|
||||
/**
|
||||
* Blowfish Decrypt operation
|
||||
*/
|
||||
class BlowfishDecrypt extends Operation {
|
||||
|
||||
/**
|
||||
* BlowfishDecrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Blowfish Decrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Blowfish_(cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Mode",
|
||||
"type": "option",
|
||||
"value": ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||
[,, mode, inputType, outputType] = args;
|
||||
|
||||
if (key.length === 0) throw new OperationError("Enter a key");
|
||||
|
||||
input = inputType === "Raw" ? Utils.strToByteArray(input) : input;
|
||||
|
||||
Blowfish.setIV(toBase64(iv), 0);
|
||||
|
||||
const result = Blowfish.decrypt(input, key, {
|
||||
outputType: BLOWFISH_OUTPUT_TYPE_LOOKUP[inputType], // This actually means inputType. The library is weird.
|
||||
cipherMode: BLOWFISH_MODE_LOOKUP[mode]
|
||||
});
|
||||
|
||||
return outputType === "Hex" ? toHexFast(Utils.strToByteArray(result)) : result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BlowfishDecrypt;
|
102
src/core/operations/BlowfishEncrypt.mjs
Normal file
102
src/core/operations/BlowfishEncrypt.mjs
Normal file
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import { Blowfish } from "../vendor/Blowfish";
|
||||
import { toBase64 } from "../lib/Base64";
|
||||
|
||||
/**
|
||||
* Lookup table for Blowfish output types.
|
||||
*/
|
||||
const BLOWFISH_OUTPUT_TYPE_LOOKUP = {
|
||||
Base64: 0, Hex: 1, String: 2, Raw: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup table for Blowfish modes.
|
||||
*/
|
||||
const BLOWFISH_MODE_LOOKUP = {
|
||||
ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Blowfish Encrypt operation
|
||||
*/
|
||||
class BlowfishEncrypt extends Operation {
|
||||
|
||||
/**
|
||||
* BlowfishEncrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Blowfish Encrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Blowfish_(cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Mode",
|
||||
"type": "option",
|
||||
"value": ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Base64", "Raw"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||
[,, mode, inputType, outputType] = args;
|
||||
|
||||
if (key.length === 0) throw new OperationError("Enter a key");
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
Blowfish.setIV(toBase64(iv), 0);
|
||||
|
||||
const enc = Blowfish.encrypt(input, key, {
|
||||
outputType: BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType],
|
||||
cipherMode: BLOWFISH_MODE_LOOKUP[mode]
|
||||
});
|
||||
|
||||
return outputType === "Raw" ? Utils.byteArrayToChars(enc) : enc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BlowfishEncrypt;
|
|
@ -1,427 +0,0 @@
|
|||
/* globals app */
|
||||
import Utils from "../Utils.js";
|
||||
|
||||
|
||||
/**
|
||||
* Byte representation operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const ByteRepr = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
HEX_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
BIN_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"],
|
||||
|
||||
/**
|
||||
* To Hex operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runToHex: function(input, args) {
|
||||
const delim = Utils.charRep[args[0] || "Space"];
|
||||
return Utils.toHex(input, delim, 2);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Hex operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFromHex: function(input, args) {
|
||||
const delim = args[0] || "Space";
|
||||
return Utils.fromHex(input, delim, 2);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* To Octal operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.pw]
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runToOct: function(input, args) {
|
||||
const delim = Utils.charRep[args[0] || "Space"];
|
||||
return input.map(val => val.toString(8)).join(delim);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Octal operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.pw]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFromOct: function(input, args) {
|
||||
const delim = Utils.charRep[args[0] || "Space"];
|
||||
if (input.length === 0) return [];
|
||||
return input.split(delim).map(val => parseInt(val, 8));
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
CHARCODE_BASE: 16,
|
||||
|
||||
/**
|
||||
* To Charcode operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runToCharcode: function(input, args) {
|
||||
let delim = Utils.charRep[args[0] || "Space"],
|
||||
base = args[1],
|
||||
output = "",
|
||||
padding = 2,
|
||||
ordinal;
|
||||
|
||||
if (base < 2 || base > 36) {
|
||||
throw "Error: Base argument must be between 2 and 36";
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
ordinal = Utils.ord(input[i]);
|
||||
|
||||
if (base === 16) {
|
||||
if (ordinal < 256) padding = 2;
|
||||
else if (ordinal < 65536) padding = 4;
|
||||
else if (ordinal < 16777216) padding = 6;
|
||||
else if (ordinal < 4294967296) padding = 8;
|
||||
else padding = 2;
|
||||
|
||||
if (padding > 2 && app) app.options.attemptHighlight = false;
|
||||
|
||||
output += Utils.hex(ordinal, padding) + delim;
|
||||
} else {
|
||||
if (app) app.options.attemptHighlight = false;
|
||||
output += ordinal.toString(base) + delim;
|
||||
}
|
||||
}
|
||||
|
||||
return output.slice(0, -delim.length);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Charcode operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFromCharcode: function(input, args) {
|
||||
let delim = Utils.charRep[args[0] || "Space"],
|
||||
base = args[1],
|
||||
bites = input.split(delim),
|
||||
i = 0;
|
||||
|
||||
if (base < 2 || base > 36) {
|
||||
throw "Error: Base argument must be between 2 and 36";
|
||||
}
|
||||
|
||||
if (base !== 16 && app) {
|
||||
app.options.attemptHighlight = false;
|
||||
}
|
||||
|
||||
// Split into groups of 2 if the whole string is concatenated and
|
||||
// too long to be a single character
|
||||
if (bites.length === 1 && input.length > 17) {
|
||||
bites = [];
|
||||
for (i = 0; i < input.length; i += 2) {
|
||||
bites.push(input.slice(i, i+2));
|
||||
}
|
||||
}
|
||||
|
||||
let latin1 = "";
|
||||
for (i = 0; i < bites.length; i++) {
|
||||
latin1 += Utils.chr(parseInt(bites[i], base));
|
||||
}
|
||||
return Utils.strToByteArray(latin1);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Highlight to hex
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightTo: function(pos, args) {
|
||||
let delim = Utils.charRep[args[0] || "Space"],
|
||||
len = delim === "\r\n" ? 1 : delim.length;
|
||||
|
||||
pos[0].start = pos[0].start * (2 + len);
|
||||
pos[0].end = pos[0].end * (2 + len) - len;
|
||||
|
||||
// 0x and \x are added to the beginning if they are selected, so increment the positions accordingly
|
||||
if (delim === "0x" || delim === "\\x") {
|
||||
pos[0].start += 2;
|
||||
pos[0].end += 2;
|
||||
}
|
||||
return pos;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Highlight to hex
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightFrom: function(pos, args) {
|
||||
let delim = Utils.charRep[args[0] || "Space"],
|
||||
len = delim === "\r\n" ? 1 : delim.length,
|
||||
width = len + 2;
|
||||
|
||||
// 0x and \x are added to the beginning if they are selected, so increment the positions accordingly
|
||||
if (delim === "0x" || delim === "\\x") {
|
||||
if (pos[0].start > 1) pos[0].start -= 2;
|
||||
else pos[0].start = 0;
|
||||
if (pos[0].end > 1) pos[0].end -= 2;
|
||||
else pos[0].end = 0;
|
||||
}
|
||||
|
||||
pos[0].start = pos[0].start === 0 ? 0 : Math.round(pos[0].start / width);
|
||||
pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / width);
|
||||
return pos;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* To Decimal operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runToDecimal: function(input, args) {
|
||||
const delim = Utils.charRep[args[0]];
|
||||
return input.join(delim);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Decimal operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFromDecimal: function(input, args) {
|
||||
const delim = Utils.charRep[args[0]];
|
||||
let byteStr = input.split(delim), output = [];
|
||||
if (byteStr[byteStr.length-1] === "")
|
||||
byteStr = byteStr.slice(0, byteStr.length-1);
|
||||
|
||||
for (let i = 0; i < byteStr.length; i++) {
|
||||
output[i] = parseInt(byteStr[i], 10);
|
||||
}
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* To Binary operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runToBinary: function(input, args) {
|
||||
let delim = Utils.charRep[args[0] || "Space"],
|
||||
output = "",
|
||||
padding = 8;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
output += Utils.pad(input[i].toString(2), padding) + delim;
|
||||
}
|
||||
|
||||
if (delim.length) {
|
||||
return output.slice(0, -delim.length);
|
||||
} else {
|
||||
return output;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Binary operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFromBinary: function(input, args) {
|
||||
if (args[0] !== "None") {
|
||||
const delimRegex = Utils.regexRep[args[0] || "Space"];
|
||||
input = input.replace(delimRegex, "");
|
||||
}
|
||||
|
||||
const output = [];
|
||||
const byteLen = 8;
|
||||
for (let i = 0; i < input.length; i += byteLen) {
|
||||
output.push(parseInt(input.substr(i, byteLen), 2));
|
||||
}
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Highlight to binary
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightToBinary: function(pos, args) {
|
||||
const delim = Utils.charRep[args[0] || "Space"];
|
||||
pos[0].start = pos[0].start * (8 + delim.length);
|
||||
pos[0].end = pos[0].end * (8 + delim.length) - delim.length;
|
||||
return pos;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Highlight from binary
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightFromBinary: function(pos, args) {
|
||||
const delim = Utils.charRep[args[0] || "Space"];
|
||||
pos[0].start = pos[0].start === 0 ? 0 : Math.floor(pos[0].start / (8 + delim.length));
|
||||
pos[0].end = pos[0].end === 0 ? 0 : Math.ceil(pos[0].end / (8 + delim.length));
|
||||
return pos;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
HEX_CONTENT_CONVERT_WHICH: ["Only special chars", "Only special chars including spaces", "All chars"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
HEX_CONTENT_SPACES_BETWEEN_BYTES: false,
|
||||
|
||||
/**
|
||||
* To Hex Content operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runToHexContent: function(input, args) {
|
||||
const convert = args[0];
|
||||
const spaces = args[1];
|
||||
if (convert === "All chars") {
|
||||
let result = "|" + Utils.toHex(input) + "|";
|
||||
if (!spaces) result = result.replace(/ /g, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
let output = "",
|
||||
inHex = false,
|
||||
convertSpaces = convert === "Only special chars including spaces",
|
||||
b;
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
b = input[i];
|
||||
if ((b === 32 && convertSpaces) || (b < 48 && b !== 32) || (b > 57 && b < 65) || (b > 90 && b < 97) || b > 122) {
|
||||
if (!inHex) {
|
||||
output += "|";
|
||||
inHex = true;
|
||||
} else if (spaces) output += " ";
|
||||
output += Utils.toHex([b]);
|
||||
} else {
|
||||
if (inHex) {
|
||||
output += "|";
|
||||
inHex = false;
|
||||
}
|
||||
output += Utils.chr(input[i]);
|
||||
}
|
||||
}
|
||||
if (inHex) output += "|";
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* From Hex Content operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runFromHexContent: function(input, args) {
|
||||
const regex = /\|([a-f\d ]{2,})\|/gi;
|
||||
let output = [], m, i = 0;
|
||||
while ((m = regex.exec(input))) {
|
||||
// Add up to match
|
||||
for (; i < m.index;)
|
||||
output.push(Utils.ord(input[i++]));
|
||||
|
||||
// Add match
|
||||
const bytes = Utils.fromHex(m[1]);
|
||||
if (bytes) {
|
||||
for (let a = 0; a < bytes.length;)
|
||||
output.push(bytes[a++]);
|
||||
} else {
|
||||
// Not valid hex, print as normal
|
||||
for (; i < regex.lastIndex;)
|
||||
output.push(Utils.ord(input[i++]));
|
||||
}
|
||||
|
||||
i = regex.lastIndex;
|
||||
}
|
||||
// Add all after final match
|
||||
for (; i < input.length;)
|
||||
output.push(Utils.ord(input[i++]));
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default ByteRepr;
|
56
src/core/operations/Bzip2Decompress.mjs
Normal file
56
src/core/operations/Bzip2Decompress.mjs
Normal file
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import bzip2 from "../vendor/bzip2";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Bzip2 Decompress operation
|
||||
*/
|
||||
class Bzip2Decompress extends Operation {
|
||||
|
||||
/**
|
||||
* Bzip2Decompress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bzip2 Decompress";
|
||||
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.patterns = [
|
||||
{
|
||||
"match": "^\\x42\\x5a\\x68",
|
||||
"flags": "",
|
||||
"args": []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Bzip2Decompress;
|
41
src/core/operations/CRC16Checksum.mjs
Normal file
41
src/core/operations/CRC16Checksum.mjs
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import JSCRC from "js-crc";
|
||||
|
||||
/**
|
||||
* CRC-16 Checksum operation
|
||||
*/
|
||||
class CRC16Checksum extends Operation {
|
||||
|
||||
/**
|
||||
* CRC16Checksum constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CRC-16 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 = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return JSCRC.crc16(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CRC16Checksum;
|
41
src/core/operations/CRC32Checksum.mjs
Normal file
41
src/core/operations/CRC32Checksum.mjs
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import JSCRC from "js-crc";
|
||||
|
||||
/**
|
||||
* CRC-32 Checksum operation
|
||||
*/
|
||||
class CRC32Checksum extends Operation {
|
||||
|
||||
/**
|
||||
* CRC32Checksum constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CRC-32 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; the 32-bit CRC function of Ethernet and many other standards is the work of several researchers and was published in 1975.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return JSCRC.crc32(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CRC32Checksum;
|
47
src/core/operations/CSSBeautify.mjs
Normal file
47
src/core/operations/CSSBeautify.mjs
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import vkbeautify from "vkbeautify";
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* CSS Beautify operation
|
||||
*/
|
||||
class CSSBeautify extends Operation {
|
||||
|
||||
/**
|
||||
* CSSBeautify constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CSS Beautify";
|
||||
this.module = "Code";
|
||||
this.description = "Indents and prettifies Cascading Style Sheets (CSS) code.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Indent string",
|
||||
"type": "binaryShortString",
|
||||
"value": "\\t"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const indentStr = args[0];
|
||||
return vkbeautify.css(input, indentStr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CSSBeautify;
|
47
src/core/operations/CSSMinify.mjs
Normal file
47
src/core/operations/CSSMinify.mjs
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import vkbeautify from "vkbeautify";
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* CSS Minify operation
|
||||
*/
|
||||
class CSSMinify extends Operation {
|
||||
|
||||
/**
|
||||
* CSSMinify constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CSS Minify";
|
||||
this.module = "Code";
|
||||
this.description = "Compresses Cascading Style Sheets (CSS) code.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Preserve comments",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const preserveComments = args[0];
|
||||
return vkbeautify.cssmin(input, preserveComments);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CSSMinify;
|
91
src/core/operations/CSSSelector.mjs
Normal file
91
src/core/operations/CSSSelector.mjs
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import xmldom from "xmldom";
|
||||
import nwmatcher from "nwmatcher";
|
||||
|
||||
/**
|
||||
* CSS selector operation
|
||||
*/
|
||||
class CSSSelector extends Operation {
|
||||
|
||||
/**
|
||||
* CSSSelector constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CSS selector";
|
||||
this.module = "Code";
|
||||
this.description = "Extract information from an HTML document with a CSS selector";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Cascading_Style_Sheets#Selector";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "CSS selector",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "Delimiter",
|
||||
"type": "binaryShortString",
|
||||
"value": "\\n"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [query, delimiter] = args,
|
||||
parser = new xmldom.DOMParser();
|
||||
let dom,
|
||||
result;
|
||||
|
||||
if (!query.length || !input.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
dom = parser.parseFromString(input);
|
||||
} catch (err) {
|
||||
throw new OperationError("Invalid input HTML.");
|
||||
}
|
||||
|
||||
try {
|
||||
const matcher = nwmatcher({document: dom});
|
||||
result = matcher.select(query, dom);
|
||||
} catch (err) {
|
||||
throw new OperationError("Invalid CSS Selector. Details:\n" + err.message);
|
||||
}
|
||||
|
||||
const nodeToString = function(node) {
|
||||
return node.toString();
|
||||
/* xmldom does not return the outerHTML value.
|
||||
switch (node.nodeType) {
|
||||
case node.ELEMENT_NODE: return node.outerHTML;
|
||||
case node.ATTRIBUTE_NODE: return node.value;
|
||||
case node.TEXT_NODE: return node.wholeText;
|
||||
case node.COMMENT_NODE: return node.data;
|
||||
case node.DOCUMENT_NODE: return node.outerHTML;
|
||||
default: throw new Error("Unknown Node Type: " + node.nodeType);
|
||||
}*/
|
||||
};
|
||||
|
||||
return result
|
||||
.map(nodeToString)
|
||||
.join(delimiter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CSSSelector;
|
80
src/core/operations/CSVToJSON.mjs
Normal file
80
src/core/operations/CSVToJSON.mjs
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Utils from "../Utils";
|
||||
|
||||
/**
|
||||
* CSV to JSON operation
|
||||
*/
|
||||
class CSVToJSON extends Operation {
|
||||
|
||||
/**
|
||||
* CSVToJSON constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CSV to JSON";
|
||||
this.module = "Default";
|
||||
this.description = "Converts a CSV file to JSON format.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
this.args = [
|
||||
{
|
||||
name: "Cell delimiters",
|
||||
type: "binaryShortString",
|
||||
value: ","
|
||||
},
|
||||
{
|
||||
name: "Row delimiters",
|
||||
type: "binaryShortString",
|
||||
value: "\\r\\n"
|
||||
},
|
||||
{
|
||||
name: "Format",
|
||||
type: "option",
|
||||
value: ["Array of dictionaries", "Array of arrays"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {JSON}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [cellDelims, rowDelims, format] = args;
|
||||
let json, header;
|
||||
|
||||
try {
|
||||
json = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split(""));
|
||||
} catch (err) {
|
||||
throw new OperationError("Unable to parse CSV: " + err);
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "Array of dictionaries":
|
||||
header = json[0];
|
||||
return json.slice(1).map(row => {
|
||||
const obj = {};
|
||||
header.forEach((h, i) => {
|
||||
obj[h] = row[i];
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
case "Array of arrays":
|
||||
default:
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CSVToJSON;
|
41
src/core/operations/CTPH.mjs
Normal file
41
src/core/operations/CTPH.mjs
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import ctphjs from "ctph.js";
|
||||
|
||||
/**
|
||||
* CTPH operation
|
||||
*/
|
||||
class CTPH extends Operation {
|
||||
|
||||
/**
|
||||
* CTPH constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CTPH";
|
||||
this.module = "Crypto";
|
||||
this.description = "Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.<br><br>CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'.";
|
||||
this.infoURL = "https://forensicswiki.org/wiki/Context_Triggered_Piecewise_Hashing";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return ctphjs.digest(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CTPH;
|
97
src/core/operations/CartesianProduct.mjs
Normal file
97
src/core/operations/CartesianProduct.mjs
Normal file
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* @author d98762625 [d98762625@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Set cartesian product operation
|
||||
*/
|
||||
class CartesianProduct extends Operation {
|
||||
|
||||
/**
|
||||
* Cartesian Product constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Cartesian Product";
|
||||
this.module = "Default";
|
||||
this.description = "Calculates the cartesian product of multiple sets of data, returning all possible combinations.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Cartesian_product";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Sample delimiter",
|
||||
type: "binaryString",
|
||||
value: "\\n\\n"
|
||||
},
|
||||
{
|
||||
name: "Item delimiter",
|
||||
type: "binaryString",
|
||||
value: ","
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input length
|
||||
*
|
||||
* @param {Object[]} sets
|
||||
* @throws {OperationError} if fewer than 2 sets
|
||||
*/
|
||||
validateSampleNumbers(sets) {
|
||||
if (!sets || sets.length < 2) {
|
||||
throw new OperationError("Incorrect number of sets, perhaps you" +
|
||||
" need to modify the sample delimiter or add more samples?");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the product operation
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
* @throws {OperationError}
|
||||
*/
|
||||
run(input, args) {
|
||||
[this.sampleDelim, this.itemDelimiter] = args;
|
||||
const sets = input.split(this.sampleDelim);
|
||||
|
||||
this.validateSampleNumbers(sets);
|
||||
|
||||
return this.runCartesianProduct(...sets.map(s => s.split(this.itemDelimiter)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cartesian product of the two inputted sets.
|
||||
*
|
||||
* @param {Object[]} a
|
||||
* @param {Object[]} b
|
||||
* @param {Object[]} c
|
||||
* @returns {string}
|
||||
*/
|
||||
runCartesianProduct(a, b, ...c) {
|
||||
/**
|
||||
* https://stackoverflow.com/a/43053803/7200497
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
|
||||
/**
|
||||
* https://stackoverflow.com/a/43053803/7200497
|
||||
* @returns {Object[][]}
|
||||
*/
|
||||
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);
|
||||
|
||||
return cartesian(a, b, ...c)
|
||||
.map(set => `(${set.join(",")})`)
|
||||
.join(this.itemDelimiter);
|
||||
}
|
||||
}
|
||||
|
||||
export default CartesianProduct;
|
120
src/core/operations/ChangeIPFormat.mjs
Normal file
120
src/core/operations/ChangeIPFormat.mjs
Normal file
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import Utils from "../Utils";
|
||||
import {fromHex} from "../lib/Hex";
|
||||
|
||||
/**
|
||||
* Change IP format operation
|
||||
*/
|
||||
class ChangeIPFormat extends Operation {
|
||||
|
||||
/**
|
||||
* ChangeIPFormat constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Change IP format";
|
||||
this.module = "Default";
|
||||
this.description = "Convert an IP address from one format to another, e.g. <code>172.20.23.54</code> to <code>ac141736</code>";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Input format",
|
||||
"type": "option",
|
||||
"value": ["Dotted Decimal", "Decimal", "Hex"]
|
||||
},
|
||||
{
|
||||
"name": "Output format",
|
||||
"type": "option",
|
||||
"value": ["Dotted Decimal", "Decimal", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inFormat, outFormat] = args,
|
||||
lines = input.split("\n");
|
||||
let output = "",
|
||||
j = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i] === "") continue;
|
||||
let baIp = [];
|
||||
let octets;
|
||||
let decimal;
|
||||
|
||||
if (inFormat === outFormat) {
|
||||
output += lines[i] + "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert to byte array IP from input format
|
||||
switch (inFormat) {
|
||||
case "Dotted Decimal":
|
||||
octets = lines[i].split(".");
|
||||
for (j = 0; j < octets.length; j++) {
|
||||
baIp.push(parseInt(octets[j], 10));
|
||||
}
|
||||
break;
|
||||
case "Decimal":
|
||||
decimal = lines[i].toString();
|
||||
baIp.push(decimal >> 24 & 255);
|
||||
baIp.push(decimal >> 16 & 255);
|
||||
baIp.push(decimal >> 8 & 255);
|
||||
baIp.push(decimal & 255);
|
||||
break;
|
||||
case "Hex":
|
||||
baIp = fromHex(lines[i]);
|
||||
break;
|
||||
default:
|
||||
throw new OperationError("Unsupported input IP format");
|
||||
}
|
||||
|
||||
let ddIp;
|
||||
let decIp;
|
||||
let hexIp;
|
||||
|
||||
// Convert byte array IP to output format
|
||||
switch (outFormat) {
|
||||
case "Dotted Decimal":
|
||||
ddIp = "";
|
||||
for (j = 0; j < baIp.length; j++) {
|
||||
ddIp += baIp[j] + ".";
|
||||
}
|
||||
output += ddIp.slice(0, ddIp.length-1) + "\n";
|
||||
break;
|
||||
case "Decimal":
|
||||
decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0;
|
||||
output += decIp.toString() + "\n";
|
||||
break;
|
||||
case "Hex":
|
||||
hexIp = "";
|
||||
for (j = 0; j < baIp.length; j++) {
|
||||
hexIp += Utils.hex(baIp[j]);
|
||||
}
|
||||
output += hexIp + "\n";
|
||||
break;
|
||||
default:
|
||||
throw new OperationError("Unsupported output IP format");
|
||||
}
|
||||
}
|
||||
|
||||
return output.slice(0, output.length-1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChangeIPFormat;
|
|
@ -1,99 +0,0 @@
|
|||
import cptable from "../lib/js-codepage/cptable.js";
|
||||
import Utils from "../Utils.js";
|
||||
import CryptoJS from "crypto-js";
|
||||
|
||||
|
||||
/**
|
||||
* Character encoding operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const CharEnc = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
IO_FORMAT: {
|
||||
"UTF-8 (65001)": 65001,
|
||||
"UTF-7 (65000)": 65000,
|
||||
"UTF16LE (1200)": 1200,
|
||||
"UTF16BE (1201)": 1201,
|
||||
"UTF16 (1201)": 1201,
|
||||
"IBM EBCDIC International (500)": 500,
|
||||
"IBM EBCDIC US-Canada (37)": 37,
|
||||
"Windows-874 Thai (874)": 874,
|
||||
"Japanese Shift-JIS (932)": 932,
|
||||
"Simplified Chinese GBK (936)": 936,
|
||||
"Korean (949)": 949,
|
||||
"Traditional Chinese Big5 (950)": 950,
|
||||
"Windows-1250 Central European (1250)": 1250,
|
||||
"Windows-1251 Cyrillic (1251)": 1251,
|
||||
"Windows-1252 Latin (1252)": 1252,
|
||||
"Windows-1253 Greek (1253)": 1253,
|
||||
"Windows-1254 Turkish (1254)": 1254,
|
||||
"Windows-1255 Hebrew (1255)": 1255,
|
||||
"Windows-1256 Arabic (1256)": 1256,
|
||||
"Windows-1257 Baltic (1257)": 1257,
|
||||
"Windows-1258 Vietnam (1258)": 1258,
|
||||
"US-ASCII (20127)": 20127,
|
||||
"Simplified Chinese GB2312 (20936)": 20936,
|
||||
"KOI8-R Russian Cyrillic (20866)": 20866,
|
||||
"KOI8-U Ukrainian Cyrillic (21866)": 21866,
|
||||
"ISO-8859-1 Latin 1 Western European (28591)": 28591,
|
||||
"ISO-8859-2 Latin 2 Central European (28592)": 28592,
|
||||
"ISO-8859-3 Latin 3 South European (28593)": 28593,
|
||||
"ISO-8859-4 Latin 4 North European (28594)": 28594,
|
||||
"ISO-8859-5 Latin/Cyrillic (28595)": 28595,
|
||||
"ISO-8859-6 Latin/Arabic (28596)": 28596,
|
||||
"ISO-8859-7 Latin/Greek (28597)": 28597,
|
||||
"ISO-8859-8 Latin/Hebrew (28598)": 28598,
|
||||
"ISO-8859-9 Latin 5 Turkish (28599)": 28599,
|
||||
"ISO-8859-10 Latin 6 Nordic (28600)": 28600,
|
||||
"ISO-8859-11 Latin/Thai (28601)": 28601,
|
||||
"ISO-8859-13 Latin 7 Baltic Rim (28603)": 28603,
|
||||
"ISO-8859-14 Latin 8 Celtic (28604)": 28604,
|
||||
"ISO-8859-15 Latin 9 (28605)": 28605,
|
||||
"ISO-8859-16 Latin 10 (28606)": 28606,
|
||||
"ISO-2022 JIS Japanese (50222)": 50222,
|
||||
"EUC Japanese (51932)": 51932,
|
||||
"EUC Korean (51949)": 51949,
|
||||
"Simplified Chinese GB18030 (54936)": 54936,
|
||||
},
|
||||
|
||||
/**
|
||||
* Encode text operation.
|
||||
* @author tlwr [toby@toby.codes]
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runEncode: function(input, args) {
|
||||
const format = CharEnc.IO_FORMAT[args[0]];
|
||||
let encoded = cptable.utils.encode(format, input);
|
||||
encoded = Array.from(encoded);
|
||||
return encoded;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Decode text operation.
|
||||
* @author tlwr [toby@toby.codes]
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runDecode: function(input, args) {
|
||||
const format = CharEnc.IO_FORMAT[args[0]];
|
||||
let decoded = cptable.utils.decode(format, input);
|
||||
return decoded;
|
||||
},
|
||||
};
|
||||
|
||||
export default CharEnc;
|
|
@ -1,195 +0,0 @@
|
|||
import Utils from "../Utils.js";
|
||||
|
||||
|
||||
/**
|
||||
* Checksum operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Checksum = {
|
||||
|
||||
/**
|
||||
* Fletcher-8 Checksum operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runFletcher8: function(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xf;
|
||||
b = (b + a) % 0xf;
|
||||
}
|
||||
|
||||
return Utils.hex(((b << 4) | a) >>> 0, 2);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Fletcher-16 Checksum operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runFletcher16: function(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xff;
|
||||
b = (b + a) % 0xff;
|
||||
}
|
||||
|
||||
return Utils.hex(((b << 8) | a) >>> 0, 4);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Fletcher-32 Checksum operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runFletcher32: function(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xffff;
|
||||
b = (b + a) % 0xffff;
|
||||
}
|
||||
|
||||
return Utils.hex(((b << 16) | a) >>> 0, 8);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Fletcher-64 Checksum operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runFletcher64: function(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xffffffff;
|
||||
b = (b + a) % 0xffffffff;
|
||||
}
|
||||
|
||||
return Utils.hex(b >>> 0, 8) + Utils.hex(a >>> 0, 8);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Adler-32 Checksum operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runAdler32: function(input, args) {
|
||||
let MOD_ADLER = 65521,
|
||||
a = 1,
|
||||
b = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a += input[i];
|
||||
b += a;
|
||||
}
|
||||
|
||||
a %= MOD_ADLER;
|
||||
b %= MOD_ADLER;
|
||||
|
||||
return Utils.hex(((b << 16) | a) >>> 0, 8);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* CRC-32 Checksum operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runCRC32: function(input, args) {
|
||||
let crcTable = global.crcTable || (global.crcTable = Checksum._genCRCTable()),
|
||||
crc = 0 ^ (-1);
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
crc = (crc >>> 8) ^ crcTable[(crc ^ input[i]) & 0xff];
|
||||
}
|
||||
|
||||
return Utils.hex((crc ^ (-1)) >>> 0);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* TCP/IP Checksum operation.
|
||||
*
|
||||
* @author GCHQ Contributor [1]
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns '3f2c'
|
||||
* Checksum.runTcpIp([0x45,0x00,0x00,0x87,0xa3,0x1b,0x40,0x00,0x40,0x06,
|
||||
* 0x00,0x00,0xac,0x11,0x00,0x04,0xac,0x11,0x00,0x03])
|
||||
*
|
||||
* // returns 'a249'
|
||||
* Checksum.runTcpIp([0x45,0x00,0x01,0x11,0x3f,0x74,0x40,0x00,0x40,0x06,
|
||||
* 0x00,0x00,0xac,0x11,0x00,0x03,0xac,0x11,0x00,0x04])
|
||||
*/
|
||||
runTCPIP: function(input, args) {
|
||||
let csum = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (i % 2 === 0) {
|
||||
csum += (input[i] << 8);
|
||||
} else {
|
||||
csum += input[i];
|
||||
}
|
||||
}
|
||||
|
||||
csum = (csum >> 16) + (csum & 0xffff);
|
||||
|
||||
return Utils.hex(0xffff - csum);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generates a CRC table for use with CRC checksums.
|
||||
*
|
||||
* @private
|
||||
* @returns {array}
|
||||
*/
|
||||
_genCRCTable: function() {
|
||||
let c,
|
||||
crcTable = [];
|
||||
|
||||
for (let n = 0; n < 256; n++) {
|
||||
c = n;
|
||||
for (let k = 0; k < 8; k++) {
|
||||
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||
}
|
||||
crcTable[n] = c;
|
||||
}
|
||||
|
||||
return crcTable;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default Checksum;
|
54
src/core/operations/ChiSquare.mjs
Normal file
54
src/core/operations/ChiSquare.mjs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* Chi Square operation
|
||||
*/
|
||||
class ChiSquare extends Operation {
|
||||
|
||||
/**
|
||||
* ChiSquare constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Chi Square";
|
||||
this.module = "Default";
|
||||
this.description = "Calculates the Chi Square distribution of values.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Chi-squared_distribution";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "number";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {number}
|
||||
*/
|
||||
run(input, args) {
|
||||
const data = new Uint8Array(input);
|
||||
const distArray = new Array(256).fill(0);
|
||||
let total = 0;
|
||||
|
||||
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] - data.length / 256, 2) / (data.length / 256);
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChiSquare;
|
|
@ -1,676 +0,0 @@
|
|||
import Utils from "../Utils.js";
|
||||
import CryptoJS from "crypto-js";
|
||||
import {blowfish as Blowfish} from "sladex-blowfish";
|
||||
|
||||
|
||||
/**
|
||||
* Cipher operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Cipher = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
IO_FORMAT1: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
IO_FORMAT2: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
IO_FORMAT3: ["Hex", "Base64", "UTF16", "UTF16LE", "UTF16BE", "Latin1"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
IO_FORMAT4: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
MODES: ["CBC", "CFB", "CTR", "OFB", "ECB"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
PADDING: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
RESULT_TYPE: ["Show all", "Ciphertext", "Key", "IV", "Salt"],
|
||||
|
||||
|
||||
/**
|
||||
* Runs encryption operations using the CryptoJS framework.
|
||||
*
|
||||
* @private
|
||||
* @param {function} algo - The CryptoJS algorithm to use
|
||||
* @param {byteArray} input
|
||||
* @param {function} args
|
||||
* @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 || ""),
|
||||
mode = CryptoJS.mode[args[3]],
|
||||
padding = CryptoJS.pad[args[4]],
|
||||
resultOption = args[5].toLowerCase(),
|
||||
outputFormat = args[6];
|
||||
|
||||
if (iv.sigBytes === 0) {
|
||||
// Use passphrase rather than key. Need to convert it to a string.
|
||||
key = key.toString(CryptoJS.enc.Latin1);
|
||||
}
|
||||
|
||||
const encrypted = algo.encrypt(input, key, {
|
||||
salt: salt.sigBytes > 0 ? salt : false,
|
||||
iv: iv.sigBytes > 0 ? iv : null,
|
||||
mode: mode,
|
||||
padding: padding
|
||||
});
|
||||
|
||||
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]);
|
||||
} else {
|
||||
result = encrypted[resultOption].toString(Utils.format[outputFormat]);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Runs decryption operations using the CryptoJS framework.
|
||||
*
|
||||
* @private
|
||||
* @param {function} algo - The CryptoJS algorithm to use
|
||||
* @param {byteArray} input
|
||||
* @param {function} args
|
||||
* @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 || ""),
|
||||
mode = CryptoJS.mode[args[3]],
|
||||
padding = CryptoJS.pad[args[4]],
|
||||
inputFormat = args[5],
|
||||
outputFormat = args[6];
|
||||
|
||||
// The ZeroPadding option causes a crash when the input length is 0
|
||||
if (!input.length) {
|
||||
return "No input";
|
||||
}
|
||||
|
||||
const ciphertext = Utils.format[inputFormat].parse(input);
|
||||
|
||||
if (iv.sigBytes === 0) {
|
||||
// Use passphrase rather than key. Need to convert it to a string.
|
||||
key = key.toString(CryptoJS.enc.Latin1);
|
||||
}
|
||||
|
||||
const decrypted = algo.decrypt({
|
||||
ciphertext: ciphertext,
|
||||
salt: salt.sigBytes > 0 ? salt : false
|
||||
}, key, {
|
||||
iv: iv.sigBytes > 0 ? iv : null,
|
||||
mode: mode,
|
||||
padding: padding
|
||||
});
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = decrypted.toString(Utils.format[outputFormat]);
|
||||
} catch (err) {
|
||||
result = "Decrypt error: " + err.message;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* AES Encrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runAesEnc: function (input, args) {
|
||||
return Cipher._enc(CryptoJS.AES, input, args);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* AES Decrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runAesDec: function (input, args) {
|
||||
return Cipher._dec(CryptoJS.AES, input, args);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* DES Encrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runDesEnc: function (input, args) {
|
||||
return Cipher._enc(CryptoJS.DES, input, args);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* DES Decrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runDesDec: function (input, args) {
|
||||
return Cipher._dec(CryptoJS.DES, input, args);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Triple DES Encrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runTripleDesEnc: function (input, args) {
|
||||
return Cipher._enc(CryptoJS.TripleDES, input, args);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Triple DES Decrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runTripleDesDec: function (input, args) {
|
||||
return Cipher._dec(CryptoJS.TripleDES, input, args);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Rabbit Encrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runRabbitEnc: function (input, args) {
|
||||
return Cipher._enc(CryptoJS.Rabbit, input, args);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Rabbit Decrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runRabbitDec: function (input, args) {
|
||||
return Cipher._dec(CryptoJS.Rabbit, input, args);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
BLOWFISH_MODES: ["ECB", "CBC", "PCBC", "CFB", "OFB", "CTR"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
BLOWFISH_OUTPUT_TYPES: ["Base64", "Hex", "String", "Raw"],
|
||||
|
||||
/**
|
||||
* Blowfish Encrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runBlowfishEnc: function (input, args) {
|
||||
let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1),
|
||||
mode = args[1],
|
||||
outputFormat = args[2];
|
||||
|
||||
if (key.length === 0) return "Enter a key";
|
||||
|
||||
let encHex = Blowfish.encrypt(input, key, {
|
||||
outputType: 1,
|
||||
cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode)
|
||||
}),
|
||||
enc = CryptoJS.enc.Hex.parse(encHex);
|
||||
|
||||
return enc.toString(Utils.format[outputFormat]);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Blowfish Decrypt operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runBlowfishDec: function (input, args) {
|
||||
let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1),
|
||||
mode = args[1],
|
||||
inputFormat = args[2];
|
||||
|
||||
if (key.length === 0) return "Enter a key";
|
||||
|
||||
input = Utils.format[inputFormat].parse(input);
|
||||
|
||||
return Blowfish.decrypt(input.toString(CryptoJS.enc.Base64), key, {
|
||||
outputType: 0, // This actually means inputType. The library is weird.
|
||||
cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode)
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
KDF_KEY_SIZE: 256,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
KDF_ITERATIONS: 1,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
HASHERS: ["MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "RIPEMD160"],
|
||||
|
||||
/**
|
||||
* Derive PBKDF2 key operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runPbkdf2: function (input, args) {
|
||||
let keySize = args[0] / 32,
|
||||
iterations = args[1],
|
||||
hasher = args[2],
|
||||
salt = CryptoJS.enc.Hex.parse(args[3] || ""),
|
||||
inputFormat = args[4],
|
||||
outputFormat = args[5],
|
||||
passphrase = Utils.format[inputFormat].parse(input),
|
||||
key = CryptoJS.PBKDF2(passphrase, salt, {
|
||||
keySize: keySize,
|
||||
hasher: CryptoJS.algo[hasher],
|
||||
iterations: iterations,
|
||||
});
|
||||
|
||||
return key.toString(Utils.format[outputFormat]);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Derive EVP key operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runEvpkdf: function (input, args) {
|
||||
let keySize = args[0] / 32,
|
||||
iterations = args[1],
|
||||
hasher = args[2],
|
||||
salt = CryptoJS.enc.Hex.parse(args[3] || ""),
|
||||
inputFormat = args[4],
|
||||
outputFormat = args[5],
|
||||
passphrase = Utils.format[inputFormat].parse(input),
|
||||
key = CryptoJS.EvpKDF(passphrase, salt, {
|
||||
keySize: keySize,
|
||||
hasher: CryptoJS.algo[hasher],
|
||||
iterations: iterations,
|
||||
});
|
||||
|
||||
return key.toString(Utils.format[outputFormat]);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* RC4 operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runRc4: function (input, args) {
|
||||
let message = Utils.format[args[1]].parse(input),
|
||||
passphrase = Utils.format[args[0].option].parse(args[0].string),
|
||||
encrypted = CryptoJS.RC4.encrypt(message, passphrase);
|
||||
|
||||
return encrypted.ciphertext.toString(Utils.format[args[2]]);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
RC4DROP_BYTES: 768,
|
||||
|
||||
/**
|
||||
* RC4 Drop operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runRc4drop: function (input, args) {
|
||||
let message = Utils.format[args[1]].parse(input),
|
||||
passphrase = Utils.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]]);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Vigenère Encode operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.pw]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runVigenereEnc: function (input, args) {
|
||||
let alphabet = "abcdefghijklmnopqrstuvwxyz",
|
||||
key = args[0].toLowerCase(),
|
||||
output = "",
|
||||
fail = 0,
|
||||
keyIndex,
|
||||
msgIndex,
|
||||
chr;
|
||||
|
||||
if (!key) return "No key entered";
|
||||
if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters";
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (alphabet.indexOf(input[i]) >= 0) {
|
||||
// Get the corresponding character of key for the current letter, accounting
|
||||
// for chars not in alphabet
|
||||
chr = key[(i - fail) % key.length];
|
||||
// Get the location in the vigenere square of the key char
|
||||
keyIndex = alphabet.indexOf(chr);
|
||||
// Get the location in the vigenere square of the message char
|
||||
msgIndex = alphabet.indexOf(input[i]);
|
||||
// Get the encoded letter by finding the sum of indexes modulo 26 and finding
|
||||
// the letter corresponding to that
|
||||
output += alphabet[(keyIndex + msgIndex) % 26];
|
||||
} else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
|
||||
chr = key[(i - fail) % key.length].toLowerCase();
|
||||
keyIndex = alphabet.indexOf(chr);
|
||||
msgIndex = alphabet.indexOf(input[i].toLowerCase());
|
||||
output += alphabet[(keyIndex + msgIndex) % 26].toUpperCase();
|
||||
} else {
|
||||
output += input[i];
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Vigenère Decode operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.pw]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runVigenereDec: function (input, args) {
|
||||
let alphabet = "abcdefghijklmnopqrstuvwxyz",
|
||||
key = args[0].toLowerCase(),
|
||||
output = "",
|
||||
fail = 0,
|
||||
keyIndex,
|
||||
msgIndex,
|
||||
chr;
|
||||
|
||||
if (!key) return "No key entered";
|
||||
if (!/^[a-zA-Z]+$/.test(key)) return "The key must consist only of letters";
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (alphabet.indexOf(input[i]) >= 0) {
|
||||
chr = key[(i - fail) % key.length];
|
||||
keyIndex = alphabet.indexOf(chr);
|
||||
msgIndex = alphabet.indexOf(input[i]);
|
||||
// Subtract indexes from each other, add 26 just in case the value is negative,
|
||||
// modulo to remove if neccessary
|
||||
output += alphabet[(msgIndex - keyIndex + alphabet.length) % 26];
|
||||
} else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
|
||||
chr = key[(i - fail) % key.length].toLowerCase();
|
||||
keyIndex = alphabet.indexOf(chr);
|
||||
msgIndex = alphabet.indexOf(input[i].toLowerCase());
|
||||
output += alphabet[(msgIndex + alphabet.length - keyIndex) % 26].toUpperCase();
|
||||
} else {
|
||||
output += input[i];
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
AFFINE_A: 1,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
AFFINE_B: 0,
|
||||
|
||||
/**
|
||||
* Affine Cipher Encode operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.pw]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runAffineEnc: function (input, args) {
|
||||
let alphabet = "abcdefghijklmnopqrstuvwxyz",
|
||||
a = args[0],
|
||||
b = args[1],
|
||||
output = "";
|
||||
|
||||
if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
|
||||
return "The values of a and b can only be integers.";
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (alphabet.indexOf(input[i]) >= 0) {
|
||||
// Uses the affine function ax+b % m = y (where m is length of the alphabet)
|
||||
output += alphabet[((a * alphabet.indexOf(input[i])) + b) % 26];
|
||||
} else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
|
||||
// Same as above, accounting for uppercase
|
||||
output += alphabet[((a * alphabet.indexOf(input[i].toLowerCase())) + b) % 26].toUpperCase();
|
||||
} else {
|
||||
// Non-alphabetic characters
|
||||
output += input[i];
|
||||
}
|
||||
}
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Affine Cipher Encode operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.pw]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runAffineDec: function (input, args) {
|
||||
let alphabet = "abcdefghijklmnopqrstuvwxyz",
|
||||
a = args[0],
|
||||
b = args[1],
|
||||
output = "",
|
||||
aModInv;
|
||||
|
||||
if (!/^\+?(0|[1-9]\d*)$/.test(a) || !/^\+?(0|[1-9]\d*)$/.test(b)) {
|
||||
return "The values of a and b can only be integers.";
|
||||
}
|
||||
|
||||
if (Utils.gcd(a, 26) !== 1) {
|
||||
return "The value of a must be coprime to 26.";
|
||||
}
|
||||
|
||||
// Calculates modular inverse of a
|
||||
aModInv = Utils.modInv(a, 26);
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (alphabet.indexOf(input[i]) >= 0) {
|
||||
// Uses the affine decode function (y-b * A') % m = x (where m is length of the alphabet and A' is modular inverse)
|
||||
output += alphabet[Utils.mod((alphabet.indexOf(input[i]) - b) * aModInv, 26)];
|
||||
} else if (alphabet.indexOf(input[i].toLowerCase()) >= 0) {
|
||||
// Same as above, accounting for uppercase
|
||||
output += alphabet[Utils.mod((alphabet.indexOf(input[i].toLowerCase()) - b) * aModInv, 26)].toUpperCase();
|
||||
} else {
|
||||
// Non-alphabetic characters
|
||||
output += input[i];
|
||||
}
|
||||
}
|
||||
return output;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Atbash Cipher Encode operation.
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.pw]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runAtbash: function (input, args) {
|
||||
return Cipher.runAffineEnc(input, [25, 25]);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
SUBS_PLAINTEXT: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
SUBS_CIPHERTEXT: "XYZABCDEFGHIJKLMNOPQRSTUVW",
|
||||
|
||||
/**
|
||||
* Substitute operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runSubstitute: function (input, args) {
|
||||
let plaintext = Utils.strToByteArray(Utils.expandAlphRange(args[0]).join()),
|
||||
ciphertext = Utils.strToByteArray(Utils.expandAlphRange(args[1]).join()),
|
||||
output = [],
|
||||
index = -1;
|
||||
|
||||
if (plaintext.length !== ciphertext.length) {
|
||||
output = Utils.strToByteArray("Warning: Plaintext and Ciphertext lengths differ\n\n");
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
index = plaintext.indexOf(input[i]);
|
||||
output.push(index > -1 && index < ciphertext.length ? ciphertext[index] : input[i]);
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default Cipher;
|
||||
|
||||
|
||||
/**
|
||||
* Overwriting the CryptoJS OpenSSL key derivation function so that it is possible to not pass a
|
||||
* salt in.
|
||||
|
||||
* @param {string} password - The password to derive from.
|
||||
* @param {number} keySize - The size in words of the key to generate.
|
||||
* @param {number} ivSize - The size in words of the IV to generate.
|
||||
* @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be
|
||||
* generated randomly. If set to false, no salt will be added.
|
||||
*
|
||||
* @returns {CipherParams} A cipher params object with the key, IV, and salt.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @example
|
||||
* // Randomly generates a salt
|
||||
* var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32);
|
||||
* // Uses the salt 'saltsalt'
|
||||
* var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt');
|
||||
* // Does not use a salt
|
||||
* var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, false);
|
||||
*/
|
||||
CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) {
|
||||
// Generate random salt if no salt specified and not set to false
|
||||
// This line changed from `if (!salt) {` to the following
|
||||
if (salt === undefined || salt === null) {
|
||||
salt = CryptoJS.lib.WordArray.random(64/8);
|
||||
}
|
||||
|
||||
// Derive key and IV
|
||||
const key = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt);
|
||||
|
||||
// Separate key and IV
|
||||
const iv = CryptoJS.lib.WordArray.create(key.words.slice(keySize), ivSize * 4);
|
||||
key.sigBytes = keySize * 4;
|
||||
|
||||
// Return params
|
||||
return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt });
|
||||
};
|
58
src/core/operations/CitrixCTX1Decode.mjs
Normal file
58
src/core/operations/CitrixCTX1Decode.mjs
Normal file
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @author bwhitn [brian.m.whitney@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import cptable from "../vendor/js-codepage/cptable.js";
|
||||
|
||||
/**
|
||||
* Citrix CTX1 Decode operation
|
||||
*/
|
||||
class CitrixCTX1Decode extends Operation {
|
||||
|
||||
/**
|
||||
* CitrixCTX1Decode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Citrix CTX1 Decode";
|
||||
this.module = "Encodings";
|
||||
this.description = "Decodes strings in a Citrix CTX1 password format to plaintext.";
|
||||
this.infoURL = "https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (input.length % 4 !== 0) {
|
||||
throw new OperationError("Incorrect hash length");
|
||||
}
|
||||
const revinput = input.reverse();
|
||||
const result = [];
|
||||
let temp = 0;
|
||||
for (let i = 0; i < revinput.length; i += 2) {
|
||||
if (i + 2 >= revinput.length) {
|
||||
temp = 0;
|
||||
} else {
|
||||
temp = ((revinput[i + 2] - 0x41) & 0xf) ^ (((revinput[i + 3]- 0x41) << 4) & 0xf0);
|
||||
}
|
||||
temp = (((revinput[i] - 0x41) & 0xf) ^ (((revinput[i + 1] - 0x41) << 4) & 0xf0)) ^ 0xa5 ^ temp;
|
||||
result.push(temp);
|
||||
}
|
||||
// Decodes a utf-16le string
|
||||
return cptable.utils.decode(1200, result.reverse());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CitrixCTX1Decode;
|
50
src/core/operations/CitrixCTX1Encode.mjs
Normal file
50
src/core/operations/CitrixCTX1Encode.mjs
Normal file
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @author bwhitn [brian.m.whitney@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import cptable from "../vendor/js-codepage/cptable.js";
|
||||
|
||||
/**
|
||||
* Citrix CTX1 Encode operation
|
||||
*/
|
||||
class CitrixCTX1Encode extends Operation {
|
||||
|
||||
/**
|
||||
* CitrixCTX1Encode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Citrix CTX1 Encode";
|
||||
this.module = "Encodings";
|
||||
this.description = "Encodes strings to Citrix CTX1 password format.";
|
||||
this.infoURL = "https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const utf16pass = Array.from(cptable.utils.encode(1200, input));
|
||||
const result = [];
|
||||
let temp = 0;
|
||||
for (let i = 0; i < utf16pass.length; i++) {
|
||||
temp = utf16pass[i] ^ 0xa5 ^ temp;
|
||||
result.push(((temp >>> 4) & 0xf) + 0x41);
|
||||
result.push((temp & 0xf) + 0x41);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CitrixCTX1Encode;
|
|
@ -1,501 +0,0 @@
|
|||
import {camelCase, kebabCase, snakeCase} from "lodash";
|
||||
|
||||
import Utils from "../Utils.js";
|
||||
import vkbeautify from "vkbeautify";
|
||||
import {DOMParser as dom} from "xmldom";
|
||||
import xpath from "xpath";
|
||||
import prettyPrintOne from "imports-loader?window=>global!exports-loader?prettyPrintOne!google-code-prettify/bin/prettify.min.js";
|
||||
|
||||
|
||||
/**
|
||||
* Code operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Code = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
LANGUAGES: ["default-code", "default-markup", "bash", "bsh", "c", "cc", "coffee", "cpp", "cs", "csh", "cv", "cxx", "cyc", "htm", "html", "in.tag", "java", "javascript", "js", "json", "m", "mxml", "perl", "pl", "pm", "py", "python", "rb", "rc", "rs", "ruby", "rust", "sh", "uq.val", "xhtml", "xml", "xsl"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
LINE_NUMS: false,
|
||||
|
||||
/**
|
||||
* Syntax highlighter operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runSyntaxHighlight: function(input, args) {
|
||||
let language = args[0],
|
||||
lineNums = args[1];
|
||||
return "<code class='prettyprint'>" + prettyPrintOne(Utils.escapeHtml(input), language, lineNums) + "</code>";
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
BEAUTIFY_INDENT: "\\t",
|
||||
|
||||
/**
|
||||
* XML Beautify operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runXmlBeautify: function(input, args) {
|
||||
const indentStr = args[0];
|
||||
return vkbeautify.xml(input, indentStr);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* JSON Beautify operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runJsonBeautify: function(input, args) {
|
||||
const indentStr = args[0];
|
||||
if (!input) return "";
|
||||
return vkbeautify.json(input, indentStr);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* CSS Beautify operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runCssBeautify: function(input, args) {
|
||||
const indentStr = args[0];
|
||||
return vkbeautify.css(input, indentStr);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* SQL Beautify operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runSqlBeautify: function(input, args) {
|
||||
const indentStr = args[0];
|
||||
return vkbeautify.sql(input, indentStr);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
PRESERVE_COMMENTS: false,
|
||||
|
||||
/**
|
||||
* XML Minify operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runXmlMinify: function(input, args) {
|
||||
const preserveComments = args[0];
|
||||
return vkbeautify.xmlmin(input, preserveComments);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* JSON Minify operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runJsonMinify: function(input, args) {
|
||||
if (!input) return "";
|
||||
return vkbeautify.jsonmin(input);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* CSS Minify operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runCssMinify: function(input, args) {
|
||||
const preserveComments = args[0];
|
||||
return vkbeautify.cssmin(input, preserveComments);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* SQL Minify operation.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runSqlMinify: function(input, args) {
|
||||
return vkbeautify.sqlmin(input);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generic Code Beautify operation.
|
||||
*
|
||||
* Yeeeaaah...
|
||||
*
|
||||
* I'm not proud of this code, but seriously, try writing a generic lexer and parser that
|
||||
* correctly generates an AST for multiple different languages. I have tried, and I can tell
|
||||
* you it's pretty much impossible.
|
||||
*
|
||||
* This basically works. That'll have to be good enough. It's not meant to produce working code,
|
||||
* just slightly more readable code.
|
||||
*
|
||||
* Things that don't work:
|
||||
* - For loop formatting
|
||||
* - Do-While loop formatting
|
||||
* - Switch/Case indentation
|
||||
* - Bit shift operators
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runGenericBeautify: function(input, args) {
|
||||
let code = input,
|
||||
t = 0,
|
||||
preservedTokens = [],
|
||||
m;
|
||||
|
||||
// Remove strings
|
||||
const sstrings = /'([^'\\]|\\.)*'/g;
|
||||
while ((m = sstrings.exec(code))) {
|
||||
code = preserveToken(code, m, t++);
|
||||
sstrings.lastIndex = m.index;
|
||||
}
|
||||
|
||||
const dstrings = /"([^"\\]|\\.)*"/g;
|
||||
while ((m = dstrings.exec(code))) {
|
||||
code = preserveToken(code, m, t++);
|
||||
dstrings.lastIndex = m.index;
|
||||
}
|
||||
|
||||
// Remove comments
|
||||
const scomments = /\/\/[^\n\r]*/g;
|
||||
while ((m = scomments.exec(code))) {
|
||||
code = preserveToken(code, m, t++);
|
||||
scomments.lastIndex = m.index;
|
||||
}
|
||||
|
||||
const mcomments = /\/\*[\s\S]*?\*\//gm;
|
||||
while ((m = mcomments.exec(code))) {
|
||||
code = preserveToken(code, m, t++);
|
||||
mcomments.lastIndex = m.index;
|
||||
}
|
||||
|
||||
const hcomments = /(^|\n)#[^\n\r#]+/g;
|
||||
while ((m = hcomments.exec(code))) {
|
||||
code = preserveToken(code, m, t++);
|
||||
hcomments.lastIndex = m.index;
|
||||
}
|
||||
|
||||
// Remove regexes
|
||||
const regexes = /\/.*?[^\\]\/[gim]{0,3}/gi;
|
||||
while ((m = regexes.exec(code))) {
|
||||
code = preserveToken(code, m, t++);
|
||||
regexes.lastIndex = m.index;
|
||||
}
|
||||
|
||||
code = code
|
||||
// Create newlines after ;
|
||||
.replace(/;/g, ";\n")
|
||||
// Create newlines after { and around }
|
||||
.replace(/{/g, "{\n")
|
||||
.replace(/}/g, "\n}\n")
|
||||
// Remove carriage returns
|
||||
.replace(/\r/g, "")
|
||||
// Remove all indentation
|
||||
.replace(/^\s+/g, "")
|
||||
.replace(/\n\s+/g, "\n")
|
||||
// Remove trailing spaces
|
||||
.replace(/\s*$/g, "")
|
||||
.replace(/\n{/g, "{");
|
||||
|
||||
// Indent
|
||||
let i = 0,
|
||||
level = 0,
|
||||
indent;
|
||||
while (i < code.length) {
|
||||
switch (code[i]) {
|
||||
case "{":
|
||||
level++;
|
||||
break;
|
||||
case "\n":
|
||||
if (i+1 >= code.length) break;
|
||||
|
||||
if (code[i+1] === "}") level--;
|
||||
indent = (level >= 0) ? Array(level*4+1).join(" ") : "";
|
||||
|
||||
code = code.substring(0, i+1) + indent + code.substring(i+1);
|
||||
if (level > 0) i += level*4;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
code = code
|
||||
// Add strategic spaces
|
||||
.replace(/\s*([!<>=+-/*]?)=\s*/g, " $1= ")
|
||||
.replace(/\s*<([=]?)\s*/g, " <$1 ")
|
||||
.replace(/\s*>([=]?)\s*/g, " >$1 ")
|
||||
.replace(/([^+])\+([^+=])/g, "$1 + $2")
|
||||
.replace(/([^-])-([^-=])/g, "$1 - $2")
|
||||
.replace(/([^*])\*([^*=])/g, "$1 * $2")
|
||||
.replace(/([^/])\/([^/=])/g, "$1 / $2")
|
||||
.replace(/\s*,\s*/g, ", ")
|
||||
.replace(/\s*{/g, " {")
|
||||
.replace(/}\n/g, "}\n\n")
|
||||
// Hacky horribleness
|
||||
.replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)\s*\n([^{])/gim, "$1 ($2)\n $3")
|
||||
.replace(/(if|for|while|with|elif|elseif)\s*\(([^\n]*)\)([^{])/gim, "$1 ($2) $3")
|
||||
.replace(/else\s*\n([^{])/gim, "else\n $1")
|
||||
.replace(/else\s+([^{])/gim, "else $1")
|
||||
// Remove strategic spaces
|
||||
.replace(/\s+;/g, ";")
|
||||
.replace(/\{\s+\}/g, "{}")
|
||||
.replace(/\[\s+\]/g, "[]")
|
||||
.replace(/}\s*(else|catch|except|finally|elif|elseif|else if)/gi, "} $1");
|
||||
|
||||
// Replace preserved tokens
|
||||
const ptokens = /###preservedToken(\d+)###/g;
|
||||
while ((m = ptokens.exec(code))) {
|
||||
const ti = parseInt(m[1], 10);
|
||||
code = code.substring(0, m.index) + preservedTokens[ti] + code.substring(m.index + m[0].length);
|
||||
ptokens.lastIndex = m.index;
|
||||
}
|
||||
|
||||
return code;
|
||||
|
||||
/**
|
||||
* Replaces a matched token with a placeholder value.
|
||||
*/
|
||||
function preserveToken(str, match, t) {
|
||||
preservedTokens[t] = match[0];
|
||||
return str.substring(0, match.index) +
|
||||
"###preservedToken" + t + "###" +
|
||||
str.substring(match.index + match[0].length);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XPATH_INITIAL: "",
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
XPATH_DELIMITER: "\\n",
|
||||
|
||||
/**
|
||||
* XPath expression operation.
|
||||
*
|
||||
* @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runXpath:function(input, args) {
|
||||
let query = args[0],
|
||||
delimiter = args[1];
|
||||
|
||||
let doc;
|
||||
try {
|
||||
doc = new dom().parseFromString(input);
|
||||
} catch (err) {
|
||||
return "Invalid input XML.";
|
||||
}
|
||||
|
||||
let nodes;
|
||||
try {
|
||||
nodes = xpath.select(query, doc);
|
||||
} catch (err) {
|
||||
return "Invalid XPath. Details:\n" + err.message;
|
||||
}
|
||||
|
||||
const nodeToString = function(node) {
|
||||
return node.toString();
|
||||
};
|
||||
|
||||
return nodes.map(nodeToString).join(delimiter);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
CSS_SELECTOR_INITIAL: "",
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
CSS_QUERY_DELIMITER: "\\n",
|
||||
|
||||
/**
|
||||
* CSS selector operation.
|
||||
*
|
||||
* @author Mikescher (https://github.com/Mikescher | https://mikescher.com)
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runCSSQuery: function(input, args) {
|
||||
let query = args[0],
|
||||
delimiter = args[1],
|
||||
parser = new DOMParser(),
|
||||
html,
|
||||
result;
|
||||
|
||||
if (!query.length || !input.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
html = parser.parseFromString(input, "text/html");
|
||||
} catch (err) {
|
||||
return "Invalid input HTML.";
|
||||
}
|
||||
|
||||
try {
|
||||
result = html.querySelectorAll(query);
|
||||
} catch (err) {
|
||||
return "Invalid CSS Selector. Details:\n" + err.message;
|
||||
}
|
||||
|
||||
const nodeToString = function(node) {
|
||||
switch (node.nodeType) {
|
||||
case Node.ELEMENT_NODE: return node.outerHTML;
|
||||
case Node.ATTRIBUTE_NODE: return node.value;
|
||||
case Node.COMMENT_NODE: return node.data;
|
||||
case Node.TEXT_NODE: return node.wholeText;
|
||||
case Node.DOCUMENT_NODE: return node.outerHTML;
|
||||
default: throw new Error("Unknown Node Type: " + node.nodeType);
|
||||
}
|
||||
};
|
||||
|
||||
return Array.apply(null, Array(result.length))
|
||||
.map(function(_, i) {
|
||||
return result[i];
|
||||
})
|
||||
.map(nodeToString)
|
||||
.join(delimiter);
|
||||
},
|
||||
|
||||
/**
|
||||
* This tries to rename variable names in a code snippet according to a function.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {function} replacer - this function will be fed the token which should be renamed.
|
||||
* @returns {string}
|
||||
*/
|
||||
_replaceVariableNames(input, replacer) {
|
||||
const tokenRegex = /\\"|"(?:\\"|[^"])*"|(\b[a-z0-9\-_]+\b)/ig;
|
||||
|
||||
return input.replace(tokenRegex, (...args) => {
|
||||
let match = args[0],
|
||||
quotes = args[1];
|
||||
|
||||
if (!quotes) {
|
||||
return match;
|
||||
} else {
|
||||
return replacer(match);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Converts to snake_case.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
runToSnakeCase(input, args) {
|
||||
const smart = args[0];
|
||||
|
||||
if (smart) {
|
||||
return Code._replaceVariableNames(input, snakeCase);
|
||||
} else {
|
||||
return snakeCase(input);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Converts to camelCase.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
runToCamelCase(input, args) {
|
||||
const smart = args[0];
|
||||
|
||||
if (smart) {
|
||||
return Code._replaceVariableNames(input, camelCase);
|
||||
} else {
|
||||
return camelCase(input);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Converts to kebab-case.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
runToKebabCase(input, args) {
|
||||
const smart = args[0];
|
||||
|
||||
if (smart) {
|
||||
return Code._replaceVariableNames(input, kebabCase);
|
||||
} else {
|
||||
return kebabCase(input);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default Code;
|
48
src/core/operations/Comment.mjs
Normal file
48
src/core/operations/Comment.mjs
Normal file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
|
||||
/**
|
||||
* Comment operation
|
||||
*/
|
||||
class Comment extends Operation {
|
||||
|
||||
/**
|
||||
* Comment constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Comment";
|
||||
this.flowControl = true;
|
||||
this.module = "Default";
|
||||
this.description = "Provides a place to write comments within the flow of the recipe. This operation has no computational effect.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "",
|
||||
"type": "text",
|
||||
"value": ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} state - The current state of the recipe.
|
||||
* @param {number} state.progress - The current position in the recipe.
|
||||
* @param {Dish} state.dish - The Dish being operated on.
|
||||
* @param {Operation[]} state.opList - The list of operations in the recipe.
|
||||
* @returns {Object} The updated state of the recipe.
|
||||
*/
|
||||
run(state) {
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Comment;
|
52
src/core/operations/CompareCTPHHashes.mjs
Normal file
52
src/core/operations/CompareCTPHHashes.mjs
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import {HASH_DELIM_OPTIONS} from "../lib/Delim";
|
||||
import ctphjs from "ctph.js";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Compare CTPH hashes operation
|
||||
*/
|
||||
class CompareCTPHHashes extends Operation {
|
||||
|
||||
/**
|
||||
* CompareCTPHHashes constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Compare CTPH hashes";
|
||||
this.module = "Crypto";
|
||||
this.description = "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
|
||||
this.infoURL = "https://forensicswiki.org/wiki/Context_Triggered_Piecewise_Hashing";
|
||||
this.inputType = "string";
|
||||
this.outputType = "Number";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Delimiter",
|
||||
"type": "option",
|
||||
"value": HASH_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {Number}
|
||||
*/
|
||||
run(input, args) {
|
||||
const samples = input.split(Utils.charRep(args[0]));
|
||||
if (samples.length !== 2) throw new OperationError("Incorrect number of samples.");
|
||||
return ctphjs.similarity(samples[0], samples[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CompareCTPHHashes;
|
52
src/core/operations/CompareSSDEEPHashes.mjs
Normal file
52
src/core/operations/CompareSSDEEPHashes.mjs
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Utils from "../Utils";
|
||||
import {HASH_DELIM_OPTIONS} from "../lib/Delim";
|
||||
import ssdeepjs from "ssdeep.js";
|
||||
import OperationError from "../errors/OperationError";
|
||||
|
||||
/**
|
||||
* Compare SSDEEP hashes operation
|
||||
*/
|
||||
class CompareSSDEEPHashes extends Operation {
|
||||
|
||||
/**
|
||||
* CompareSSDEEPHashes constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Compare SSDEEP hashes";
|
||||
this.module = "Crypto";
|
||||
this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
|
||||
this.infoURL = "https://forensicswiki.org/wiki/Ssdeep";
|
||||
this.inputType = "string";
|
||||
this.outputType = "Number";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Delimiter",
|
||||
"type": "option",
|
||||
"value": HASH_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {Number}
|
||||
*/
|
||||
run(input, args) {
|
||||
const samples = input.split(Utils.charRep(args[0]));
|
||||
if (samples.length !== 2) throw new OperationError("Incorrect number of samples.");
|
||||
return ssdeepjs.similarity(samples[0], samples[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CompareSSDEEPHashes;
|
|
@ -1,578 +0,0 @@
|
|||
import Utils from "../Utils.js";
|
||||
import rawdeflate from "zlibjs/bin/rawdeflate.min";
|
||||
import rawinflate from "zlibjs/bin/rawinflate.min";
|
||||
import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min";
|
||||
import zip from "zlibjs/bin/zip.min";
|
||||
import unzip from "zlibjs/bin/unzip.min";
|
||||
import bzip2 from "exports-loader?bzip2!../lib/bzip2.js";
|
||||
|
||||
const Zlib = {
|
||||
RawDeflate: rawdeflate.Zlib.RawDeflate,
|
||||
RawInflate: rawinflate.Zlib.RawInflate,
|
||||
Deflate: zlibAndGzip.Zlib.Deflate,
|
||||
Inflate: zlibAndGzip.Zlib.Inflate,
|
||||
Gzip: zlibAndGzip.Zlib.Gzip,
|
||||
Gunzip: zlibAndGzip.Zlib.Gunzip,
|
||||
Zip: zip.Zlib.Zip,
|
||||
Unzip: unzip.Zlib.Unzip,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compression operations.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
const Compress = {
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
COMPRESSION_TYPE: ["Dynamic Huffman Coding", "Fixed Huffman Coding", "None (Store)"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
INFLATE_BUFFER_TYPE: ["Adaptive", "Block"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
COMPRESSION_METHOD: ["Deflate", "None (Store)"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
OS: ["MSDOS", "Unix", "Macintosh"],
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
RAW_COMPRESSION_TYPE_LOOKUP: {
|
||||
"Fixed Huffman Coding" : Zlib.RawDeflate.CompressionType.FIXED,
|
||||
"Dynamic Huffman Coding" : Zlib.RawDeflate.CompressionType.DYNAMIC,
|
||||
"None (Store)" : Zlib.RawDeflate.CompressionType.NONE,
|
||||
},
|
||||
|
||||
/**
|
||||
* Raw Deflate operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runRawDeflate: function(input, args) {
|
||||
const deflate = new Zlib.RawDeflate(input, {
|
||||
compressionType: Compress.RAW_COMPRESSION_TYPE_LOOKUP[args[0]]
|
||||
});
|
||||
return Array.prototype.slice.call(deflate.compress());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
INFLATE_INDEX: 0,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
INFLATE_BUFFER_SIZE: 0,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
INFLATE_RESIZE: false,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
INFLATE_VERIFY: false,
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
RAW_BUFFER_TYPE_LOOKUP: {
|
||||
"Adaptive" : Zlib.RawInflate.BufferType.ADAPTIVE,
|
||||
"Block" : Zlib.RawInflate.BufferType.BLOCK,
|
||||
},
|
||||
|
||||
/**
|
||||
* Raw Inflate operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runRawInflate: function(input, args) {
|
||||
// Deal with character encoding issues
|
||||
input = Utils.strToByteArray(Utils.byteArrayToUtf8(input));
|
||||
let inflate = new Zlib.RawInflate(input, {
|
||||
index: args[0],
|
||||
bufferSize: args[1],
|
||||
bufferType: Compress.RAW_BUFFER_TYPE_LOOKUP[args[2]],
|
||||
resize: args[3],
|
||||
verify: args[4]
|
||||
}),
|
||||
result = Array.prototype.slice.call(inflate.decompress());
|
||||
|
||||
// Raw Inflate somethimes messes up and returns nonsense like this:
|
||||
// ]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]....]...
|
||||
// e.g. Input data of [8b, 1d, dc, 44]
|
||||
// Look for the first two square brackets:
|
||||
if (result.length > 158 && result[0] === 93 && result[5] === 93) {
|
||||
// If the first two square brackets are there, check that the others
|
||||
// are also there. If they are, throw an error. If not, continue.
|
||||
let valid = false;
|
||||
for (let i = 0; i < 155; i += 5) {
|
||||
if (result[i] !== 93) {
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
throw "Error: Unable to inflate data";
|
||||
}
|
||||
}
|
||||
// Trust me, this is the easiest way...
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
ZLIB_COMPRESSION_TYPE_LOOKUP: {
|
||||
"Fixed Huffman Coding" : Zlib.Deflate.CompressionType.FIXED,
|
||||
"Dynamic Huffman Coding" : Zlib.Deflate.CompressionType.DYNAMIC,
|
||||
"None (Store)" : Zlib.Deflate.CompressionType.NONE,
|
||||
},
|
||||
|
||||
/**
|
||||
* Zlib Deflate operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runZlibDeflate: function(input, args) {
|
||||
const deflate = new Zlib.Deflate(input, {
|
||||
compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]]
|
||||
});
|
||||
return Array.prototype.slice.call(deflate.compress());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
ZLIB_BUFFER_TYPE_LOOKUP: {
|
||||
"Adaptive" : Zlib.Inflate.BufferType.ADAPTIVE,
|
||||
"Block" : Zlib.Inflate.BufferType.BLOCK,
|
||||
},
|
||||
|
||||
/**
|
||||
* Zlib Inflate operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runZlibInflate: function(input, args) {
|
||||
// Deal with character encoding issues
|
||||
input = Utils.strToByteArray(Utils.byteArrayToUtf8(input));
|
||||
const inflate = new Zlib.Inflate(input, {
|
||||
index: args[0],
|
||||
bufferSize: args[1],
|
||||
bufferType: Compress.ZLIB_BUFFER_TYPE_LOOKUP[args[2]],
|
||||
resize: args[3],
|
||||
verify: args[4]
|
||||
});
|
||||
return Array.prototype.slice.call(inflate.decompress());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
GZIP_CHECKSUM: false,
|
||||
|
||||
/**
|
||||
* Gzip operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runGzip: function(input, args) {
|
||||
let filename = args[1],
|
||||
comment = args[2],
|
||||
options = {
|
||||
deflateOptions: {
|
||||
compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[0]]
|
||||
},
|
||||
flags: {
|
||||
fhcrc: args[3]
|
||||
}
|
||||
};
|
||||
|
||||
if (filename.length) {
|
||||
options.flags.fname = true;
|
||||
options.filename = filename;
|
||||
}
|
||||
if (comment.length) {
|
||||
options.flags.fcommenct = true;
|
||||
options.comment = comment;
|
||||
}
|
||||
|
||||
const gzip = new Zlib.Gzip(input, options);
|
||||
return Array.prototype.slice.call(gzip.compress());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Gunzip operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runGunzip: function(input, args) {
|
||||
// Deal with character encoding issues
|
||||
input = Utils.strToByteArray(Utils.byteArrayToUtf8(input));
|
||||
const gunzip = new Zlib.Gunzip(input);
|
||||
return Array.prototype.slice.call(gunzip.decompress());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
PKZIP_FILENAME: "file.txt",
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
ZIP_COMPRESSION_METHOD_LOOKUP: {
|
||||
"Deflate" : Zlib.Zip.CompressionMethod.DEFLATE,
|
||||
"None (Store)" : Zlib.Zip.CompressionMethod.STORE
|
||||
},
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
ZIP_OS_LOOKUP: {
|
||||
"MSDOS" : Zlib.Zip.OperatingSystem.MSDOS,
|
||||
"Unix" : Zlib.Zip.OperatingSystem.UNIX,
|
||||
"Macintosh" : Zlib.Zip.OperatingSystem.MACINTOSH
|
||||
},
|
||||
|
||||
/**
|
||||
* Zip operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runPkzip: function(input, args) {
|
||||
let password = Utils.strToByteArray(args[2]),
|
||||
options = {
|
||||
filename: Utils.strToByteArray(args[0]),
|
||||
comment: Utils.strToByteArray(args[1]),
|
||||
compressionMethod: Compress.ZIP_COMPRESSION_METHOD_LOOKUP[args[3]],
|
||||
os: Compress.ZIP_OS_LOOKUP[args[4]],
|
||||
deflateOption: {
|
||||
compressionType: Compress.ZLIB_COMPRESSION_TYPE_LOOKUP[args[5]]
|
||||
},
|
||||
},
|
||||
zip = new Zlib.Zip();
|
||||
|
||||
if (password.length)
|
||||
zip.setPassword(password);
|
||||
zip.addFile(input, options);
|
||||
return Array.prototype.slice.call(zip.compress());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
PKUNZIP_VERIFY: false,
|
||||
|
||||
/**
|
||||
* Unzip operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runPkunzip: function(input, args) {
|
||||
let options = {
|
||||
password: Utils.strToByteArray(args[0]),
|
||||
verify: args[1]
|
||||
},
|
||||
unzip = new Zlib.Unzip(input, options),
|
||||
filenames = unzip.getFilenames(),
|
||||
files = [];
|
||||
|
||||
filenames.forEach(function(fileName) {
|
||||
const bytes = unzip.decompress(fileName);
|
||||
const contents = Utils.byteArrayToUtf8(bytes);
|
||||
|
||||
const file = {
|
||||
fileName: fileName,
|
||||
size: contents.length,
|
||||
};
|
||||
|
||||
const isDir = contents.length === 0 && fileName.endsWith("/");
|
||||
if (!isDir) {
|
||||
file.bytes = bytes;
|
||||
file.contents = contents;
|
||||
}
|
||||
|
||||
files.push(file);
|
||||
});
|
||||
|
||||
return Utils.displayFilesAsHTML(files);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Bzip2 Decompress operation.
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
runBzip2Decompress: function(input, args) {
|
||||
let compressed = new Uint8Array(input),
|
||||
bzip2Reader,
|
||||
plain = "";
|
||||
|
||||
bzip2Reader = bzip2.array(compressed);
|
||||
plain = bzip2.simple(bzip2Reader);
|
||||
return plain;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
TAR_FILENAME: "file.txt",
|
||||
|
||||
|
||||
/**
|
||||
* Tar pack operation.
|
||||
*
|
||||
* @author tlwr [toby@toby.codes]
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
runTar: function(input, args) {
|
||||
const Tarball = function() {
|
||||
this.bytes = new Array(512);
|
||||
this.position = 0;
|
||||
};
|
||||
|
||||
Tarball.prototype.addEmptyBlock = function() {
|
||||
const filler = new Array(512);
|
||||
filler.fill(0);
|
||||
this.bytes = this.bytes.concat(filler);
|
||||
};
|
||||
|
||||
Tarball.prototype.writeBytes = function(bytes) {
|
||||
const self = this;
|
||||
|
||||
if (this.position + bytes.length > this.bytes.length) {
|
||||
this.addEmptyBlock();
|
||||
}
|
||||
|
||||
Array.prototype.forEach.call(bytes, function(b, i) {
|
||||
if (typeof b.charCodeAt !== "undefined") {
|
||||
b = b.charCodeAt();
|
||||
}
|
||||
|
||||
self.bytes[self.position] = b;
|
||||
self.position += 1;
|
||||
});
|
||||
};
|
||||
|
||||
Tarball.prototype.writeEndBlocks = function() {
|
||||
const numEmptyBlocks = 2;
|
||||
for (let i = 0; i < numEmptyBlocks; i++) {
|
||||
this.addEmptyBlock();
|
||||
}
|
||||
};
|
||||
|
||||
const fileSize = Utils.padLeft(input.length.toString(8), 11, "0");
|
||||
const currentUnixTimestamp = Math.floor(Date.now() / 1000);
|
||||
const lastModTime = Utils.padLeft(currentUnixTimestamp.toString(8), 11, "0");
|
||||
|
||||
const file = {
|
||||
fileName: Utils.padBytesRight(args[0], 100),
|
||||
fileMode: Utils.padBytesRight("0000664", 8),
|
||||
ownerUID: Utils.padBytesRight("0", 8),
|
||||
ownerGID: Utils.padBytesRight("0", 8),
|
||||
size: Utils.padBytesRight(fileSize, 12),
|
||||
lastModTime: Utils.padBytesRight(lastModTime, 12),
|
||||
checksum: " ",
|
||||
type: "0",
|
||||
linkedFileName: Utils.padBytesRight("", 100),
|
||||
USTARFormat: Utils.padBytesRight("ustar", 6),
|
||||
version: "00",
|
||||
ownerUserName: Utils.padBytesRight("", 32),
|
||||
ownerGroupName: Utils.padBytesRight("", 32),
|
||||
deviceMajor: Utils.padBytesRight("", 8),
|
||||
deviceMinor: Utils.padBytesRight("", 8),
|
||||
fileNamePrefix: Utils.padBytesRight("", 155),
|
||||
};
|
||||
|
||||
let checksum = 0;
|
||||
for (const key in file) {
|
||||
const bytes = file[key];
|
||||
Array.prototype.forEach.call(bytes, function(b) {
|
||||
if (typeof b.charCodeAt !== "undefined") {
|
||||
checksum += b.charCodeAt();
|
||||
} else {
|
||||
checksum += b;
|
||||
}
|
||||
});
|
||||
}
|
||||
checksum = Utils.padBytesRight(Utils.padLeft(checksum.toString(8), 7, "0"), 8);
|
||||
file.checksum = checksum;
|
||||
|
||||
const tarball = new Tarball();
|
||||
tarball.writeBytes(file.fileName);
|
||||
tarball.writeBytes(file.fileMode);
|
||||
tarball.writeBytes(file.ownerUID);
|
||||
tarball.writeBytes(file.ownerGID);
|
||||
tarball.writeBytes(file.size);
|
||||
tarball.writeBytes(file.lastModTime);
|
||||
tarball.writeBytes(file.checksum);
|
||||
tarball.writeBytes(file.type);
|
||||
tarball.writeBytes(file.linkedFileName);
|
||||
tarball.writeBytes(file.USTARFormat);
|
||||
tarball.writeBytes(file.version);
|
||||
tarball.writeBytes(file.ownerUserName);
|
||||
tarball.writeBytes(file.ownerGroupName);
|
||||
tarball.writeBytes(file.deviceMajor);
|
||||
tarball.writeBytes(file.deviceMinor);
|
||||
tarball.writeBytes(file.fileNamePrefix);
|
||||
tarball.writeBytes(Utils.padBytesRight("", 12));
|
||||
tarball.writeBytes(input);
|
||||
tarball.writeEndBlocks();
|
||||
|
||||
return tarball.bytes;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Untar unpack operation.
|
||||
*
|
||||
* @author tlwr [toby@toby.codes]
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
runUntar: function(input, args) {
|
||||
const Stream = function(input) {
|
||||
this.bytes = input;
|
||||
this.position = 0;
|
||||
};
|
||||
|
||||
Stream.prototype.getBytes = function(bytesToGet) {
|
||||
const newPosition = this.position + bytesToGet;
|
||||
const bytes = this.bytes.slice(this.position, newPosition);
|
||||
this.position = newPosition;
|
||||
return bytes;
|
||||
};
|
||||
|
||||
Stream.prototype.readString = function(numBytes) {
|
||||
let result = "";
|
||||
for (let i = this.position; i < this.position + numBytes; i++) {
|
||||
const currentByte = this.bytes[i];
|
||||
if (currentByte === 0) break;
|
||||
result += String.fromCharCode(currentByte);
|
||||
}
|
||||
this.position += numBytes;
|
||||
return result;
|
||||
};
|
||||
|
||||
Stream.prototype.readInt = function(numBytes, base) {
|
||||
const string = this.readString(numBytes);
|
||||
return parseInt(string, base);
|
||||
};
|
||||
|
||||
Stream.prototype.hasMore = function() {
|
||||
return this.position < this.bytes.length;
|
||||
};
|
||||
|
||||
let stream = new Stream(input),
|
||||
files = [];
|
||||
|
||||
while (stream.hasMore()) {
|
||||
const dataPosition = stream.position + 512;
|
||||
|
||||
const file = {
|
||||
fileName: stream.readString(100),
|
||||
fileMode: stream.readString(8),
|
||||
ownerUID: stream.readString(8),
|
||||
ownerGID: stream.readString(8),
|
||||
size: parseInt(stream.readString(12), 8), // Octal
|
||||
lastModTime: new Date(1000 * stream.readInt(12, 8)), // Octal
|
||||
checksum: stream.readString(8),
|
||||
type: stream.readString(1),
|
||||
linkedFileName: stream.readString(100),
|
||||
USTARFormat: stream.readString(6).indexOf("ustar") >= 0,
|
||||
};
|
||||
|
||||
if (file.USTARFormat) {
|
||||
file.version = stream.readString(2);
|
||||
file.ownerUserName = stream.readString(32);
|
||||
file.ownerGroupName = stream.readString(32);
|
||||
file.deviceMajor = stream.readString(8);
|
||||
file.deviceMinor = stream.readString(8);
|
||||
file.filenamePrefix = stream.readString(155);
|
||||
}
|
||||
|
||||
stream.position = dataPosition;
|
||||
|
||||
if (file.type === "0") {
|
||||
// File
|
||||
files.push(file);
|
||||
let endPosition = stream.position + file.size;
|
||||
if (file.size % 512 !== 0) {
|
||||
endPosition += 512 - (file.size % 512);
|
||||
}
|
||||
|
||||
file.bytes = stream.getBytes(file.size);
|
||||
file.contents = Utils.byteArrayToUtf8(file.bytes);
|
||||
stream.position = endPosition;
|
||||
} else if (file.type === "5") {
|
||||
// Directory
|
||||
files.push(file);
|
||||
} else {
|
||||
// Symlink or empty bytes
|
||||
}
|
||||
}
|
||||
|
||||
return Utils.displayFilesAsHTML(files);
|
||||
},
|
||||
};
|
||||
|
||||
export default Compress;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue