Merge remote-tracking branch 'upstream/master' into ssh-host-key

Bring up to date with master
This commit is contained in:
j433866 2019-08-13 13:26:40 +01:00
commit 1cdcaebb4d
540 changed files with 30965 additions and 5829 deletions

View File

@ -38,6 +38,8 @@
// disable rules from base configurations
"no-control-regex": "off",
"require-atomic-updates": "off",
"no-async-promise-executor": "off",
// stylistic conventions
"brace-style": ["error", "1tbs"],
@ -106,9 +108,6 @@
"COMPILE_TIME": false,
"COMPILE_MSG": false,
"PKG_VERSION": false,
"ENVIRONMENT_IS_WORKER": false,
"ENVIRONMENT_IS_NODE": false,
"ENVIRONMENT_IS_WEB": false
"PKG_VERSION": false
}
}

View File

@ -22,7 +22,7 @@ Before your contributions can be accepted, you must:
* Line endings: UNIX style (\n)
## Design Principals
## Design Principles
1. If at all possible, all operations and features should be client-side and not rely on connections to an external server. This increases the utility of CyberChef on closed networks and in virtual machines that are not connected to the Internet. Calls to external APIs may be accepted if there is no other option, but not for critical components.
2. Latency should be kept to a minimum to enhance the user experience. This means that all operation code should sit on the client, rather than being loaded dynamically from a server.
@ -30,7 +30,7 @@ Before your contributions can be accepted, you must:
4. Minimise the use of large libraries, especially for niche operations that won't be used very often - these will be downloaded by everyone using the app, whether they use that operation or not (due to principal 2).
With these principals in mind, any changes or additions to CyberChef should keep it:
With these principles in mind, any changes or additions to CyberChef should keep it:
- Standalone
- Efficient

View File

@ -1,14 +1 @@
<!-- Prefix the title above with one of the following: -->
<!-- Bug report: -->
<!-- Operation request: -->
<!-- Feature request: -->
<!-- Misc: -->
### Summary
### Example
<!-- If describing a bug, tell us what happens instead of the expected behavior -->
<!-- Include a link that triggers the bug if possible -->
<!-- If you are requesting a new operation, include example input and output -->
<!-- Prefix the title above with 'Misc:' -->

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
title: 'Bug report: <Insert title here>'
labels: bug
assignees: ''
---
<!-- Prefix the title above with 'Bug report:' -->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior or a link to the recipe / input used to cause the bug:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (if relevant, please complete the following information):**
- OS: [e.g. Windows]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for the project
title: 'Feature request: <Insert title here>'
labels: feature
assignees: ''
---
<!-- Prefix the title above with 'Feature request:' -->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,16 @@
---
name: Operation request
about: Suggest a new operation
title: 'Operation request: <Insert title here>'
labels: operation
assignees: ''
---
<!-- Prefix the title above with 'Operation request:' -->
## Summary
### Example Input
### Example Output

3
.gitignore vendored
View File

@ -11,5 +11,8 @@ docs/*
src/core/config/modules/*
src/core/config/OperationConfig.json
src/core/operations/index.mjs
src/node/config/OperationConfig.json
src/node/index.mjs
**/*.DS_Store
tests/browser/output/*

View File

@ -1,6 +1,7 @@
language: node_js
node_js:
- lts/*
cache: npm
addons:
chrome: stable
install: npm install
@ -11,9 +12,9 @@ script:
- grunt lint
- grunt test
- grunt docs
- grunt node
- grunt prod --msg="$COMPILE_MSG"
- xvfb-run --server-args="-screen 0 1200x800x24" grunt testui
- grunt testnodeconsumer
before_deploy:
- grunt exec:sitemap
- grunt copy:ghPages
@ -33,7 +34,7 @@ deploy:
file_glob: true
file:
- build/prod/*.zip
- build/node/CyberChef.js
- src/node/cjs.js
on:
repo: gchq/CyberChef
tags: true

View File

@ -2,6 +2,34 @@
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
## [9.0.0] - 2019-07-09
- [Multiple inputs](https://github.com/gchq/CyberChef/wiki/Multiple-Inputs) are now supported in the main web UI, allowing you to upload and process multiple files at once [@j433866] | [#566]
- A [Node.js API](https://github.com/gchq/CyberChef/wiki/Node-API) has been implemented, meaning that CyberChef can now be used as a library, either to provide specific operations, or an entire baking environment [@d98762625] | [#291]
- A [read-eval-print loop (REPL)](https://github.com/gchq/CyberChef/wiki/Node-API#repl) is also included to enable prototyping and experimentation with the API [@d98762625] | [#291]
- Light and dark Solarized themes added [@j433866] | [#566]
### [8.38.0] - 2019-07-03
- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]
### [8.37.0] - 2019-07-03
- 'CRC-8 Checksum' operation added [@MShwed] | [#591]
### [8.36.0] - 2019-07-03
- 'PGP Verify' operation added [@artemisbot] | [#585]
### [8.35.0] - 2019-07-03
- 'Sharpen Image', 'Convert Image Format' and 'Add Text To Image' operations added [@j433866] | [#515]
### [8.34.0] - 2019-06-28
- Various new visualisations added to the 'Entropy' operation [@MShwed] | [#535]
- Efficiency improvements made to the 'Entropy' operation for large file support [@n1474335]
### [8.33.0] - 2019-06-27
- 'Bzip2 Compress' operation added and 'Bzip2 Decompress' operation greatly improved [@artemisbot] | [#531]
### [8.32.0] - 2019-06-27
- 'Index of Coincidence' operation added [@Ge0rg3] | [#571]
### [8.31.0] - 2019-04-12
- The downloadable version of CyberChef is now a .zip file containing separate modules rather than a single .htm file. It is still completely standalone and will not make any external network requests. This change reduces the complexity of the build process significantly. [@n1474335]
@ -130,6 +158,14 @@ All major and minor version changes will be documented in this file. Details of
[9.0.0]: https://github.com/gchq/CyberChef/releases/tag/v9.0.0
[8.38.0]: https://github.com/gchq/CyberChef/releases/tag/v8.38.0
[8.37.0]: https://github.com/gchq/CyberChef/releases/tag/v8.37.0
[8.36.0]: https://github.com/gchq/CyberChef/releases/tag/v8.36.0
[8.35.0]: https://github.com/gchq/CyberChef/releases/tag/v8.35.0
[8.34.0]: https://github.com/gchq/CyberChef/releases/tag/v8.34.0
[8.33.0]: https://github.com/gchq/CyberChef/releases/tag/v8.33.0
[8.32.0]: https://github.com/gchq/CyberChef/releases/tag/v8.32.0
[8.31.0]: https://github.com/gchq/CyberChef/releases/tag/v8.31.0
[8.30.0]: https://github.com/gchq/CyberChef/releases/tag/v8.30.0
[8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0
@ -189,6 +225,8 @@ All major and minor version changes will be documented in this file. Details of
[@Cynser]: https://github.com/Cynser
[@anthony-arnold]: https://github.com/anthony-arnold
[@masq]: https://github.com/masq
[@Ge0rg3]: https://github.com/Ge0rg3
[@MShwed]: https://github.com/MShwed
[#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173
@ -200,6 +238,7 @@ All major and minor version changes will be documented in this file. Details of
[#277]: https://github.com/gchq/CyberChef/issues/277
[#281]: https://github.com/gchq/CyberChef/pull/281
[#284]: https://github.com/gchq/CyberChef/pull/284
[#291]: https://github.com/gchq/CyberChef/pull/291
[#294]: https://github.com/gchq/CyberChef/pull/294
[#296]: https://github.com/gchq/CyberChef/pull/296
[#298]: https://github.com/gchq/CyberChef/pull/298
@ -229,6 +268,14 @@ All major and minor version changes will be documented in this file. Details of
[#489]: https://github.com/gchq/CyberChef/pull/489
[#496]: https://github.com/gchq/CyberChef/pull/496
[#506]: https://github.com/gchq/CyberChef/pull/506
[#515]: https://github.com/gchq/CyberChef/pull/515
[#516]: https://github.com/gchq/CyberChef/pull/516
[#525]: https://github.com/gchq/CyberChef/pull/525
[#530]: https://github.com/gchq/CyberChef/pull/530
[#531]: https://github.com/gchq/CyberChef/pull/531
[#533]: https://github.com/gchq/CyberChef/pull/533
[#535]: https://github.com/gchq/CyberChef/pull/535
[#566]: https://github.com/gchq/CyberChef/pull/566
[#571]: https://github.com/gchq/CyberChef/pull/571
[#585]: https://github.com/gchq/CyberChef/pull/585
[#591]: https://github.com/gchq/CyberChef/pull/591

View File

@ -3,7 +3,6 @@
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const NodeExternals = require("webpack-node-externals");
const glob = require("glob");
const path = require("path");
@ -15,6 +14,7 @@ const path = require("path");
* @license Apache-2.0
*/
module.exports = function (grunt) {
grunt.file.defaultEncoding = "utf8";
grunt.file.preserveBOM = false;
@ -24,22 +24,6 @@ module.exports = function (grunt) {
"A persistent task which creates a development build whenever source files are modified.",
["clean:dev", "clean:config", "exec:generateConfig", "concurrent:dev"]);
grunt.registerTask("node",
"Compiles CyberChef into a single NodeJS module.",
["clean:node", "clean:config", "exec:generateConfig", "webpack:node", "chmod:build"]);
grunt.registerTask("test",
"A task which runs all the operation tests in the tests directory.",
["exec:generateConfig", "exec:opTests"]);
grunt.registerTask("testui",
"A task which runs all the UI tests in the tests directory. The prod task must already have been run.",
["connect:prod", "exec:browserTests"]);
grunt.registerTask("docs",
"Compiles documentation in the /docs directory.",
["clean:docs", "jsdoc", "chmod:docs"]);
grunt.registerTask("prod",
"Creates a production-ready build. Use the --msg flag to add a compile message.",
[
@ -47,6 +31,32 @@ module.exports = function (grunt) {
"copy:standalone", "zip:standalone", "clean:standalone", "chmod"
]);
grunt.registerTask("node",
"Compiles CyberChef into a single NodeJS module.",
[
"clean:node", "clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex"
]);
grunt.registerTask("test",
"A task which runs all the operation tests in the tests directory.",
[
"clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex",
"exec:nodeTests", "exec:opTests"
]);
grunt.registerTask("testui",
"A task which runs all the UI tests in the tests directory. The prod task must already have been run.",
["connect:prod", "exec:browserTests"]);
grunt.registerTask("testnodeconsumer",
"A task which checks whether consuming CJS and ESM apps work with the CyberChef build",
["exec:setupNodeConsumers", "exec:testCJSNodeConsumer", "exec:testESMNodeConsumer", "exec:testESMDeepImportNodeConsumer", "exec:teardownNodeConsumers"]);
grunt.registerTask("docs",
"Compiles documentation in the /docs directory.",
["clean:docs", "jsdoc", "chmod:docs"]);
grunt.registerTask("default",
"Lints the code base",
["eslint", "exec:repoSize"]);
@ -80,17 +90,9 @@ module.exports = function (grunt) {
COMPILE_TIME: JSON.stringify(compileTime),
COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
PKG_VERSION: JSON.stringify(pkg.version),
ENVIRONMENT_IS_WORKER: function() {
return typeof importScripts === "function";
},
ENVIRONMENT_IS_NODE: function() {
return typeof process === "object" && typeof require === "function";
},
ENVIRONMENT_IS_WEB: function() {
return typeof window === "object";
}
},
moduleEntryPoints = listEntryModules();
moduleEntryPoints = listEntryModules(),
nodeConsumerTestPath = "~/tmp-cyberchef";
/**
@ -114,6 +116,7 @@ module.exports = function (grunt) {
prod: ["build/prod/*"],
node: ["build/node/*"],
config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"],
nodeConfig: ["src/node/index.mjs", "src/node/config/OperationConfig.json"],
docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
standalone: ["build/prod/CyberChef*.html"]
},
@ -174,7 +177,7 @@ module.exports = function (grunt) {
},
resolve: {
alias: {
"./config/modules/OpModules": "./config/modules/Default"
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
}
},
plugins: [
@ -200,21 +203,6 @@ module.exports = function (grunt) {
]
};
},
node: {
mode: "production",
target: "node",
entry: "./src/node/index.mjs",
externals: [NodeExternals()],
output: {
filename: "CyberChef.js",
path: __dirname + "/build/node",
library: "CyberChef",
libraryTarget: "commonjs2"
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS)
]
}
},
"webpack-dev-server": {
options: {
@ -246,12 +234,9 @@ module.exports = function (grunt) {
}, moduleEntryPoints),
resolve: {
alias: {
"./config/modules/OpModules": "./config/modules/Default"
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
}
},
output: {
globalObject: "this",
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS),
new HtmlWebpackPlugin({
@ -358,7 +343,7 @@ module.exports = function (grunt) {
watch: {
config: {
files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"],
tasks: ["exec:generateConfig"]
tasks: ["exec:generateNodeIndex", "exec:generateConfig"]
}
},
concurrent: {
@ -390,12 +375,59 @@ module.exports = function (grunt) {
"echo '--- Config scripts finished. ---\n'"
].join(";")
},
generateNodeIndex: {
command: [
"echo '\n--- Regenerating node index ---'",
"node --experimental-modules --no-warnings --no-deprecation src/node/config/scripts/generateNodeIndex.mjs",
"echo '--- Node index generated. ---\n'"
].join(";"),
},
opTests: {
command: "node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs"
},
browserTests: {
command: "./node_modules/.bin/nightwatch --env prod"
}
},
nodeTests: {
command: "node --experimental-modules --no-warnings --no-deprecation tests/node/index.mjs"
},
setupNodeConsumers: {
command: [
"echo '\n--- Testing node conumers ---'",
"npm link",
`mkdir ${nodeConsumerTestPath}`,
`cp tests/node/consumers/* ${nodeConsumerTestPath}`,
`cd ${nodeConsumerTestPath}`,
"npm link cyberchef"
].join(";"),
},
teardownNodeConsumers: {
command: [
`rm -rf ${nodeConsumerTestPath}`,
"echo '\n--- Node consumer tests complete ---'"
].join(";"),
},
testCJSNodeConsumer: {
command: [
`cd ${nodeConsumerTestPath}`,
"node --no-warnings cjs-consumer.js",
].join(";"),
stdout: false,
},
testESMNodeConsumer: {
command: [
`cd ${nodeConsumerTestPath}`,
"node --no-warnings --experimental-modules esm-consumer.mjs",
].join(";"),
stdout: false,
},
testESMDeepImportNodeConsumer: {
command: [
`cd ${nodeConsumerTestPath}`,
"node --no-warnings --experimental-modules esm-deep-import-consumer.mjs",
].join(";"),
stdout: false,
},
},
});
};

View File

@ -50,7 +50,7 @@ You can use as many operations as you like in simple or complex ways. Some examp
- Drag and drop
- Operations can be dragged in and out of the recipe list, or reorganised.
- Files up to 500MB can be dragged over the input box to load them directly into the browser.
- Files up to 2GB can be dragged over the input box to load them directly into the browser.
- Auto Bake
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
@ -67,7 +67,7 @@ You can use as many operations as you like in simple or complex ways. Some examp
- Highlighting
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]).
- Save to file and load from file
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 500MB are supported (depending on your browser), however some operations may take a very long time to run over this much data.
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 2GB are supported (depending on your browser), however some operations may take a very long time to run over this much data.
- CyberChef is entirely client-side
- It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
- Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your local machine.
@ -81,6 +81,10 @@ CyberChef is built to support
- Mozilla Firefox 35+
- Microsoft Edge 14+
## Node.js support
CyberChef is built to fully support Node.js `v10` and partially supports `v12`. Named imports using a deep import specifier does not work in `v12`. For more information, see the Node API page in the project [wiki pages](https://github.com/gchq/CyberChef/wiki)
## Contributing

View File

@ -4,19 +4,13 @@ module.exports = function(api) {
return {
"presets": [
["@babel/preset-env", {
"targets": {
"chrome": 40,
"firefox": 35,
"edge": 14,
"node": "6.5"
},
"modules": false,
"useBuiltIns": "entry",
"corejs": 3
}]
],
"plugins": [
"babel-plugin-syntax-dynamic-import",
"dynamic-import-node",
[
"babel-plugin-transform-builtin-extend", {
"globals": ["Error"]

5553
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "8.31.3",
"version": "9.0.5",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
@ -27,31 +27,38 @@
"type": "git",
"url": "https://github.com/gchq/CyberChef/"
},
"main": "build/node/CyberChef.js",
"main": "src/node/cjs.js",
"module": "src/node/index.mjs",
"bugs": "https://github.com/gchq/CyberChef/issues",
"browserslist": [
"Chrome >= 40",
"Firefox >= 35",
"Edge >= 14",
"node >= 6.5"
],
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"autoprefixer": "^9.5.1",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.5",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"chromedriver": "^74.0.0",
"@babel/core": "^7.5.0",
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/preset-env": "^7.5.0",
"autoprefixer": "^9.6.0",
"babel-eslint": "^10.0.2",
"babel-loader": "^8.0.6",
"babel-plugin-dynamic-import-node": "^2.2.0",
"chromedriver": "^75.0.1",
"colors": "^1.3.3",
"css-loader": "^2.1.1",
"eslint": "^5.16.0",
"css-loader": "^3.0.0",
"eslint": "^6.0.1",
"exports-loader": "^0.7.0",
"file-loader": "^3.0.1",
"file-loader": "^4.0.0",
"grunt": "^1.0.4",
"grunt-accessibility": "~6.0.0",
"grunt-chmod": "~1.1.1",
"grunt-concurrent": "^2.3.1",
"grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "~2.0.0",
"grunt-contrib-connect": "^2.0.0",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^21.0.0",
"grunt-eslint": "^22.0.0",
"grunt-exec": "~3.0.0",
"grunt-jsdoc": "^2.4.0",
"grunt-webpack": "^3.1.3",
@ -60,71 +67,73 @@
"imports-loader": "^0.8.0",
"ink-docstrap": "^1.3.2",
"jsdoc-babel": "^0.5.0",
"mini-css-extract-plugin": "^0.6.0",
"nightwatch": "^1.0.19",
"mini-css-extract-plugin": "^0.7.0",
"nightwatch": "^1.1.13",
"node-sass": "^4.12.0",
"postcss-css-variables": "^0.12.0",
"postcss-css-variables": "^0.13.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"prompt": "^1.0.0",
"sass-loader": "^7.1.0",
"sitemap": "^2.2.0",
"sitemap": "^3.2.0",
"style-loader": "^0.23.1",
"svg-url-loader": "^2.3.2",
"url-loader": "^1.1.2",
"webpack": "^4.31.0",
"svg-url-loader": "^3.0.0",
"url-loader": "^2.0.1",
"webpack": "^4.35.2",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-dev-server": "^3.3.1",
"webpack-dev-server": "^3.7.2",
"webpack-node-externals": "^1.7.2",
"worker-loader": "^2.0.0"
},
"dependencies": {
"@babel/polyfill": "^7.4.4",
"@babel/runtime": "^7.4.4",
"@babel/runtime": "^7.5.0",
"arrive": "^2.4.1",
"babel-plugin-transform-builtin-extend": "1.1.2",
"bcryptjs": "^2.4.3",
"bignumber.js": "^8.1.1",
"bignumber.js": "^9.0.0",
"blakejs": "^1.1.0",
"bootstrap": "4.2.1",
"bootstrap-colorpicker": "^2.5.3",
"bootstrap-material-design": "^4.1.1",
"bootstrap": "4.3.1",
"bootstrap-colorpicker": "^3.1.2",
"bootstrap-material-design": "^4.1.2",
"bson": "^4.0.2",
"chi-squared": "^1.1.0",
"clippyjs": "0.0.3",
"core-js": "^3.0.1",
"core-js": "^3.1.4",
"crypto-api": "^0.8.3",
"crypto-js": "^3.1.9-1",
"ctph.js": "0.0.5",
"d3": "^5.9.2",
"d3": "^5.9.7",
"d3-hexbin": "^0.2.2",
"diff": "^4.0.1",
"es6-promisify": "^6.0.1",
"escodegen": "^1.11.1",
"esm": "^3.2.25",
"esmangle": "^1.0.1",
"esprima": "^4.0.1",
"exif-parser": "^0.1.12",
"file-saver": "^2.0.1",
"file-saver": "^2.0.2",
"geodesy": "^1.1.3",
"highlight.js": "^9.15.6",
"highlight.js": "^9.15.8",
"jimp": "^0.6.4",
"jquery": "3.4.1",
"js-crc": "^0.2.0",
"js-sha3": "^0.8.0",
"jsesc": "^2.5.2",
"jsonpath": "^1.0.1",
"jsonpath": "^1.0.2",
"jsonwebtoken": "^8.5.1",
"jsqr": "^1.2.0",
"jsrsasign": "8.0.12",
"kbpgp": "2.1.0",
"kbpgp": "2.1.2",
"libbzip2-wasm": "0.0.4",
"libyara-wasm": "0.0.12",
"lodash": "^4.17.11",
"loglevel": "^1.6.1",
"lodash": "^4.17.15",
"loglevel": "^1.6.3",
"loglevel-message-prefix": "^3.0.0",
"moment": "^2.24.0",
"moment-timezone": "^0.5.25",
"ngeohash": "^0.6.3",
"node-forge": "^0.8.2",
"node-forge": "^0.8.5",
"node-md6": "^0.1.0",
"nodom": "^2.2.0",
"notepack.io": "^2.2.0",
@ -132,12 +141,12 @@
"otp": "^0.1.3",
"popper.js": "^1.15.0",
"qr-image": "^3.2.0",
"scryptsy": "^2.0.0",
"scryptsy": "^2.1.0",
"snackbarjs": "^1.1.0",
"sortablejs": "^1.9.0",
"split.js": "^1.5.10",
"split.js": "^1.5.11",
"ssdeep.js": "0.0.2",
"ua-parser-js": "^0.7.19",
"ua-parser-js": "^0.7.20",
"utf8": "^3.0.0",
"vkbeautify": "^0.99.3",
"xmldom": "^0.1.27",
@ -148,7 +157,9 @@
"scripts": {
"start": "grunt dev",
"build": "grunt prod",
"repl": "node src/node/repl.js",
"test": "grunt test",
"test-node-consumer": "grunt testnodeconsumer",
"testui": "grunt testui",
"docs": "grunt docs",
"lint": "grunt lint",

View File

@ -1,13 +1,7 @@
module.exports = {
plugins: [
require("postcss-import"),
require("autoprefixer")({
browsers: [
"Chrome >= 40",
"Firefox >= 35",
"Edge >= 14"
]
}),
require("autoprefixer"),
require("postcss-css-variables")({
preserve: true
}),

View File

@ -4,9 +4,10 @@
* @license Apache-2.0
*/
import Dish from "./Dish";
import Recipe from "./Recipe";
import Dish from "./Dish.mjs";
import Recipe from "./Recipe.mjs";
import log from "loglevel";
import { isWorkerEnvironment } from "./Utils.mjs";
/**
* The main controller for CyberChef.
@ -28,8 +29,6 @@ class Chef {
* @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
@ -38,46 +37,20 @@ class Chef {
* @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) {
async bake(input, recipeConfig, options) {
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) {
notUTF8 = options && "treatAsUtf8" in options && !options.treatAsUtf8;
let error = false,
progress = 0;
}
if (step) {
// Unset breakpoint on this step
recipe.setBreakpoint(progress, false);
// Set breakpoint on next step
recipe.setBreakpoint(progress + 1, true);
}
if (containsFc && isWorkerEnvironment()) self.setOption("attemptHighlight", false);
// 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);
}
// Load data
const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
this.dish.set(input, type);
try {
progress = await recipe.execute(this.dish, progress);
@ -196,6 +169,18 @@ class Chef {
return await newDish.get(type);
}
/**
* Gets the title of a dish and returns it
*
* @param {Dish} dish
* @param {number} [maxLength=100]
* @returns {string}
*/
async getDishTitle(dish, maxLength=100) {
const newDish = new Dish(dish);
return await newDish.getTitle(maxLength);
}
}
export default Chef;

View File

@ -6,9 +6,9 @@
* @license Apache-2.0
*/
import Chef from "./Chef";
import Chef from "./Chef.mjs";
import OperationConfig from "./config/OperationConfig.json";
import OpModules from "./config/modules/OpModules";
import OpModules from "./config/modules/OpModules.mjs";
// Add ">" to the start of all log messages in the Chef Worker
import loglevelMessagePrefix from "loglevel-message-prefix";
@ -25,6 +25,8 @@ self.chef = new Chef();
self.OpModules = OpModules;
self.OperationConfig = OperationConfig;
self.inputNum = -1;
// Tell the app that the worker has loaded and is ready to operate
self.postMessage({
@ -35,6 +37,9 @@ self.postMessage({
/**
* Respond to message from parent thread.
*
* inputNum is optional and only used for baking multiple inputs.
* Defaults to -1 when one isn't sent with the bake message.
*
* Messages should have the following format:
* {
* action: "bake" | "silentBake",
@ -43,8 +48,9 @@ self.postMessage({
* recipeConfig: {[Object]},
* options: {Object},
* progress: {number},
* step: {boolean}
* } | undefined
* step: {boolean},
* [inputNum=-1]: {number}
* }
* }
*/
self.addEventListener("message", function(e) {
@ -62,6 +68,9 @@ self.addEventListener("message", function(e) {
case "getDishAs":
getDishAs(r.data);
break;
case "getDishTitle":
getDishTitle(r.data);
break;
case "docURL":
// Used to set the URL of the current document so that scripts can be
// imported into an inline worker.
@ -91,30 +100,35 @@ self.addEventListener("message", function(e) {
async function bake(data) {
// Ensure the relevant modules are loaded
self.loadRequiredModules(data.recipeConfig);
try {
self.inputNum = (data.inputNum !== undefined) ? data.inputNum : -1;
const response = await self.chef.bake(
data.input, // The user's input
data.recipeConfig, // The configuration of the recipe
data.options, // Options set by the user
data.progress, // The current position in the recipe
data.step // Whether or not to take one step or execute the whole recipe
data.options // Options set by the user
);
const transferable = (data.input instanceof ArrayBuffer) ? [data.input] : undefined;
self.postMessage({
action: "bakeComplete",
data: Object.assign(response, {
id: data.id
id: data.id,
inputNum: data.inputNum,
bakeId: data.bakeId
})
});
}, transferable);
} catch (err) {
self.postMessage({
action: "bakeError",
data: Object.assign(err, {
id: data.id
})
data: {
error: err.message || err,
id: data.id,
inputNum: data.inputNum
}
});
}
self.inputNum = -1;
}
@ -136,13 +150,33 @@ function silentBake(data) {
*/
async function getDishAs(data) {
const value = await self.chef.getDishAs(data.dish, data.type);
const transferable = (data.type === "ArrayBuffer") ? [value] : undefined;
self.postMessage({
action: "dishReturned",
data: {
value: value,
id: data.id
}
}, transferable);
}
/**
* Gets the dish title
*
* @param {object} data
* @param {Dish} data.dish
* @param {number} data.maxLength
* @param {number} data.id
*/
async function getDishTitle(data) {
const title = await self.chef.getDishTitle(data.dish, data.maxLength);
self.postMessage({
action: "dishReturned",
data: {
value: title,
id: data.id
}
});
}
@ -175,7 +209,7 @@ self.loadRequiredModules = function(recipeConfig) {
recipeConfig.forEach(op => {
const module = self.OperationConfig[op.op].module;
if (!OpModules.hasOwnProperty(module)) {
if (!(module in OpModules)) {
log.info(`Loading ${module} module`);
self.sendStatusMessage(`Loading ${module} module`);
self.importScripts(`${self.docURL}/modules/${module}.js`);
@ -193,7 +227,28 @@ self.loadRequiredModules = function(recipeConfig) {
self.sendStatusMessage = function(msg) {
self.postMessage({
action: "statusMessage",
data: msg
data: {
message: msg,
inputNum: self.inputNum
}
});
};
/**
* Send progress update to the app.
*
* @param {number} progress
* @param {number} total
*/
self.sendProgressMessage = function(progress, total) {
self.postMessage({
action: "progressMessage",
data: {
progress: progress,
total: total,
inputNum: self.inputNum
}
});
};

View File

@ -5,11 +5,22 @@
* @license Apache-2.0
*/
import Utils from "./Utils";
import DishError from "./errors/DishError";
import Utils, { isNodeEnvironment } from "./Utils.mjs";
import DishError from "./errors/DishError.mjs";
import BigNumber from "bignumber.js";
import { detectFileType } from "./lib/FileType.mjs";
import log from "loglevel";
import DishByteArray from "./dishTypes/DishByteArray.mjs";
import DishBigNumber from "./dishTypes/DishBigNumber.mjs";
import DishFile from "./dishTypes/DishFile.mjs";
import DishHTML from "./dishTypes/DishHTML.mjs";
import DishJSON from "./dishTypes/DishJSON.mjs";
import DishListFile from "./dishTypes/DishListFile.mjs";
import DishNumber from "./dishTypes/DishNumber.mjs";
import DishString from "./dishTypes/DishString.mjs";
/**
* The data being operated on by each operation.
*/
@ -18,16 +29,27 @@ class Dish {
/**
* Dish constructor
*
* @param {Dish} [dish=null] - A dish to clone
* @param {Dish || *} [dishOrInput=null] - A dish to clone OR an object
* literal to make into a dish
* @param {Enum} [type=null] (optional) - A type to accompany object
* literal input
*/
constructor(dish=null) {
constructor(dishOrInput=null, type = null) {
this.value = new ArrayBuffer(0);
this.type = Dish.ARRAY_BUFFER;
if (dish &&
dish.hasOwnProperty("value") &&
dish.hasOwnProperty("type")) {
this.set(dish.value, dish.type);
// Case: dishOrInput is dish object
if (dishOrInput &&
Object.prototype.hasOwnProperty.call(dishOrInput, "value") &&
Object.prototype.hasOwnProperty.call(dishOrInput, "type")) {
this.set(dishOrInput.value, dishOrInput.type);
// input and type defined separately
} else if (dishOrInput && type !== null) {
this.set(dishOrInput, type);
// No type declared, so infer it.
} else if (dishOrInput) {
const inferredType = Dish.typeEnum(dishOrInput.constructor.name);
this.set(dishOrInput, inferredType);
}
}
@ -56,6 +78,7 @@ class Dish {
case "big number":
return Dish.BIG_NUMBER;
case "json":
case "object": // object constructor name. To allow JSON input in node.
return Dish.JSON;
case "file":
return Dish.FILE;
@ -99,6 +122,43 @@ class Dish {
}
/**
* Returns the value of the data in the type format specified.
*
* If running in a browser, get is asynchronous.
*
* @param {number} type - The data type of value, see Dish enums.
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
* @returns {* | Promise} - (Broswer) A promise | (Node) value of dish in given type
*/
get(type, notUTF8=false) {
if (typeof type === "string") {
type = Dish.typeEnum(type);
}
if (this.type !== type) {
// Node environment => _translate is sync
if (isNodeEnvironment()) {
this._translate(type, notUTF8);
return this.value;
// Browser environment => _translate is async
} else {
return new Promise((resolve, reject) => {
this._translate(type, notUTF8)
.then(() => {
resolve(this.value);
})
.catch(reject);
});
}
}
return this.value;
}
/**
* Sets the data value and type and then validates them.
*
@ -122,116 +182,81 @@ class Dish {
}
}
/**
* Returns the value of the data in the type format specified.
* Returns the Dish as the given type, without mutating the original dish.
*
* If running in a browser, get is asynchronous.
*
* @Node
*
* @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.
* @returns {Dish | Promise} - (Broswer) A promise | (Node) value of dish in given type
*/
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;
presentAs(type, notUTF8=false) {
const clone = this.clone();
return clone.get(type, notUTF8);
}
/**
* 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.
* Detects the MIME type of the current dish
* @returns {string}
*/
async _translate(toType, notUTF8=false) {
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
detectDishType() {
const data = new Uint8Array(this.value.slice(0, 2048)),
types = detectFileType(data);
// Convert data to intermediate ArrayBuffer type
try {
switch (this.type) {
case Dish.STRING:
this.value = this.value ? Utils.strToArrayBuffer(this.value) : new ArrayBuffer;
break;
case Dish.NUMBER:
this.value = typeof this.value === "number" ? Utils.strToArrayBuffer(this.value.toString()) : new ArrayBuffer;
break;
case Dish.HTML:
this.value = this.value ? Utils.strToArrayBuffer(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : new ArrayBuffer;
break;
case Dish.BYTE_ARRAY:
this.value = new Uint8Array(this.value).buffer;
break;
case Dish.BIG_NUMBER:
this.value = BigNumber.isBigNumber(this.value) ? Utils.strToArrayBuffer(this.value.toFixed()) : new ArrayBuffer;
break;
case Dish.JSON:
this.value = this.value ? Utils.strToArrayBuffer(JSON.stringify(this.value, null, 4)) : new ArrayBuffer;
break;
case Dish.FILE:
this.value = (await Utils.readFile(this.value)).buffer;
break;
case Dish.LIST_FILE:
this.value = await Promise.all(this.value.map(async f => Utils.readFile(f)));
this.value = concatenateTypedArrays(...this.value).buffer;
break;
default:
break;
}
} catch (err) {
throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to ArrayBuffer: ${err}`);
}
this.type = Dish.ARRAY_BUFFER;
// Convert from ArrayBuffer to toType
try {
switch (toType) {
case Dish.STRING:
case Dish.HTML:
this.value = this.value ? Utils.arrayBufferToStr(this.value, !notUTF8) : "";
this.type = Dish.STRING;
break;
case Dish.NUMBER:
this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value, !notUTF8)) : 0;
this.type = Dish.NUMBER;
break;
case Dish.BYTE_ARRAY:
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
this.type = Dish.ARRAY_BUFFER;
break;
case Dish.BIG_NUMBER:
try {
this.value = new BigNumber(Utils.arrayBufferToStr(this.value, !notUTF8));
} catch (err) {
this.value = new BigNumber(NaN);
}
this.type = Dish.BIG_NUMBER;
break;
case Dish.JSON:
this.value = JSON.parse(Utils.arrayBufferToStr(this.value, !notUTF8));
this.type = Dish.JSON;
break;
case Dish.FILE:
this.value = new File(this.value, "unknown");
this.type = Dish.FILE;
break;
case Dish.LIST_FILE:
this.value = [new File(this.value, "unknown")];
this.type = Dish.LIST_FILE;
break;
default:
break;
}
} catch (err) {
throw new DishError(`Error translating from ArrayBuffer to ${Dish.enumLookup(toType)}: ${err}`);
if (!types.length || !types[0].mime || !types[0].mime === "text/plain") {
return null;
} else {
return types[0].mime;
}
}
/**
* Returns the title of the data up to the specified length
*
* @param {number} maxLength - The maximum title length
* @returns {string}
*/
async getTitle(maxLength) {
let title = "";
let cloned;
switch (this.type) {
case Dish.FILE:
title = this.value.name;
break;
case Dish.LIST_FILE:
title = `${this.value.length} file(s)`;
break;
case Dish.JSON:
title = "application/json";
break;
case Dish.NUMBER:
case Dish.BIG_NUMBER:
title = this.value.toString();
break;
case Dish.ARRAY_BUFFER:
case Dish.BYTE_ARRAY:
title = this.detectDishType();
if (title !== null) break;
// fall through if no mime type was detected
default:
try {
cloned = this.clone();
cloned.value = cloned.value.slice(0, 256);
title = await cloned.get(Dish.STRING);
} catch (err) {
log.error(`${Dish.enumLookup(this.type)} cannot be sliced. ${err}`);
}
}
return title.slice(0, maxLength);
}
/**
* Validates that the value is the type that has been specified.
* May have to disable parts of BYTE_ARRAY validation if it effects performance.
@ -241,7 +266,7 @@ class Dish {
valid() {
switch (this.type) {
case Dish.BYTE_ARRAY:
if (!(this.value instanceof Array)) {
if (!(this.value instanceof Uint8Array) && !(this.value instanceof Array)) {
return false;
}
@ -262,7 +287,21 @@ class Dish {
case Dish.ARRAY_BUFFER:
return this.value instanceof ArrayBuffer;
case Dish.BIG_NUMBER:
return BigNumber.isBigNumber(this.value);
if (BigNumber.isBigNumber(this.value)) return true;
/*
If a BigNumber is passed between WebWorkers it is serialised as a JSON
object with a coefficient (c), exponent (e) and sign (s). We detect this
and reinitialise it as a BigNumber object.
*/
if (Object.keys(this.value).sort().equals(["c", "e", "s"])) {
const temp = new BigNumber();
temp.c = this.value.c;
temp.e = this.value.e;
temp.s = this.value.s;
this.value = temp;
return true;
}
return false;
case Dish.JSON:
// All values can be serialised in some manner, so we return true in all cases
return true;
@ -369,26 +408,109 @@ class Dish {
return newDish;
}
}
/**
* Translates the data to the given type format.
*
* If running in the browser, _translate is asynchronous.
*
* @param {number} toType - The data type of value, see Dish enums.
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
* @returns {Promise || undefined}
*/
_translate(toType, notUTF8=false) {
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
// Node environment => translate is sync
if (isNodeEnvironment()) {
this._toArrayBuffer();
this.type = Dish.ARRAY_BUFFER;
this._fromArrayBuffer(toType, notUTF8);
// Browser environment => translate is async
} else {
return new Promise((resolve, reject) => {
this._toArrayBuffer()
.then(() => this.type = Dish.ARRAY_BUFFER)
.then(() => {
this._fromArrayBuffer(toType);
resolve();
})
.catch(reject);
});
}
/**
* Concatenates a list of Uint8Arrays together
*
* @param {Uint8Array[]} arrays
* @returns {Uint8Array}
*/
function concatenateTypedArrays(...arrays) {
let totalLength = 0;
for (const arr of arrays) {
totalLength += arr.length;
}
const result = new Uint8Array(totalLength);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
/**
* Convert this.value to an ArrayBuffer
*
* If running in a browser, _toByteArray is asynchronous.
*
* @returns {Promise || undefined}
*/
_toArrayBuffer() {
// Using 'bind' here to allow this.value to be mutated within translation functions
const toByteArrayFuncs = {
browser: {
[Dish.STRING]: () => Promise.resolve(DishString.toArrayBuffer.bind(this)()),
[Dish.NUMBER]: () => Promise.resolve(DishNumber.toArrayBuffer.bind(this)()),
[Dish.HTML]: () => Promise.resolve(DishHTML.toArrayBuffer.bind(this)()),
[Dish.ARRAY_BUFFER]: () => Promise.resolve(),
[Dish.BIG_NUMBER]: () => Promise.resolve(DishBigNumber.toArrayBuffer.bind(this)()),
[Dish.JSON]: () => Promise.resolve(DishJSON.toArrayBuffer.bind(this)()),
[Dish.FILE]: () => DishFile.toArrayBuffer.bind(this)(),
[Dish.LIST_FILE]: () => Promise.resolve(DishListFile.toArrayBuffer.bind(this)()),
[Dish.BYTE_ARRAY]: () => Promise.resolve(DishByteArray.toArrayBuffer.bind(this)()),
},
node: {
[Dish.STRING]: () => DishString.toArrayBuffer.bind(this)(),
[Dish.NUMBER]: () => DishNumber.toArrayBuffer.bind(this)(),
[Dish.HTML]: () => DishHTML.toArrayBuffer.bind(this)(),
[Dish.ARRAY_BUFFER]: () => {},
[Dish.BIG_NUMBER]: () => DishBigNumber.toArrayBuffer.bind(this)(),
[Dish.JSON]: () => DishJSON.toArrayBuffer.bind(this)(),
[Dish.FILE]: () => DishFile.toArrayBuffer.bind(this)(),
[Dish.LIST_FILE]: () => DishListFile.toArrayBuffer.bind(this)(),
[Dish.BYTE_ARRAY]: () => DishByteArray.toArrayBuffer.bind(this)(),
}
};
try {
return toByteArrayFuncs[isNodeEnvironment() && "node" || "browser"][this.type]();
} catch (err) {
throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to ArrayBuffer: ${err}`);
}
}
return result;
/**
* Convert this.value to the given type from ArrayBuffer
*
* @param {number} toType - the Dish enum to convert to
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
*/
_fromArrayBuffer(toType, notUTF8) {
// Using 'bind' here to allow this.value to be mutated within translation functions
const toTypeFunctions = {
[Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(notUTF8),
[Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(notUTF8),
[Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(notUTF8),
[Dish.ARRAY_BUFFER]: () => {},
[Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(notUTF8),
[Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(notUTF8),
[Dish.FILE]: () => DishFile.fromArrayBuffer.bind(this)(),
[Dish.LIST_FILE]: () => DishListFile.fromArrayBuffer.bind(this)(),
[Dish.BYTE_ARRAY]: () => DishByteArray.fromArrayBuffer.bind(this)(),
};
try {
toTypeFunctions[toType]();
this.type = toType;
} catch (err) {
throw new DishError(`Error translating from ArrayBuffer to ${Dish.enumLookup(toType)}: ${err}`);
}
}
}

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Utils from "./Utils";
import {fromHex} from "./lib/Hex";
import Utils from "./Utils.mjs";
import {fromHex} from "./lib/Hex.mjs";
/**
* The arguments to operations.

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Dish from "./Dish";
import Ingredient from "./Ingredient";
import Dish from "./Dish.mjs";
import Ingredient from "./Ingredient.mjs";
/**
* The Operation specified by the user to be run.

View File

@ -5,10 +5,11 @@
*/
import OperationConfig from "./config/OperationConfig.json";
import OperationError from "./errors/OperationError";
import Operation from "./Operation";
import DishError from "./errors/DishError";
import OperationError from "./errors/OperationError.mjs";
import Operation from "./Operation.mjs";
import DishError from "./errors/DishError.mjs";
import log from "loglevel";
import { isWorkerEnvironment } from "./Utils.mjs";
// Cache container for modules
let modules = null;
@ -61,7 +62,7 @@ class Recipe {
if (!modules) {
// Using Webpack Magic Comments to force the dynamic import to be included in the main chunk
// https://webpack.js.org/api/module-methods/
modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules");
modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules.mjs");
modules = modules.default;
}
@ -200,7 +201,12 @@ class Recipe {
try {
input = await dish.get(op.inputType);
log.debug("Executing operation");
log.debug(`Executing operation '${op.name}'`);
if (isWorkerEnvironment()) {
self.sendStatusMessage(`Baking... (${i+1}/${this.opList.length})`);
self.sendProgressMessage(i + 1, this.opList.length);
}
if (op.flowControl) {
// Package up the current state

View File

@ -5,11 +5,10 @@
*/
import utf8 from "utf8";
import {fromBase64, toBase64} from "./lib/Base64";
import {fromHex} from "./lib/Hex";
import {fromDecimal} from "./lib/Decimal";
import {fromBinary} from "./lib/Binary";
import {fromBase64, toBase64} from "./lib/Base64.mjs";
import {fromHex} from "./lib/Hex.mjs";
import {fromDecimal} from "./lib/Decimal.mjs";
import {fromBinary} from "./lib/Binary.mjs";
/**
* Utility functions for use in operations, the core framework and the stage.
@ -95,7 +94,7 @@ class Utils {
const paddedBytes = new Array(numBytes);
paddedBytes.fill(padByte);
Array.prototype.map.call(arr, function(b, i) {
[...arr].forEach((b, i) => {
paddedBytes[i] = b;
});
@ -174,10 +173,11 @@ class Utils {
* @returns {string}
*/
static printable(str, preserveWs=false) {
if (ENVIRONMENT_IS_WEB() && window.app && !window.app.options.treatAsUtf8) {
if (isWebEnvironment() && window.app && !window.app.options.treatAsUtf8) {
str = Utils.byteArrayToChars(Utils.strToByteArray(str));
}
// eslint-disable-next-line no-misleading-character-class
const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g;
const wsRe = /[\x09-\x10\x0D\u2028\u2029]/g;
@ -201,11 +201,20 @@ class Utils {
* Utils.parseEscapedChars("\\n");
*/
static parseEscapedChars(str) {
return str.replace(/(\\)?\\([bfnrtv0'"]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) {
return str.replace(/(\\)?\\([bfnrtv'"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\}|\\)/g, function(m, a, b) {
if (a === "\\") return "\\"+b;
switch (b[0]) {
case "\\":
return "\\";
case "0":
return "\0";
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
return String.fromCharCode(parseInt(b, 8));
case "b":
return "\b";
case "t":
@ -411,9 +420,9 @@ class Utils {
const utf8Str = utf8.encode(str);
if (str.length !== utf8Str.length) {
if (ENVIRONMENT_IS_WORKER()) {
if (isWorkerEnvironment()) {
self.setOption("attemptHighlight", false);
} else if (ENVIRONMENT_IS_WEB()) {
} else if (isWebEnvironment()) {
window.app.options.attemptHighlight = false;
}
}
@ -466,9 +475,9 @@ class Utils {
const utf8Str = utf8.encode(str);
if (str.length !== utf8Str.length) {
if (ENVIRONMENT_IS_WORKER()) {
if (isWorkerEnvironment()) {
self.setOption("attemptHighlight", false);
} else if (ENVIRONMENT_IS_WEB()) {
} else if (isWebEnvironment()) {
window.app.options.attemptHighlight = false;
}
}
@ -528,11 +537,10 @@ class Utils {
const str = Utils.byteArrayToChars(byteArray);
try {
const utf8Str = utf8.decode(str);
if (str.length !== utf8Str.length) {
if (ENVIRONMENT_IS_WORKER()) {
if (isWorkerEnvironment()) {
self.setOption("attemptHighlight", false);
} else if (ENVIRONMENT_IS_WEB()) {
} else if (isWebEnvironment()) {
window.app.options.attemptHighlight = false;
}
}
@ -985,7 +993,7 @@ class Utils {
/**
* Reads a File and returns the data as a Uint8Array.
*
* @param {File} file
* @param {File | for node: array|arrayBuffer|buffer|string} file
* @returns {Uint8Array}
*
* @example
@ -993,33 +1001,57 @@ class Utils {
* await Utils.readFile(new File(["hello"], "test"))
*/
static readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
const data = new Uint8Array(file.size);
let offset = 0;
const CHUNK_SIZE = 10485760; // 10MiB
const seek = function() {
if (offset >= file.size) {
resolve(data);
return;
}
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.readAsArrayBuffer(slice);
};
if (isNodeEnvironment()) {
return Buffer.from(file).buffer;
} else {
return new Promise((resolve, reject) => {
const reader = new FileReader();
const data = new Uint8Array(file.size);
let offset = 0;
const CHUNK_SIZE = 10485760; // 10MiB
const seek = function() {
if (offset >= file.size) {
resolve(data);
return;
}
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.readAsArrayBuffer(slice);
};
reader.onload = function(e) {
data.set(new Uint8Array(reader.result), offset);
offset += CHUNK_SIZE;
seek();
};
reader.onerror = function(e) {
reject(reader.error.message);
};
reader.onload = function(e) {
data.set(new Uint8Array(reader.result), offset);
offset += CHUNK_SIZE;
seek();
};
});
}
}
reader.onerror = function(e) {
reject(reader.error.message);
};
/**
* Synchronously read the raw data from a File object.
*
* Only works in the Node environment
*
* @param {File} file - a File shim object (see src/node/File.mjs)
* @returns {ArrayBuffer} the data from the file in an ArrayBuffer
* @throws {TypeError} thrown if the method is called from a browser environment
*/
static readFileSync(file) {
if (!isNodeEnvironment()) {
throw new TypeError("Browser environment cannot support readFileSync");
}
seek();
});
const arrayBuffer = Uint8Array.from(file.data);
return arrayBuffer.buffer;
}
@ -1121,6 +1153,30 @@ class Utils {
}
/**
* Check whether the code is running in a Node.js environment
* @returns {boolean}
*/
export function isNodeEnvironment() {
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
}
/**
* Check whether the code is running in a web environment
* @returns {boolean}
*/
export function isWebEnvironment() {
return typeof window === "object";
}
/**
* Check whether the code is running in a worker
* @returns {boolean}
*/
export function isWorkerEnvironment() {
return typeof importScripts === "function";
}
export default Utils;
@ -1139,7 +1195,7 @@ export default Utils;
Array.prototype.unique = function() {
const u = {}, a = [];
for (let i = 0, l = this.length; i < l; i++) {
if (u.hasOwnProperty(this[i])) {
if (Object.prototype.hasOwnProperty.call(u, this[i])) {
continue;
}
a.push(this[i]);
@ -1238,12 +1294,13 @@ String.prototype.count = function(chr) {
* @param {string} msg
*/
export function sendStatusMessage(msg) {
if (ENVIRONMENT_IS_WORKER())
if (isWorkerEnvironment())
self.sendStatusMessage(msg);
else if (ENVIRONMENT_IS_WEB())
else if (isWebEnvironment())
app.alert(msg, 10000);
else if (ENVIRONMENT_IS_NODE())
log.debug(msg);
else if (isNodeEnvironment())
// eslint-disable-next-line no-console
console.debug(msg);
}

View File

@ -198,8 +198,8 @@
"Remove null bytes",
"To Upper case",
"To Lower case",
"To Case Insensitive Regex",
"From Case Insensitive Regex",
"To Case Insensitive Regex",
"From Case Insensitive Regex",
"Add line numbers",
"Remove line numbers",
"To Table",
@ -303,6 +303,8 @@
"Snefru",
"BLAKE2b",
"BLAKE2s",
"GOST hash",
"Streebog",
"SSDEEP",
"CTPH",
"Compare SSDEEP hashes",
@ -317,6 +319,7 @@
"Fletcher-32 Checksum",
"Fletcher-64 Checksum",
"Adler-32 Checksum",
"CRC-8 Checksum",
"CRC-16 Checksum",
"CRC-32 Checksum",
"TCP/IP Checksum"
@ -385,6 +388,9 @@
"Contain Image",
"Cover Image",
"Image Hue/Saturation/Lightness",
"Sharpen Image",
"Convert Image Format",
"Add Text To Image",
"Hex Density chart",
"Scatter chart",
"Series chart",
@ -396,6 +402,7 @@
"ops": [
"Entropy",
"Frequency distribution",
"Index of Coincidence",
"Chi Square",
"Disassemble x86",
"Pseudo-Random Number Generator",

View File

@ -14,7 +14,7 @@
import path from "path";
import fs from "fs";
import process from "process";
import * as Ops from "../../operations/index";
import * as Ops from "../../operations/index.mjs";
const dir = path.join(process.cwd() + "/src/core/config/");
if (!fs.existsSync(dir)) {
@ -45,11 +45,11 @@ for (const opObj in Ops) {
args: op.args
};
if (op.hasOwnProperty("patterns")) {
if ("patterns" in op) {
operationConfig[op.name].patterns = op.patterns;
}
if (!modules.hasOwnProperty(op.module))
if (!(op.module in modules))
modules[op.module] = {};
modules[op.module][op.name] = opObj;
}
@ -84,7 +84,7 @@ for (const module in modules) {
for (const opName in modules[module]) {
const objName = modules[module][opName];
code += `import ${objName} from "../../operations/${objName}";\n`;
code += `import ${objName} from "../../operations/${objName}.mjs";\n`;
}
code += `
@ -124,7 +124,7 @@ let opModulesCode = `/**
`;
for (const module in modules) {
opModulesCode += `import ${module}Module from "./${module}";\n`;
opModulesCode += `import ${module}Module from "./${module}.mjs";\n`;
}
opModulesCode += `

View File

@ -39,7 +39,7 @@ let code = `/**
`;
opObjs.forEach(obj => {
code += `import ${obj} from "./${obj}";\n`;
code += `import ${obj} from "./${obj}.mjs";\n`;
});
code += `

View File

@ -13,7 +13,7 @@ import colors from "colors";
import process from "process";
import fs from "fs";
import path from "path";
import EscapeString from "../../operations/EscapeString";
import EscapeString from "../../operations/EscapeString.mjs";
const dir = path.join(process.cwd() + "/src/core/operations/");
@ -130,8 +130,8 @@ prompt.get(schema, (err, result) => {
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* ${result.opName} operation

View File

@ -0,0 +1,39 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishType from "./DishType.mjs";
import Utils from "../Utils.mjs";
import BigNumber from "bignumber.js";
/**
* translation methods for BigNumber Dishes
*/
class DishBigNumber extends DishType {
/**
* convert the given value to a ArrayBuffer
* @param {BigNumber} value
*/
static toArrayBuffer() {
DishBigNumber.checkForValue(this.value);
this.value = BigNumber.isBigNumber(this.value) ? Utils.strToArrayBuffer(this.value.toFixed()) : new ArrayBuffer;
}
/**
* convert the given value from a ArrayBuffer
* @param {boolean} notUTF8
*/
static fromArrayBuffer(notUTF8) {
DishBigNumber.checkForValue(this.value);
try {
this.value = new BigNumber(Utils.arrayBufferToStr(this.value, !notUTF8));
} catch (err) {
this.value = new BigNumber(NaN);
}
}
}
export default DishBigNumber;

View File

@ -0,0 +1,31 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishType from "./DishType.mjs";
/**
* Translation methods for ArrayBuffer Dishes
*/
class DishByteArray extends DishType {
/**
* convert the given value to a ArrayBuffer
*/
static toArrayBuffer() {
DishByteArray.checkForValue(this.value);
this.value = new Uint8Array(this.value).buffer;
}
/**
* convert the given value from a ArrayBuffer
*/
static fromArrayBuffer() {
DishByteArray.checkForValue(this.value);
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
}
}
export default DishByteArray;

View File

@ -0,0 +1,42 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishType from "./DishType.mjs";
import Utils, { isNodeEnvironment } from "../Utils.mjs";
/**
* Translation methods for file Dishes
*/
class DishFile extends DishType {
/**
* convert the given value to an ArrayBuffer
* @param {File} value
*/
static toArrayBuffer() {
DishFile.checkForValue(this.value);
if (isNodeEnvironment()) {
this.value = Utils.readFileSync(this.value);
} else {
return new Promise((resolve, reject) => {
Utils.readFile(this.value)
.then(v => this.value = v.buffer)
.then(resolve)
.catch(reject);
});
}
}
/**
* convert the given value from an ArrayBuffer
*/
static fromArrayBuffer() {
DishFile.checkForValue(this.value);
this.value = new File(this.value, "unknown");
}
}
export default DishFile;

View File

@ -0,0 +1,26 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishString from "./DishString.mjs";
import Utils from "../Utils.mjs";
/**
* Translation methods for HTML Dishes
*/
class DishHTML extends DishString {
/**
* convert the given value to a ArrayBuffer
* @param {String} value
*/
static toArrayBuffer() {
DishHTML.checkForValue(this.value);
this.value = this.value ? Utils.strToArrayBuffer(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : new ArrayBuffer;
}
}
export default DishHTML;

View File

@ -0,0 +1,33 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishType from "./DishType.mjs";
import Utils from "../Utils.mjs";
/**
* Translation methods for JSON dishes
*/
class DishJSON extends DishType {
/**
* convert the given value to a ArrayBuffer
*/
static toArrayBuffer() {
DishJSON.checkForValue(this.value);
this.value = this.value ? Utils.strToArrayBuffer(JSON.stringify(this.value, null, 4)) : new ArrayBuffer;
}
/**
* convert the given value from a ArrayBuffer
* @param {boolean} notUTF8
*/
static fromArrayBuffer(notUTF8) {
DishJSON.checkForValue(this.value);
this.value = JSON.parse(Utils.arrayBufferToStr(this.value, !notUTF8));
}
}
export default DishJSON;

View File

@ -0,0 +1,58 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishType from "./DishType.mjs";
import { isNodeEnvironment } from "../Utils.mjs";
/**
* Translation methods for ListFile Dishes
*/
class DishListFile extends DishType {
/**
* convert the given value to a ArrayBuffer
*/
static toArrayBuffer() {
DishListFile.checkForValue(this.value);
if (isNodeEnvironment()) {
this.value = this.value.map(file => Uint8Array.from(file.data));
}
this.value = DishListFile.concatenateTypedArrays(...this.value).buffer;
}
/**
* convert the given value from a ArrayBuffer
*/
static fromArrayBuffer() {
DishListFile.checkForValue(this.value);
this.value = [new File(this.value, "unknown")];
}
/**
* Concatenates a list of Uint8Arrays together
*
* @param {Uint8Array[]} arrays
* @returns {Uint8Array}
*/
static concatenateTypedArrays(...arrays) {
let totalLength = 0;
for (const arr of arrays) {
totalLength += arr.length;
}
const result = new Uint8Array(totalLength);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
}
export default DishListFile;

View File

@ -0,0 +1,34 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishType from "./DishType.mjs";
import Utils from "../Utils.mjs";
/**
* Translation methods for number dishes
*/
class DishNumber extends DishType {
/**
* convert the given value to a ArrayBuffer
*/
static toArrayBuffer() {
DishNumber.checkForValue(this.value);
this.value = typeof this.value === "number" ? Utils.strToArrayBuffer(this.value.toString()) : new ArrayBuffer;
}
/**
* convert the given value from a ArrayBuffer
* @param {boolean} notUTF8
*/
static fromArrayBuffer(notUTF8) {
DishNumber.checkForValue(this.value);
this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value, !notUTF8)) : 0;
}
}
export default DishNumber;

View File

@ -0,0 +1,34 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishType from "./DishType.mjs";
import Utils from "../Utils.mjs";
/**
* Translation methods for string dishes
*/
class DishString extends DishType {
/**
* convert the given value to a ArrayBuffer
*/
static toArrayBuffer() {
DishString.checkForValue(this.value);
this.value = this.value ? Utils.strToArrayBuffer(this.value) : new ArrayBuffer;
}
/**
* convert the given value from a ArrayBuffer
* @param {boolean} notUTF8
*/
static fromArrayBuffer(notUTF8) {
DishString.checkForValue(this.value);
this.value = this.value ? Utils.arrayBufferToStr(this.value, !notUTF8) : "";
}
}
export default DishString;

View File

@ -0,0 +1,39 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
/**
* Abstract class for dish translation methods
*/
class DishType {
/**
* Warn translations dont work without value from bind
*/
static checkForValue(value) {
if (value === undefined) {
throw new Error("only use translation methods with .bind");
}
}
/**
* convert the given value to a ArrayBuffer
* @param {*} value
*/
static toArrayBuffer() {
throw new Error("toArrayBuffer has not been implemented");
}
/**
* convert the given value from a ArrayBuffer
* @param {boolean} notUTF8
*/
static fromArrayBuffer(notUTF8=undefined) {
throw new Error("fromArrayBuffer has not been implemented");
}
}
export default DishType;

View File

@ -0,0 +1,26 @@
/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import DishByteArray from "./DishByteArray.mjs";
import DishBigNumber from "./DishBigNumber.mjs";
import DishFile from "./DishFile.mjs";
import DishHTML from "./DishHTML.mjs";
import DishJSON from "./DishJSON.mjs";
import DishListFile from "./DishListFile.mjs";
import DishNumber from "./DishNumber.mjs";
import DishString from "./DishString.mjs";
export {
DishByteArray,
DishBigNumber,
DishFile,
DishHTML,
DishJSON,
DishListFile,
DishNumber,
DishString,
};

View File

@ -0,0 +1,25 @@
/**
* Custom error type for handling operation that isnt included in node.js API
*
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
class ExcludedOperationError extends Error {
/**
* Standard error constructor. Adds no new behaviour.
*
* @param args - Standard error args
*/
constructor(...args) {
super(...args);
this.type = "ExcludedOperationError";
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ExcludedOperationError);
}
}
}
export default ExcludedOperationError;

View File

@ -5,7 +5,7 @@
* @license Apache-2.0
*/
import Utils from "../Utils";
import Utils from "../Utils.mjs";
import BigNumber from "bignumber.js";

View File

@ -6,7 +6,7 @@
* @license Apache-2.0
*/
import Utils from "../Utils";
import Utils from "../Utils.mjs";
/**
@ -63,7 +63,7 @@ export function toBase64(data, alphabet="A-Za-z0-9+/=") {
/**
* UnBase64's the input string using the given alphabet, returning a byte array.
*
* @param {byteArray} data
* @param {string} data
* @param {string} [alphabet="A-Za-z0-9+/="]
* @param {string} [returnType="string"] - Either "string" or "byteArray"
* @param {boolean} [removeNonAlphChars=true]

View File

@ -6,7 +6,7 @@
* @license Apache-2.0
*/
import Utils from "../Utils";
import Utils from "../Utils.mjs";
/**

View File

@ -9,7 +9,7 @@
/**
* Runs bitwise operations across the input data.
*
* @param {byteArray} input
* @param {byteArray|Uint8Array} input
* @param {byteArray} key
* @param {function} func - The bitwise calculation to carry out
* @param {boolean} nullPreserving

View File

@ -7,9 +7,9 @@
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import {Rotor, Plugboard, a2i, i2a} from "./Enigma";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import {Rotor, Plugboard, a2i, i2a} from "./Enigma.mjs";
/**
* Convenience/optimisation subclass of Rotor

View File

@ -5,7 +5,7 @@
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import OperationError from "../errors/OperationError.mjs";
/**
* @constant

View File

@ -9,7 +9,7 @@
*
*/
import OperationError from "../errors/OperationError";
import OperationError from "../errors/OperationError.mjs";
import CryptoJS from "crypto-js";
/**

View File

@ -8,7 +8,7 @@
import geohash from "ngeohash";
import geodesy from "geodesy";
import OperationError from "../errors/OperationError";
import OperationError from "../errors/OperationError.mjs";
/**
* Co-ordinate formats

View File

@ -6,7 +6,7 @@
* @license Apache-2.0
*/
import Utils from "../Utils";
import Utils from "../Utils.mjs";
/**

View File

@ -5,8 +5,8 @@
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
/**
* Provided default Enigma rotor set.
@ -184,10 +184,10 @@ class PairMapBase {
// self-stecker
return;
}
if (this.map.hasOwnProperty(a)) {
if (Object.prototype.hasOwnProperty.call(this.map, a)) {
throw new OperationError(`${name} connects ${pair[0]} more than once`);
}
if (this.map.hasOwnProperty(b)) {
if (Object.prototype.hasOwnProperty.call(this.map, b)) {
throw new OperationError(`${name} connects ${pair[1]} more than once`);
}
this.map[a] = b;
@ -203,7 +203,7 @@ class PairMapBase {
* @returns {number}
*/
transform(c) {
if (!this.map.hasOwnProperty(c)) {
if (!Object.prototype.hasOwnProperty.call(this.map, c)) {
return c;
}
return this.map[c];

View File

@ -6,7 +6,7 @@
* @license Apache-2.0
*
*/
import Stream from "./Stream";
import Stream from "./Stream.mjs";
/**
* A categorised table of file types, including signatures to identify them and functions
@ -170,7 +170,7 @@ export const FILE_SIGNATURES = {
mime: "image/vnd.adobe.photoshop",
description: "",
signature: {
0: 0x38,
0: 0x38, // 8BPS
1: 0x42,
2: 0x50,
3: 0x53,
@ -185,6 +185,28 @@ export const FILE_SIGNATURES = {
},
extractor: null
},
{
name: "Photoshop Large Document",
extension: "psb",
mime: "application/x-photoshop",
description: "",
signature: {
0: 0x38, // 8BPS
1: 0x42,
2: 0x50,
3: 0x53,
4: 0x0,
5: 0x2,
6: 0x0,
7: 0x0,
8: 0x0,
9: 0x0,
10: 0x0,
11: 0x0,
12: 0x0
},
extractor: null
},
{
name: "Paint Shop Pro image",
extension: "psp",
@ -233,10 +255,114 @@ export const FILE_SIGNATURES = {
5: 0x0,
6: [0x10, 0x20, 0x30, 0x40, 0x80],
7: [0x10, 0x20, 0x30, 0x40, 0x80],
9: 0x00,
9: 0x0,
10: [0x0, 0x1]
},
extractor: null
},
{
name: "Radiance High Dynamic Range image",
extension: "hdr",
mime: "image/vnd.radiance",
description: "",
signature: {
0: 0x23, // #?RADIANCE
1: 0x3f,
2: 0x52,
3: 0x41,
4: 0x44,
5: 0x49,
6: 0x41,
7: 0x4e,
8: 0x43,
9: 0x45,
10: 0x0a
},
extractor: null
},
{
name: "Sony ARW image",
extension: "arw",
mime: "image/x-raw",
description: "",
signature: {
0: 0x05,
1: 0x0,
2: 0x0,
3: 0x0,
4: 0x41,
5: 0x57,
6: 0x31,
7: 0x2e
},
extractor: null
},
{
name: "Fujifilm Raw Image",
extension: "raf",
mime: "image/x-raw",
description: "",
signature: {
0: 0x46, // FUJIFILMCCD-RAW
1: 0x55,
2: 0x4a,
3: 0x49,
4: 0x46,
5: 0x49,
6: 0x4c,
7: 0x4d,
8: 0x43,
9: 0x43,
10: 0x44,
11: 0x2d,
12: 0x52,
13: 0x41,
14: 0x57
},
extractor: null
},
{
name: "Minolta RAW image",
extension: "mrw",
mime: "image/x-raw",
description: "",
signature: {
0: 0x0,
1: 0x4d, // MRM
2: 0x52,
3: 0x4d
},
extractor: null
},
{
name: "Adobe Bridge Thumbnail Cache",
extension: "bct",
mime: "application/octet-stream",
description: "",
signature: {
0: 0x6c,
1: 0x6e,
2: 0x62,
3: 0x74,
4: 0x02,
5: 0x0,
6: 0x0,
7: 0x0
},
extractor: null
},
{
name: "Microsoft Document Imaging",
extension: "mdi",
mime: "image/vnd.ms-modi",
description: "",
signature: {
0: 0x45,
1: 0x50,
2: 0x2a,
3: 0x00
},
extractor: null
}
],
"Video": [
@ -534,6 +660,97 @@ export const FILE_SIGNATURES = {
},
extractor: null
},
{
name: "Audacity",
extension: "au",
mime: "audio/x-au",
description: "",
signature: {
0: 0x64, // dns.
1: 0x6e,
2: 0x73,
3: 0x2e,
24: 0x41, // AudacityBlockFile
25: 0x75,
26: 0x64,
27: 0x61,
28: 0x63,
29: 0x69,
30: 0x74,
31: 0x79,
32: 0x42,
33: 0x6c,
34: 0x6f,
35: 0x63,
36: 0x6b,
37: 0x46,
38: 0x69,
39: 0x6c,
40: 0x65
},
extractor: null
},
{
name: "Audacity Block",
extension: "auf",
mime: "application/octet-stream",
description: "",
signature: {
0: 0x41, // AudacityBlockFile
1: 0x75,
2: 0x64,
3: 0x61,
4: 0x63,
5: 0x69,
6: 0x74,
7: 0x79,
8: 0x42,
9: 0x6c,
10: 0x6f,
11: 0x63,
12: 0x6b,
13: 0x46,
14: 0x69,
15: 0x6c,
16: 0x65
},
extractor: null
},
{
name: "Audio Interchange File",
extension: "aif",
mime: "audio/x-aiff",
description: "",
signature: {
0: 0x46, // FORM
1: 0x4f,
2: 0x52,
3: 0x4d,
8: 0x41, // AIFF
9: 0x49,
10: 0x46,
11: 0x46
},
extractor: null
},
{
name: "Audio Interchange File (compressed)",
extension: "aifc",
mime: "audio/x-aifc",
description: "",
signature: {
0: 0x46, // FORM
1: 0x4f,
2: 0x52,
3: 0x4d,
8: 0x41, // AIFC
9: 0x49,
10: 0x46,
11: 0x43
},
extractor: null
}
],
"Documents": [
{
@ -898,6 +1115,110 @@ export const FILE_SIGNATURES = {
},
extractor: null
},
{
name: "ARJ Archive",
extension: "arj",
mime: "application/x-arj-compressed",
description: "",
signature: {
0: 0x60,
1: 0xea,
8: [0x0, 0x10, 0x14],
9: 0x0,
10: 0x2
},
extractor: null
},
{
name: "WinAce Archive",
extension: "ace",
mime: "application/x-ace-compressed",
description: "",
signature: {
7: 0x2a, // **ACE**
8: 0x2a,
9: 0x41,
10: 0x43,
11: 0x45,
12: 0x2a,
13: 0x2a
},
extractor: null
},
{
name: "Macintosh BinHex Encoded File",
extension: "hqx",
mime: "application/mac-binhex",
description: "",
signature: {
11: 0x6d, // must be converted with BinHex
12: 0x75,
13: 0x73,
14: 0x74,
15: 0x20,
16: 0x62,
17: 0x65,
18: 0x20,
19: 0x63,
20: 0x6f,
21: 0x6e,
22: 0x76,
23: 0x65,
24: 0x72,
25: 0x74,
26: 0x65,
27: 0x64,
28: 0x20,
29: 0x77,
30: 0x69,
31: 0x74,
32: 0x68,
33: 0x20,
34: 0x42,
35: 0x69,
36: 0x6e,
37: 0x48,
38: 0x65,
39: 0x78
},
extractor: null
},
{
name: "ALZip Archive",
extension: "alz",
mime: "application/octet-stream",
description: "",
signature: {
0: 0x41, // ALZ
1: 0x4c,
2: 0x5a,
3: 0x01,
4: 0x0a,
5: 0x0,
6: 0x0,
7: 0x0
},
extractor: null
},
{
name: "KGB Compressed Archive",
extension: "kgb",
mime: "application/x-kgb-compressed",
description: "",
signature: {
0: 0x4b, // KGB_arch -
1: 0x47,
2: 0x42,
3: 0x5f,
4: 0x61,
5: 0x72,
6: 0x63,
7: 0x68,
8: 0x20,
9: 0x2d
},
extractor: null
}
],
"Miscellaneous": [
{
@ -1041,6 +1362,43 @@ export const FILE_SIGNATURES = {
},
extractor: null
},
{
name: "BitTorrent link",
extension: "torrent",
mime: "application/x-bittorrent",
description: "",
signature: [
{
0: 0x64, // d8:announce##:
1: 0x38,
2: 0x3a,
3: 0x61,
4: 0x6e,
5: 0x6e,
6: 0x6f,
7: 0x75,
8: 0x6e,
9: 0x63,
10: 0x65,
11: 0x23,
12: 0x23,
13: 0x3a
},
{
0: 0x64, // d4:infod
1: 0x34,
2: 0x3a,
3: 0x69,
4: 0x6e,
5: 0x66,
6: 0x6f,
7: 0x64,
8: [0x34, 0x35, 0x36],
9: 0x3a
}
],
extractor: null
}
]
};

View File

@ -6,8 +6,8 @@
* @license Apache-2.0
*
*/
import {FILE_SIGNATURES} from "./FileSignatures";
import {sendStatusMessage} from "../Utils";
import {FILE_SIGNATURES} from "./FileSignatures.mjs";
import {sendStatusMessage} from "../Utils.mjs";
/**

View File

@ -7,8 +7,8 @@
* @license Apache-2.0
*/
import Utils from "../Utils";
import CryptoApi from "crypto-api/src/crypto-api";
import Utils from "../Utils.mjs";
import CryptoApi from "crypto-api/src/crypto-api.mjs";
/**

View File

@ -6,13 +6,13 @@
* @license Apache-2.0
*/
import Utils from "../Utils";
import Utils from "../Utils.mjs";
/**
* Convert a byte array into a hex string.
*
* @param {Uint8Array|byteArray} data
* @param {byteArray|Uint8Array|ArrayBuffer} data
* @param {string} [delim=" "]
* @param {number} [padding=2]
* @returns {string}
@ -26,6 +26,7 @@ import Utils from "../Utils";
*/
export function toHex(data, delim=" ", padding=2) {
if (!data) return "";
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
let output = "";
@ -47,7 +48,7 @@ export function toHex(data, delim=" ", padding=2) {
/**
* Convert a byte array into a hex string as efficiently as possible with no options.
*
* @param {byteArray} data
* @param {byteArray|Uint8Array|ArrayBuffer} data
* @returns {string}
*
* @example
@ -56,6 +57,7 @@ export function toHex(data, delim=" ", padding=2) {
*/
export function toHexFast(data) {
if (!data) return "";
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
const output = [];

View File

@ -8,8 +8,8 @@
* @license Apache-2.0
*/
import Utils from "../Utils";
import OperationError from "../errors/OperationError";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Parses an IPv4 CIDR range (e.g. 192.168.0.0/24) and displays information about it.

View File

@ -0,0 +1,251 @@
/**
* Image manipulation resources
*
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError.mjs";
/**
* Gaussian blurs an image.
*
* @param {jimp} input
* @param {number} radius
* @param {boolean} fast
* @returns {jimp}
*/
export function gaussianBlur (input, radius) {
try {
// From http://blog.ivank.net/fastest-gaussian-blur.html
const boxes = boxesForGauss(radius, 3);
for (let i = 0; i < 3; i++) {
input = boxBlur(input, (boxes[i] - 1) / 2);
}
} catch (err) {
throw new OperationError(`Error blurring image. (${err})`);
}
return input;
}
/**
*
* @param {number} radius
* @param {number} numBoxes
* @returns {Array}
*/
function boxesForGauss(radius, numBoxes) {
const idealWidth = Math.sqrt((12 * radius * radius / numBoxes) + 1);
let wl = Math.floor(idealWidth);
if (wl % 2 === 0) {
wl--;
}
const wu = wl + 2;
const mIdeal = (12 * radius * radius - numBoxes * wl * wl - 4 * numBoxes * wl - 3 * numBoxes) / (-4 * wl - 4);
const m = Math.round(mIdeal);
const sizes = [];
for (let i = 0; i < numBoxes; i++) {
sizes.push(i < m ? wl : wu);
}
return sizes;
}
/**
* Applies a box blur effect to the image
*
* @param {jimp} source
* @param {number} radius
* @returns {jimp}
*/
function boxBlur (source, radius) {
const width = source.bitmap.width;
const height = source.bitmap.height;
let output = source.clone();
output = boxBlurH(source, output, width, height, radius);
source = boxBlurV(output, source, width, height, radius);
return source;
}
/**
* Applies the horizontal blur
*
* @param {jimp} source
* @param {jimp} output
* @param {number} width
* @param {number} height
* @param {number} radius
* @returns {jimp}
*/
function boxBlurH (source, output, width, height, radius) {
const iarr = 1 / (radius + radius + 1);
for (let i = 0; i < height; i++) {
let ti = 0,
li = ti,
ri = ti + radius;
const idx = source.getPixelIndex(ti, i);
const firstValRed = source.bitmap.data[idx],
firstValGreen = source.bitmap.data[idx + 1],
firstValBlue = source.bitmap.data[idx + 2],
firstValAlpha = source.bitmap.data[idx + 3];
const lastIdx = source.getPixelIndex(width - 1, i),
lastValRed = source.bitmap.data[lastIdx],
lastValGreen = source.bitmap.data[lastIdx + 1],
lastValBlue = source.bitmap.data[lastIdx + 2],
lastValAlpha = source.bitmap.data[lastIdx + 3];
let red = (radius + 1) * firstValRed;
let green = (radius + 1) * firstValGreen;
let blue = (radius + 1) * firstValBlue;
let alpha = (radius + 1) * firstValAlpha;
for (let j = 0; j < radius; j++) {
const jIdx = source.getPixelIndex(ti + j, i);
red += source.bitmap.data[jIdx];
green += source.bitmap.data[jIdx + 1];
blue += source.bitmap.data[jIdx + 2];
alpha += source.bitmap.data[jIdx + 3];
}
for (let j = 0; j <= radius; j++) {
const jIdx = source.getPixelIndex(ri++, i);
red += source.bitmap.data[jIdx] - firstValRed;
green += source.bitmap.data[jIdx + 1] - firstValGreen;
blue += source.bitmap.data[jIdx + 2] - firstValBlue;
alpha += source.bitmap.data[jIdx + 3] - firstValAlpha;
const tiIdx = source.getPixelIndex(ti++, i);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
for (let j = radius + 1; j < width - radius; j++) {
const riIdx = source.getPixelIndex(ri++, i);
const liIdx = source.getPixelIndex(li++, i);
red += source.bitmap.data[riIdx] - source.bitmap.data[liIdx];
green += source.bitmap.data[riIdx + 1] - source.bitmap.data[liIdx + 1];
blue += source.bitmap.data[riIdx + 2] - source.bitmap.data[liIdx + 2];
alpha += source.bitmap.data[riIdx + 3] - source.bitmap.data[liIdx + 3];
const tiIdx = source.getPixelIndex(ti++, i);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
for (let j = width - radius; j < width; j++) {
const liIdx = source.getPixelIndex(li++, i);
red += lastValRed - source.bitmap.data[liIdx];
green += lastValGreen - source.bitmap.data[liIdx + 1];
blue += lastValBlue - source.bitmap.data[liIdx + 2];
alpha += lastValAlpha - source.bitmap.data[liIdx + 3];
const tiIdx = source.getPixelIndex(ti++, i);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
}
return output;
}
/**
* Applies the vertical blur
*
* @param {jimp} source
* @param {jimp} output
* @param {number} width
* @param {number} height
* @param {number} radius
* @returns {jimp}
*/
function boxBlurV (source, output, width, height, radius) {
const iarr = 1 / (radius + radius + 1);
for (let i = 0; i < width; i++) {
let ti = 0,
li = ti,
ri = ti + radius;
const idx = source.getPixelIndex(i, ti);
const firstValRed = source.bitmap.data[idx],
firstValGreen = source.bitmap.data[idx + 1],
firstValBlue = source.bitmap.data[idx + 2],
firstValAlpha = source.bitmap.data[idx + 3];
const lastIdx = source.getPixelIndex(i, height - 1),
lastValRed = source.bitmap.data[lastIdx],
lastValGreen = source.bitmap.data[lastIdx + 1],
lastValBlue = source.bitmap.data[lastIdx + 2],
lastValAlpha = source.bitmap.data[lastIdx + 3];
let red = (radius + 1) * firstValRed;
let green = (radius + 1) * firstValGreen;
let blue = (radius + 1) * firstValBlue;
let alpha = (radius + 1) * firstValAlpha;
for (let j = 0; j < radius; j++) {
const jIdx = source.getPixelIndex(i, ti + j);
red += source.bitmap.data[jIdx];
green += source.bitmap.data[jIdx + 1];
blue += source.bitmap.data[jIdx + 2];
alpha += source.bitmap.data[jIdx + 3];
}
for (let j = 0; j <= radius; j++) {
const riIdx = source.getPixelIndex(i, ri++);
red += source.bitmap.data[riIdx] - firstValRed;
green += source.bitmap.data[riIdx + 1] - firstValGreen;
blue += source.bitmap.data[riIdx + 2] - firstValBlue;
alpha += source.bitmap.data[riIdx + 3] - firstValAlpha;
const tiIdx = source.getPixelIndex(i, ti++);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
for (let j = radius + 1; j < height - radius; j++) {
const riIdx = source.getPixelIndex(i, ri++);
const liIdx = source.getPixelIndex(i, li++);
red += source.bitmap.data[riIdx] - source.bitmap.data[liIdx];
green += source.bitmap.data[riIdx + 1] - source.bitmap.data[liIdx + 1];
blue += source.bitmap.data[riIdx + 2] - source.bitmap.data[liIdx + 2];
alpha += source.bitmap.data[riIdx + 3] - source.bitmap.data[liIdx + 3];
const tiIdx = source.getPixelIndex(i, ti++);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
for (let j = height - radius; j < height; j++) {
const liIdx = source.getPixelIndex(i, li++);
red += lastValRed - source.bitmap.data[liIdx];
green += lastValGreen - source.bitmap.data[liIdx + 1];
blue += lastValBlue - source.bitmap.data[liIdx + 2];
alpha += lastValAlpha - source.bitmap.data[liIdx + 3];
const tiIdx = source.getPixelIndex(i, ti++);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
}
return output;
}

View File

@ -1,8 +1,8 @@
import OperationConfig from "../config/OperationConfig.json";
import Utils from "../Utils";
import Recipe from "../Recipe";
import Dish from "../Dish";
import {detectFileType} from "./FileType";
import Utils, { isWorkerEnvironment } from "../Utils.mjs";
import Recipe from "../Recipe.mjs";
import Dish from "../Dish.mjs";
import {detectFileType} from "./FileType.mjs";
import chiSquared from "chi-squared";
/**
@ -312,6 +312,11 @@ class Magic {
return;
}
// If the recipe returned an empty buffer, do not continue
if (_buffersEqual(output, new ArrayBuffer())) {
return;
}
const magic = new Magic(output, this.opPatterns),
speculativeResults = await magic.speculativeExecution(
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful, crib);
@ -333,7 +338,7 @@ class Magic {
}
// Prune branches that result in unhelpful outputs
results = results.filter(r =>
const prunedResults = results.filter(r =>
(r.useful || r.data.length > 0) && // The operation resulted in ""
( // One of the following must be true
r.languageScores[0].probability > 0 || // Some kind of language was found
@ -344,7 +349,7 @@ class Magic {
);
// Return a sorted list of possible recipes along with their properties
return results.sort((a, b) => {
return prunedResults.sort((a, b) => {
// Each option is sorted based on its most likely language (lower is better)
let aScore = a.languageScores[0].score,
bScore = b.languageScores[0].score;
@ -390,12 +395,17 @@ class Magic {
const dish = new Dish();
dish.set(input, Dish.ARRAY_BUFFER);
if (ENVIRONMENT_IS_WORKER()) self.loadRequiredModules(recipeConfig);
if (isWorkerEnvironment()) self.loadRequiredModules(recipeConfig);
const recipe = new Recipe(recipeConfig);
try {
await recipe.execute(dish);
return dish.get(Dish.ARRAY_BUFFER);
// Return an empty buffer if the recipe did not run to completion
if (recipe.lastRunOp === recipe.opList[recipe.opList.length - 1]) {
return dish.get(Dish.ARRAY_BUFFER);
} else {
return new ArrayBuffer();
}
} catch (err) {
// If there are errors, return an empty buffer
return new ArrayBuffer();
@ -440,7 +450,7 @@ class Magic {
const opPatterns = [];
for (const op in OperationConfig) {
if (!OperationConfig[op].hasOwnProperty("patterns")) continue;
if (!("patterns" in OperationConfig[op])) continue;
OperationConfig[op].patterns.forEach(pattern => {
opPatterns.push({

View File

@ -10,7 +10,8 @@
*
*/
import OperationError from "../errors/OperationError";
import OperationError from "../errors/OperationError.mjs";
import { isWorkerEnvironment } from "../Utils.mjs";
import kbpgp from "kbpgp";
import * as es6promisify from "es6-promisify";
const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
@ -45,7 +46,7 @@ export const ASP = kbpgp.ASP({
msg = `Stage: ${info.what}`;
}
if (ENVIRONMENT_IS_WORKER())
if (isWorkerEnvironment())
self.sendStatusMessage(msg);
}
});

View File

@ -1,4 +1,4 @@
import Utils from "../Utils";
import Utils from "../Utils.mjs";
/**
* Protobuf lib. Contains functions to decode protobuf serialised
@ -16,14 +16,14 @@ class Protobuf {
/**
* Protobuf constructor
*
* @param {byteArray} data
* @param {byteArray|Uint8Array} data
*/
constructor(data) {
// Check we have a byteArray
if (data instanceof Array) {
// Check we have a byteArray or Uint8Array
if (data instanceof Array || data instanceof Uint8Array) {
this.data = data;
} else {
throw new Error("Protobuf input must be a byteArray");
throw new Error("Protobuf input must be a byteArray or Uint8Array");
}
// Set up masks
@ -124,7 +124,7 @@ class Protobuf {
// Get the field key/values
const key = field.key;
const value = field.value;
object[key] = object.hasOwnProperty(key) ?
object[key] = Object.prototype.hasOwnProperty.call(object, key) ?
object[key] instanceof Array ?
object[key].concat([value]) :
[object[key], value] :

View File

@ -6,7 +6,7 @@
* @license Apache-2.0
*/
import { toHex, fromHex } from "./Hex";
import { toHex, fromHex } from "./Hex.mjs";
/**
* Formats Distinguished Name (DN) strings.

93
src/core/lib/QRCode.mjs Normal file
View File

@ -0,0 +1,93 @@
/**
* QR code resources
*
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError.mjs";
import jsQR from "jsqr";
import qr from "qr-image";
import jimp from "jimp";
import Utils from "../Utils.mjs";
/**
* Parses a QR code image from an image
*
* @param {ArrayBuffer} input
* @param {boolean} normalise
* @returns {string}
*/
export async function parseQrCode(input, normalise) {
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error opening image. (${err})`);
}
try {
if (normalise) {
image.rgba(false);
image.background(0xFFFFFFFF);
image.normalize();
image.greyscale();
image = await image.getBufferAsync(jimp.MIME_JPEG);
image = await jimp.read(image);
}
} catch (err) {
throw new OperationError(`Error normalising iamge. (${err})`);
}
const qrData = jsQR(image.bitmap.data, image.getWidth(), image.getHeight());
if (qrData) {
return qrData.data;
} else {
throw new OperationError("Could not read a QR code from the image.");
}
}
/**
* Generates a QR code from the input string
*
* @param {string} input
* @param {string} format
* @param {number} moduleSize
* @param {number} margin
* @param {string} errorCorrection
* @returns {ArrayBuffer}
*/
export function generateQrCode(input, format, moduleSize, margin, errorCorrection) {
const formats = ["SVG", "EPS", "PDF", "PNG"];
if (!formats.includes(format.toUpperCase())) {
throw new OperationError("Unsupported QR code format.");
}
let qrImage;
try {
qrImage = qr.imageSync(input, {
type: format,
size: moduleSize,
margin: margin,
"ec_level": errorCorrection.charAt(0).toUpperCase()
});
} catch (err) {
throw new OperationError(`Error generating QR code. (${err})`);
}
if (!qrImage) {
throw new OperationError("Error generating QR code.");
}
switch (format) {
case "SVG":
case "EPS":
case "PDF":
return Utils.strToArrayBuffer(qrImage);
case "PNG":
return qrImage.buffer;
default:
throw new OperationError("Unsupported QR code format.");
}
}

View File

@ -21,7 +21,7 @@ export default class TLVParser {
/**
* TLVParser constructor
*
* @param {byteArray} input
* @param {byteArray|Uint8Array} input
* @param {Object} options
*/
constructor(input, options) {

View File

@ -6,9 +6,9 @@
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import * as Enigma from "../lib/Enigma";
import Utils from "../Utils";
import OperationError from "../errors/OperationError.mjs";
import * as Enigma from "../lib/Enigma.mjs";
import Utils from "../Utils.mjs";
/**
* A set of example Typex rotors. No Typex rotor wirings are publicly available, so these are
@ -98,14 +98,14 @@ export class TypexMachine extends Enigma.EnigmaBase {
if (x === " ") {
inputMod += "X";
} else if (mode) {
if (KEYBOARD_REV.hasOwnProperty(x)) {
if (Object.prototype.hasOwnProperty.call(KEYBOARD_REV, x)) {
inputMod += KEYBOARD_REV[x];
} else {
mode = false;
inputMod += "V" + x;
}
} else {
if (KEYBOARD_REV.hasOwnProperty(x)) {
if (Object.prototype.hasOwnProperty.call(KEYBOARD_REV, x)) {
mode = true;
inputMod += "Z" + KEYBOARD_REV[x];
} else {

View File

@ -6,7 +6,7 @@
* @license Apache-2.0
*/
import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min";
import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min.js";
const Zlib = zlibAndGzip.Zlib;

View File

@ -4,10 +4,10 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {DELIM_OPTIONS} from "../lib/Delim";
import OperationError from "../errors/OperationError";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import {DELIM_OPTIONS} from "../lib/Delim.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* A1Z26 Cipher Decode operation

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {DELIM_OPTIONS} from "../lib/Delim";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import {DELIM_OPTIONS} from "../lib/Delim.mjs";
/**
* A1Z26 Cipher Encode operation

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import { bitOp, add, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import { bitOp, add, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs";
/**
* ADD operation

View File

@ -4,10 +4,10 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import forge from "node-forge/dist/forge.min.js";
import OperationError from "../errors/OperationError";
import OperationError from "../errors/OperationError.mjs";
/**
* AES Decrypt operation

View File

@ -4,10 +4,10 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import forge from "node-forge/dist/forge.min.js";
import OperationError from "../errors/OperationError";
import OperationError from "../errors/OperationError.mjs";
/**
* AES Encrypt operation
@ -65,8 +65,8 @@ class AESEncrypt extends Operation {
* @throws {OperationError} if invalid key length
*/
run(input, args) {
const key = Utils.convertToByteArray(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteString(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import { bitOp, and, BITWISE_OP_DELIMS } from "../lib/BitwiseOp";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import { bitOp, and, BITWISE_OP_DELIMS } from "../lib/BitwiseOp.mjs";
/**
* AND operation

View File

@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
/**
* Add line numbers operation

View File

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

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
/**
* Adler-32 Checksum operation
@ -22,13 +22,13 @@ class Adler32Checksum extends Operation {
this.module = "Crypto";
this.description = "Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995, and is a modification of the Fletcher checksum. Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter).<br><br>Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32.";
this.infoURL = "https://wikipedia.org/wiki/Adler-32";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
@ -36,6 +36,7 @@ class Adler32Checksum extends Operation {
const MOD_ADLER = 65521;
let a = 1,
b = 0;
input = new Uint8Array(input);
for (let i = 0; i < input.length; i++) {
a += input[i];

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import OperationError from "../errors/OperationError";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Affine Cipher Decode operation

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import { affineEncode } from "../lib/Ciphers";
import Operation from "../Operation.mjs";
import { affineEncode } from "../lib/Ciphers.mjs";
/**
* Affine Cipher Encode operation

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Analyse hash operation

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import { affineEncode } from "../lib/Ciphers";
import Operation from "../Operation.mjs";
import { affineEncode } from "../lib/Ciphers.mjs";
/**
* Atbash Cipher operation

View File

@ -4,11 +4,11 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import blakejs from "blakejs";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import { toBase64 } from "../lib/Base64";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import { toBase64 } from "../lib/Base64.mjs";
/**
* BLAKE2b operation

View File

@ -4,11 +4,11 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import blakejs from "blakejs";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import { toBase64 } from "../lib/Base64";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import { toBase64 } from "../lib/Base64.mjs";
/**
* BLAKE2s Operation

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import bson from "bson";
import OperationError from "../errors/OperationError";
import OperationError from "../errors/OperationError.mjs";
/**
* BSON deserialise operation

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import bson from "bson";
import OperationError from "../errors/OperationError";
import OperationError from "../errors/OperationError.mjs";
/**
* BSON serialise operation

View File

@ -4,8 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import bcrypt from "bcryptjs";
import { isWorkerEnvironment } from "../Utils.mjs";
/**
* Bcrypt operation
@ -44,7 +45,7 @@ class Bcrypt extends Operation {
return await bcrypt.hash(input, salt, null, p => {
// Progress callback
if (ENVIRONMENT_IS_WORKER())
if (isWorkerEnvironment())
self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);
});

View File

@ -4,8 +4,10 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import bcrypt from "bcryptjs";
import { isWorkerEnvironment } from "../Utils.mjs";
/**
* Bcrypt compare operation
@ -43,7 +45,7 @@ class BcryptCompare extends Operation {
const match = await bcrypt.compare(input, hash, null, p => {
// Progress callback
if (ENVIRONMENT_IS_WORKER())
if (isWorkerEnvironment())
self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);
});

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import bcrypt from "bcryptjs";
/**

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import { genPolybiusSquare } from "../lib/Ciphers";
import OperationError from "../errors/OperationError";
import Operation from "../Operation.mjs";
import { genPolybiusSquare } from "../lib/Ciphers.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Bifid Cipher Decode operation

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { genPolybiusSquare } from "../lib/Ciphers";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { genPolybiusSquare } from "../lib/Ciphers.mjs";
/**
* Bifid Cipher Encode operation

View File

@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
/**
* Bit shift left operation
@ -21,8 +21,8 @@ class BitShiftLeft extends Operation {
this.module = "Default";
this.description = "Shifts the bits in each byte towards the left by the specified amount.";
this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
"name": "Amount",
@ -33,16 +33,17 @@ class BitShiftLeft extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
* @returns {ArrayBuffer}
*/
run(input, args) {
const amount = args[0];
input = new Uint8Array(input);
return input.map(b => {
return (b << amount) & 0xff;
});
}).buffer;
}
/**

View File

@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
/**
* Bit shift right operation
@ -21,8 +21,8 @@ class BitShiftRight extends Operation {
this.module = "Default";
this.description = "Shifts the bits in each byte towards the right by the specified amount.<br><br><i>Logical shifts</i> replace the leftmost bits with zeros.<br><i>Arithmetic shifts</i> preserve the most significant bit (MSB) of the original byte keeping the sign the same (positive or negative).";
this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#Bit_shifts";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
"name": "Amount",
@ -38,18 +38,19 @@ class BitShiftRight extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
* @returns {ArrayBuffer}
*/
run(input, args) {
const amount = args[0],
type = args[1],
mask = type === "Logical shift" ? 0 : 0x80;
input = new Uint8Array(input);
return input.map(b => {
return (b >>> amount) ^ (b & mask);
});
}).buffer;
}
/**

View File

@ -4,12 +4,12 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import OperationError from "../errors/OperationError";
import { Blowfish } from "../vendor/Blowfish";
import { toBase64 } from "../lib/Base64";
import { toHexFast } from "../lib/Hex";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import { Blowfish } from "../vendor/Blowfish.mjs";
import { toBase64 } from "../lib/Base64.mjs";
import { toHexFast } from "../lib/Hex.mjs";
/**
* Lookup table for Blowfish output types.

View File

@ -4,11 +4,11 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import OperationError from "../errors/OperationError";
import { Blowfish } from "../vendor/Blowfish";
import { toBase64 } from "../lib/Base64";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import { Blowfish } from "../vendor/Blowfish.mjs";
import { toBase64 } from "../lib/Base64.mjs";
/**
* Lookup table for Blowfish output types.

View File

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

View File

@ -6,10 +6,11 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {BombeMachine} from "../lib/Bombe";
import {ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector} from "../lib/Enigma";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { isWorkerEnvironment } from "../Utils.mjs";
import { BombeMachine } from "../lib/Bombe.mjs";
import { ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector } from "../lib/Enigma.mjs";
/**
* Bombe operation
@ -139,7 +140,7 @@ class Bombe extends Operation {
const ciphertext = input.slice(offset);
const reflector = new Reflector(reflectorstr);
let update;
if (ENVIRONMENT_IS_WORKER()) {
if (isWorkerEnvironment()) {
update = this.updateStatus;
} else {
update = undefined;

View File

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

View File

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

View File

@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import JSCRC from "js-crc";
/**

View File

@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import JSCRC from "js-crc";
/**

View File

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

View File

@ -5,7 +5,7 @@
*/
import vkbeautify from "vkbeautify";
import Operation from "../Operation";
import Operation from "../Operation.mjs";
/**
* CSS Beautify operation

View File

@ -5,7 +5,7 @@
*/
import vkbeautify from "vkbeautify";
import Operation from "../Operation";
import Operation from "../Operation.mjs";
/**
* CSS Minify operation

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import xmldom from "xmldom";
import nwmatcher from "nwmatcher";

View File

@ -4,9 +4,9 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
/**
* CSV to JSON operation

View File

@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import Operation from "../Operation.mjs";
import ctphjs from "ctph.js";
/**

View File

@ -4,8 +4,8 @@
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Set cartesian product operation

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