1
0
mirror of synced 2024-12-01 01:37:18 +01:00

Merged esm branch into feature-magic. Ported FileType ops.

This commit is contained in:
n1474335 2018-05-20 16:49:42 +01:00
commit ee519c7352
385 changed files with 34205 additions and 39954 deletions

View File

@ -5,10 +5,15 @@
"chrome": 40, "chrome": 40,
"firefox": 35, "firefox": 35,
"edge": 14, "edge": 14,
"node": "6.5", "node": "6.5"
}, },
"modules": false, "modules": false,
"useBuiltIns": true "useBuiltIns": true
}] }]
],
"plugins": [
["babel-plugin-transform-builtin-extend", {
"globals": ["Error"]
}]
] ]
} }

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

View File

@ -1,3 +1,2 @@
src/core/lib/** src/core/vendor/**
!src/core/lib/Magic.js src/core/operations/legacy/**
src/core/config/MetaConfig.js

View File

@ -1,6 +1,6 @@
{ {
"parserOptions": { "parserOptions": {
"ecmaVersion": 8, "ecmaVersion": 9,
"ecmaFeatures": { "ecmaFeatures": {
"impliedStrict": true "impliedStrict": true
}, },
@ -51,6 +51,7 @@
"mode": "minimum" "mode": "minimum"
}], }],
"indent": ["error", 4, { "indent": ["error", 4, {
"ignoreComments": true,
"ArrayExpression": "first", "ArrayExpression": "first",
"SwitchCase": 1 "SwitchCase": 1
}], }],
@ -83,12 +84,12 @@
"no-whitespace-before-property": "error", "no-whitespace-before-property": "error",
"operator-linebreak": ["error", "after"], "operator-linebreak": ["error", "after"],
"space-in-parens": "error", "space-in-parens": "error",
"no-var": "error" "no-var": "error",
"prefer-const": "error"
}, },
"globals": { "globals": {
"$": false, "$": false,
"jQuery": false, "jQuery": false,
"moment": false,
"log": false, "log": false,
"COMPILE_TIME": false, "COMPILE_TIME": false,

5
.gitignore vendored
View File

@ -6,4 +6,7 @@ docs/*
!docs/*.conf.json !docs/*.conf.json
!docs/*.ico !docs/*.ico
.vscode .vscode
src/core/config/MetaConfig.js src/core/config/modules/*
src/core/config/OperationConfig.json
src/core/operations/index.mjs

View File

@ -1,6 +1,6 @@
language: node_js language: node_js
node_js: node_js:
- "8.4" - node
install: npm install install: npm install
before_script: before_script:
- npm install -g grunt - npm install -g grunt
@ -11,6 +11,7 @@ script:
- grunt node - grunt node
- grunt prod --msg="$COMPILE_MSG" - grunt prod --msg="$COMPILE_MSG"
before_deploy: before_deploy:
- grunt exec:sitemap
- grunt copy:ghPages - grunt copy:ghPages
deploy: deploy:
- provider: pages - provider: pages

View File

@ -4,7 +4,8 @@ const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
const NodeExternals = require("webpack-node-externals"); const NodeExternals = require("webpack-node-externals");
const Inliner = require("web-resource-inliner"); const Inliner = require("web-resource-inliner");
const fs = require("fs"); const glob = require("glob");
const path = require("path");
/** /**
* Grunt configuration for building the app in various formats. * Grunt configuration for building the app in various formats.
@ -21,15 +22,15 @@ module.exports = function (grunt) {
// Tasks // Tasks
grunt.registerTask("dev", grunt.registerTask("dev",
"A persistent task which creates a development build whenever source files are modified.", "A persistent task which creates a development build whenever source files are modified.",
["clean:dev", "concurrent:dev"]); ["clean:dev", "exec:generateConfig", "concurrent:dev"]);
grunt.registerTask("node", grunt.registerTask("node",
"Compiles CyberChef into a single NodeJS module.", "Compiles CyberChef into a single NodeJS module.",
["clean:node", "webpack:metaConf", "webpack:node", "chmod:build"]); ["clean:node", "clean:config", "exec:generateConfig", "webpack:node", "chmod:build"]);
grunt.registerTask("test", grunt.registerTask("test",
"A task which runs all the tests in test/tests.", "A task which runs all the tests in test/tests.",
["clean:test", "webpack:metaConf", "webpack:tests", "execute:test"]); ["exec:generateConfig", "exec:tests"]);
grunt.registerTask("docs", grunt.registerTask("docs",
"Compiles documentation in the /docs directory.", "Compiles documentation in the /docs directory.",
@ -37,7 +38,7 @@ module.exports = function (grunt) {
grunt.registerTask("prod", grunt.registerTask("prod",
"Creates a production-ready build. Use the --msg flag to add a compile message.", "Creates a production-ready build. Use the --msg flag to add a compile message.",
["eslint", "clean:prod", "webpack:metaConf", "webpack:web", "inline", "chmod"]); ["eslint", "clean:prod", "exec:generateConfig", "webpack:web", "inline", "chmod"]);
grunt.registerTask("default", grunt.registerTask("default",
"Lints the code base", "Lints the code base",
@ -45,7 +46,7 @@ module.exports = function (grunt) {
grunt.registerTask("inline", grunt.registerTask("inline",
"Compiles a production build of CyberChef into a single, portable web page.", "Compiles a production build of CyberChef into a single, portable web page.",
["webpack:webInline", "runInliner", "clean:inlineScripts"]); ["exec:generateConfig", "webpack:webInline", "runInliner", "clean:inlineScripts"]);
grunt.registerTask("runInliner", runInliner); grunt.registerTask("runInliner", runInliner);
@ -60,9 +61,9 @@ module.exports = function (grunt) {
grunt.loadNpmTasks("grunt-jsdoc"); grunt.loadNpmTasks("grunt-jsdoc");
grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-contrib-clean");
grunt.loadNpmTasks("grunt-contrib-copy"); grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-chmod"); grunt.loadNpmTasks("grunt-chmod");
grunt.loadNpmTasks("grunt-exec"); grunt.loadNpmTasks("grunt-exec");
grunt.loadNpmTasks("grunt-execute");
grunt.loadNpmTasks("grunt-accessibility"); grunt.loadNpmTasks("grunt-accessibility");
grunt.loadNpmTasks("grunt-concurrent"); grunt.loadNpmTasks("grunt-concurrent");
@ -118,12 +119,12 @@ module.exports = function (grunt) {
* Generates an entry list for all the modules. * Generates an entry list for all the modules.
*/ */
function listEntryModules() { function listEntryModules() {
const path = "./src/core/config/modules/"; const entryModules = {};
let entryModules = {};
fs.readdirSync(path).forEach(file => { glob.sync("./src/core/config/modules/*.mjs").forEach(file => {
if (file !== "Default.js" && file !== "OpModules.js") const basename = path.basename(file);
entryModules[file.split(".js")[0]] = path + file; if (basename !== "Default.mjs" && basename !== "OpModules.mjs")
entryModules[basename.split(".mjs")[0]] = path.resolve(file);
}); });
return entryModules; return entryModules;
@ -131,10 +132,10 @@ module.exports = function (grunt) {
grunt.initConfig({ grunt.initConfig({
clean: { clean: {
dev: ["build/dev/*", "src/core/config/MetaConfig.js"], dev: ["build/dev/*"],
prod: ["build/prod/*", "src/core/config/MetaConfig.js"], prod: ["build/prod/*"],
test: ["build/test/*", "src/core/config/MetaConfig.js"], node: ["build/node/*"],
node: ["build/node/*", "src/core/config/MetaConfig.js"], config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"],
docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"], docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
inlineScripts: ["build/prod/scripts.js"], inlineScripts: ["build/prod/scripts.js"],
}, },
@ -143,10 +144,10 @@ module.exports = function (grunt) {
configFile: "./.eslintrc.json" configFile: "./.eslintrc.json"
}, },
configs: ["Gruntfile.js"], configs: ["Gruntfile.js"],
core: ["src/core/**/*.js", "!src/core/lib/**/*", "!src/core/config/MetaConfig.js"], core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
web: ["src/web/**/*.js"], web: ["src/web/**/*.{js,mjs}"],
node: ["src/node/**/*.js"], node: ["src/node/**/*.{js,mjs}"],
tests: ["test/**/*.js"], tests: ["test/**/*.{js,mjs}"],
}, },
jsdoc: { jsdoc: {
options: { options: {
@ -159,17 +160,11 @@ module.exports = function (grunt) {
all: { all: {
src: [ src: [
"src/**/*.js", "src/**/*.js",
"!src/core/lib/**/*", "src/**/*.mjs",
"!src/core/config/MetaConfig.js" "!src/core/vendor/**/*"
], ],
} }
}, },
concurrent: {
options: {
logConcurrentOutput: true
},
dev: ["webpack:metaConfDev", "webpack-dev-server:start"]
},
accessibility: { accessibility: {
options: { options: {
accessibilityLevel: "WCAG2A", accessibilityLevel: "WCAG2A",
@ -184,55 +179,23 @@ module.exports = function (grunt) {
}, },
webpack: { webpack: {
options: webpackConfig, options: webpackConfig,
metaConf: {
target: "node",
entry: "./src/core/config/OperationConfig.js",
output: {
filename: "MetaConfig.js",
path: __dirname + "/src/core/config/",
library: "MetaConfig",
libraryTarget: "commonjs2",
libraryExport: "default"
},
externals: [NodeExternals()],
},
metaConfDev: {
target: "node",
entry: "./src/core/config/OperationConfig.js",
output: {
filename: "MetaConfig.js",
path: __dirname + "/src/core/config/",
library: "MetaConfig",
libraryTarget: "commonjs2",
libraryExport: "default"
},
externals: [NodeExternals()],
watch: true
},
web: { web: {
mode: "production",
target: "web", target: "web",
entry: Object.assign({ entry: Object.assign({
main: "./src/web/index.js" main: "./src/web/index.js",
sitemap: "./src/web/static/sitemap.js"
}, moduleEntryPoints), }, moduleEntryPoints),
output: { output: {
path: __dirname + "/build/prod" path: __dirname + "/build/prod"
}, },
resolve: { resolve: {
alias: { alias: {
"./config/modules/OpModules.js": "./config/modules/Default.js" "./config/modules/OpModules": "./config/modules/Default"
} }
}, },
plugins: [ plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS), new webpack.DefinePlugin(BUILD_CONSTANTS),
new webpack.optimize.UglifyJsPlugin({
compress: {
"screw_ie8": true,
"dead_code": true,
"unused": true,
"warnings": false
},
comments: false,
}),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: "index.html", filename: "index.html",
template: "./src/web/html/index.html", template: "./src/web/html/index.html",
@ -249,6 +212,7 @@ module.exports = function (grunt) {
] ]
}, },
webInline: { webInline: {
mode: "production",
target: "web", target: "web",
entry: "./src/web/index.js", entry: "./src/web/index.js",
output: { output: {
@ -256,21 +220,14 @@ module.exports = function (grunt) {
path: __dirname + "/build/prod" path: __dirname + "/build/prod"
}, },
plugins: [ plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS), new webpack.DefinePlugin(Object.assign({}, BUILD_CONSTANTS, {
new webpack.optimize.UglifyJsPlugin({ INLINE: "true"
compress: { })),
"screw_ie8": true,
"dead_code": true,
"unused": true,
"warnings": false
},
comments: false,
}),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: "cyberchef.htm", filename: "cyberchef.htm",
template: "./src/web/html/index.html", template: "./src/web/html/index.html",
compileTime: compileTime, compileTime: compileTime,
version: pkg.version, version: pkg.version + "s",
inline: true, inline: true,
minify: { minify: {
removeComments: true, removeComments: true,
@ -282,8 +239,9 @@ module.exports = function (grunt) {
] ]
}, },
tests: { tests: {
mode: "development",
target: "node", target: "node",
entry: "./test/index.js", entry: "./test/index.mjs",
externals: [NodeExternals()], externals: [NodeExternals()],
output: { output: {
filename: "index.js", filename: "index.js",
@ -294,8 +252,9 @@ module.exports = function (grunt) {
] ]
}, },
node: { node: {
mode: "production",
target: "node", target: "node",
entry: "./src/node/index.js", entry: "./src/node/index.mjs",
externals: [NodeExternals()], externals: [NodeExternals()],
output: { output: {
filename: "CyberChef.js", filename: "CyberChef.js",
@ -320,18 +279,20 @@ module.exports = function (grunt) {
children: false, children: false,
chunks: false, chunks: false,
modules: false, modules: false,
warningsFilter: /source-map/, entrypoints: false,
warningsFilter: [/source-map/, /dependency is an expression/],
} }
}, },
start: { start: {
webpack: { webpack: {
mode: "development",
target: "web", target: "web",
entry: Object.assign({ entry: Object.assign({
main: "./src/web/index.js" main: "./src/web/index.js"
}, moduleEntryPoints), }, moduleEntryPoints),
resolve: { resolve: {
alias: { alias: {
"./config/modules/OpModules.js": "./config/modules/Default.js" "./config/modules/OpModules": "./config/modules/Default"
} }
}, },
plugins: [ plugins: [
@ -371,7 +332,7 @@ module.exports = function (grunt) {
expand: true, expand: true,
src: "docs/**", src: "docs/**",
dest: "build/prod/" dest: "build/prod/"
} },
] ]
} }
}, },
@ -389,6 +350,18 @@ module.exports = function (grunt) {
src: ["docs/**/*", "docs/"] src: ["docs/**/*", "docs/"]
} }
}, },
watch: {
config: {
files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"],
tasks: ["exec:generateConfig"]
}
},
concurrent: {
dev: ["watch:config", "webpack-dev-server:start"],
options: {
logConcurrentOutput: true
}
},
exec: { exec: {
repoSize: { repoSize: {
command: [ command: [
@ -400,9 +373,21 @@ module.exports = function (grunt) {
cleanGit: { cleanGit: {
command: "git gc --prune=now --aggressive" command: "git gc --prune=now --aggressive"
}, },
}, sitemap: {
execute: { command: "node build/prod/sitemap.js > build/prod/sitemap.xml"
test: "build/test/index.js" },
generateConfig: {
command: [
"echo '\n--- Regenerating config files. ---'",
"node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs",
"echo 'export default {};\n' > src/core/config/modules/OpModules.mjs",
"node --experimental-modules src/core/config/scripts/generateConfig.mjs",
"echo '--- Config scripts finished. ---\n'"
].join(";")
},
tests: {
command: "node --experimental-modules test/index.mjs"
}
}, },
}); });
}; };

View File

@ -103,4 +103,4 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice
[8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA [8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA
[9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ [9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ
[10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg [10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
[11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4 [11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4

9094
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "cyberchef", "name": "cyberchef",
"version": "7.5.4", "version": "7.11.1",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>", "author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef", "homepage": "https://gchq.github.io/CyberChef",
@ -30,87 +30,101 @@
"main": "build/node/CyberChef.js", "main": "build/node/CyberChef.js",
"bugs": "https://github.com/gchq/CyberChef/issues", "bugs": "https://github.com/gchq/CyberChef/issues",
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.0", "babel-core": "^6.26.3",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"css-loader": "^0.28.7", "css-loader": "^0.28.11",
"exports-loader": "^0.6.4", "eslint": "^4.19.1",
"extract-text-webpack-plugin": "^3.0.2", "exports-loader": "^0.7.0",
"file-loader": "^1.1.6", "extract-text-webpack-plugin": "^4.0.0-alpha0",
"grunt": ">=1.0.1", "file-loader": "^1.1.11",
"grunt-accessibility": "~5.0.0", "grunt": ">=1.0.2",
"grunt-accessibility": "~6.0.0",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",
"grunt-concurrent": "^2.3.1", "grunt-concurrent": "^2.3.1",
"grunt-contrib-clean": "~1.1.0", "grunt-contrib-clean": "~1.1.0",
"grunt-contrib-copy": "~1.0.0", "grunt-contrib-copy": "~1.0.0",
"grunt-contrib-watch": "^1.0.1",
"grunt-eslint": "^20.1.0", "grunt-eslint": "^20.1.0",
"grunt-exec": "~3.0.0", "grunt-exec": "~3.0.0",
"grunt-execute": "^0.2.2", "grunt-jsdoc": "^2.2.1",
"grunt-jsdoc": "^2.2.0", "grunt-webpack": "^3.1.1",
"grunt-webpack": "^3.0.2", "html-webpack-plugin": "^3.2.0",
"html-webpack-plugin": "^2.30.1", "imports-loader": "^0.8.0",
"imports-loader": "^0.7.1",
"ink-docstrap": "^1.3.2", "ink-docstrap": "^1.3.2",
"jsdoc-babel": "^0.3.0", "js-to-mjs": "^0.2.0",
"less": "^2.7.3", "jsdoc-babel": "^0.4.0",
"less-loader": "^4.0.5", "less": "^3.0.2",
"postcss-css-variables": "^0.8.0", "less-loader": "^4.1.0",
"postcss-import": "^11.0.0", "postcss-css-variables": "^0.8.1",
"postcss-loader": "^2.0.9", "postcss-import": "^11.1.0",
"style-loader": "^0.19.1", "postcss-loader": "^2.1.4",
"url-loader": "^0.6.2", "sitemap": "^1.13.0",
"val-loader": "^1.1.0", "style-loader": "^0.21.0",
"web-resource-inliner": "^4.2.0", "url-loader": "^1.0.1",
"webpack": "^3.10.0", "web-resource-inliner": "^4.2.1",
"webpack-dev-server": "^2.9.7", "webpack": "^4.6.0",
"webpack-node-externals": "^1.6.0", "webpack-dev-server": "^3.1.3",
"worker-loader": "^1.1.0" "webpack-node-externals": "^1.7.2",
"worker-loader": "^1.1.1"
}, },
"dependencies": { "dependencies": {
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"bignumber.js": "^5.0.0", "babel-plugin-transform-builtin-extend": "1.1.2",
"bcryptjs": "^2.4.3",
"bignumber.js": "^7.0.1",
"bootstrap": "^3.3.7", "bootstrap": "^3.3.7",
"bootstrap-colorpicker": "^2.5.2", "bootstrap-colorpicker": "^2.5.2",
"bootstrap-switch": "^3.3.4", "bootstrap-switch": "^3.3.4",
"bson": "^2.0.6",
"chi-squared": "^1.1.0", "chi-squared": "^1.1.0",
"crypto-api": "^0.7.5", "crypto-api": "^0.8.0",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"diff": "^3.4.0", "ctph.js": "0.0.5",
"escodegen": "^1.9.0", "diff": "^3.5.0",
"escodegen": "^1.9.1",
"es6-promisify": "^6.0.0",
"esmangle": "^1.0.1", "esmangle": "^1.0.1",
"esprima": "^4.0.0", "esprima": "^4.0.0",
"exif-parser": "^0.1.12", "exif-parser": "^0.1.12",
"file-saver": "^1.3.3", "file-saver": "^1.3.8",
"google-code-prettify": "^1.0.5", "highlight.js": "^9.12.0",
"jquery": "^3.2.1", "jquery": "^3.3.1",
"js-crc": "^0.2.0", "js-crc": "^0.2.0",
"js-sha3": "^0.7.0", "js-sha3": "^0.7.0",
"jsbn": "^1.1.0", "jsbn": "^1.1.0",
"jsesc": "^2.5.1",
"jsonpath": "^1.0.0", "jsonpath": "^1.0.0",
"jsrsasign": "8.0.4", "jsrsasign": "8.0.12",
"lodash": "^4.17.4", "lodash": "^4.17.10",
"loglevel": "^1.6.0", "loglevel": "^1.6.1",
"kbpgp": "^2.0.77",
"loglevel-message-prefix": "^3.0.0", "loglevel-message-prefix": "^3.0.0",
"moment": "^2.20.1", "moment": "^2.22.1",
"moment-timezone": "^0.5.14", "moment-timezone": "^0.5.16",
"node-forge": "^0.7.1", "node-forge": "^0.7.5",
"node-md6": "^0.1.0", "node-md6": "^0.1.0",
"nwmatcher": "^1.4.3", "nwmatcher": "^1.4.4",
"otp": "^0.1.3", "otp": "^0.1.3",
"scryptsy": "^2.0.0",
"sladex-blowfish": "^0.8.1", "sladex-blowfish": "^0.8.1",
"sortablejs": "^1.7.0", "sortablejs": "^1.7.0",
"split.js": "^1.3.5", "split.js": "^1.3.5",
"ssdeep.js": "0.0.2",
"ua-parser-js": "^0.7.17",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"xmldom": "^0.1.27", "xmldom": "^0.1.27",
"xpath": "0.0.27", "xpath": "0.0.27",
"xregexp": "^4.0.0", "xregexp": "^4.1.1",
"zlibjs": "^0.3.1" "zlibjs": "^0.3.1"
}, },
"scripts": { "scripts": {
"start": "grunt dev", "start": "grunt dev",
"build": "grunt prod", "build": "grunt prod",
"test": "grunt test", "test": "grunt test",
"docs": "grunt docs" "docs": "grunt docs",
"lint": "grunt lint",
"postinstall": "npx j2m node_modules/crypto-api/src/crypto-api.js"
} }
} }

View File

@ -1,165 +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|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)
*/
Chef.prototype.bake = async function(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 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;
return {
result: this.dish.type === Dish.HTML ?
this.dish.get(Dish.HTML, notUTF8) :
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.
*/
Chef.prototype.silentBake = function(recipeConfig) {
log.debug("Running silent bake");
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;
};
/**
* 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}
*/
Chef.prototype.calculateHighlights = function(recipeConfig, direction, pos) {
const recipe = new Recipe(recipeConfig);
const highlights = 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
};
};
export default Chef;

198
src/core/Chef.mjs Executable file
View 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 = new Dish(this.dish);
// 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}
*/
calculateHighlights(recipeConfig, direction, pos) {
const recipe = new Recipe(recipeConfig);
const highlights = 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;

View File

@ -7,9 +7,9 @@
*/ */
import "babel-polyfill"; import "babel-polyfill";
import Chef from "./Chef.js"; import Chef from "./Chef";
import OperationConfig from "./config/MetaConfig.js"; import OperationConfig from "./config/OperationConfig.json";
import OpModules from "./config/modules/Default.js"; import OpModules from "./config/modules/Default";
// Add ">" to the start of all log messages in the Chef Worker // Add ">" to the start of all log messages in the Chef Worker
import loglevelMessagePrefix from "loglevel-message-prefix"; import loglevelMessagePrefix from "loglevel-message-prefix";
@ -60,6 +60,9 @@ self.addEventListener("message", function(e) {
case "silentBake": case "silentBake":
silentBake(r.data); silentBake(r.data);
break; break;
case "getDishAs":
getDishAs(r.data);
break;
case "docURL": case "docURL":
// Used to set the URL of the current document so that scripts can be // Used to set the URL of the current document so that scripts can be
// imported into an inline worker. // imported into an inline worker.
@ -125,6 +128,22 @@ function silentBake(data) {
} }
/**
* 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. * Calculates highlight offsets if possible.
* *
@ -151,12 +170,13 @@ function calculateHighlights(recipeConfig, direction, pos) {
*/ */
self.loadRequiredModules = function(recipeConfig) { self.loadRequiredModules = function(recipeConfig) {
recipeConfig.forEach(op => { recipeConfig.forEach(op => {
let module = self.OperationConfig[op.op].module; const module = self.OperationConfig[op.op].module;
if (!OpModules.hasOwnProperty(module)) { if (!OpModules.hasOwnProperty(module)) {
log.info("Loading module " + module); log.info(`Loading ${module} module`);
self.sendStatusMessage("Loading module " + module); self.sendStatusMessage(`Loading ${module} module`);
self.importScripts(self.docURL + "/" + module + ".js"); self.importScripts(`${self.docURL}/${module}.js`);
self.sendStatusMessage("");
} }
}); });
}; };

View File

@ -1,274 +0,0 @@
import Utils from "./Utils.js";
import BigNumber from "bignumber.js";
/**
* The data being operated on by each operation.
*
* @author n1474335 [n1474335@gmail.com]
* @author Matt C [matt@artemisbot.uk]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*
* @class
* @param {byteArray|string|number|ArrayBuffer|BigNumber} 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;
/**
* Dish data type enum for ArrayBuffers.
* @readonly
* @enum
*/
Dish.ARRAY_BUFFER = 4;
/**
* Dish data type enum for BigNumbers.
* @readonly
* @enum
*/
Dish.BIG_NUMBER = 5;
/**
* 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.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;
default:
throw "Invalid data type string. No matching enum.";
}
};
/**
* Returns the data type string for the given type enum.
*
* @static
* @param {number} typeEnum - The enum value of the data type.
* @returns {string} 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";
case Dish.ARRAY_BUFFER:
return "ArrayBuffer";
case Dish.BIG_NUMBER:
return "BigNumber";
default:
throw "Invalid data type enum. No matching type.";
}
};
/**
* Sets the data value and type and then validates them.
*
* @param {byteArray|string|number|ArrayBuffer|BigNumber} value - The value of the input data.
* @param {number} type - The data type of value, see Dish enums.
*/
Dish.prototype.set = function(value, 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 "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] - Do not treat strings as UTF8.
* @returns {byteArray|string|number|ArrayBuffer|BigNumber} The value of the output data.
*/
Dish.prototype.get = function(type, notUTF8) {
if (this.type !== type) {
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] - Do not treat strings as UTF8.
*/
Dish.prototype.translate = function(toType, notUTF8) {
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
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 = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : [];
break;
default:
break;
}
this.type = Dish.BYTE_ARRAY;
// Convert from byteArray to toType
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;
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:
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 this.value instanceof BigNumber;
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}
*/
Dish.prototype.size = function() {
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;
default:
return -1;
}
};
export default Dish;

362
src/core/Dish.mjs Executable file
View File

@ -0,0 +1,362 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @author Matt C [matt@artemisbot.uk]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Utils from "./Utils";
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 "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 "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 "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
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 = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : [];
break;
case Dish.JSON:
this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value)) : [];
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;
}
this.type = Dish.BYTE_ARRAY;
// Convert from byteArray to toType
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;
}
}
/**
* 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 this.value instanceof BigNumber;
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;
}
}
}
/**
* 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;

View File

@ -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.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;

112
src/core/Ingredient.mjs Executable file
View File

@ -0,0 +1,112 @@
/**
* @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.toggleValues = [];
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.toggleValues = ingredientConfig.toggleValues;
}
/**
* 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":
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;

View File

@ -1,174 +0,0 @@
import Dish from "./Dish.js";
import Ingredient from "./Ingredient.js";
import OperationConfig from "./config/MetaConfig.js";
import OpModules from "./config/modules/OpModules.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
*/
const Operation = function(operationName) {
this.name = operationName;
this.module = "";
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.hasOwnProperty(this.name)) {
this._parseConfig(OperationConfig[this.name]);
}
};
/**
* Reads and parses the given config.
*
* @private
* @param {Object} operationConfig
*/
Operation.prototype._parseConfig = function(operationConfig) {
this.module = operationConfig.module;
this.description = operationConfig.description;
this.inputType = Dish.typeEnum(operationConfig.inputType);
this.outputType = Dish.typeEnum(operationConfig.outputType);
this.highlight = operationConfig.highlight;
this.highlightReverse = operationConfig.highlightReverse;
this.flowControl = operationConfig.flowControl;
this.run = OpModules[this.module][this.name];
for (let a = 0; a < operationConfig.args.length; a++) {
const ingredientConfig = operationConfig.args[a];
const ingredient = new Ingredient(ingredientConfig);
this.addIngredient(ingredient);
}
if (this.highlight === "func") {
this.highlight = OpModules[this.module][`${this.name}-highlight`];
}
if (this.highlightReverse === "func") {
this.highlightReverse = OpModules[this.module][`${this.name}-highlightReverse`];
}
};
/**
* 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;

288
src/core/Operation.mjs Executable file
View File

@ -0,0 +1,288 @@
/**
* @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._ingList = [];
// Public fields
this.name = "";
this.module = "";
this.description = "";
}
/**
* 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 => {
return {
name: ing.name,
type: ing.type,
value: ing.defaultValue,
toggleValues: ing.toggleValues || []
};
});
}
/**
* 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;
}
}
export default Operation;

View File

@ -1,259 +0,0 @@
import Operation from "./Operation.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 operation = new Operation(operationName);
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, numRegisters = 0;
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.getIngValues())}`);
if (op.isDisabled()) {
log.debug("Operation is disabled, skipping");
continue;
}
if (op.isBreakpoint()) {
log.debug("Pausing at breakpoint");
return i;
}
try {
input = dish.get(op.inputType);
log.debug("Executing operation");
if (op.isFlowControl()) {
// Package up the current state
let state = {
"progress": i,
"dish": dish,
"opList": this.opList,
"numJumps": numJumps,
"numRegisters": numRegisters
};
state = await op.run(state);
i = state.progress;
numJumps = state.numJumps;
numRegisters = state.numRegisters;
} 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;
}
}
log.debug("Recipe complete");
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);
};
/**
* 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
*/
Recipe.prototype.generateHighlightList = function() {
const highlights = [];
for (let i = 0; i < this.opList.length; i++) {
let op = this.opList[i];
if (op.isDisabled()) continue;
// If any breakpoints are set, do not attempt to highlight
if (op.isBreakpoint()) 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.getIngValues()
});
}
return highlights;
};
export default Recipe;

290
src/core/Recipe.mjs Executable file
View File

@ -0,0 +1,290 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
// import Operation from "./Operation.js";
import OpModules from "./config/modules/OpModules";
import OperationConfig from "./config/OperationConfig.json";
import OperationError from "./errors/OperationError";
import log from "loglevel";
/**
* 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) {
for (let c = 0; c < recipeConfig.length; c++) {
const operationName = recipeConfig[c].op;
const opConf = OperationConfig[operationName];
const opObj = OpModules[opConf.module][operationName];
const operation = new opObj();
operation.ingValues = recipeConfig[c].args;
operation.breakpoint = recipeConfig[c].breakpoint;
operation.disabled = recipeConfig[c].disabled;
this.addOperation(operation);
}
}
/**
* 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.config);
}
/**
* 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) {
this.opList = this.opList.concat(operations);
}
/**
* 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 an 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;
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 {
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
*/
generateHighlightList() {
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;

File diff suppressed because it is too large Load Diff

View File

@ -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", "name": "Favourites",
ops: [] "ops": []
}, },
{ {
name: "Data format", "name": "Data format",
ops: [ "ops": [
"To Hexdump", "To Hexdump",
"From Hexdump", "From Hexdump",
"To Hex", "To Hex",
@ -52,6 +33,7 @@ const Categories = [
"From HTML Entity", "From HTML Entity",
"URL Encode", "URL Encode",
"URL Decode", "URL Decode",
"Escape Unicode Characters",
"Unescape Unicode Characters", "Unescape Unicode Characters",
"To Quoted Printable", "To Quoted Printable",
"From Quoted Printable", "From Quoted Printable",
@ -65,12 +47,12 @@ const Categories = [
"Change IP format", "Change IP format",
"Encode text", "Encode text",
"Decode text", "Decode text",
"Swap endianness", "Swap endianness"
] ]
}, },
{ {
name: "Encryption / Encoding", "name": "Encryption / Encoding",
ops: [ "ops": [
"AES Encrypt", "AES Encrypt",
"AES Decrypt", "AES Decrypt",
"Blowfish Encrypt", "Blowfish Encrypt",
@ -99,23 +81,36 @@ const Categories = [
"Substitute", "Substitute",
"Derive PBKDF2 key", "Derive PBKDF2 key",
"Derive EVP key", "Derive EVP key",
"Pseudo-Random Number Generator", "Bcrypt",
"Scrypt",
"Pseudo-Random Number Generator"
] ]
}, },
{ {
name: "Public Key", "name": "Public Key",
ops: [ "ops": [
"Parse X.509 certificate", "Parse X.509 certificate",
"Parse ASN.1 hex string", "Parse ASN.1 hex string",
"PEM to Hex", "PEM to Hex",
"Hex to PEM", "Hex to PEM",
"Hex to Object Identifier", "Hex to Object Identifier",
"Object Identifier to Hex", "Object Identifier to Hex",
"Generate PGP Key Pair",
"PGP Encrypt",
"PGP Decrypt",
"PGP Encrypt and Sign",
"PGP Decrypt and Verify"
] ]
}, },
{ {
name: "Arithmetic / Logic", "name": "Arithmetic / Logic",
ops: [ "ops": [
"Set Union",
"Set Intersection",
"Set Difference",
"Symmetric Difference",
"Cartesian Product",
"Power Set",
"XOR", "XOR",
"XOR Brute Force", "XOR Brute Force",
"OR", "OR",
@ -134,12 +129,12 @@ const Categories = [
"Bit shift right", "Bit shift right",
"Rotate left", "Rotate left",
"Rotate right", "Rotate right",
"ROT13", "ROT13"
] ]
}, },
{ {
name: "Networking", "name": "Networking",
ops: [ "ops": [
"HTTP request", "HTTP request",
"Strip HTTP headers", "Strip HTTP headers",
"Parse User Agent", "Parse User Agent",
@ -153,20 +148,20 @@ const Categories = [
"Change IP format", "Change IP format",
"Group IP addresses", "Group IP addresses",
"Encode NetBIOS Name", "Encode NetBIOS Name",
"Decode NetBIOS Name", "Decode NetBIOS Name"
] ]
}, },
{ {
name: "Language", "name": "Language",
ops: [ "ops": [
"Encode text", "Encode text",
"Decode text", "Decode text",
"Unescape Unicode Characters", "Unescape Unicode Characters"
] ]
}, },
{ {
name: "Utils", "name": "Utils",
ops: [ "ops": [
"Diff", "Diff",
"Remove whitespace", "Remove whitespace",
"Remove null bytes", "Remove null bytes",
@ -174,6 +169,7 @@ const Categories = [
"To Lower case", "To Lower case",
"Add line numbers", "Add line numbers",
"Remove line numbers", "Remove line numbers",
"To Table",
"Reverse", "Reverse",
"Sort", "Sort",
"Unique", "Unique",
@ -201,11 +197,12 @@ const Categories = [
"Escape string", "Escape string",
"Unescape string", "Unescape string",
"Pseudo-Random Number Generator", "Pseudo-Random Number Generator",
"Sleep"
] ]
}, },
{ {
name: "Date / Time", "name": "Date / Time",
ops: [ "ops": [
"Parse DateTime", "Parse DateTime",
"Translate DateTime Format", "Translate DateTime Format",
"From UNIX Timestamp", "From UNIX Timestamp",
@ -213,11 +210,12 @@ const Categories = [
"Windows Filetime to UNIX Timestamp", "Windows Filetime to UNIX Timestamp",
"UNIX Timestamp to Windows Filetime", "UNIX Timestamp to Windows Filetime",
"Extract dates", "Extract dates",
"Sleep"
] ]
}, },
{ {
name: "Extractors", "name": "Extractors",
ops: [ "ops": [
"Strings", "Strings",
"Extract IP addresses", "Extract IP addresses",
"Extract email addresses", "Extract email addresses",
@ -230,12 +228,12 @@ const Categories = [
"XPath expression", "XPath expression",
"JPath expression", "JPath expression",
"CSS selector", "CSS selector",
"Extract EXIF", "Extract EXIF"
] ]
}, },
{ {
name: "Compression", "name": "Compression",
ops: [ "ops": [
"Raw Deflate", "Raw Deflate",
"Raw Inflate", "Raw Inflate",
"Zlib Deflate", "Zlib Deflate",
@ -246,12 +244,12 @@ const Categories = [
"Unzip", "Unzip",
"Bzip2 Decompress", "Bzip2 Decompress",
"Tar", "Tar",
"Untar", "Untar"
] ]
}, },
{ {
name: "Hashing", "name": "Hashing",
ops: [ "ops": [
"Analyse hash", "Analyse hash",
"Generate all hashes", "Generate all hashes",
"MD2", "MD2",
@ -268,7 +266,15 @@ const Categories = [
"HAS-160", "HAS-160",
"Whirlpool", "Whirlpool",
"Snefru", "Snefru",
"SSDEEP",
"CTPH",
"Compare SSDEEP hashes",
"Compare CTPH hashes",
"HMAC", "HMAC",
"Bcrypt",
"Bcrypt compare",
"Bcrypt parse",
"Scrypt",
"Fletcher-8 Checksum", "Fletcher-8 Checksum",
"Fletcher-16 Checksum", "Fletcher-16 Checksum",
"Fletcher-32 Checksum", "Fletcher-32 Checksum",
@ -276,12 +282,12 @@ const Categories = [
"Adler-32 Checksum", "Adler-32 Checksum",
"CRC-16 Checksum", "CRC-16 Checksum",
"CRC-32 Checksum", "CRC-32 Checksum",
"TCP/IP Checksum", "TCP/IP Checksum"
] ]
}, },
{ {
name: "Code tidy", "name": "Code tidy",
ops: [ "ops": [
"Syntax highlighter", "Syntax highlighter",
"Generic Code Beautify", "Generic Code Beautify",
"JavaScript Parser", "JavaScript Parser",
@ -305,11 +311,13 @@ const Categories = [
"To Snake case", "To Snake case",
"To Camel case", "To Camel case",
"To Kebab case", "To Kebab case",
"BSON serialise",
"BSON deserialise"
] ]
}, },
{ {
name: "Other", "name": "Other",
ops: [ "ops": [
"Entropy", "Entropy",
"Frequency distribution", "Frequency distribution",
"Chi Square", "Chi Square",
@ -324,11 +332,12 @@ const Categories = [
"Remove EXIF", "Remove EXIF",
"Extract EXIF", "Extract EXIF",
"Numberwang", "Numberwang",
"XKCD Random Number"
] ]
}, },
{ {
name: "Flow control", "name": "Flow control",
ops: [ "ops": [
"Magic", "Magic",
"Fork", "Fork",
"Merge", "Merge",
@ -339,7 +348,5 @@ const Categories = [
"Return", "Return",
"Comment" "Comment"
] ]
}, }
]; ]
export default Categories;

View File

@ -1,21 +0,0 @@
import CharEnc from "../../operations/CharEnc.js";
/**
* CharEnc module.
*
* Libraries:
* - cptable
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.CharEnc = {
"Encode text": CharEnc.runEncode,
"Decode text": CharEnc.runDecode,
};
export default OpModules;

View File

@ -1,44 +0,0 @@
import Cipher from "../../operations/Cipher.js";
/**
* Ciphers module.
*
* Libraries:
* - CryptoJS
* - Blowfish
* - Forge
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Ciphers = {
"AES Encrypt": Cipher.runAesEnc,
"AES Decrypt": Cipher.runAesDec,
"Blowfish Encrypt": Cipher.runBlowfishEnc,
"Blowfish Decrypt": Cipher.runBlowfishDec,
"DES Encrypt": Cipher.runDesEnc,
"DES Decrypt": Cipher.runDesDec,
"Triple DES Encrypt": Cipher.runTripleDesEnc,
"Triple DES Decrypt": Cipher.runTripleDesDec,
"Derive PBKDF2 key": Cipher.runPbkdf2,
"Derive EVP key": Cipher.runEvpkdf,
"RC4": Cipher.runRc4,
"RC4 Drop": Cipher.runRc4drop,
"RC2 Encrypt": Cipher.runRc2Enc,
"RC2 Decrypt": Cipher.runRc2Dec,
"Vigenère Encode": Cipher.runVigenereEnc,
"Vigenère Decode": Cipher.runVigenereDec,
"Bifid Cipher Encode": Cipher.runBifidEnc,
"Bifid Cipher Decode": Cipher.runBifidDec,
"Affine Cipher Encode": Cipher.runAffineEnc,
"Affine Cipher Decode": Cipher.runAffineDec,
"Atbash Cipher": Cipher.runAtbash,
"Substitute": Cipher.runSubstitute,
"Pseudo-Random Number Generator": Cipher.runPRNG,
};
export default OpModules;

View File

@ -1,44 +0,0 @@
import JS from "../../operations/JS.js";
import Code from "../../operations/Code.js";
/**
* Code module.
*
* Libraries:
* - lodash
* - vkbeautify
* - xmldom
* - xpath
* - jpath
* - googlecodeprettify
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Code = {
"JavaScript Parser": JS.runParse,
"JavaScript Beautify": JS.runBeautify,
"JavaScript Minify": JS.runMinify,
"Syntax highlighter": Code.runSyntaxHighlight,
"Generic Code Beautify": Code.runGenericBeautify,
"JSON Beautify": Code.runJsonBeautify,
"JSON Minify": Code.runJsonMinify,
"XML Beautify": Code.runXmlBeautify,
"XML Minify": Code.runXmlMinify,
"SQL Beautify": Code.runSqlBeautify,
"SQL Minify": Code.runSqlMinify,
"CSS Beautify": Code.runCssBeautify,
"CSS Minify": Code.runCssMinify,
"XPath expression": Code.runXpath,
"CSS selector": Code.runCSSQuery,
"To Snake case": Code.runToSnakeCase,
"To Camel case": Code.runToCamelCase,
"To Kebab case": Code.runToKebabCase,
"JPath expression": Code.runJpath,
};
export default OpModules;

View File

@ -1,32 +0,0 @@
import Compress from "../../operations/Compress.js";
/**
* Compression module.
*
* Libraries:
* - zlib.js
* - bzip2.js
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Compression = {
"Raw Deflate": Compress.runRawDeflate,
"Raw Inflate": Compress.runRawInflate,
"Zlib Deflate": Compress.runZlibDeflate,
"Zlib Inflate": Compress.runZlibInflate,
"Gzip": Compress.runGzip,
"Gunzip": Compress.runGunzip,
"Zip": Compress.runPkzip,
"Unzip": Compress.runPkunzip,
"Bzip2 Decompress": Compress.runBzip2Decompress,
"Tar": Compress.runTar,
"Untar": Compress.runUntar,
};
export default OpModules;

View File

@ -1,190 +0,0 @@
import FlowControl from "../../FlowControl.js";
import Arithmetic from "../../operations/Arithmetic.js";
import Base from "../../operations/Base.js";
import Base58 from "../../operations/Base58.js";
import Base64 from "../../operations/Base64.js";
import BCD from "../../operations/BCD.js";
import BitwiseOp from "../../operations/BitwiseOp.js";
import ByteRepr from "../../operations/ByteRepr.js";
import Convert from "../../operations/Convert.js";
import DateTime from "../../operations/DateTime.js";
import Endian from "../../operations/Endian.js";
import Entropy from "../../operations/Entropy.js";
import FileType from "../../operations/FileType.js";
import Hexdump from "../../operations/Hexdump.js";
import HTML from "../../operations/HTML.js";
import MAC from "../../operations/MAC.js";
import MorseCode from "../../operations/MorseCode.js";
import MS from "../../operations/MS.js";
import NetBIOS from "../../operations/NetBIOS.js";
import Numberwang from "../../operations/Numberwang.js";
import OS from "../../operations/OS.js";
import OTP from "../../operations/OTP.js";
import PHP from "../../operations/PHP.js";
import QuotedPrintable from "../../operations/QuotedPrintable.js";
import Rotate from "../../operations/Rotate.js";
import SeqUtils from "../../operations/SeqUtils.js";
import StrUtils from "../../operations/StrUtils.js";
import Tidy from "../../operations/Tidy.js";
import Unicode from "../../operations/Unicode.js";
import UUID from "../../operations/UUID.js";
/**
* Default module.
*
* The Default module is for operations that are expected to be very commonly used or
* do not require any libraries. This module is loaded into the app at compile time.
*
* Libraries:
* - Utils.js
* - otp
* - crypto
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Default = {
"To Hexdump": Hexdump.runTo,
"From Hexdump": Hexdump.runFrom,
"To Hex": ByteRepr.runToHex,
"From Hex": ByteRepr.runFromHex,
"To Octal": ByteRepr.runToOct,
"From Octal": ByteRepr.runFromOct,
"To Charcode": ByteRepr.runToCharcode,
"From Charcode": ByteRepr.runFromCharcode,
"To Decimal": ByteRepr.runToDecimal,
"From Decimal": ByteRepr.runFromDecimal,
"To Binary": ByteRepr.runToBinary,
"From Binary": ByteRepr.runFromBinary,
"To Hex Content": ByteRepr.runToHexContent,
"From Hex Content": ByteRepr.runFromHexContent,
"To Base64": Base64.runTo,
"From Base64": Base64.runFrom,
"Show Base64 offsets": Base64.runOffsets,
"To Base32": Base64.runTo32,
"From Base32": Base64.runFrom32,
"To Base58": Base58.runTo,
"From Base58": Base58.runFrom,
"To Base": Base.runTo,
"From Base": Base.runFrom,
"To BCD": BCD.runToBCD,
"From BCD": BCD.runFromBCD,
"To HTML Entity": HTML.runToEntity,
"From HTML Entity": HTML.runFromEntity,
"Strip HTML tags": HTML.runStripTags,
"Parse colour code": HTML.runParseColourCode,
"Unescape Unicode Characters": Unicode.runUnescape,
"To Quoted Printable": QuotedPrintable.runTo,
"From Quoted Printable": QuotedPrintable.runFrom,
"Swap endianness": Endian.runSwapEndianness,
"ROT13": Rotate.runRot13,
"ROT47": Rotate.runRot47,
"Rotate left": Rotate.runRotl,
"Rotate right": Rotate.runRotr,
"Bit shift left": BitwiseOp.runBitShiftLeft,
"Bit shift right": BitwiseOp.runBitShiftRight,
"XOR": BitwiseOp.runXor,
"XOR Brute Force": BitwiseOp.runXorBrute,
"OR": BitwiseOp.runXor,
"NOT": BitwiseOp.runNot,
"AND": BitwiseOp.runAnd,
"ADD": BitwiseOp.runAdd,
"SUB": BitwiseOp.runSub,
"To Morse Code": MorseCode.runTo,
"From Morse Code": MorseCode.runFrom,
"Format MAC addresses": MAC.runFormat,
"Encode NetBIOS Name": NetBIOS.runEncodeName,
"Decode NetBIOS Name": NetBIOS.runDecodeName,
"Offset checker": StrUtils.runOffsetChecker,
"To Upper case": StrUtils.runUpper,
"To Lower case": StrUtils.runLower,
"Split": StrUtils.runSplit,
"Filter": StrUtils.runFilter,
"Escape string": StrUtils.runEscape,
"Unescape string": StrUtils.runUnescape,
"Head": StrUtils.runHead,
"Tail": StrUtils.runTail,
"Hamming Distance": StrUtils.runHamming,
"Remove whitespace": Tidy.runRemoveWhitespace,
"Remove null bytes": Tidy.runRemoveNulls,
"Drop bytes": Tidy.runDropBytes,
"Take bytes": Tidy.runTakeBytes,
"Pad lines": Tidy.runPad,
"Reverse": SeqUtils.runReverse,
"Sort": SeqUtils.runSort,
"Unique": SeqUtils.runUnique,
"Count occurrences": SeqUtils.runCount,
"Add line numbers": SeqUtils.runAddLineNumbers,
"Remove line numbers": SeqUtils.runRemoveLineNumbers,
"Expand alphabet range": SeqUtils.runExpandAlphRange,
"Convert distance": Convert.runDistance,
"Convert area": Convert.runArea,
"Convert mass": Convert.runMass,
"Convert speed": Convert.runSpeed,
"Convert data units": Convert.runDataSize,
"Parse UNIX file permissions": OS.runParseUnixPerms,
"Parse DateTime": DateTime.runParse,
"Translate DateTime Format": DateTime.runTranslateFormat,
"From UNIX Timestamp": DateTime.runFromUnixTimestamp,
"To UNIX Timestamp": DateTime.runToUnixTimestamp,
"Microsoft Script Decoder": MS.runDecodeScript,
"Entropy": Entropy.runEntropy,
"Frequency distribution": Entropy.runFreqDistrib,
"Chi Square": Entropy.runChiSq,
"Detect File Type": FileType.runDetect,
"Scan for Embedded Files": FileType.runScanForEmbeddedFiles,
"Generate UUID": UUID.runGenerateV4,
"Numberwang": Numberwang.run,
"Generate TOTP": OTP.runTOTP,
"Generate HOTP": OTP.runHOTP,
"Magic": FlowControl.runMagic,
"Fork": FlowControl.runFork,
"Merge": FlowControl.runMerge,
"Register": FlowControl.runRegister,
"Label": FlowControl.runComment,
"Jump": FlowControl.runJump,
"Conditional Jump": FlowControl.runCondJump,
"Return": FlowControl.runReturn,
"Comment": FlowControl.runComment,
"PHP Deserialize": PHP.runDeserialize,
"Sum": Arithmetic.runSum,
"Subtract": Arithmetic.runSub,
"Multiply": Arithmetic.runMulti,
"Divide": Arithmetic.runDiv,
"Mean": Arithmetic.runMean,
"Median": Arithmetic.runMedian,
"Standard Deviation": Arithmetic.runStdDev,
/*
Highlighting functions.
This is a temporary solution as highlighting should be entirely
overhauled at some point.
*/
"From Base64-highlight": Base64.highlightFrom,
"From Base64-highlightReverse": Base64.highlightTo,
"To Base64-highlight": Base64.highlightTo,
"To Base64-highlightReverse": Base64.highlightFrom,
"From Hex-highlight": ByteRepr.highlightFrom,
"From Hex-highlightReverse": ByteRepr.highlightTo,
"To Hex-highlight": ByteRepr.highlightTo,
"To Hex-highlightReverse": ByteRepr.highlightFrom,
"From Charcode-highlight": ByteRepr.highlightFrom,
"From Charcode-highlightReverse": ByteRepr.highlightTo,
"To Charcode-highlight": ByteRepr.highlightTo,
"To Charcode-highlightReverse": ByteRepr.highlightFrom,
"From Binary-highlight": ByteRepr.highlightFromBinary,
"From Binary-highlightReverse": ByteRepr.highlightToBinary,
"To Binary-highlight": ByteRepr.highlightToBinary,
"To Binary-highlightReverse": ByteRepr.highlightFromBinary,
"From Hexdump-highlight": Hexdump.highlightFrom,
"From Hexdump-highlightReverse": Hexdump.highlightTo,
"To Hexdump-highlight": Hexdump.highlightTo,
"To Hexdump-highlightReverse": Hexdump.highlightFrom,
};
export default OpModules;

View File

@ -1,20 +0,0 @@
import Diff from "../../operations/Diff.js";
/**
* Diff module.
*
* Libraries:
* - JsDIff
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Diff = {
"Diff": Diff.runDiff,
};
export default OpModules;

View File

@ -1,21 +0,0 @@
import Punycode from "../../operations/Punycode.js";
/**
* Encodings module.
*
* Libraries:
* - punycode
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Encodings = {
"To Punycode": Punycode.runToAscii,
"From Punycode": Punycode.runToUnicode,
};
export default OpModules;

View File

@ -1,22 +0,0 @@
import HTTP from "../../operations/HTTP.js";
/**
* HTTP module.
*
* Libraries:
* - UAS_parser
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.HTTP = {
"HTTP request": HTTP.runHTTPRequest,
"Strip HTTP headers": HTTP.runStripHeaders,
"Parse User Agent": HTTP.runParseUserAgent,
};
export default OpModules;

View File

@ -1,48 +0,0 @@
import Checksum from "../../operations/Checksum.js";
import Hash from "../../operations/Hash.js";
/**
* Hashing module.
*
* Libraries:
* - CryptoApi
* - node-md6
* - js-sha3
* - ./Checksum.js
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Hashing = {
"Analyse hash": Hash.runAnalyse,
"Generate all hashes": Hash.runAll,
"MD2": Hash.runMD2,
"MD4": Hash.runMD4,
"MD5": Hash.runMD5,
"MD6": Hash.runMD6,
"SHA0": Hash.runSHA0,
"SHA1": Hash.runSHA1,
"SHA2": Hash.runSHA2,
"SHA3": Hash.runSHA3,
"Keccak": Hash.runKeccak,
"Shake": Hash.runShake,
"RIPEMD": Hash.runRIPEMD,
"HAS-160": Hash.runHAS,
"Whirlpool": Hash.runWhirlpool,
"Snefru": Hash.runSnefru,
"HMAC": Hash.runHMAC,
"Fletcher-8 Checksum": Checksum.runFletcher8,
"Fletcher-16 Checksum": Checksum.runFletcher16,
"Fletcher-32 Checksum": Checksum.runFletcher32,
"Fletcher-64 Checksum": Checksum.runFletcher64,
"Adler-32 Checksum": Checksum.runAdler32,
"CRC-16 Checksum": Checksum.runCRC16,
"CRC-32 Checksum": Checksum.runCRC32,
"TCP/IP Checksum": Checksum.runTCPIP,
};
export default OpModules;

View File

@ -1,25 +0,0 @@
import Image from "../../operations/Image.js";
/**
* Image module.
*
* Libraries:
* - exif-parser
* - remove-exif
* - ./FileType.js
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Image = {
"Extract EXIF": Image.runExtractEXIF,
"Remove EXIF": Image.runRemoveEXIF,
"Render Image": Image.runRenderImage,
};
export default OpModules;

View File

@ -1,28 +0,0 @@
import IP from "../../operations/IP.js";
import Filetime from "../../operations/Filetime.js";
/**
* JSBN module.
*
* Libraries:
* - jsbn
* - ./Checksum.js
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.JSBN = {
"Parse IP range": IP.runParseIpRange,
"Parse IPv6 address": IP.runParseIPv6,
"Parse IPv4 header": IP.runParseIPv4Header,
"Change IP format": IP.runChangeIpFormat,
"Group IP addresses": IP.runGroupIps,
"Windows Filetime to UNIX Timestamp": Filetime.runFromFiletimeToUnix,
"UNIX Timestamp to Windows Filetime": Filetime.runToFiletimeFromUnix,
};
export default OpModules;

View File

@ -1,43 +0,0 @@
/**
* Imports all modules for builds which do not load modules separately.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import OpModules from "./Default.js";
import CharEncModule from "./CharEnc.js";
import CipherModule from "./Ciphers.js";
import CodeModule from "./Code.js";
import CompressionModule from "./Compression.js";
import DiffModule from "./Diff.js";
import EncodingModule from "./Encodings.js";
import HashingModule from "./Hashing.js";
import HTTPModule from "./HTTP.js";
import ImageModule from "./Image.js";
import JSBNModule from "./JSBN.js";
import PublicKeyModule from "./PublicKey.js";
import RegexModule from "./Regex.js";
import ShellcodeModule from "./Shellcode.js";
import URLModule from "./URL.js";
Object.assign(
OpModules,
CharEncModule,
CipherModule,
CodeModule,
CompressionModule,
DiffModule,
EncodingModule,
HashingModule,
HTTPModule,
ImageModule,
JSBNModule,
PublicKeyModule,
RegexModule,
ShellcodeModule,
URLModule
);
export default OpModules;

View File

@ -1,25 +0,0 @@
import PublicKey from "../../operations/PublicKey.js";
/**
* PublicKey module.
*
* Libraries:
* - jsrsasign
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.PublicKey = {
"Parse X.509 certificate": PublicKey.runParseX509,
"Parse ASN.1 hex string": PublicKey.runParseAsn1HexString,
"PEM to Hex": PublicKey.runPemToHex,
"Hex to PEM": PublicKey.runHexToPem,
"Hex to Object Identifier": PublicKey.runHexToObjectIdentifier,
"Object Identifier to Hex": PublicKey.runObjectIdentifierToHex,
};
export default OpModules;

View File

@ -1,30 +0,0 @@
import Extract from "../../operations/Extract.js";
import Regex from "../../operations/Regex.js";
/**
* Regex module.
*
* Libraries:
* - XRegExp
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Regex = {
"Regular expression": Regex.runRegex,
"Find / Replace": Regex.runFindReplace,
"Strings": Extract.runStrings,
"Extract IP addresses": Extract.runIp,
"Extract email addresses": Extract.runEmail,
"Extract MAC addresses": Extract.runMac,
"Extract URLs": Extract.runUrls,
"Extract domains": Extract.runDomains,
"Extract file paths": Extract.runFilePaths,
"Extract dates": Extract.runDates,
};
export default OpModules;

View File

@ -1,20 +0,0 @@
import Shellcode from "../../operations/Shellcode.js";
/**
* Shellcode module.
*
* Libraries:
* - DisassembleX86-64.js
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.Shellcode = {
"Disassemble x86": Shellcode.runDisassemble,
};
export default OpModules;

View File

@ -1,23 +0,0 @@
import URL_ from "../../operations/URL.js";
/**
* URL module.
*
* Libraries:
* - Utils.js
* - url
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
let OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
OpModules.URL = {
"URL Encode": URL_.runTo,
"URL Decode": URL_.runFrom,
"Parse URI": URL_.runParse,
};
export default OpModules;

View File

@ -0,0 +1,148 @@
/**
* 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,
inputType: op.inputType,
outputType: op.presentType,
flowControl: op.flowControl,
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");

View 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.");

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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
View 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",
},
];

141
src/core/lib/Base64.mjs Executable file
View File

@ -0,0 +1,141 @@
/**
* 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 = 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: 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"},
];

204
src/core/lib/CanvasComponents.mjs Executable file
View 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.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";
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.3,
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
View 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
View 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,
};

313
src/core/lib/DateTime.mjs Normal file
View 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-condensed table-bordered" style="font-family: sans-serif">
<thead>
<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>`;

69
src/core/lib/Delim.mjs Normal file
View File

@ -0,0 +1,69 @@
/**
* 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"];
/**
* 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: ""}
];

41
src/core/lib/Extract.mjs Normal file
View File

@ -0,0 +1,41 @@
/**
* 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;
}

28
src/core/lib/Hash.mjs Normal file
View 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());
}

110
src/core/lib/Hex.mjs Normal file
View File

@ -0,0 +1,110 @@
/**
* Byte representation 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, byteLen=2) {
delim = delim || "Auto";
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);

View File

@ -1,11 +1,9 @@
import OperationConfig from "../config/MetaConfig.js"; import OperationConfig from "../config/OperationConfig.json";
import Utils from "../Utils.js"; import Utils from "../Utils";
import Recipe from "../Recipe.js"; import Recipe from "../Recipe";
import Dish from "../Dish.js"; import Dish from "../Dish";
import FileType from "../operations/FileType.js";
import chiSquared from "chi-squared"; import chiSquared from "chi-squared";
/** /**
* A class for detecting encodings, file types and byte frequencies and * A class for detecting encodings, file types and byte frequencies and
* speculatively executing recipes. * speculatively executing recipes.
@ -35,7 +33,7 @@ class Magic {
* @returns {Object[]} * @returns {Object[]}
*/ */
findMatchingOps() { findMatchingOps() {
let matches = []; const matches = [];
for (let i = 0; i < this.opPatterns.length; i++) { for (let i = 0; i < this.opPatterns.length; i++) {
const pattern = this.opPatterns[i], const pattern = this.opPatterns[i],
@ -66,10 +64,10 @@ class Magic {
const inputFreq = this._freqDist(); const inputFreq = this._freqDist();
const langFreqs = extLang ? EXTENSIVE_LANG_FREQS : COMMON_LANG_FREQS; const langFreqs = extLang ? EXTENSIVE_LANG_FREQS : COMMON_LANG_FREQS;
let chiSqrs = []; const chiSqrs = [];
for (let lang in langFreqs) { for (const lang in langFreqs) {
let [score, prob] = Magic._chiSqr(inputFreq, langFreqs[lang]); const [score, prob] = Magic._chiSqr(inputFreq, langFreqs[lang]);
chiSqrs.push({ chiSqrs.push({
lang: lang, lang: lang,
score: score, score: score,
@ -94,7 +92,7 @@ class Magic {
* @returns {string} [type.desc] - Description * @returns {string} [type.desc] - Description
*/ */
detectFileType() { detectFileType() {
return FileType.magicType(this.inputBuffer); return Magic.magicFileType(this.inputBuffer);
} }
/** /**
@ -179,8 +177,8 @@ class Magic {
* @returns {number} * @returns {number}
*/ */
calcEntropy() { calcEntropy() {
let prob = this._freqDist(), const prob = this._freqDist();
entropy = 0, let entropy = 0,
p; p;
for (let i = 0; i < prob.length; i++) { for (let i = 0; i < prob.length; i++) {
@ -198,8 +196,7 @@ class Magic {
*/ */
async bruteForce() { async bruteForce() {
const sample = new Uint8Array(this.inputBuffer).slice(0, 100); const sample = new Uint8Array(this.inputBuffer).slice(0, 100);
const results = [];
let results = [];
// 1-byte XOR // 1-byte XOR
for (let i = 1; i < 256; i++) { for (let i = 1; i < 256; i++) {
@ -269,7 +266,7 @@ class Magic {
if (depth < 0) return []; if (depth < 0) return [];
// Find any operations that can be run on this data // Find any operations that can be run on this data
let matchingOps = this.findMatchingOps(); const matchingOps = this.findMatchingOps();
let results = []; let results = [];
@ -359,7 +356,8 @@ class Magic {
* @returns {ArrayBuffer} * @returns {ArrayBuffer}
*/ */
async _runRecipe(recipeConfig, input=this.inputBuffer) { async _runRecipe(recipeConfig, input=this.inputBuffer) {
const dish = new Dish(input, Dish.ARRAY_BUFFER); const dish = new Dish();
dish.set(input.buffer, Dish.ARRAY_BUFFER);
if (ENVIRONMENT_IS_WORKER()) self.loadRequiredModules(recipeConfig); if (ENVIRONMENT_IS_WORKER()) self.loadRequiredModules(recipeConfig);
@ -383,8 +381,8 @@ class Magic {
if (this.freqDist) return this.freqDist; if (this.freqDist) return this.freqDist;
const len = this.inputBuffer.length; const len = this.inputBuffer.length;
let i = len, let i = len;
counts = new Array(256).fill(0); const counts = new Array(256).fill(0);
if (!len) return counts; if (!len) return counts;
@ -405,9 +403,9 @@ class Magic {
* @returns {Object[]} * @returns {Object[]}
*/ */
static _generateOpPatterns() { static _generateOpPatterns() {
let opPatterns = []; const opPatterns = [];
for (let op in OperationConfig) { for (const op in OperationConfig) {
if (!OperationConfig[op].hasOwnProperty("patterns")) continue; if (!OperationConfig[op].hasOwnProperty("patterns")) continue;
OperationConfig[op].patterns.forEach(pattern => { OperationConfig[op].patterns.forEach(pattern => {
@ -761,6 +759,450 @@ class Magic {
}[code]; }[code];
} }
/**
* Given a buffer, detects magic byte sequences at specific positions and returns the
* extension and mime type.
*
* @param {Uint8Array} buf
* @returns {Object} type
* @returns {string} type.ext - File extension
* @returns {string} type.mime - Mime type
* @returns {string} [type.desc] - Description
*/
static magicFileType(buf) {
if (!(buf && buf.length > 1)) {
return null;
}
if (buf[0] === 0xFF && buf[1] === 0xD8 && buf[2] === 0xFF) {
return {
ext: "jpg",
mime: "image/jpeg"
};
}
if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4E && buf[3] === 0x47) {
return {
ext: "png",
mime: "image/png"
};
}
if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) {
return {
ext: "gif",
mime: "image/gif"
};
}
if (buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) {
return {
ext: "webp",
mime: "image/webp"
};
}
// needs to be before `tif` check
if (((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) && buf[8] === 0x43 && buf[9] === 0x52) {
return {
ext: "cr2",
mime: "image/x-canon-cr2"
};
}
if ((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) {
return {
ext: "tif",
mime: "image/tiff"
};
}
if (buf[0] === 0x42 && buf[1] === 0x4D) {
return {
ext: "bmp",
mime: "image/bmp"
};
}
if (buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0xBC) {
return {
ext: "jxr",
mime: "image/vnd.ms-photo"
};
}
if (buf[0] === 0x38 && buf[1] === 0x42 && buf[2] === 0x50 && buf[3] === 0x53) {
return {
ext: "psd",
mime: "image/vnd.adobe.photoshop"
};
}
// needs to be before `zip` check
if (buf[0] === 0x50 && buf[1] === 0x4B && buf[2] === 0x3 && buf[3] === 0x4 && buf[30] === 0x6D && buf[31] === 0x69 && buf[32] === 0x6D && buf[33] === 0x65 && buf[34] === 0x74 && buf[35] === 0x79 && buf[36] === 0x70 && buf[37] === 0x65 && buf[38] === 0x61 && buf[39] === 0x70 && buf[40] === 0x70 && buf[41] === 0x6C && buf[42] === 0x69 && buf[43] === 0x63 && buf[44] === 0x61 && buf[45] === 0x74 && buf[46] === 0x69 && buf[47] === 0x6F && buf[48] === 0x6E && buf[49] === 0x2F && buf[50] === 0x65 && buf[51] === 0x70 && buf[52] === 0x75 && buf[53] === 0x62 && buf[54] === 0x2B && buf[55] === 0x7A && buf[56] === 0x69 && buf[57] === 0x70) {
return {
ext: "epub",
mime: "application/epub+zip"
};
}
if (buf[0] === 0x50 && buf[1] === 0x4B && (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) && (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)) {
return {
ext: "zip",
mime: "application/zip"
};
}
if (buf[257] === 0x75 && buf[258] === 0x73 && buf[259] === 0x74 && buf[260] === 0x61 && buf[261] === 0x72) {
return {
ext: "tar",
mime: "application/x-tar"
};
}
if (buf[0] === 0x52 && buf[1] === 0x61 && buf[2] === 0x72 && buf[3] === 0x21 && buf[4] === 0x1A && buf[5] === 0x7 && (buf[6] === 0x0 || buf[6] === 0x1)) {
return {
ext: "rar",
mime: "application/x-rar-compressed"
};
}
if (buf[0] === 0x1F && buf[1] === 0x8B && buf[2] === 0x8) {
return {
ext: "gz",
mime: "application/gzip"
};
}
if (buf[0] === 0x42 && buf[1] === 0x5A && buf[2] === 0x68) {
return {
ext: "bz2",
mime: "application/x-bzip2"
};
}
if (buf[0] === 0x37 && buf[1] === 0x7A && buf[2] === 0xBC && buf[3] === 0xAF && buf[4] === 0x27 && buf[5] === 0x1C) {
return {
ext: "7z",
mime: "application/x-7z-compressed"
};
}
if (buf[0] === 0x78 && buf[1] === 0x01) {
return {
ext: "dmg, zlib",
mime: "application/x-apple-diskimage, application/x-deflate"
};
}
if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && (buf[3] === 0x18 || buf[3] === 0x20) && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) || (buf[0] === 0x33 && buf[1] === 0x67 && buf[2] === 0x70 && buf[3] === 0x35) || (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x6D && buf[9] === 0x70 && buf[10] === 0x34 && buf[11] === 0x32 && buf[16] === 0x6D && buf[17] === 0x70 && buf[18] === 0x34 && buf[19] === 0x31 && buf[20] === 0x6D && buf[21] === 0x70 && buf[22] === 0x34 && buf[23] === 0x32 && buf[24] === 0x69 && buf[25] === 0x73 && buf[26] === 0x6F && buf[27] === 0x6D)) {
return {
ext: "mp4",
mime: "video/mp4"
};
}
if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x56)) {
return {
ext: "m4v",
mime: "video/x-m4v"
};
}
if (buf[0] === 0x4D && buf[1] === 0x54 && buf[2] === 0x68 && buf[3] === 0x64) {
return {
ext: "mid",
mime: "audio/midi"
};
}
// needs to be before the `webm` check
if (buf[31] === 0x6D && buf[32] === 0x61 && buf[33] === 0x74 && buf[34] === 0x72 && buf[35] === 0x6f && buf[36] === 0x73 && buf[37] === 0x6B && buf[38] === 0x61) {
return {
ext: "mkv",
mime: "video/x-matroska"
};
}
if (buf[0] === 0x1A && buf[1] === 0x45 && buf[2] === 0xDF && buf[3] === 0xA3) {
return {
ext: "webm",
mime: "video/webm"
};
}
if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x14 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) {
return {
ext: "mov",
mime: "video/quicktime"
};
}
if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x41 && buf[9] === 0x56 && buf[10] === 0x49) {
return {
ext: "avi",
mime: "video/x-msvideo"
};
}
if (buf[0] === 0x30 && buf[1] === 0x26 && buf[2] === 0xB2 && buf[3] === 0x75 && buf[4] === 0x8E && buf[5] === 0x66 && buf[6] === 0xCF && buf[7] === 0x11 && buf[8] === 0xA6 && buf[9] === 0xD9) {
return {
ext: "wmv",
mime: "video/x-ms-wmv"
};
}
if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x1 && buf[3].toString(16)[0] === "b") {
return {
ext: "mpg",
mime: "video/mpeg"
};
}
if ((buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) || (buf[0] === 0xFF && buf[1] === 0xfb)) {
return {
ext: "mp3",
mime: "audio/mpeg"
};
}
if ((buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x41) || (buf[0] === 0x4D && buf[1] === 0x34 && buf[2] === 0x41 && buf[3] === 0x20)) {
return {
ext: "m4a",
mime: "audio/m4a"
};
}
if (buf[0] === 0x4F && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53) {
return {
ext: "ogg",
mime: "audio/ogg"
};
}
if (buf[0] === 0x66 && buf[1] === 0x4C && buf[2] === 0x61 && buf[3] === 0x43) {
return {
ext: "flac",
mime: "audio/x-flac"
};
}
if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45) {
return {
ext: "wav",
mime: "audio/x-wav"
};
}
if (buf[0] === 0x23 && buf[1] === 0x21 && buf[2] === 0x41 && buf[3] === 0x4D && buf[4] === 0x52 && buf[5] === 0x0A) {
return {
ext: "amr",
mime: "audio/amr"
};
}
if (buf[0] === 0x25 && buf[1] === 0x50 && buf[2] === 0x44 && buf[3] === 0x46) {
return {
ext: "pdf",
mime: "application/pdf"
};
}
if (buf[0] === 0x4D && buf[1] === 0x5A) {
return {
ext: "exe",
mime: "application/x-msdownload"
};
}
if ((buf[0] === 0x43 || buf[0] === 0x46) && buf[1] === 0x57 && buf[2] === 0x53) {
return {
ext: "swf",
mime: "application/x-shockwave-flash"
};
}
if (buf[0] === 0x7B && buf[1] === 0x5C && buf[2] === 0x72 && buf[3] === 0x74 && buf[4] === 0x66) {
return {
ext: "rtf",
mime: "application/rtf"
};
}
if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x46 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) {
return {
ext: "woff",
mime: "application/font-woff"
};
}
if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x32 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) {
return {
ext: "woff2",
mime: "application/font-woff"
};
}
if (buf[34] === 0x4C && buf[35] === 0x50 && ((buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x01) || (buf[8] === 0x01 && buf[9] === 0x00 && buf[10] === 0x00) || (buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x02))) {
return {
ext: "eot",
mime: "application/octet-stream"
};
}
if (buf[0] === 0x00 && buf[1] === 0x01 && buf[2] === 0x00 && buf[3] === 0x00 && buf[4] === 0x00) {
return {
ext: "ttf",
mime: "application/font-sfnt"
};
}
if (buf[0] === 0x4F && buf[1] === 0x54 && buf[2] === 0x54 && buf[3] === 0x4F && buf[4] === 0x00) {
return {
ext: "otf",
mime: "application/font-sfnt"
};
}
if (buf[0] === 0x00 && buf[1] === 0x00 && buf[2] === 0x01 && buf[3] === 0x00) {
return {
ext: "ico",
mime: "image/x-icon"
};
}
if (buf[0] === 0x46 && buf[1] === 0x4C && buf[2] === 0x56 && buf[3] === 0x01) {
return {
ext: "flv",
mime: "video/x-flv"
};
}
if (buf[0] === 0x25 && buf[1] === 0x21) {
return {
ext: "ps",
mime: "application/postscript"
};
}
if (buf[0] === 0xFD && buf[1] === 0x37 && buf[2] === 0x7A && buf[3] === 0x58 && buf[4] === 0x5A && buf[5] === 0x00) {
return {
ext: "xz",
mime: "application/x-xz"
};
}
if (buf[0] === 0x53 && buf[1] === 0x51 && buf[2] === 0x4C && buf[3] === 0x69) {
return {
ext: "sqlite",
mime: "application/x-sqlite3"
};
}
/**
*
* Added by n1474335 [n1474335@gmail.com] from here on
*
*/
if ((buf[0] === 0x1F && buf[1] === 0x9D) || (buf[0] === 0x1F && buf[1] === 0xA0)) {
return {
ext: "z, tar.z",
mime: "application/x-gtar"
};
}
if (buf[0] === 0x7F && buf[1] === 0x45 && buf[2] === 0x4C && buf[3] === 0x46) {
return {
ext: "none, axf, bin, elf, o, prx, puff, so",
mime: "application/x-executable",
desc: "Executable and Linkable Format file. No standard file extension."
};
}
if (buf[0] === 0xCA && buf[1] === 0xFE && buf[2] === 0xBA && buf[3] === 0xBE) {
return {
ext: "class",
mime: "application/java-vm"
};
}
if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) {
return {
ext: "txt",
mime: "text/plain",
desc: "UTF-8 encoded Unicode byte order mark detected, commonly but not exclusively seen in text files."
};
}
// Must be before Little-endian UTF-16 BOM
if (buf[0] === 0xFF && buf[1] === 0xFE && buf[2] === 0x00 && buf[3] === 0x00) {
return {
ext: "UTF32LE",
mime: "charset/utf32le",
desc: "Little-endian UTF-32 encoded Unicode byte order mark detected."
};
}
if (buf[0] === 0xFF && buf[1] === 0xFE) {
return {
ext: "UTF16LE",
mime: "charset/utf16le",
desc: "Little-endian UTF-16 encoded Unicode byte order mark detected."
};
}
if ((buf[0x8001] === 0x43 && buf[0x8002] === 0x44 && buf[0x8003] === 0x30 && buf[0x8004] === 0x30 && buf[0x8005] === 0x31) ||
(buf[0x8801] === 0x43 && buf[0x8802] === 0x44 && buf[0x8803] === 0x30 && buf[0x8804] === 0x30 && buf[0x8805] === 0x31) ||
(buf[0x9001] === 0x43 && buf[0x9002] === 0x44 && buf[0x9003] === 0x30 && buf[0x9004] === 0x30 && buf[0x9005] === 0x31)) {
return {
ext: "iso",
mime: "application/octet-stream",
desc: "ISO 9660 CD/DVD image file"
};
}
if (buf[0] === 0xD0 && buf[1] === 0xCF && buf[2] === 0x11 && buf[3] === 0xE0 && buf[4] === 0xA1 && buf[5] === 0xB1 && buf[6] === 0x1A && buf[7] === 0xE1) {
return {
ext: "doc, xls, ppt",
mime: "application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint",
desc: "Microsoft Office documents"
};
}
if (buf[0] === 0x64 && buf[1] === 0x65 && buf[2] === 0x78 && buf[3] === 0x0A && buf[4] === 0x30 && buf[5] === 0x33 && buf[6] === 0x35 && buf[7] === 0x00) {
return {
ext: "dex",
mime: "application/octet-stream",
desc: "Dalvik Executable (Android)"
};
}
if (buf[0] === 0x4B && buf[1] === 0x44 && buf[2] === 0x4D) {
return {
ext: "vmdk",
mime: "application/vmdk, application/x-virtualbox-vmdk"
};
}
if (buf[0] === 0x43 && buf[1] === 0x72 && buf[2] === 0x32 && buf[3] === 0x34) {
return {
ext: "crx",
mime: "application/crx",
desc: "Google Chrome extension or packaged app"
};
}
if (buf[0] === 0x78 && (buf[1] === 0x01 || buf[1] === 0x9C || buf[1] === 0xDA || buf[1] === 0x5e)) {
return {
ext: "zlib",
mime: "application/x-deflate"
};
}
return null;
}
} }
/** /**

116
src/core/lib/PGP.mjs Normal file
View File

@ -0,0 +1,116 @@
/**
* 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 kbpgp from "kbpgp";
import promisifyDefault from "es6-promisify";
const promisify = promisifyDefault.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 "Did not provide passphrase with locked private key.";
}
}
return key;
} catch (err) {
throw `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 `Could not import public key: ${err}`;
}
}

103
src/core/lib/Rotate.mjs Normal file
View 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;
}

19
src/core/lib/Zlib.mjs Normal file
View 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,
};

View File

@ -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

View File

@ -0,0 +1,108 @@
/**
* @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.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;

View File

@ -0,0 +1,106 @@
/**
* @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.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;

View 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;

View 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";
/**
* Adler-32 Checksum operation
*/
class Adler32Checksum extends Operation {
/**
* Adler32Checksum constructor
*/
constructor() {
super();
this.name = "Adler-32 Checksum";
this.module = "Hashing";
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.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;

View File

@ -0,0 +1,105 @@
/**
* @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.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;

View File

@ -0,0 +1,77 @@
/**
* @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.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;

View File

@ -0,0 +1,183 @@
/**
* @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 = "Hashing";
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.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;

View File

@ -0,0 +1,66 @@
/**
* @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.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;

View File

@ -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 {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
runTo: function(input, args) {
const alphabet = args[0] || Base64.ALPHABET;
return Utils.toBase64(new Uint8Array(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='hl5'>" + offset0.substr(offset0.length - 3, 1) + "</span>" +
"<span class='hl3'>" + 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='hl5'>" + offset0.substr(offset0.length - 2, 1) + "</span>" +
"<span class='hl3'>" + 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='hl3'>" + offset1.substr(0, 1) + "</span>" +
"<span class='hl5'>" + 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='hl5'>" + offset1.substr(offset1.length - 3, 1) + "</span>" +
"<span class='hl3'>" + 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='hl5'>" + offset1.substr(offset1.length - 2, 1) + "</span>" +
"<span class='hl3'>" + 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='hl3'>" + offset2.substr(0, 2) + "</span>" +
"<span class='hl5'>" + 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='hl5'>" + offset2.substr(offset2.length - 3, 1) + "</span>" +
"<span class='hl3'>" + 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='hl5'>" + offset2.substr(offset2.length - 2, 1) + "</span>" +
"<span class='hl3'>" + 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='hl5'>green</span> could change if the input is surrounded by more data." +
"\nCharacters highlighted in <span class='hl3'>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;

View File

@ -0,0 +1,54 @@
/**
* @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 = "Hashing";
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.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;

View 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 compare operation
*/
class BcryptCompare extends Operation {
/**
* BcryptCompare constructor
*/
constructor() {
super();
this.name = "Bcrypt compare";
this.module = "Hashing";
this.description = "Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation.";
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;

View File

@ -0,0 +1,47 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import bcrypt from "bcryptjs";
/**
* Bcrypt parse operation
*/
class BcryptParse extends Operation {
/**
* BcryptParse constructor
*/
constructor() {
super();
this.name = "Bcrypt parse";
this.module = "Hashing";
this.description = "Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash.";
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) {
return "Error: " + err.toString();
}
}
}
export default BcryptParse;

View File

@ -0,0 +1,124 @@
/**
* @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.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;

View File

@ -0,0 +1,129 @@
/**
* @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.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;

View File

@ -0,0 +1,75 @@
/**
* @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.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;

View File

@ -0,0 +1,82 @@
/**
* @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.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;

View File

@ -0,0 +1,40 @@
/**
* @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 = "Hashing";
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.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;

View File

@ -0,0 +1,40 @@
/**
* @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 = "Hashing";
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.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;

View File

@ -0,0 +1,40 @@
/**
* @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 = "Hashing";
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.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;

View File

@ -0,0 +1,96 @@
/**
* @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.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;

View File

@ -0,0 +1,53 @@
/**
* @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.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;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
/**
* @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 = "Hashing";
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.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;

View File

@ -0,0 +1,51 @@
/**
* @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 = "Hashing";
this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
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;

View File

@ -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 = input.length.toString(8).padStart(11, "0");
const currentUnixTimestamp = Math.floor(Date.now() / 1000);
const lastModTime = currentUnixTimestamp.toString(8).padStart(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(checksum.toString(8).padStart(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;

View File

@ -0,0 +1,112 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Convert area operation
*/
class ConvertArea extends Operation {
/**
* ConvertArea constructor
*/
constructor() {
super();
this.name = "Convert area";
this.module = "Default";
this.description = "Converts a unit of area to another format.";
this.inputType = "BigNumber";
this.outputType = "BigNumber";
this.args = [
{
"name": "Input units",
"type": "option",
"value": AREA_UNITS
},
{
"name": "Output units",
"type": "option",
"value": AREA_UNITS
}
];
}
/**
* @param {BigNumber} input
* @param {Object[]} args
* @returns {BigNumber}
*/
run(input, args) {
const [inputUnits, outputUnits] = args;
input = input.times(AREA_FACTOR[inputUnits]);
return input.div(AREA_FACTOR[outputUnits]);
}
}
const AREA_UNITS = [
"[Metric]", "Square metre (sq m)", "Square kilometre (sq km)", "Centiare (ca)", "Deciare (da)", "Are (a)", "Decare (daa)", "Hectare (ha)", "[/Metric]",
"[Imperial]", "Square inch (sq in)", "Square foot (sq ft)", "Square yard (sq yd)", "Square mile (sq mi)", "Perch (sq per)", "Rood (ro)", "International acre (ac)", "[/Imperial]",
"[US customary units]", "US survey acre (ac)", "US survey square mile (sq mi)", "US survey township", "[/US customary units]",
"[Nuclear physics]", "Yoctobarn (yb)", "Zeptobarn (zb)", "Attobarn (ab)", "Femtobarn (fb)", "Picobarn (pb)", "Nanobarn (nb)", "Microbarn (μb)", "Millibarn (mb)", "Barn (b)", "Kilobarn (kb)", "Megabarn (Mb)", "Outhouse", "Shed", "Planck area", "[/Nuclear physics]",
"[Comparisons]", "Washington D.C.", "Isle of Wight", "Wales", "Texas", "[/Comparisons]",
];
const AREA_FACTOR = { // Multiples of a square metre
// Metric
"Square metre (sq m)": 1,
"Square kilometre (sq km)": 1e6,
"Centiare (ca)": 1,
"Deciare (da)": 10,
"Are (a)": 100,
"Decare (daa)": 1e3,
"Hectare (ha)": 1e4,
// Imperial
"Square inch (sq in)": 0.00064516,
"Square foot (sq ft)": 0.09290304,
"Square yard (sq yd)": 0.83612736,
"Square mile (sq mi)": 2589988.110336,
"Perch (sq per)": 42.21,
"Rood (ro)": 1011,
"International acre (ac)": 4046.8564224,
// US customary units
"US survey acre (ac)": 4046.87261,
"US survey square mile (sq mi)": 2589998.470305239,
"US survey township": 93239944.9309886,
// Nuclear physics
"Yoctobarn (yb)": 1e-52,
"Zeptobarn (zb)": 1e-49,
"Attobarn (ab)": 1e-46,
"Femtobarn (fb)": 1e-43,
"Picobarn (pb)": 1e-40,
"Nanobarn (nb)": 1e-37,
"Microbarn (μb)": 1e-34,
"Millibarn (mb)": 1e-31,
"Barn (b)": 1e-28,
"Kilobarn (kb)": 1e-25,
"Megabarn (Mb)": 1e-22,
"Planck area": 2.6e-70,
"Shed": 1e-52,
"Outhouse": 1e-34,
// Comparisons
"Washington D.C.": 176119191.502848,
"Isle of Wight": 380000000,
"Wales": 20779000000,
"Texas": 696241000000,
};
export default ConvertArea;

View File

@ -0,0 +1,111 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Convert data units operation
*/
class ConvertDataUnits extends Operation {
/**
* ConvertDataUnits constructor
*/
constructor() {
super();
this.name = "Convert data units";
this.module = "Default";
this.description = "Converts a unit of data to another format.";
this.inputType = "BigNumber";
this.outputType = "BigNumber";
this.args = [
{
"name": "Input units",
"type": "option",
"value": DATA_UNITS
},
{
"name": "Output units",
"type": "option",
"value": DATA_UNITS
}
];
}
/**
* @param {BigNumber} input
* @param {Object[]} args
* @returns {BigNumber}
*/
run(input, args) {
const [inputUnits, outputUnits] = args;
input = input.times(DATA_FACTOR[inputUnits]);
return input.div(DATA_FACTOR[outputUnits]);
}
}
const DATA_UNITS = [
"Bits (b)", "Nibbles", "Octets", "Bytes (B)",
"[Binary bits (2^n)]", "Kibibits (Kib)", "Mebibits (Mib)", "Gibibits (Gib)", "Tebibits (Tib)", "Pebibits (Pib)", "Exbibits (Eib)", "Zebibits (Zib)", "Yobibits (Yib)", "[/Binary bits (2^n)]",
"[Decimal bits (10^n)]", "Decabits", "Hectobits", "Kilobits (kb)", "Megabits (Mb)", "Gigabits (Gb)", "Terabits (Tb)", "Petabits (Pb)", "Exabits (Eb)", "Zettabits (Zb)", "Yottabits (Yb)", "[/Decimal bits (10^n)]",
"[Binary bytes (8 x 2^n)]", "Kibibytes (KiB)", "Mebibytes (MiB)", "Gibibytes (GiB)", "Tebibytes (TiB)", "Pebibytes (PiB)", "Exbibytes (EiB)", "Zebibytes (ZiB)", "Yobibytes (YiB)", "[/Binary bytes (8 x 2^n)]",
"[Decimal bytes (8 x 10^n)]", "Kilobytes (KB)", "Megabytes (MB)", "Gigabytes (GB)", "Terabytes (TB)", "Petabytes (PB)", "Exabytes (EB)", "Zettabytes (ZB)", "Yottabytes (YB)", "[/Decimal bytes (8 x 10^n)]"
];
const DATA_FACTOR = { // Multiples of a bit
"Bits (b)": 1,
"Nibbles": 4,
"Octets": 8,
"Bytes (B)": 8,
// Binary bits (2^n)
"Kibibits (Kib)": 1024,
"Mebibits (Mib)": 1048576,
"Gibibits (Gib)": 1073741824,
"Tebibits (Tib)": 1099511627776,
"Pebibits (Pib)": 1125899906842624,
"Exbibits (Eib)": 1152921504606846976,
"Zebibits (Zib)": 1180591620717411303424,
"Yobibits (Yib)": 1208925819614629174706176,
// Decimal bits (10^n)
"Decabits": 10,
"Hectobits": 100,
"Kilobits (Kb)": 1e3,
"Megabits (Mb)": 1e6,
"Gigabits (Gb)": 1e9,
"Terabits (Tb)": 1e12,
"Petabits (Pb)": 1e15,
"Exabits (Eb)": 1e18,
"Zettabits (Zb)": 1e21,
"Yottabits (Yb)": 1e24,
// Binary bytes (8 x 2^n)
"Kibibytes (KiB)": 8192,
"Mebibytes (MiB)": 8388608,
"Gibibytes (GiB)": 8589934592,
"Tebibytes (TiB)": 8796093022208,
"Pebibytes (PiB)": 9007199254740992,
"Exbibytes (EiB)": 9223372036854775808,
"Zebibytes (ZiB)": 9444732965739290427392,
"Yobibytes (YiB)": 9671406556917033397649408,
// Decimal bytes (8 x 10^n)
"Kilobytes (KB)": 8e3,
"Megabytes (MB)": 8e6,
"Gigabytes (GB)": 8e9,
"Terabytes (TB)": 8e12,
"Petabytes (PB)": 8e15,
"Exabytes (EB)": 8e18,
"Zettabytes (ZB)": 8e21,
"Yottabytes (YB)": 8e24,
};
export default ConvertDataUnits;

View File

@ -0,0 +1,95 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Convert distance operation
*/
class ConvertDistance extends Operation {
/**
* ConvertDistance constructor
*/
constructor() {
super();
this.name = "Convert distance";
this.module = "Default";
this.description = "Converts a unit of distance to another format.";
this.inputType = "BigNumber";
this.outputType = "BigNumber";
this.args = [
{
"name": "Input units",
"type": "option",
"value": DISTANCE_UNITS
},
{
"name": "Output units",
"type": "option",
"value": DISTANCE_UNITS
}
];
}
/**
* @param {BigNumber} input
* @param {Object[]} args
* @returns {BigNumber}
*/
run(input, args) {
const [inputUnits, outputUnits] = args;
input = input.times(DISTANCE_FACTOR[inputUnits]);
return input.div(DISTANCE_FACTOR[outputUnits]);
}
}
const DISTANCE_UNITS = [
"[Metric]", "Nanometres (nm)", "Micrometres (µm)", "Millimetres (mm)", "Centimetres (cm)", "Metres (m)", "Kilometers (km)", "[/Metric]",
"[Imperial]", "Thou (th)", "Inches (in)", "Feet (ft)", "Yards (yd)", "Chains (ch)", "Furlongs (fur)", "Miles (mi)", "Leagues (lea)", "[/Imperial]",
"[Maritime]", "Fathoms (ftm)", "Cables", "Nautical miles", "[/Maritime]",
"[Comparisons]", "Cars (4m)", "Buses (8.4m)", "American football fields (91m)", "Football pitches (105m)", "[/Comparisons]",
"[Astronomical]", "Earth-to-Moons", "Earth's equators", "Astronomical units (au)", "Light-years (ly)", "Parsecs (pc)", "[/Astronomical]",
];
const DISTANCE_FACTOR = { // Multiples of a metre
"Nanometres (nm)": 1e-9,
"Micrometres (µm)": 1e-6,
"Millimetres (mm)": 1e-3,
"Centimetres (cm)": 1e-2,
"Metres (m)": 1,
"Kilometers (km)": 1e3,
"Thou (th)": 0.0000254,
"Inches (in)": 0.0254,
"Feet (ft)": 0.3048,
"Yards (yd)": 0.9144,
"Chains (ch)": 20.1168,
"Furlongs (fur)": 201.168,
"Miles (mi)": 1609.344,
"Leagues (lea)": 4828.032,
"Fathoms (ftm)": 1.853184,
"Cables": 185.3184,
"Nautical miles": 1853.184,
"Cars (4m)": 4,
"Buses (8.4m)": 8.4,
"American football fields (91m)": 91,
"Football pitches (105m)": 105,
"Earth-to-Moons": 380000000,
"Earth's equators": 40075016.686,
"Astronomical units (au)": 149597870700,
"Light-years (ly)": 9460730472580800,
"Parsecs (pc)": 3.0856776e16
};
export default ConvertDistance;

View File

@ -0,0 +1,143 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Convert mass operation
*/
class ConvertMass extends Operation {
/**
* ConvertMass constructor
*/
constructor() {
super();
this.name = "Convert mass";
this.module = "Default";
this.description = "Converts a unit of mass to another format.";
this.inputType = "BigNumber";
this.outputType = "BigNumber";
this.args = [
{
"name": "Input units",
"type": "option",
"value": MASS_UNITS
},
{
"name": "Output units",
"type": "option",
"value": MASS_UNITS
}
];
}
/**
* @param {BigNumber} input
* @param {Object[]} args
* @returns {BigNumber}
*/
run(input, args) {
const [inputUnits, outputUnits] = args;
input = input.times(MASS_FACTOR[inputUnits]);
return input.div(MASS_FACTOR[outputUnits]);
}
}
const MASS_UNITS = [
"[Metric]", "Yoctogram (yg)", "Zeptogram (zg)", "Attogram (ag)", "Femtogram (fg)", "Picogram (pg)", "Nanogram (ng)", "Microgram (μg)", "Milligram (mg)", "Centigram (cg)", "Decigram (dg)", "Gram (g)", "Decagram (dag)", "Hectogram (hg)", "Kilogram (kg)", "Megagram (Mg)", "Tonne (t)", "Gigagram (Gg)", "Teragram (Tg)", "Petagram (Pg)", "Exagram (Eg)", "Zettagram (Zg)", "Yottagram (Yg)", "[/Metric]",
"[Imperial Avoirdupois]", "Grain (gr)", "Dram (dr)", "Ounce (oz)", "Pound (lb)", "Nail", "Stone (st)", "Quarter (gr)", "Tod", "US hundredweight (cwt)", "Imperial hundredweight (cwt)", "US ton (t)", "Imperial ton (t)", "[/Imperial Avoirdupois]",
"[Imperial Troy]", "Grain (gr)", "Pennyweight (dwt)", "Troy dram (dr t)", "Troy ounce (oz t)", "Troy pound (lb t)", "Mark", "[/Imperial Troy]",
"[Archaic]", "Wey", "Wool wey", "Suffolk wey", "Wool sack", "Coal sack", "Load", "Last", "Flax or feather last", "Gunpowder last", "Picul", "Rice last", "[/Archaic]",
"[Comparisons]", "Big Ben (14 tonnes)", "Blue whale (180 tonnes)", "International Space Station (417 tonnes)", "Space Shuttle (2,041 tonnes)", "RMS Titanic (52,000 tonnes)", "Great Pyramid of Giza (6,000,000 tonnes)", "Earth's oceans (1.4 yottagrams)", "[/Comparisons]",
"[Astronomical]", "A teaspoon of neutron star (5,500 million tonnes)", "Lunar mass (ML)", "Earth mass (M⊕)", "Jupiter mass (MJ)", "Solar mass (M☉)", "Sagittarius A* (7.5 x 10^36 kgs-ish)", "Milky Way galaxy (1.2 x 10^42 kgs)", "The observable universe (1.45 x 10^53 kgs)", "[/Astronomical]",
];
const MASS_FACTOR = { // Multiples of a gram
// Metric
"Yoctogram (yg)": 1e-24,
"Zeptogram (zg)": 1e-21,
"Attogram (ag)": 1e-18,
"Femtogram (fg)": 1e-15,
"Picogram (pg)": 1e-12,
"Nanogram (ng)": 1e-9,
"Microgram (μg)": 1e-6,
"Milligram (mg)": 1e-3,
"Centigram (cg)": 1e-2,
"Decigram (dg)": 1e-1,
"Gram (g)": 1,
"Decagram (dag)": 10,
"Hectogram (hg)": 100,
"Kilogram (kg)": 1000,
"Megagram (Mg)": 1e6,
"Tonne (t)": 1e6,
"Gigagram (Gg)": 1e9,
"Teragram (Tg)": 1e12,
"Petagram (Pg)": 1e15,
"Exagram (Eg)": 1e18,
"Zettagram (Zg)": 1e21,
"Yottagram (Yg)": 1e24,
// Imperial Avoirdupois
"Grain (gr)": 64.79891e-3,
"Dram (dr)": 1.7718451953125,
"Ounce (oz)": 28.349523125,
"Pound (lb)": 453.59237,
"Nail": 3175.14659,
"Stone (st)": 6.35029318e3,
"Quarter (gr)": 12700.58636,
"Tod": 12700.58636,
"US hundredweight (cwt)": 45.359237e3,
"Imperial hundredweight (cwt)": 50.80234544e3,
"US ton (t)": 907.18474e3,
"Imperial ton (t)": 1016.0469088e3,
// Imperial Troy
"Pennyweight (dwt)": 1.55517384,
"Troy dram (dr t)": 3.8879346,
"Troy ounce (oz t)": 31.1034768,
"Troy pound (lb t)": 373.2417216,
"Mark": 248.8278144,
// Archaic
"Wey": 76.5e3,
"Wool wey": 101.7e3,
"Suffolk wey": 161.5e3,
"Wool sack": 153000,
"Coal sack": 50.80234544e3,
"Load": 918000,
"Last": 1836000,
"Flax or feather last": 770e3,
"Gunpowder last": 1090e3,
"Picul": 60.478982e3,
"Rice last": 1200e3,
// Comparisons
"Big Ben (14 tonnes)": 14e6,
"Blue whale (180 tonnes)": 180e6,
"International Space Station (417 tonnes)": 417e6,
"Space Shuttle (2,041 tonnes)": 2041e6,
"RMS Titanic (52,000 tonnes)": 52000e6,
"Great Pyramid of Giza (6,000,000 tonnes)": 6e12,
"Earth's oceans (1.4 yottagrams)": 1.4e24,
// Astronomical
"A teaspoon of neutron star (5,500 million tonnes)": 5.5e15,
"Lunar mass (ML)": 7.342e25,
"Earth mass (M⊕)": 5.97219e27,
"Jupiter mass (MJ)": 1.8981411476999997e30,
"Solar mass (M☉)": 1.98855e33,
"Sagittarius A* (7.5 x 10^36 kgs-ish)": 7.5e39,
"Milky Way galaxy (1.2 x 10^42 kgs)": 1.2e45,
"The observable universe (1.45 x 10^53 kgs)": 1.45e56,
};
export default ConvertMass;

View File

@ -0,0 +1,96 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Convert speed operation
*/
class ConvertSpeed extends Operation {
/**
* ConvertSpeed constructor
*/
constructor() {
super();
this.name = "Convert speed";
this.module = "Default";
this.description = "Converts a unit of speed to another format.";
this.inputType = "BigNumber";
this.outputType = "BigNumber";
this.args = [
{
"name": "Input units",
"type": "option",
"value": SPEED_UNITS
},
{
"name": "Output units",
"type": "option",
"value": SPEED_UNITS
}
];
}
/**
* @param {BigNumber} input
* @param {Object[]} args
* @returns {BigNumber}
*/
run(input, args) {
const [inputUnits, outputUnits] = args;
input = input.times(SPEED_FACTOR[inputUnits]);
return input.div(SPEED_FACTOR[outputUnits]);
}
}
const SPEED_UNITS = [
"[Metric]", "Metres per second (m/s)", "Kilometres per hour (km/h)", "[/Metric]",
"[Imperial]", "Miles per hour (mph)", "Knots (kn)", "[/Imperial]",
"[Comparisons]", "Human hair growth rate", "Bamboo growth rate", "World's fastest snail", "Usain Bolt's top speed", "Jet airliner cruising speed", "Concorde", "SR-71 Blackbird", "Space Shuttle", "International Space Station", "[/Comparisons]",
"[Scientific]", "Sound in standard atmosphere", "Sound in water", "Lunar escape velocity", "Earth escape velocity", "Earth's solar orbit", "Solar system's Milky Way orbit", "Milky Way relative to the cosmic microwave background", "Solar escape velocity", "Neutron star escape velocity (0.3c)", "Light in a diamond (0.4136c)", "Signal in an optical fibre (0.667c)", "Light (c)", "[/Scientific]",
];
const SPEED_FACTOR = { // Multiples of m/s
// Metric
"Metres per second (m/s)": 1,
"Kilometres per hour (km/h)": 0.2778,
// Imperial
"Miles per hour (mph)": 0.44704,
"Knots (kn)": 0.5144,
// Comparisons
"Human hair growth rate": 4.8e-9,
"Bamboo growth rate": 1.4e-5,
"World's fastest snail": 0.00275,
"Usain Bolt's top speed": 12.42,
"Jet airliner cruising speed": 250,
"Concorde": 603,
"SR-71 Blackbird": 981,
"Space Shuttle": 1400,
"International Space Station": 7700,
// Scientific
"Sound in standard atmosphere": 340.3,
"Sound in water": 1500,
"Lunar escape velocity": 2375,
"Earth escape velocity": 11200,
"Earth's solar orbit": 29800,
"Solar system's Milky Way orbit": 200000,
"Milky Way relative to the cosmic microwave background": 552000,
"Solar escape velocity": 617700,
"Neutron star escape velocity (0.3c)": 100000000,
"Light in a diamond (0.4136c)": 124000000,
"Signal in an optical fibre (0.667c)": 200000000,
"Light (c)": 299792458,
};
export default ConvertSpeed;

View File

@ -0,0 +1,65 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Count occurrences operation
*/
class CountOccurrences extends Operation {
/**
* CountOccurrences constructor
*/
constructor() {
super();
this.name = "Count occurrences";
this.module = "Default";
this.description = "Counts the number of times the provided string occurs in the input.";
this.inputType = "string";
this.outputType = "number";
this.args = [
{
"name": "Search string",
"type": "toggleString",
"value": "",
"toggleValues": ["Regex", "Extended (\\n, \\t, \\x...)", "Simple string"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {number}
*/
run(input, args) {
let search = args[0].string;
const type = args[0].option;
if (type === "Regex" && search) {
try {
const regex = new RegExp(search, "gi"),
matches = input.match(regex);
return matches.length;
} catch (err) {
return 0;
}
} else if (search) {
if (type.indexOf("Extended") === 0) {
search = Utils.parseEscapedChars(search);
}
return input.count(search);
} else {
return 0;
}
}
}
export default CountOccurrences;

View File

@ -0,0 +1,59 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Decode NetBIOS Name operation
*/
class DecodeNetBIOSName extends Operation {
/**
* DecodeNetBIOSName constructor
*/
constructor() {
super();
this.name = "Decode NetBIOS Name";
this.module = "Default";
this.description = "NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.<br><br>There are two levels of encoding. The first level maps a NetBIOS name into a domain system name. The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.<br><br>This operation decodes the first level of encoding. See RFC 1001 for full details.";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.args = [
{
"name": "Offset",
"type": "number",
"value": 65
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const output = [],
offset = args[0];
if (input.length <= 32 && (input.length % 2) === 0) {
for (let i = 0; i < input.length; i += 2) {
output.push((((input[i] & 0xff) - offset) << 4) |
(((input[i + 1] & 0xff) - offset) & 0xf));
}
for (let i = output.length - 1; i > 0; i--) {
if (output[i] === 32) output.splice(i, i);
else break;
}
}
return output;
}
}
export default DecodeNetBIOSName;

View File

@ -0,0 +1,55 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import cptable from "../vendor/js-codepage/cptable.js";
import {IO_FORMAT} from "../lib/ChrEnc";
/**
* Decode text operation
*/
class DecodeText extends Operation {
/**
* DecodeText constructor
*/
constructor() {
super();
this.name = "Decode text";
this.module = "CharEnc";
this.description = [
"Decodes text from the chosen character encoding.",
"<br><br>",
"Supported charsets are:",
"<ul>",
Object.keys(IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
"</ul>",
].join("\n");
this.inputType = "byteArray";
this.outputType = "string";
this.args = [
{
"name": "Encoding",
"type": "option",
"value": Object.keys(IO_FORMAT)
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const format = IO_FORMAT[args[0]];
return cptable.utils.decode(format, input);
}
}
export default DecodeText;

View File

@ -0,0 +1,144 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import CryptoJS from "crypto-js";
/**
* Derive EVP key operation
*/
class DeriveEVPKey extends Operation {
/**
* DeriveEVPKey constructor
*/
constructor() {
super();
this.name = "Derive EVP key";
this.module = "Ciphers";
this.description = "EVP is a password-based key derivation function (PBKDF) used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.<br><br>A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.<br><br>If you leave the salt argument empty, a random salt will be generated.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Passphrase",
"type": "toggleString",
"value": "",
"toggleValues": ["UTF8", "Latin1", "Hex", "Base64"]
},
{
"name": "Key size",
"type": "number",
"value": 128
},
{
"name": "Iterations",
"type": "number",
"value": 1
},
{
"name": "Hashing function",
"type": "option",
"value": ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"]
},
{
"name": "Salt",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const passphrase = Utils.convertToByteString(args[0].string, args[0].option),
keySize = args[1] / 32,
iterations = args[2],
hasher = args[3],
salt = Utils.convertToByteString(args[4].string, args[4].option),
key = CryptoJS.EvpKDF(passphrase, salt, {
keySize: keySize,
hasher: CryptoJS.algo[hasher],
iterations: iterations,
});
return key.toString(CryptoJS.enc.Hex);
}
}
export default DeriveEVPKey;
/**
* 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 });
};
/**
* Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse
* the hex string.
*
* @param {string} hexStr
* @returns {CryptoJS.lib.WordArray}
*/
CryptoJS.enc.Hex.parse = function (hexStr) {
// Remove whitespace
hexStr = hexStr.replace(/\s/g, "");
// Shortcut
const hexStrLength = hexStr.length;
// Convert
const words = [];
for (let i = 0; i < hexStrLength; i += 2) {
words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
}
return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2);
};

View File

@ -0,0 +1,54 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Magic from "../lib/Magic";
/**
* Detect File Type operation
*/
class DetectFileType extends Operation {
/**
* DetectFileType constructor
*/
constructor() {
super();
this.name = "Detect File Type";
this.module = "Default";
this.description = "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.<br><br>Currently supports the following file types: 7z, amr, avi, bmp, bz2, class, cr2, crx, dex, dmg, doc, elf, eot, epub, exe, flac, flv, gif, gz, ico, iso, jpg, jxr, m4a, m4v, mid, mkv, mov, mp3, mp4, mpg, ogg, otf, pdf, png, ppt, ps, psd, rar, rtf, sqlite, swf, tar, tar.z, tif, ttf, utf8, vmdk, wav, webm, webp, wmv, woff, woff2, xls, xz, zip.";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const data = new Uint8Array(input),
type = Magic.magicFileType(data);
if (!type) {
return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?";
} else {
let output = "File extension: " + type.ext + "\n" +
"MIME type: " + type.mime;
if (type.desc && type.desc.length) {
output += "\nDescription: " + type.desc;
}
return output;
}
}
}
export default DetectFileType;

View File

@ -0,0 +1,133 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import Operation from "../Operation";
import * as disassemble from "../vendor/DisassembleX86-64";
import OperationError from "../errors/OperationError";
/**
* Disassemble x86 operation
*/
class DisassembleX86 extends Operation {
/**
* DisassembleX86 constructor
*/
constructor() {
super();
this.name = "Disassemble x86";
this.module = "Shellcode";
this.description = "Disassembly is the process of translating machine language into assembly language.<br><br>This operation supports 64-bit, 32-bit and 16-bit code written for Intel or AMD x86 processors. It is particularly useful for reverse engineering shellcode.<br><br>Input should be in hexadecimal.";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Bit mode",
"type": "option",
"value": ["64", "32", "16"]
},
{
"name": "Compatibility",
"type": "option",
"value": [
"Full x86 architecture",
"Knights Corner",
"Larrabee",
"Cyrix",
"Geode",
"Centaur",
"X86/486"
]
},
{
"name": "Code Segment (CS)",
"type": "number",
"value": 16
},
{
"name": "Offset (IP)",
"type": "number",
"value": 0
},
{
"name": "Show instruction hex",
"type": "boolean",
"value": true
},
{
"name": "Show instruction position",
"type": "boolean",
"value": true
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*
* @throws {OperationError} if invalid mode value
*/
run(input, args) {
const [
mode,
compatibility,
codeSegment,
offset,
showInstructionHex,
showInstructionPos
] = args;
switch (mode) {
case "64":
disassemble.setBitMode(2);
break;
case "32":
disassemble.setBitMode(1);
break;
case "16":
disassemble.setBitMode(0);
break;
default:
throw new OperationError("Invalid mode value");
}
switch (compatibility) {
case "Full x86 architecture":
disassemble.CompatibilityMode(0);
break;
case "Knights Corner":
disassemble.CompatibilityMode(1);
break;
case "Larrabee":
disassemble.CompatibilityMode(2);
break;
case "Cyrix":
disassemble.CompatibilityMode(3);
break;
case "Geode":
disassemble.CompatibilityMode(4);
break;
case "Centaur":
disassemble.CompatibilityMode(5);
break;
case "X86/486":
disassemble.CompatibilityMode(6);
break;
}
disassemble.SetBasePosition(codeSegment + ":" + offset);
disassemble.setShowInstructionHex(showInstructionHex);
disassemble.setShowInstructionPos(showInstructionPos);
disassemble.LoadBinCode(input.replace(/\s/g, ""));
return disassemble.LDisassemble();
}
}
export default DisassembleX86;

View File

@ -0,0 +1,51 @@
/**
* @author bwhitn [brian.m.whitney@outlook.com]
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import BigNumber from "bignumber.js";
import Operation from "../Operation";
import { div, createNumArray } from "../lib/Arithmetic";
import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim";
/**
* Divide operation
*/
class Divide extends Operation {
/**
* Divide constructor
*/
constructor() {
super();
this.name = "Divide";
this.module = "Default";
this.description = "Divides a list of numbers. If an item in the string is not a number it is excluded from the list.<br><br>e.g. <code>0x0a 8 .5</code> becomes <code>2.5</code>";
this.inputType = "string";
this.outputType = "BigNumber";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": ARITHMETIC_DELIM_OPTIONS,
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {BigNumber}
*/
run(input, args) {
const val = div(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN);
}
}
export default Divide;

View File

@ -0,0 +1,95 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
/**
* Drop bytes operation
*/
class DropBytes extends Operation {
/**
* DropBytes constructor
*/
constructor() {
super();
this.name = "Drop bytes";
this.module = "Default";
this.description = "Cuts a slice of the specified number of bytes out of the data.";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
"name": "Start",
"type": "number",
"value": 0
},
{
"name": "Length",
"type": "number",
"value": 5
},
{
"name": "Apply to each line",
"type": "boolean",
"value": false
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*
* @throws {OperationError} if invalid input
*/
run(input, args) {
const start = args[0],
length = args[1],
applyToEachLine = args[2];
if (start < 0 || length < 0)
throw new OperationError("Error: Invalid value");
if (!applyToEachLine) {
const left = input.slice(0, start),
right = input.slice(start + length, input.byteLength);
const result = new Uint8Array(left.byteLength + right.byteLength);
result.set(new Uint8Array(left), 0);
result.set(new Uint8Array(right), left.byteLength);
return result.buffer;
}
// Split input into lines
const data = new Uint8Array(input);
const lines = [];
let line = [],
i;
for (i = 0; i < data.length; i++) {
if (data[i] === 0x0a) {
lines.push(line);
line = [];
} else {
line.push(data[i]);
}
}
lines.push(line);
let output = [];
for (i = 0; i < lines.length; i++) {
output = output.concat(lines[i].slice(0, start).concat(lines[i].slice(start+length, lines[i].length)));
output.push(0x0a);
}
return new Uint8Array(output.slice(0, output.length-1)).buffer;
}
}
export default DropBytes;

View File

@ -0,0 +1,59 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Encode NetBIOS Name operation
*/
class EncodeNetBIOSName extends Operation {
/**
* EncodeNetBIOSName constructor
*/
constructor() {
super();
this.name = "Encode NetBIOS Name";
this.module = "Default";
this.description = "NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a longer representation is used.<br><br>There are two levels of encoding. The first level maps a NetBIOS name into a domain system name. The second level maps the domain system name into the 'compressed' representation required for interaction with the domain name system.<br><br>This operation carries out the first level of encoding. See RFC 1001 for full details.";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.args = [
{
"name": "Offset",
"type": "number",
"value": 65
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const output = [],
offset = args[0];
if (input.length <= 16) {
const len = input.length;
input.length = 16;
input.fill(32, len, 16);
for (let i = 0; i < input.length; i++) {
output.push((input[i] >> 4) + offset);
output.push((input[i] & 0xf) + offset);
}
}
return output;
}
}
export default EncodeNetBIOSName;

Some files were not shown because too many files have changed in this diff Show More