mirror of
https://github.com/gchq/CyberChef.git
synced 2024-11-02 14:11:02 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
d3138a7fdf
@ -1 +1 @@
|
||||
src/core/vendor/**
|
||||
src/core/vendor/**
|
@ -38,9 +38,12 @@
|
||||
|
||||
// disable rules from base configurations
|
||||
"no-control-regex": "off",
|
||||
"require-atomic-updates": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
|
||||
// stylistic conventions
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"block-spacing": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"comma-spacing": "error",
|
||||
@ -106,9 +109,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
|
||||
}
|
||||
}
|
||||
|
12
.github/CONTRIBUTING.md
vendored
12
.github/CONTRIBUTING.md
vendored
@ -6,7 +6,7 @@ There are lots of opportunities to contribute to CyberChef. If you want ideas, t
|
||||
|
||||
Before your contributions can be accepted, you must:
|
||||
|
||||
- Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0)
|
||||
- Sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef)
|
||||
- Push your changes to your fork.
|
||||
- Submit a pull request.
|
||||
|
||||
@ -22,15 +22,15 @@ 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.
|
||||
3. Use Vanilla JS if at all possible to reduce the number of libraries required and relied upon. Frameworks like jQuery, although included, should not be used unless absolutely necessary.
|
||||
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).
|
||||
2. Latency should be kept to a minimum to enhance the user experience. This means that operation code should sit on the client and be executed there. However, as a trade-off between latency and bandwidth, operation code with large dependencies can be loaded in discrete modules in order to reduce the size of the initial download. The downloading of additional modules must remain entirely transparent so that the user is not inconvenienced.
|
||||
3. Large libraries should be kept in separate modules so that they are not downloaded by everyone who uses the app, just those who specifically require the relevant operations.
|
||||
4. Use Vanilla JS if at all possible to reduce the number of libraries required and relied upon. Frameworks like jQuery, although included, should not be used unless absolutely necessary.
|
||||
|
||||
|
||||
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
|
||||
|
15
.github/ISSUE_TEMPLATE.md
vendored
15
.github/ISSUE_TEMPLATE.md
vendored
@ -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:' -->
|
||||
|
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: 'Bug report: <Insert title here>'
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour 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 behaviour**
|
||||
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 72, firefox 60]
|
||||
- CyberChef version: [e.g. 9.7.14]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for the project
|
||||
title: 'Feature request: <Insert title here>'
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. E.g. 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.
|
14
.github/ISSUE_TEMPLATE/operation-request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/operation-request.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Operation request
|
||||
about: Suggest a new operation
|
||||
title: 'Operation request: <Insert title here>'
|
||||
labels: operation
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### Example Input
|
||||
|
||||
### Example Output
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -2,14 +2,13 @@ node_modules
|
||||
npm-debug.log
|
||||
travis.log
|
||||
build
|
||||
docs/*
|
||||
!docs/*.conf.json
|
||||
!docs/*.ico
|
||||
.vscode
|
||||
.*.swp
|
||||
.DS_Store
|
||||
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/*
|
||||
|
||||
|
@ -3,6 +3,5 @@ npm-debug.log
|
||||
travis.log
|
||||
build/*
|
||||
!build/node
|
||||
docs
|
||||
.vscode
|
||||
.github
|
||||
|
11
.travis.yml
11
.travis.yml
@ -1,6 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- node
|
||||
- lts/dubnium
|
||||
cache: npm
|
||||
addons:
|
||||
chrome: stable
|
||||
install: npm install
|
||||
@ -10,8 +11,7 @@ before_script:
|
||||
script:
|
||||
- grunt lint
|
||||
- grunt test
|
||||
- grunt docs
|
||||
- grunt node
|
||||
- grunt testnodeconsumer
|
||||
- grunt prod --msg="$COMPILE_MSG"
|
||||
- xvfb-run --server-args="-screen 0 1200x800x24" grunt testui
|
||||
before_deploy:
|
||||
@ -30,9 +30,10 @@ deploy:
|
||||
skip_cleanup: true
|
||||
api_key:
|
||||
secure: "HV1WSKv4l/0Y2bKKs1iBJocBcmLj08PCRUeEM/jTwA4jqJ8EiLHWiXtER/D5sEg2iibRVKd2OQjfrmS6bo4AiwdeVgAKmv0FtS2Jw+391N8Nd5AkEANHa5Om/IpHLTL2YRAjpJTsDpY72bMUTJIwjQA3TFJkgrpOw6KYfohOcgbxLpZ4XuNJRU3VL4Hsxdv5V9aOVmfFOmMOVPQlakXy7NgtW5POp1f2WJwgcZxylkR1CjwaqMyXmSoVl46pyH3tr5+dptsQoKSGdi6sIHGA60oDotFPcm+0ifa47wZw+vapuuDi4tdNxhrHGaDMG8xiE0WFDHwQUDlk2/+W7j9SEX0H3Em7us371JXRp56EDwEcDa34VpVkC6i8HGcHK55hnxVbMZXGf3qhOFD8wY7qMbjMRvIpucrMHBi86OfkDfv0vDj2LyvIl5APj/AX50BrE0tfH1MZbH26Jkx4NdlkcxQ14GumarmUqfmVvbX/fsoA6oUuAAE9ZgRRi3KHO4wci6KUcRfdm+XOeUkaBFsL86G3EEYIvrtBTuaypdz+Cx7nd1iPZyWMx5Y1gXnVzha4nBdV4+7l9JIsFggD8QVpw2uHXQiS1KXFjOeqA3DBD8tjMB7q26Fl2fD3jkOo4BTbQ2NrRIZUu/iL+fOmMPsyMt2qulB0yaSBCfkbEq8xrUA="
|
||||
file_glob: true
|
||||
file:
|
||||
- build/prod/cyberchef.htm
|
||||
- build/node/CyberChef.js
|
||||
- build/prod/*.zip
|
||||
- src/node/cjs.js
|
||||
on:
|
||||
repo: gchq/CyberChef
|
||||
tags: true
|
||||
|
111
CHANGELOG.md
111
CHANGELOG.md
@ -2,6 +2,71 @@
|
||||
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.7.0] - 2019-09-13
|
||||
- 'Optical Character Recognition' operation added [@MShwed] [@n1474335] | [#632]
|
||||
|
||||
### [9.6.0] - 2019-09-04
|
||||
- 'Bacon Cipher Encode' and 'Bacon Cipher Decode' operations added [@kassi] | [#500]
|
||||
|
||||
### [9.5.0] - 2019-09-04
|
||||
- Various Steganography operations added: 'Extract LSB', 'Extract RGBA', 'Randomize Colour Palette', and 'View Bit Plane' [@Ge0rg3] | [#625]
|
||||
|
||||
### [9.4.0] - 2019-08-30
|
||||
- 'Render Markdown' operation added [@j433866] | [#627]
|
||||
|
||||
### [9.3.0] - 2019-08-30
|
||||
- 'Show on map' operation added [@j433866] | [#477]
|
||||
|
||||
### [9.2.0] - 2019-08-23
|
||||
- 'Parse UDP' operation added [@h345983745] | [#614]
|
||||
|
||||
### [9.1.0] - 2019-08-22
|
||||
- 'Parse SSH Host Key' operation added [@j433866] | [#595]
|
||||
- 'Defang IP Addresses' operation added [@h345983745] | [#556]
|
||||
|
||||
## [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]
|
||||
|
||||
<details>
|
||||
<summary>Click to expand v8 minor versions</summary>
|
||||
|
||||
### [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]
|
||||
|
||||
### [8.30.0] - 2019-04-12
|
||||
- 'Decode Protobuf' operation added [@n1474335] | [#533]
|
||||
|
||||
### [8.29.0] - 2019-03-31
|
||||
- 'BLAKE2s' and 'BLAKE2b' hashing operations added [@h345983745] | [#525]
|
||||
|
||||
### [8.28.0] - 2019-03-31
|
||||
- 'Heatmap Chart', 'Hex Density Chart', 'Scatter Chart' and 'Series Chart' operation added [@artemisbot] [@tlwr] | [#496] [#143]
|
||||
|
||||
### [8.27.0] - 2019-03-14
|
||||
- 'Enigma', 'Typex', 'Bombe' and 'Multiple Bombe' operations added [@s2224834] | [#516]
|
||||
- See [this wiki article](https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex) for a full explanation of these operations.
|
||||
@ -89,6 +154,8 @@ All major and minor version changes will be documented in this file. Details of
|
||||
### [8.1.0] - 2018-08-19
|
||||
- 'Dechunk HTTP response' operation added [@sevzero] | [#311]
|
||||
|
||||
</details>
|
||||
|
||||
## [8.0.0] - 2018-08-05
|
||||
- Codebase rewritten using [ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) and [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) [@n1474335] [@d98762625] [@artemisbot] [@picapi] | [#284]
|
||||
- Operation architecture restructured to make adding new operations a lot simpler [@n1474335] | [#284]
|
||||
@ -118,6 +185,25 @@ All major and minor version changes will be documented in this file. Details of
|
||||
|
||||
|
||||
|
||||
[9.7.0]: https://github.com/gchq/CyberChef/releases/tag/v9.7.0
|
||||
[9.6.0]: https://github.com/gchq/CyberChef/releases/tag/v9.6.0
|
||||
[9.5.0]: https://github.com/gchq/CyberChef/releases/tag/v9.5.0
|
||||
[9.4.0]: https://github.com/gchq/CyberChef/releases/tag/v9.4.0
|
||||
[9.3.0]: https://github.com/gchq/CyberChef/releases/tag/v9.3.0
|
||||
[9.2.0]: https://github.com/gchq/CyberChef/releases/tag/v9.2.0
|
||||
[9.1.0]: https://github.com/gchq/CyberChef/releases/tag/v9.1.0
|
||||
[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
|
||||
[8.28.0]: https://github.com/gchq/CyberChef/releases/tag/v8.28.0
|
||||
[8.27.0]: https://github.com/gchq/CyberChef/releases/tag/v8.27.0
|
||||
[8.26.0]: https://github.com/gchq/CyberChef/releases/tag/v8.26.0
|
||||
[8.25.0]: https://github.com/gchq/CyberChef/releases/tag/v8.25.0
|
||||
@ -159,6 +245,7 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[@h345983745]: https://github.com/h345983745
|
||||
[@s2224834]: https://github.com/s2224834
|
||||
[@artemisbot]: https://github.com/artemisbot
|
||||
[@tlwr]: https://github.com/tlwr
|
||||
[@picapi]: https://github.com/picapi
|
||||
[@Dachande663]: https://github.com/Dachande663
|
||||
[@JustAnotherMark]: https://github.com/JustAnotherMark
|
||||
@ -172,9 +259,13 @@ 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
|
||||
[@kassi]: https://github.com/kassi
|
||||
|
||||
[#95]: https://github.com/gchq/CyberChef/pull/299
|
||||
[#173]: https://github.com/gchq/CyberChef/pull/173
|
||||
[#143]: https://github.com/gchq/CyberChef/pull/143
|
||||
[#224]: https://github.com/gchq/CyberChef/pull/224
|
||||
[#239]: https://github.com/gchq/CyberChef/pull/239
|
||||
[#248]: https://github.com/gchq/CyberChef/pull/248
|
||||
@ -182,6 +273,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
|
||||
@ -208,6 +300,25 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[#467]: https://github.com/gchq/CyberChef/pull/467
|
||||
[#468]: https://github.com/gchq/CyberChef/pull/468
|
||||
[#476]: https://github.com/gchq/CyberChef/pull/476
|
||||
[#477]: https://github.com/gchq/CyberChef/pull/477
|
||||
[#489]: https://github.com/gchq/CyberChef/pull/489
|
||||
[#496]: https://github.com/gchq/CyberChef/pull/496
|
||||
[#500]: https://github.com/gchq/CyberChef/pull/500
|
||||
[#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
|
||||
[#556]: https://github.com/gchq/CyberChef/pull/556
|
||||
[#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
|
||||
[#595]: https://github.com/gchq/CyberChef/pull/595
|
||||
[#614]: https://github.com/gchq/CyberChef/pull/614
|
||||
[#625]: https://github.com/gchq/CyberChef/pull/625
|
||||
[#627]: https://github.com/gchq/CyberChef/pull/627
|
||||
[#632]: https://github.com/gchq/CyberChef/pull/632
|
||||
|
290
Gruntfile.js
290
Gruntfile.js
@ -3,8 +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 Inliner = require("web-resource-inliner");
|
||||
const glob = require("glob");
|
||||
const path = require("path");
|
||||
|
||||
@ -25,37 +23,38 @@ 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("prod",
|
||||
"Creates a production-ready build. Use the --msg flag to add a compile message.",
|
||||
[
|
||||
"eslint", "clean:prod", "clean:config", "exec:generateConfig", "webpack:web",
|
||||
"copy:standalone", "zip:standalone", "clean:standalone", "chmod"
|
||||
]);
|
||||
|
||||
grunt.registerTask("node",
|
||||
"Compiles CyberChef into a single NodeJS module.",
|
||||
["clean:node", "clean:config", "exec:generateConfig", "webpack:node", "chmod:build"]);
|
||||
[
|
||||
"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.",
|
||||
["exec:generateConfig", "exec:opTests"]);
|
||||
[
|
||||
"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("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.",
|
||||
["eslint", "clean:prod", "clean:config", "exec:generateConfig", "webpack:web", "inline", "chmod"]);
|
||||
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("default",
|
||||
"Lints the code base",
|
||||
["eslint", "exec:repoSize"]);
|
||||
|
||||
grunt.registerTask("inline",
|
||||
"Compiles a production build of CyberChef into a single, portable web page.",
|
||||
["exec:generateConfig", "webpack:webInline", "runInliner", "clean:inlineScripts"]);
|
||||
|
||||
|
||||
grunt.registerTask("runInliner", runInliner);
|
||||
grunt.registerTask("doc", "docs");
|
||||
grunt.registerTask("tests", "test");
|
||||
grunt.registerTask("lint", "eslint");
|
||||
|
||||
@ -63,7 +62,6 @@ module.exports = function (grunt) {
|
||||
// Load tasks provided by each plugin
|
||||
grunt.loadNpmTasks("grunt-eslint");
|
||||
grunt.loadNpmTasks("grunt-webpack");
|
||||
grunt.loadNpmTasks("grunt-jsdoc");
|
||||
grunt.loadNpmTasks("grunt-contrib-clean");
|
||||
grunt.loadNpmTasks("grunt-contrib-copy");
|
||||
grunt.loadNpmTasks("grunt-contrib-watch");
|
||||
@ -72,6 +70,7 @@ module.exports = function (grunt) {
|
||||
grunt.loadNpmTasks("grunt-accessibility");
|
||||
grunt.loadNpmTasks("grunt-concurrent");
|
||||
grunt.loadNpmTasks("grunt-contrib-connect");
|
||||
grunt.loadNpmTasks("grunt-zip");
|
||||
|
||||
|
||||
// Project configuration
|
||||
@ -82,44 +81,10 @@ 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";
|
||||
|
||||
/**
|
||||
* Compiles a production build of CyberChef into a single, portable web page.
|
||||
*/
|
||||
function runInliner() {
|
||||
const done = this.async();
|
||||
Inliner.html({
|
||||
relativeTo: "build/prod/",
|
||||
fileContent: grunt.file.read("build/prod/cyberchef.htm"),
|
||||
images: true,
|
||||
svgs: true,
|
||||
scripts: true,
|
||||
links: true,
|
||||
strict: true
|
||||
}, function(error, result) {
|
||||
if (error) {
|
||||
if (error instanceof Error) {
|
||||
done(error);
|
||||
} else {
|
||||
done(new Error(error));
|
||||
}
|
||||
} else {
|
||||
grunt.file.write("build/prod/cyberchef.htm", result);
|
||||
done(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an entry list for all the modules.
|
||||
@ -130,20 +95,40 @@ module.exports = function (grunt) {
|
||||
glob.sync("./src/core/config/modules/*.mjs").forEach(file => {
|
||||
const basename = path.basename(file);
|
||||
if (basename !== "Default.mjs" && basename !== "OpModules.mjs")
|
||||
entryModules[basename.split(".mjs")[0]] = path.resolve(file);
|
||||
entryModules["modules/" + basename.split(".mjs")[0]] = path.resolve(file);
|
||||
});
|
||||
|
||||
return entryModules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the correct delimiter to use to chain shell commands together
|
||||
* based on the current OS.
|
||||
*
|
||||
* @param {string[]} cmds
|
||||
* @returns {string}
|
||||
*/
|
||||
function chainCommands(cmds) {
|
||||
const win = process.platform === "win32";
|
||||
if (!win) {
|
||||
return cmds.join(";");
|
||||
}
|
||||
return cmds
|
||||
// && means that subsequent commands will not be executed if the
|
||||
// previous one fails. & would coninue on a fail
|
||||
.join("&&")
|
||||
// Windows does not support \n properly
|
||||
.replace("\n", "\\n");
|
||||
}
|
||||
|
||||
grunt.initConfig({
|
||||
clean: {
|
||||
dev: ["build/dev/*"],
|
||||
prod: ["build/prod/*"],
|
||||
node: ["build/node/*"],
|
||||
config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"],
|
||||
docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
|
||||
inlineScripts: ["build/prod/scripts.js"],
|
||||
nodeConfig: ["src/node/index.mjs", "src/node/config/OperationConfig.json"],
|
||||
standalone: ["build/prod/CyberChef*.html"]
|
||||
},
|
||||
eslint: {
|
||||
options: {
|
||||
@ -151,26 +136,10 @@ module.exports = function (grunt) {
|
||||
},
|
||||
configs: ["*.{js,mjs}"],
|
||||
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
|
||||
web: ["src/web/**/*.{js,mjs}"],
|
||||
web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"],
|
||||
node: ["src/node/**/*.{js,mjs}"],
|
||||
tests: ["tests/**/*.{js,mjs}"],
|
||||
},
|
||||
jsdoc: {
|
||||
options: {
|
||||
destination: "docs",
|
||||
template: "node_modules/ink-docstrap/template",
|
||||
recurse: true,
|
||||
readme: "./README.md",
|
||||
configure: "docs/jsdoc.conf.json"
|
||||
},
|
||||
all: {
|
||||
src: [
|
||||
"src/**/*.js",
|
||||
"src/**/*.mjs",
|
||||
"!src/core/vendor/**/*"
|
||||
],
|
||||
}
|
||||
},
|
||||
accessibility: {
|
||||
options: {
|
||||
accessibilityLevel: "WCAG2A",
|
||||
@ -190,16 +159,18 @@ module.exports = function (grunt) {
|
||||
mode: "production",
|
||||
target: "web",
|
||||
entry: Object.assign({
|
||||
main: "./src/web/index.js",
|
||||
sitemap: "./src/web/static/sitemap.js"
|
||||
main: "./src/web/index.js"
|
||||
}, moduleEntryPoints),
|
||||
output: {
|
||||
path: __dirname + "/build/prod",
|
||||
filename: chunkData => {
|
||||
return chunkData.chunk.name === "main" ? "assets/[name].js": "[name].js";
|
||||
},
|
||||
globalObject: "this"
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"./config/modules/OpModules": "./config/modules/Default"
|
||||
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
@ -225,48 +196,6 @@ module.exports = function (grunt) {
|
||||
]
|
||||
};
|
||||
},
|
||||
webInline: {
|
||||
mode: "production",
|
||||
target: "web",
|
||||
entry: "./src/web/index.js",
|
||||
output: {
|
||||
filename: "scripts.js",
|
||||
path: __dirname + "/build/prod"
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(Object.assign({}, BUILD_CONSTANTS, {
|
||||
INLINE: "true"
|
||||
})),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "cyberchef.htm",
|
||||
template: "./src/web/html/index.html",
|
||||
compileTime: compileTime,
|
||||
version: pkg.version + "s",
|
||||
inline: true,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true
|
||||
}
|
||||
}),
|
||||
]
|
||||
},
|
||||
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: {
|
||||
@ -284,7 +213,8 @@ module.exports = function (grunt) {
|
||||
warningsFilter: [
|
||||
/source-map/,
|
||||
/dependency is an expression/,
|
||||
/export 'default'/
|
||||
/export 'default'/,
|
||||
/Can't resolve 'sodium'/
|
||||
],
|
||||
}
|
||||
},
|
||||
@ -297,12 +227,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({
|
||||
@ -316,6 +243,17 @@ module.exports = function (grunt) {
|
||||
}
|
||||
}
|
||||
},
|
||||
zip: {
|
||||
standalone: {
|
||||
cwd: "build/prod/",
|
||||
src: [
|
||||
"build/prod/**/*",
|
||||
"!build/prod/index.html",
|
||||
"!build/prod/BundleAnalyzerReport.html",
|
||||
],
|
||||
dest: `build/prod/CyberChef_v${pkg.version}.zip`
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
prod: {
|
||||
options: {
|
||||
@ -328,10 +266,16 @@ module.exports = function (grunt) {
|
||||
ghPages: {
|
||||
options: {
|
||||
process: function (content, srcpath) {
|
||||
// Add Google Analytics code to index.html
|
||||
if (srcpath.indexOf("index.html") >= 0) {
|
||||
// Add Google Analytics code to index.html
|
||||
content = content.replace("</body></html>",
|
||||
grunt.file.read("src/web/static/ga.html") + "</body></html>");
|
||||
|
||||
// Add Structured Data for SEO
|
||||
content = content.replace("</head>",
|
||||
"<script type='application/ld+json'>" +
|
||||
JSON.stringify(JSON.parse(grunt.file.read("src/web/static/structuredData.json"))) +
|
||||
"</script></head>");
|
||||
return grunt.template.process(content, srcpath);
|
||||
} else {
|
||||
return content;
|
||||
@ -343,12 +287,29 @@ module.exports = function (grunt) {
|
||||
{
|
||||
src: "build/prod/index.html",
|
||||
dest: "build/prod/index.html"
|
||||
}
|
||||
]
|
||||
},
|
||||
standalone: {
|
||||
options: {
|
||||
process: function (content, srcpath) {
|
||||
if (srcpath.indexOf("index.html") >= 0) {
|
||||
// Replace download link with version number
|
||||
content = content.replace(/<a [^>]+>Download CyberChef.+?<\/a>/,
|
||||
`<span>Version ${pkg.version}</span>`);
|
||||
|
||||
return grunt.template.process(content, srcpath);
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
},
|
||||
noProcess: ["**", "!**/*.html"]
|
||||
},
|
||||
files: [
|
||||
{
|
||||
expand: true,
|
||||
src: "docs/**",
|
||||
dest: "build/prod/"
|
||||
},
|
||||
src: "build/prod/index.html",
|
||||
dest: `build/prod/CyberChef_v${pkg.version}.html`
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -358,18 +319,12 @@ module.exports = function (grunt) {
|
||||
mode: "755",
|
||||
},
|
||||
src: ["build/**/*", "build/"]
|
||||
},
|
||||
docs: {
|
||||
options: {
|
||||
mode: "755",
|
||||
},
|
||||
src: ["docs/**/*", "docs/"]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
config: {
|
||||
files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"],
|
||||
tasks: ["exec:generateConfig"]
|
||||
tasks: ["exec:generateNodeIndex", "exec:generateConfig"]
|
||||
}
|
||||
},
|
||||
concurrent: {
|
||||
@ -380,33 +335,80 @@ module.exports = function (grunt) {
|
||||
},
|
||||
exec: {
|
||||
repoSize: {
|
||||
command: [
|
||||
command: chainCommands([
|
||||
"git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'",
|
||||
"du -hs | egrep -o '^[^\t]*' | xargs printf '%b\trepository size\n'"
|
||||
].join(";"),
|
||||
]),
|
||||
stderr: false
|
||||
},
|
||||
cleanGit: {
|
||||
command: "git gc --prune=now --aggressive"
|
||||
},
|
||||
sitemap: {
|
||||
command: "node build/prod/sitemap.js > build/prod/sitemap.xml"
|
||||
command: "node --experimental-modules --no-warnings --no-deprecation src/web/static/sitemap.mjs > build/prod/sitemap.xml"
|
||||
},
|
||||
generateConfig: {
|
||||
command: [
|
||||
command: chainCommands([
|
||||
"echo '\n--- Regenerating config files. ---'",
|
||||
"echo [] > src/core/config/OperationConfig.json",
|
||||
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs",
|
||||
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs",
|
||||
"echo '--- Config scripts finished. ---\n'"
|
||||
].join(";")
|
||||
])
|
||||
},
|
||||
generateNodeIndex: {
|
||||
command: chainCommands([
|
||||
"echo '\n--- Regenerating node index ---'",
|
||||
"node --experimental-modules --no-warnings --no-deprecation src/node/config/scripts/generateNodeIndex.mjs",
|
||||
"echo '--- Node index generated. ---\n'"
|
||||
]),
|
||||
},
|
||||
opTests: {
|
||||
command: "node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs"
|
||||
},
|
||||
browserTests: {
|
||||
command: "./node_modules/.bin/nightwatch --env prod,inline"
|
||||
}
|
||||
command: "./node_modules/.bin/nightwatch --env prod"
|
||||
},
|
||||
nodeTests: {
|
||||
command: "node --experimental-modules --no-warnings --no-deprecation tests/node/index.mjs"
|
||||
},
|
||||
setupNodeConsumers: {
|
||||
command: chainCommands([
|
||||
"echo '\n--- Testing node consumers ---'",
|
||||
"npm link",
|
||||
`mkdir ${nodeConsumerTestPath}`,
|
||||
`cp tests/node/consumers/* ${nodeConsumerTestPath}`,
|
||||
`cd ${nodeConsumerTestPath}`,
|
||||
"npm link cyberchef"
|
||||
]),
|
||||
},
|
||||
teardownNodeConsumers: {
|
||||
command: chainCommands([
|
||||
`rm -rf ${nodeConsumerTestPath}`,
|
||||
"echo '\n--- Node consumer tests complete ---'"
|
||||
]),
|
||||
},
|
||||
testCJSNodeConsumer: {
|
||||
command: chainCommands([
|
||||
`cd ${nodeConsumerTestPath}`,
|
||||
"node --no-warnings cjs-consumer.js",
|
||||
]),
|
||||
stdout: false,
|
||||
},
|
||||
testESMNodeConsumer: {
|
||||
command: chainCommands([
|
||||
`cd ${nodeConsumerTestPath}`,
|
||||
"node --no-warnings --experimental-modules esm-consumer.mjs",
|
||||
]),
|
||||
stdout: false,
|
||||
},
|
||||
testESMDeepImportNodeConsumer: {
|
||||
command: chainCommands([
|
||||
`cd ${nodeConsumerTestPath}`,
|
||||
"node --no-warnings --experimental-modules esm-deep-import-consumer.mjs",
|
||||
]),
|
||||
stdout: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
25
README.md
25
README.md
@ -3,7 +3,6 @@
|
||||
[![Build Status](https://travis-ci.org/gchq/CyberChef.svg?branch=master)](https://travis-ci.org/gchq/CyberChef)
|
||||
[![dependencies Status](https://david-dm.org/gchq/CyberChef/status.svg)](https://david-dm.org/gchq/CyberChef)
|
||||
[![npm](https://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef)
|
||||
![](https://reposs.herokuapp.com/?path=gchq/CyberChef&color=blue)
|
||||
[![](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/gchq/CyberChef/blob/master/LICENSE)
|
||||
[![Gitter](https://badges.gitter.im/gchq/CyberChef.svg)](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
@ -50,7 +49,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,19 +66,31 @@ 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.
|
||||
- Due to this feature, CyberChef can be downloaded and run locally. You can use the link in the top left corner of the app to download a full copy of CyberChef and drop it into a virtual machine, share it with other people, or host it in a closed network.
|
||||
|
||||
|
||||
## Deep linking
|
||||
|
||||
By manipulation of CyberChef's URL hash, you can change the initial settings with which the page opens.
|
||||
The format is `https://gchq.github.io/CyberChef/#recipe=Operation()&input=...`
|
||||
|
||||
Supported arguments are `recipe`, `input` (encoded in Base64), and `theme`.
|
||||
|
||||
|
||||
## Browser support
|
||||
|
||||
CyberChef is built to support
|
||||
|
||||
- Google Chrome 40+
|
||||
- Mozilla Firefox 35+
|
||||
- Microsoft Edge 14+
|
||||
- Google Chrome 50+
|
||||
- Mozilla Firefox 38+
|
||||
|
||||
|
||||
## 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
|
||||
|
26
SECURITY.md
Normal file
26
SECURITY.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
CyberChef is supported on a best endeavours basis. Patches will be applied to
|
||||
the latest version rather than retroactively to older versions. To ensure you
|
||||
are using the most secure version of CyberChef, please make sure you have the
|
||||
[latest release](https://github.com/gchq/CyberChef/releases/latest). The
|
||||
official [live demo](https://gchq.github.io/CyberChef/) is always up to date.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
In most scenarios, the most appropriate way to report a vulnerability is to
|
||||
[raise a new issue](https://github.com/gchq/CyberChef/issues/new/choose)
|
||||
describing the problem in as much detail as possible, ideally with examples.
|
||||
This will obviously be public. If you feel that the vulnerability is
|
||||
significant enough to warrant a private disclosure, please email
|
||||
[oss@gchq.gov.uk](mailto:oss@gchq.gov.uk) and
|
||||
[n1474335@gmail.com](mailto:n1474335@gmail.com).
|
||||
|
||||
Disclosures of vulnerabilities in CyberChef are always welcomed. Whilst we aim
|
||||
to write clean and secure code free from bugs, we recognise that this is an open
|
||||
source project written by analysts in their spare time, relying on dozens of
|
||||
open source libraries that are modified and updated on a regular basis. We hope
|
||||
that the community will continue to support us as we endeavour to maintain and
|
||||
develop this tool together.
|
@ -4,21 +4,23 @@ module.exports = function(api) {
|
||||
return {
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"chrome": 40,
|
||||
"firefox": 35,
|
||||
"edge": 14,
|
||||
"node": "6.5"
|
||||
},
|
||||
"modules": false,
|
||||
"useBuiltIns": "entry"
|
||||
"useBuiltIns": "entry",
|
||||
"corejs": 3
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"babel-plugin-syntax-dynamic-import",
|
||||
["babel-plugin-transform-builtin-extend", {
|
||||
"globals": ["Error"]
|
||||
}]
|
||||
"dynamic-import-node",
|
||||
[
|
||||
"babel-plugin-transform-builtin-extend", {
|
||||
"globals": ["Error"]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@babel/plugin-transform-runtime", {
|
||||
"regenerator": true
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
};
|
||||
|
BIN
docs/favicon.ico
BIN
docs/favicon.ico
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
@ -1,32 +0,0 @@
|
||||
{
|
||||
"tags": {
|
||||
"allowUnknownTags": true
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"node_modules/jsdoc-babel"
|
||||
],
|
||||
"templates": {
|
||||
"systemName": "CyberChef",
|
||||
"footer": "",
|
||||
"copyright": "© Crown Copyright 2017",
|
||||
"navType": "inline",
|
||||
"theme": "cerulean",
|
||||
"linenums": true,
|
||||
"collapseSymbols": false,
|
||||
"inverseNav": true,
|
||||
"outputSourceFiles": true,
|
||||
"outputSourcePath": true,
|
||||
"dateFormat": "ddd MMM Do YYYY",
|
||||
"sort": false,
|
||||
"logoFile": "cyberchef-32x32.png",
|
||||
"cleverLinks": false,
|
||||
"monospaceLinks": false,
|
||||
"protocol": "html://",
|
||||
"methodHeadingReturns": false
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "gfm",
|
||||
"hardwrap": true
|
||||
}
|
||||
}
|
@ -23,10 +23,6 @@
|
||||
|
||||
"prod": {
|
||||
"launch_url": "http://localhost:8000/index.html"
|
||||
},
|
||||
|
||||
"inline": {
|
||||
"launch_url": "http://localhost:8000/cyberchef.htm"
|
||||
}
|
||||
|
||||
}
|
||||
|
7358
package-lock.json
generated
7358
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
139
package.json
139
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "8.27.0",
|
||||
"version": "9.7.19",
|
||||
"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,109 +27,124 @@
|
||||
"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 >= 50",
|
||||
"Firefox >= 38",
|
||||
"node >= 10"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/preset-env": "^7.2.3",
|
||||
"autoprefixer": "^9.4.3",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"bootstrap": "^4.2.1",
|
||||
"chromedriver": "^2.45.0",
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-transform-runtime": "^7.5.5",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"autoprefixer": "^9.6.1",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.0",
|
||||
"chromedriver": "^77.0.0",
|
||||
"colors": "^1.3.3",
|
||||
"css-loader": "^2.1.0",
|
||||
"eslint": "^5.12.1",
|
||||
"copy-webpack-plugin": "^5.0.4",
|
||||
"css-loader": "^3.2.0",
|
||||
"eslint": "^6.2.2",
|
||||
"exports-loader": "^0.7.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"grunt": "^1.0.3",
|
||||
"file-loader": "^4.2.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.3.0",
|
||||
"grunt-webpack": "^3.1.3",
|
||||
"grunt-zip": "^0.18.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"imports-loader": "^0.8.0",
|
||||
"ink-docstrap": "^1.3.2",
|
||||
"jsdoc-babel": "^0.5.0",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"nightwatch": "^1.0.18",
|
||||
"node-sass": "^4.11.0",
|
||||
"postcss-css-variables": "^0.11.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"nightwatch": "^1.2.1",
|
||||
"node-sass": "^4.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.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"url-loader": "^1.1.2",
|
||||
"web-resource-inliner": "^4.2.1",
|
||||
"webpack": "^4.28.3",
|
||||
"webpack-bundle-analyzer": "^3.0.3",
|
||||
"webpack-dev-server": "^3.1.14",
|
||||
"sass-loader": "^8.0.0",
|
||||
"sitemap": "^4.1.1",
|
||||
"style-loader": "^1.0.0",
|
||||
"svg-url-loader": "^3.0.1",
|
||||
"url-loader": "^2.1.0",
|
||||
"webpack": "^4.39.3",
|
||||
"webpack-bundle-analyzer": "^3.4.1",
|
||||
"webpack-dev-server": "^3.8.0",
|
||||
"webpack-node-externals": "^1.7.2",
|
||||
"worker-loader": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.4.4",
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"arrive": "^2.4.1",
|
||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bignumber.js": "^8.0.2",
|
||||
"bootstrap-colorpicker": "^2.5.3",
|
||||
"bootstrap-material-design": "^4.1.1",
|
||||
"bson": "^4.0.1",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"blakejs": "^1.1.0",
|
||||
"bootstrap": "4.3.1",
|
||||
"bootstrap-colorpicker": "^3.1.2",
|
||||
"bootstrap-material-design": "^4.1.2",
|
||||
"bson": "^4.0.2",
|
||||
"chi-squared": "^1.1.0",
|
||||
"crypto-api": "^0.8.3",
|
||||
"core-js": "^3.2.1",
|
||||
"crypto-api": "^0.8.5",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"ctph.js": "0.0.5",
|
||||
"diff": "^3.5.0",
|
||||
"es6-promisify": "^6.0.1",
|
||||
"escodegen": "^1.11.0",
|
||||
"d3": "^5.11.0",
|
||||
"d3-hexbin": "^0.2.2",
|
||||
"diff": "^4.0.1",
|
||||
"es6-promisify": "^6.0.2",
|
||||
"escodegen": "^1.12.0",
|
||||
"esm": "^3.2.25",
|
||||
"esmangle": "^1.0.1",
|
||||
"esprima": "^4.0.1",
|
||||
"exif-parser": "^0.1.12",
|
||||
"file-saver": "^2.0.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"geodesy": "^1.1.3",
|
||||
"highlight.js": "^9.13.1",
|
||||
"jimp": "^0.6.0",
|
||||
"jquery": "^3.3.1",
|
||||
"highlight.js": "^9.15.10",
|
||||
"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.0",
|
||||
"jsonwebtoken": "^8.4.0",
|
||||
"jsqr": "^1.1.1",
|
||||
"jsonpath": "^1.0.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsqr": "^1.2.0",
|
||||
"jsrsasign": "8.0.12",
|
||||
"kbpgp": "^2.0.82",
|
||||
"libyara-wasm": "0.0.12",
|
||||
"lodash": "^4.17.11",
|
||||
"loglevel": "^1.6.1",
|
||||
"kbpgp": "2.1.3",
|
||||
"libbzip2-wasm": "0.0.4",
|
||||
"libyara-wasm": "^1.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"loglevel": "^1.6.3",
|
||||
"loglevel-message-prefix": "^3.0.0",
|
||||
"moment": "^2.23.0",
|
||||
"moment-timezone": "^0.5.23",
|
||||
"markdown-it": "^9.1.0",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.26",
|
||||
"ngeohash": "^0.6.3",
|
||||
"node-forge": "^0.7.6",
|
||||
"node-forge": "^0.9.1",
|
||||
"node-md6": "^0.1.0",
|
||||
"nodom": "^2.2.0",
|
||||
"notepack.io": "^2.2.0",
|
||||
"nwmatcher": "^1.4.4",
|
||||
"otp": "^0.1.3",
|
||||
"popper.js": "^1.14.6",
|
||||
"popper.js": "^1.15.0",
|
||||
"qr-image": "^3.2.0",
|
||||
"scryptsy": "^2.0.0",
|
||||
"scryptsy": "^2.1.0",
|
||||
"snackbarjs": "^1.1.0",
|
||||
"sortablejs": "^1.8.0-rc1",
|
||||
"split.js": "^1.5.10",
|
||||
"sortablejs": "^1.9.0",
|
||||
"split.js": "^1.5.11",
|
||||
"ssdeep.js": "0.0.2",
|
||||
"ua-parser-js": "^0.7.19",
|
||||
"tesseract.js": "^2.0.0-alpha.15",
|
||||
"ua-parser-js": "^0.7.20",
|
||||
"utf8": "^3.0.0",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"xmldom": "^0.1.27",
|
||||
@ -140,9 +155,11 @@
|
||||
"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",
|
||||
"testuidev": "npx nightwatch --env=dev",
|
||||
"lint": "grunt lint",
|
||||
"newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs"
|
||||
}
|
||||
|
@ -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
|
||||
}),
|
||||
|
@ -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;
|
||||
|
@ -6,10 +6,9 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import "babel-polyfill";
|
||||
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";
|
||||
@ -26,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({
|
||||
@ -36,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",
|
||||
@ -44,8 +48,9 @@ self.postMessage({
|
||||
* recipeConfig: {[Object]},
|
||||
* options: {Object},
|
||||
* progress: {number},
|
||||
* step: {boolean}
|
||||
* } | undefined
|
||||
* step: {boolean},
|
||||
* [inputNum=-1]: {number}
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
self.addEventListener("message", function(e) {
|
||||
@ -63,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.
|
||||
@ -92,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;
|
||||
}
|
||||
|
||||
|
||||
@ -137,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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -176,10 +209,10 @@ 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}/${module}.js`);
|
||||
self.importScripts(`${self.docURL}/modules/${module}.js`);
|
||||
self.sendStatusMessage("");
|
||||
}
|
||||
});
|
||||
@ -194,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
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
this.value = [];
|
||||
this.type = Dish.BYTE_ARRAY;
|
||||
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} - (Browser) 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.
|
||||
*
|
||||
@ -117,124 +177,86 @@ class Dish {
|
||||
this.type = type;
|
||||
|
||||
if (!this.valid()) {
|
||||
const sample = Utils.truncate(JSON.stringify(this.value), 13);
|
||||
const sample = Utils.truncate(JSON.stringify(this.value), 25);
|
||||
throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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} - (Browser) 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)}`);
|
||||
const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8;
|
||||
detectDishType() {
|
||||
const data = new Uint8Array(this.value.slice(0, 2048)),
|
||||
types = detectFileType(data);
|
||||
|
||||
// Convert data to intermediate byteArray type
|
||||
try {
|
||||
switch (this.type) {
|
||||
case Dish.STRING:
|
||||
this.value = this.value ? Utils.strToByteArray(this.value) : [];
|
||||
break;
|
||||
case Dish.NUMBER:
|
||||
this.value = typeof this.value === "number" ? Utils.strToByteArray(this.value.toString()) : [];
|
||||
break;
|
||||
case Dish.HTML:
|
||||
this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : [];
|
||||
break;
|
||||
case Dish.ARRAY_BUFFER:
|
||||
// Array.from() would be nicer here, but it's slightly slower
|
||||
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
|
||||
break;
|
||||
case Dish.BIG_NUMBER:
|
||||
this.value = BigNumber.isBigNumber(this.value) ? Utils.strToByteArray(this.value.toFixed()) : [];
|
||||
break;
|
||||
case Dish.JSON:
|
||||
this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value, null, 4)) : [];
|
||||
break;
|
||||
case Dish.FILE:
|
||||
this.value = await Utils.readFile(this.value);
|
||||
this.value = Array.prototype.slice.call(this.value);
|
||||
break;
|
||||
case Dish.LIST_FILE:
|
||||
this.value = await Promise.all(this.value.map(async f => Utils.readFile(f)));
|
||||
this.value = this.value.map(b => Array.prototype.slice.call(b));
|
||||
this.value = [].concat.apply([], this.value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to byteArray: ${err}`);
|
||||
}
|
||||
|
||||
this.type = Dish.BYTE_ARRAY;
|
||||
|
||||
// Convert from byteArray to toType
|
||||
try {
|
||||
switch (toType) {
|
||||
case Dish.STRING:
|
||||
case Dish.HTML:
|
||||
this.value = this.value ? byteArrayToStr(this.value) : "";
|
||||
this.type = Dish.STRING;
|
||||
break;
|
||||
case Dish.NUMBER:
|
||||
this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0;
|
||||
this.type = Dish.NUMBER;
|
||||
break;
|
||||
case Dish.ARRAY_BUFFER:
|
||||
this.value = new Uint8Array(this.value).buffer;
|
||||
this.type = Dish.ARRAY_BUFFER;
|
||||
break;
|
||||
case Dish.BIG_NUMBER:
|
||||
try {
|
||||
this.value = new BigNumber(byteArrayToStr(this.value));
|
||||
} catch (err) {
|
||||
this.value = new BigNumber(NaN);
|
||||
}
|
||||
this.type = Dish.BIG_NUMBER;
|
||||
break;
|
||||
case Dish.JSON:
|
||||
this.value = JSON.parse(byteArrayToStr(this.value));
|
||||
this.type = Dish.JSON;
|
||||
break;
|
||||
case Dish.FILE:
|
||||
this.value = new File(this.value, "unknown");
|
||||
break;
|
||||
case Dish.LIST_FILE:
|
||||
this.value = [new File(this.value, "unknown")];
|
||||
this.type = Dish.LIST_FILE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new DishError(`Error translating from byteArray to ${Dish.enumLookup(toType)}: ${err}`);
|
||||
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.
|
||||
@ -244,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;
|
||||
}
|
||||
|
||||
@ -265,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;
|
||||
@ -372,6 +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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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,19 @@ 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) {
|
||||
if (a === "\\") return "\\"+b;
|
||||
switch (b[0]) {
|
||||
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) {
|
||||
switch (a[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(a, 8));
|
||||
case "b":
|
||||
return "\b";
|
||||
case "t":
|
||||
@ -223,12 +231,12 @@ class Utils {
|
||||
case "'":
|
||||
return "'";
|
||||
case "x":
|
||||
return String.fromCharCode(parseInt(b.substr(1), 16));
|
||||
return String.fromCharCode(parseInt(a.substr(1), 16));
|
||||
case "u":
|
||||
if (b[1] === "{")
|
||||
return String.fromCodePoint(parseInt(b.slice(2, -1), 16));
|
||||
if (a[1] === "{")
|
||||
return String.fromCodePoint(parseInt(a.slice(2, -1), 16));
|
||||
else
|
||||
return String.fromCharCode(parseInt(b.substr(1), 16));
|
||||
return String.fromCharCode(parseInt(a.substr(1), 16));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -367,6 +375,61 @@ class Utils {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to an ArrayBuffer.
|
||||
* Treats the string as UTF-8 if any values are over 255.
|
||||
*
|
||||
* @param {string} str
|
||||
* @returns {ArrayBuffer}
|
||||
*
|
||||
* @example
|
||||
* // returns [72,101,108,108,111]
|
||||
* Utils.strToArrayBuffer("Hello");
|
||||
*
|
||||
* // returns [228,189,160,229,165,189]
|
||||
* Utils.strToArrayBuffer("你好");
|
||||
*/
|
||||
static strToArrayBuffer(str) {
|
||||
const arr = new Uint8Array(str.length);
|
||||
let i = str.length, b;
|
||||
while (i--) {
|
||||
b = str.charCodeAt(i);
|
||||
arr[i] = b;
|
||||
// If any of the bytes are over 255, read as UTF-8
|
||||
if (b > 255) return Utils.strToUtf8ArrayBuffer(str);
|
||||
}
|
||||
return arr.buffer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to a UTF-8 ArrayBuffer.
|
||||
*
|
||||
* @param {string} str
|
||||
* @returns {ArrayBuffer}
|
||||
*
|
||||
* @example
|
||||
* // returns [72,101,108,108,111]
|
||||
* Utils.strToUtf8ArrayBuffer("Hello");
|
||||
*
|
||||
* // returns [228,189,160,229,165,189]
|
||||
* Utils.strToUtf8ArrayBuffer("你好");
|
||||
*/
|
||||
static strToUtf8ArrayBuffer(str) {
|
||||
const utf8Str = utf8.encode(str);
|
||||
|
||||
if (str.length !== utf8Str.length) {
|
||||
if (isWorkerEnvironment()) {
|
||||
self.setOption("attemptHighlight", false);
|
||||
} else if (isWebEnvironment()) {
|
||||
window.app.options.attemptHighlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Utils.strToArrayBuffer(utf8Str);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to a byte array.
|
||||
* Treats the string as UTF-8 if any values are over 255.
|
||||
@ -411,9 +474,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;
|
||||
}
|
||||
}
|
||||
@ -459,7 +522,7 @@ class Utils {
|
||||
/**
|
||||
* Attempts to convert a byte array to a UTF-8 string.
|
||||
*
|
||||
* @param {byteArray} byteArray
|
||||
* @param {byteArray|Uint8Array} byteArray
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
@ -473,11 +536,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;
|
||||
}
|
||||
}
|
||||
@ -505,6 +567,7 @@ class Utils {
|
||||
static byteArrayToChars(byteArray) {
|
||||
if (!byteArray) return "";
|
||||
let str = "";
|
||||
// String concatenation appears to be faster than an array join
|
||||
for (let i = 0; i < byteArray.length;) {
|
||||
str += String.fromCharCode(byteArray[i++]);
|
||||
}
|
||||
@ -524,8 +587,8 @@ class Utils {
|
||||
* Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
|
||||
*/
|
||||
static arrayBufferToStr(arrayBuffer, utf8=true) {
|
||||
const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer));
|
||||
return utf8 ? Utils.byteArrayToUtf8(byteArray) : Utils.byteArrayToChars(byteArray);
|
||||
const arr = new Uint8Array(arrayBuffer);
|
||||
return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr);
|
||||
}
|
||||
|
||||
|
||||
@ -780,7 +843,7 @@ class Utils {
|
||||
args = m[2]
|
||||
.replace(/"/g, '\\"') // Escape double quotes
|
||||
.replace(/(^|,|{|:)'/g, '$1"') // Replace opening ' with "
|
||||
.replace(/([^\\]|[^\\]\\\\)'(,|:|}|$)/g, '$1"$2') // Replace closing ' with "
|
||||
.replace(/([^\\]|(?:\\\\)+)'(,|:|}|$)/g, '$1"$2') // Replace closing ' with "
|
||||
.replace(/\\'/g, "'"); // Unescape single quotes
|
||||
args = "[" + args + "]";
|
||||
|
||||
@ -929,7 +992,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
|
||||
@ -937,33 +1000,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;
|
||||
}
|
||||
|
||||
|
||||
@ -1023,9 +1110,11 @@ class Utils {
|
||||
static charRep(token) {
|
||||
return {
|
||||
"Space": " ",
|
||||
"Percent": "%",
|
||||
"Comma": ",",
|
||||
"Semi-colon": ";",
|
||||
"Colon": ":",
|
||||
"Tab": "\t",
|
||||
"Line feed": "\n",
|
||||
"CRLF": "\r\n",
|
||||
"Forward slash": "/",
|
||||
@ -1047,6 +1136,7 @@ class Utils {
|
||||
static regexRep(token) {
|
||||
return {
|
||||
"Space": /\s+/g,
|
||||
"Percent": /%/g,
|
||||
"Comma": /,/g,
|
||||
"Semi-colon": /;/g,
|
||||
"Colon": /:/g,
|
||||
@ -1062,6 +1152,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;
|
||||
|
||||
|
||||
@ -1080,7 +1194,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]);
|
||||
@ -1179,12 +1293,37 @@ 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);
|
||||
}
|
||||
|
||||
const debounceTimeouts = {};
|
||||
|
||||
/**
|
||||
* Debouncer to stop functions from being executed multiple times in a
|
||||
* short space of time
|
||||
* https://davidwalsh.name/javascript-debounce-function
|
||||
*
|
||||
* @param {function} func - The function to be executed after the debounce time
|
||||
* @param {number} wait - The time (ms) to wait before executing the function
|
||||
* @param {string} id - Unique ID to reference the timeout for the function
|
||||
* @param {object} scope - The object to bind to the debounced function
|
||||
* @param {array} args - Array of arguments to be passed to func
|
||||
* @returns {function}
|
||||
*/
|
||||
export function debounce(func, wait, id, scope, args) {
|
||||
return function() {
|
||||
const later = function() {
|
||||
func.apply(scope, args);
|
||||
};
|
||||
clearTimeout(debounceTimeouts[id]);
|
||||
debounceTimeouts[id] = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -85,6 +85,8 @@
|
||||
"Vigenère Decode",
|
||||
"To Morse Code",
|
||||
"From Morse Code",
|
||||
"Bacon Cipher Encode",
|
||||
"Bacon Cipher Decode",
|
||||
"Bifid Cipher Encode",
|
||||
"Bifid Cipher Decode",
|
||||
"Affine Cipher Encode",
|
||||
@ -122,8 +124,10 @@
|
||||
"Generate PGP Key Pair",
|
||||
"PGP Encrypt",
|
||||
"PGP Decrypt",
|
||||
"PGP Verify",
|
||||
"PGP Encrypt and Sign",
|
||||
"PGP Decrypt and Verify"
|
||||
"PGP Decrypt and Verify",
|
||||
"Parse SSH Host Key"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -167,15 +171,21 @@
|
||||
"Parse IP range",
|
||||
"Parse IPv6 address",
|
||||
"Parse IPv4 header",
|
||||
"Parse UDP",
|
||||
"Parse SSH Host Key",
|
||||
"Parse URI",
|
||||
"URL Encode",
|
||||
"URL Decode",
|
||||
"Protobuf Decode",
|
||||
"VarInt Encode",
|
||||
"VarInt Decode",
|
||||
"Format MAC addresses",
|
||||
"Change IP format",
|
||||
"Group IP addresses",
|
||||
"Encode NetBIOS Name",
|
||||
"Decode NetBIOS Name",
|
||||
"Defang URL"
|
||||
"Defang URL",
|
||||
"Defang IP Addresses"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -195,8 +205,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",
|
||||
@ -222,6 +232,7 @@
|
||||
"Convert speed",
|
||||
"Convert data units",
|
||||
"Convert co-ordinate format",
|
||||
"Show on map",
|
||||
"Parse UNIX file permissions",
|
||||
"Swap endianness",
|
||||
"Parse colour code",
|
||||
@ -275,6 +286,7 @@
|
||||
"Zip",
|
||||
"Unzip",
|
||||
"Bzip2 Decompress",
|
||||
"Bzip2 Compress",
|
||||
"Tar",
|
||||
"Untar"
|
||||
]
|
||||
@ -298,6 +310,10 @@
|
||||
"HAS-160",
|
||||
"Whirlpool",
|
||||
"Snefru",
|
||||
"BLAKE2b",
|
||||
"BLAKE2s",
|
||||
"GOST hash",
|
||||
"Streebog",
|
||||
"SSDEEP",
|
||||
"CTPH",
|
||||
"Compare SSDEEP hashes",
|
||||
@ -312,6 +328,7 @@
|
||||
"Fletcher-32 Checksum",
|
||||
"Fletcher-64 Checksum",
|
||||
"Adler-32 Checksum",
|
||||
"CRC-8 Checksum",
|
||||
"CRC-16 Checksum",
|
||||
"CRC-32 Checksum",
|
||||
"TCP/IP Checksum"
|
||||
@ -346,7 +363,8 @@
|
||||
"BSON serialise",
|
||||
"BSON deserialise",
|
||||
"To MessagePack",
|
||||
"From MessagePack"
|
||||
"From MessagePack",
|
||||
"Render Markdown"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -355,8 +373,13 @@
|
||||
"Detect File Type",
|
||||
"Scan for Embedded Files",
|
||||
"Extract Files",
|
||||
"YARA Rules",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF"
|
||||
"Extract EXIF",
|
||||
"Extract RGBA",
|
||||
"View Bit Plane",
|
||||
"Randomize Colour Palette",
|
||||
"Extract LSB"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -364,6 +387,7 @@
|
||||
"ops": [
|
||||
"Render Image",
|
||||
"Play Media",
|
||||
"Optical Character Recognition",
|
||||
"Remove EXIF",
|
||||
"Extract EXIF",
|
||||
"Split Colour Channels",
|
||||
@ -379,7 +403,15 @@
|
||||
"Image Filter",
|
||||
"Contain Image",
|
||||
"Cover Image",
|
||||
"Image Hue/Saturation/Lightness"
|
||||
"Image Hue/Saturation/Lightness",
|
||||
"Sharpen Image",
|
||||
"Normalise Image",
|
||||
"Convert Image Format",
|
||||
"Add Text To Image",
|
||||
"Hex Density chart",
|
||||
"Scatter chart",
|
||||
"Series chart",
|
||||
"Heatmap chart"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -387,6 +419,7 @@
|
||||
"ops": [
|
||||
"Entropy",
|
||||
"Frequency distribution",
|
||||
"Index of Coincidence",
|
||||
"Chi Square",
|
||||
"Disassemble x86",
|
||||
"Pseudo-Random Number Generator",
|
||||
@ -396,6 +429,7 @@
|
||||
"Generate QR Code",
|
||||
"Parse QR Code",
|
||||
"Haversine distance",
|
||||
"HTML To Text",
|
||||
"Generate Lorem Ipsum",
|
||||
"Numberwang",
|
||||
"XKCD Random Number"
|
||||
|
@ -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 += `
|
||||
|
@ -39,7 +39,7 @@ let code = `/**
|
||||
`;
|
||||
|
||||
opObjs.forEach(obj => {
|
||||
code += `import ${obj} from "./${obj}";\n`;
|
||||
code += `import ${obj} from "./${obj}.mjs";\n`;
|
||||
});
|
||||
|
||||
code += `
|
||||
|
@ -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
|
||||
|
39
src/core/dishTypes/DishBigNumber.mjs
Normal file
39
src/core/dishTypes/DishBigNumber.mjs
Normal 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;
|
31
src/core/dishTypes/DishByteArray.mjs
Normal file
31
src/core/dishTypes/DishByteArray.mjs
Normal 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;
|
42
src/core/dishTypes/DishFile.mjs
Normal file
42
src/core/dishTypes/DishFile.mjs
Normal 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;
|
26
src/core/dishTypes/DishHTML.mjs
Normal file
26
src/core/dishTypes/DishHTML.mjs
Normal 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;
|
33
src/core/dishTypes/DishJSON.mjs
Normal file
33
src/core/dishTypes/DishJSON.mjs
Normal 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;
|
58
src/core/dishTypes/DishListFile.mjs
Normal file
58
src/core/dishTypes/DishListFile.mjs
Normal 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;
|
34
src/core/dishTypes/DishNumber.mjs
Normal file
34
src/core/dishTypes/DishNumber.mjs
Normal 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;
|
34
src/core/dishTypes/DishString.mjs
Normal file
34
src/core/dishTypes/DishString.mjs
Normal 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;
|
39
src/core/dishTypes/DishType.mjs
Normal file
39
src/core/dishTypes/DishType.mjs
Normal 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;
|
26
src/core/dishTypes/index.mjs
Normal file
26
src/core/dishTypes/index.mjs
Normal 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,
|
||||
};
|
25
src/core/errors/ExcludedOperationError.mjs
Normal file
25
src/core/errors/ExcludedOperationError.mjs
Normal 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;
|
9
src/core/errors/index.mjs
Normal file
9
src/core/errors/index.mjs
Normal file
@ -0,0 +1,9 @@
|
||||
import OperationError from "./OperationError.mjs";
|
||||
import DishError from "./DishError.mjs";
|
||||
import ExcludedOperationError from "./ExcludedOperationError";
|
||||
|
||||
export {
|
||||
OperationError,
|
||||
DishError,
|
||||
ExcludedOperationError,
|
||||
};
|
@ -5,7 +5,7 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
import Utils from "../Utils.mjs";
|
||||
import BigNumber from "bignumber.js";
|
||||
|
||||
|
||||
@ -109,7 +109,7 @@ export function mean(data) {
|
||||
*/
|
||||
export function median(data) {
|
||||
if ((data.length % 2) === 0 && data.length > 0) {
|
||||
data.sort(function(a, b){
|
||||
data.sort(function(a, b) {
|
||||
return a.minus(b);
|
||||
});
|
||||
const first = data[Math.floor(data.length / 2)];
|
||||
|
@ -22,7 +22,7 @@ export const ENCODING_SCHEME = [
|
||||
/**
|
||||
* Lookup table for the binary value of each digit representation.
|
||||
*
|
||||
* I wrote a very nice algorithm to generate 8 4 2 1 encoding programatically,
|
||||
* I wrote a very nice algorithm to generate 8 4 2 1 encoding programmatically,
|
||||
* but unfortunately it's much easier (if less elegant) to use lookup tables
|
||||
* when supporting multiple encoding schemes.
|
||||
*
|
||||
|
66
src/core/lib/Bacon.mjs
Normal file
66
src/core/lib/Bacon.mjs
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Bacon Cipher resources.
|
||||
*
|
||||
* @author Karsten Silkenbäumer [github.com/kassi]
|
||||
* @copyright Karsten Silkenbäumer 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bacon definitions.
|
||||
*/
|
||||
export const BACON_ALPHABETS = {
|
||||
"Standard (I=J and U=V)": {
|
||||
alphabet: "ABCDEFGHIKLMNOPQRSTUWXYZ",
|
||||
codes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23]
|
||||
},
|
||||
"Complete": {
|
||||
alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
}
|
||||
};
|
||||
export const BACON_TRANSLATION_01 = "0/1";
|
||||
export const BACON_TRANSLATION_AB = "A/B";
|
||||
export const BACON_TRANSLATION_CASE = "Case";
|
||||
export const BACON_TRANSLATION_AMNZ = "A-M/N-Z first letter";
|
||||
export const BACON_TRANSLATIONS = [
|
||||
BACON_TRANSLATION_01,
|
||||
BACON_TRANSLATION_AB,
|
||||
BACON_TRANSLATION_CASE,
|
||||
BACON_TRANSLATION_AMNZ,
|
||||
];
|
||||
export const BACON_TRANSLATIONS_FOR_ENCODING = [
|
||||
BACON_TRANSLATION_01,
|
||||
BACON_TRANSLATION_AB
|
||||
];
|
||||
export const BACON_CLEARER_MAP = {
|
||||
[BACON_TRANSLATION_01]: /[^01]/g,
|
||||
[BACON_TRANSLATION_AB]: /[^ABab]/g,
|
||||
[BACON_TRANSLATION_CASE]: /[^A-Za-z]/g,
|
||||
};
|
||||
export const BACON_NORMALIZE_MAP = {
|
||||
[BACON_TRANSLATION_AB]: {
|
||||
"A": "0",
|
||||
"B": "1",
|
||||
"a": "0",
|
||||
"b": "1"
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Swaps zeros to ones and ones to zeros.
|
||||
*
|
||||
* @param {string} data
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "11001 01010"
|
||||
* swapZeroAndOne("00110 10101");
|
||||
*/
|
||||
export function swapZeroAndOne(string) {
|
||||
return string.replace(/[01]/g, function (c) {
|
||||
return {
|
||||
"0": "1",
|
||||
"1": "0"
|
||||
}[c];
|
||||
});
|
||||
}
|
@ -6,13 +6,13 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Base64's the input byte array using the given alphabet, returning a string.
|
||||
*
|
||||
* @param {byteArray|Uint8Array|string} data
|
||||
* @param {byteArray|Uint8Array|ArrayBuffer|string} data
|
||||
* @param {string} [alphabet="A-Za-z0-9+/="]
|
||||
* @returns {string}
|
||||
*
|
||||
@ -25,11 +25,17 @@ import Utils from "../Utils";
|
||||
*/
|
||||
export function toBase64(data, alphabet="A-Za-z0-9+/=") {
|
||||
if (!data) return "";
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = new Uint8Array(data);
|
||||
}
|
||||
if (typeof data == "string") {
|
||||
data = Utils.strToByteArray(data);
|
||||
}
|
||||
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
||||
throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`);
|
||||
}
|
||||
|
||||
let output = "",
|
||||
chr1, chr2, chr3,
|
||||
@ -63,7 +69,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]
|
||||
@ -83,6 +89,9 @@ export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", r
|
||||
|
||||
alphabet = alphabet || "A-Za-z0-9+/=";
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
||||
throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`);
|
||||
}
|
||||
|
||||
const output = [];
|
||||
let chr1, chr2, chr3,
|
||||
|
@ -6,7 +6,7 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
178
src/core/lib/Charts.mjs
Normal file
178
src/core/lib/Charts.mjs
Normal file
@ -0,0 +1,178 @@
|
||||
/**
|
||||
* @author tlwr [toby@toby.codes]
|
||||
* @author Matt C [me@mitt.dev]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const RECORD_DELIMITER_OPTIONS = ["Line feed", "CRLF"];
|
||||
|
||||
|
||||
/**
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const FIELD_DELIMITER_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Tab"];
|
||||
|
||||
|
||||
/**
|
||||
* Default from colour
|
||||
*
|
||||
* @constant
|
||||
* @default
|
||||
*/
|
||||
export const COLOURS = {
|
||||
min: "white",
|
||||
max: "black"
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @param {number} length
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) {
|
||||
let headings;
|
||||
const values = [];
|
||||
|
||||
input
|
||||
.split(recordDelimiter)
|
||||
.forEach((row, rowIndex) => {
|
||||
const split = row.split(fieldDelimiter);
|
||||
if (split.length !== length) throw new OperationError(`Each row must have length ${length}.`);
|
||||
|
||||
if (columnHeadingsAreIncluded && rowIndex === 0) {
|
||||
headings = split;
|
||||
} else {
|
||||
values.push(split);
|
||||
}
|
||||
});
|
||||
return { headings, values };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a scatter plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = getValues(
|
||||
input,
|
||||
recordDelimiter,
|
||||
fieldDelimiter,
|
||||
columnHeadingsAreIncluded,
|
||||
2
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
headings = {x: headings[0], y: headings[1]};
|
||||
}
|
||||
|
||||
values = values.map(row => {
|
||||
const x = parseFloat(row[0], 10),
|
||||
y = parseFloat(row[1], 10);
|
||||
|
||||
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
|
||||
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
return [x, y];
|
||||
});
|
||||
|
||||
return { headings, values };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets values from input for a scatter plot with colour from the third column.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
let { headings, values } = getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
columnHeadingsAreIncluded,
|
||||
3
|
||||
);
|
||||
|
||||
if (headings) {
|
||||
headings = {x: headings[0], y: headings[1]};
|
||||
}
|
||||
|
||||
values = values.map(row => {
|
||||
const x = parseFloat(row[0], 10),
|
||||
y = parseFloat(row[1], 10),
|
||||
colour = row[2];
|
||||
|
||||
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
|
||||
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
return [x, y, colour];
|
||||
});
|
||||
|
||||
return { headings, values };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets values from input for a time series plot.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {string} recordDelimiter
|
||||
* @param {string} fieldDelimiter
|
||||
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
|
||||
const { values } = getValues(
|
||||
input,
|
||||
recordDelimiter, fieldDelimiter,
|
||||
false,
|
||||
3
|
||||
);
|
||||
|
||||
let xValues = new Set();
|
||||
const series = {};
|
||||
|
||||
values.forEach(row => {
|
||||
const serie = row[0],
|
||||
xVal = row[1],
|
||||
val = parseFloat(row[2], 10);
|
||||
|
||||
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
||||
|
||||
xValues.add(xVal);
|
||||
if (typeof series[serie] === "undefined") series[serie] = {};
|
||||
series[serie][xVal] = val;
|
||||
});
|
||||
|
||||
xValues = new Array(...xValues);
|
||||
|
||||
const seriesList = [];
|
||||
for (const seriesName in series) {
|
||||
const serie = series[seriesName];
|
||||
seriesList.push({name: seriesName, data: serie});
|
||||
}
|
||||
|
||||
return { xValues, series: seriesList };
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import CryptoJS from "crypto-js";
|
||||
|
||||
/**
|
||||
|
@ -6,9 +6,22 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import geohash from "ngeohash";
|
||||
/*
|
||||
Currently unable to update to geodesy v2 as we cannot load .js modules into a .mjs file.
|
||||
When we do update, imports will look like this:
|
||||
|
||||
import LatLonEllipsoidal from "geodesy/latlon-ellipsoidal.js";
|
||||
import Mgrs from "geodesy/mgrs.js";
|
||||
import OsGridRef from "geodesy/osgridref.js";
|
||||
import Utm from "geodesy/utm.js";
|
||||
*/
|
||||
import geodesy from "geodesy";
|
||||
import OperationError from "../errors/OperationError";
|
||||
const LatLonEllipsoidal = geodesy.LatLonEllipsoidal,
|
||||
Mgrs = geodesy.Mgrs,
|
||||
OsGridRef = geodesy.OsGridRef,
|
||||
Utm = geodesy.Utm;
|
||||
|
||||
/**
|
||||
* Co-ordinate formats
|
||||
@ -116,22 +129,22 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli
|
||||
switch (inFormat) {
|
||||
case "Geohash":
|
||||
hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = new geodesy.LatLonEllipsoidal(hash.latitude, hash.longitude);
|
||||
latlon = new LatLonEllipsoidal(hash.latitude, hash.longitude);
|
||||
break;
|
||||
case "Military Grid Reference System":
|
||||
utm = geodesy.Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm();
|
||||
utm = Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm();
|
||||
latlon = utm.toLatLonE();
|
||||
break;
|
||||
case "Ordnance Survey National Grid":
|
||||
osng = geodesy.OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = geodesy.OsGridRef.osGridToLatLon(osng);
|
||||
osng = OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, ""));
|
||||
latlon = OsGridRef.osGridToLatLon(osng);
|
||||
break;
|
||||
case "Universal Transverse Mercator":
|
||||
// Geodesy needs a space between the first 2 digits and the next letter
|
||||
if (/^[\d]{2}[A-Za-z]/.test(input)) {
|
||||
input = input.slice(0, 2) + " " + input.slice(2);
|
||||
}
|
||||
utm = geodesy.Utm.parse(input);
|
||||
utm = Utm.parse(input);
|
||||
latlon = utm.toLatLonE();
|
||||
break;
|
||||
case "Degrees Minutes Seconds":
|
||||
@ -143,7 +156,7 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli
|
||||
if (splitLat.length >= 3 && splitLong.length >= 3) {
|
||||
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10);
|
||||
lon = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
latlon = new LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
} else {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
|
||||
}
|
||||
@ -152,7 +165,7 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli
|
||||
splitLat = splitInput(split[0]);
|
||||
if (splitLat.length >= 3) {
|
||||
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
latlon = new LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
} else {
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
|
||||
}
|
||||
@ -168,7 +181,7 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli
|
||||
// Convert to decimal degrees, and then convert to a geodesy object
|
||||
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
|
||||
lon = convDDMToDD(splitLong[0], splitLong[1], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
latlon = new LatLonEllipsoidal(lat.degrees, lon.degrees);
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(input);
|
||||
@ -176,7 +189,7 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli
|
||||
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
|
||||
}
|
||||
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
|
||||
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
latlon = new LatLonEllipsoidal(lat.degrees, lat.degrees);
|
||||
}
|
||||
break;
|
||||
case "Decimal Degrees":
|
||||
@ -186,14 +199,14 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli
|
||||
if (splitLat.length !== 1 || splitLong.length !== 1) {
|
||||
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
|
||||
}
|
||||
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLong[0]);
|
||||
latlon = new LatLonEllipsoidal(splitLat[0], splitLong[0]);
|
||||
} else {
|
||||
// Not a pair, so only try to convert one set of co-ordinates
|
||||
splitLat = splitInput(split[0]);
|
||||
if (splitLat.length !== 1) {
|
||||
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
|
||||
}
|
||||
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLat[0]);
|
||||
latlon = new LatLonEllipsoidal(splitLat[0], splitLat[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -260,7 +273,7 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli
|
||||
convLat = mgrs.toString(precision);
|
||||
break;
|
||||
case "Ordnance Survey National Grid":
|
||||
osng = geodesy.OsGridRef.latLonToOsGrid(latlon);
|
||||
osng = OsGridRef.latLonToOsGrid(latlon);
|
||||
if (osng.toString() === "") {
|
||||
throw new OperationError("Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?");
|
||||
}
|
||||
@ -327,13 +340,13 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli
|
||||
* @param {string} input - The input data to be split
|
||||
* @returns {number[]} An array of the different items in the string, stored as floats
|
||||
*/
|
||||
function splitInput (input){
|
||||
function splitInput (input) {
|
||||
const split = [];
|
||||
|
||||
input.split(/\s+/).forEach(item => {
|
||||
// Remove any character that isn't a digit, decimal point or negative sign
|
||||
item = item.replace(/[^0-9.-]/g, "");
|
||||
if (item.length > 0){
|
||||
if (item.length > 0) {
|
||||
// Turn the item into a float
|
||||
split.push(parseFloat(item));
|
||||
}
|
||||
@ -350,7 +363,7 @@ function splitInput (input){
|
||||
* @param {number} precision - The precision the result should be rounded to
|
||||
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
|
||||
*/
|
||||
function convDMSToDD (degrees, minutes, seconds, precision){
|
||||
function convDMSToDD (degrees, minutes, seconds, precision) {
|
||||
const absDegrees = Math.abs(degrees);
|
||||
let conv = absDegrees + (minutes / 60) + (seconds / 3600);
|
||||
let outString = round(conv, precision) + "°";
|
||||
@ -566,7 +579,7 @@ export function findFormat (input, delim) {
|
||||
// Test DMS/DDM/DD formats
|
||||
if (testData !== undefined) {
|
||||
const split = splitInput(testData);
|
||||
switch (split.length){
|
||||
switch (split.length) {
|
||||
case 3:
|
||||
return "Degrees Minutes Seconds";
|
||||
case 2:
|
||||
|
@ -6,7 +6,7 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -32,7 +32,7 @@ export const WORD_DELIM_OPTIONS = ["Line feed", "CRLF", "Forward slash", "Backsl
|
||||
export const INPUT_DELIM_OPTIONS = ["Line feed", "CRLF", "Space", "Comma", "Semi-colon", "Colon", "Nothing (separate chars)"];
|
||||
|
||||
/**
|
||||
* Armithmetic sequence delimiters
|
||||
* Arithmetic sequence delimiters
|
||||
*/
|
||||
export const ARITHMETIC_DELIM_OPTIONS = ["Line feed", "Space", "Comma", "Semi-colon", "Colon", "CRLF"];
|
||||
|
||||
@ -72,3 +72,12 @@ export const JOIN_DELIM_OPTIONS = [
|
||||
{name: "Nothing (join chars)", value: ""}
|
||||
];
|
||||
|
||||
/**
|
||||
* RGBA list delimiters.
|
||||
*/
|
||||
export const RGBA_DELIM_OPTIONS = [
|
||||
{name: "Comma", value: ","},
|
||||
{name: "Space", value: " "},
|
||||
{name: "CRLF", value: "\\r\\n"},
|
||||
{name: "Line Feed", value: "\n"}
|
||||
];
|
||||
|
@ -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];
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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";
|
||||
|
||||
|
||||
/**
|
||||
@ -75,7 +75,7 @@ function bytesMatch(sig, buf, offset=0) {
|
||||
* Given a buffer, detects magic byte sequences at specific positions and returns the
|
||||
* extension and mime type.
|
||||
*
|
||||
* @param {Uint8Array} buf
|
||||
* @param {Uint8Array|ArrayBuffer} buf
|
||||
* @param {string[]} [categories=All] - Which categories of file to look for
|
||||
* @returns {Object[]} types
|
||||
* @returns {string} type.name - Name of file type
|
||||
@ -84,6 +84,10 @@ function bytesMatch(sig, buf, offset=0) {
|
||||
* @returns {string} [type.desc] - Description
|
||||
*/
|
||||
export function detectFileType(buf, categories=Object.keys(FILE_SIGNATURES)) {
|
||||
if (buf instanceof ArrayBuffer) {
|
||||
buf = new Uint8Array(buf);
|
||||
}
|
||||
|
||||
if (!(buf && buf.length > 1)) {
|
||||
return [];
|
||||
}
|
||||
@ -174,7 +178,7 @@ export function scanForFileTypes(buf, categories=Object.keys(FILE_SIGNATURES)) {
|
||||
* @param {Uint8Array} buf - The buffer to search
|
||||
* @param {Object} sig - A single signature object (Not an array of signatures)
|
||||
* @param {number} offset - Where to start search from
|
||||
* @returs {number} The position of the match or -1 if one cannot be found.
|
||||
* @returns {number} The position of the match or -1 if one cannot be found.
|
||||
*/
|
||||
function locatePotentialSig(buf, sig, offset) {
|
||||
// Find values for first key and value in sig
|
||||
@ -203,7 +207,7 @@ function locatePotentialSig(buf, sig, offset) {
|
||||
* Detects whether the given buffer is a file of the type specified.
|
||||
*
|
||||
* @param {string|RegExp} type
|
||||
* @param {Uint8Array} buf
|
||||
* @param {Uint8Array|ArrayBuffer} buf
|
||||
* @returns {string|false} The mime type or false if the type does not match
|
||||
*/
|
||||
export function isType(type, buf) {
|
||||
@ -230,7 +234,7 @@ export function isType(type, buf) {
|
||||
/**
|
||||
* Detects whether the given buffer contains an image file.
|
||||
*
|
||||
* @param {Uint8Array} buf
|
||||
* @param {Uint8Array|ArrayBuffer} buf
|
||||
* @returns {string|false} The mime type or false if the type does not match
|
||||
*/
|
||||
export function isImage(buf) {
|
||||
|
@ -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";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -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 = [];
|
||||
|
||||
@ -100,7 +102,7 @@ export function fromHex(data, delim="Auto", byteLen=2) {
|
||||
/**
|
||||
* To Hexadecimal delimiters.
|
||||
*/
|
||||
export const TO_HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
|
||||
export const TO_HEX_DELIM_OPTIONS = ["Space", "Percent", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
|
||||
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
@ -241,7 +241,7 @@ export function ipv6ListedRange(match, includeNetworkInfo) {
|
||||
ipv6List = ipv6List.filter(function(str) {
|
||||
return str.trim();
|
||||
});
|
||||
for (let i =0; i < ipv6List.length; i++){
|
||||
for (let i =0; i < ipv6List.length; i++) {
|
||||
ipv6List[i] = ipv6List[i].trim();
|
||||
}
|
||||
const ipv6CidrList = ipv6List.filter(function(a) {
|
||||
@ -502,8 +502,8 @@ export function ipv6Compare(a, b) {
|
||||
const a_ = strToIpv6(a),
|
||||
b_ = strToIpv6(b);
|
||||
|
||||
for (let i = 0; i < a_.length; i++){
|
||||
if (a_[i] !== b_[i]){
|
||||
for (let i = 0; i < a_.length; i++) {
|
||||
if (a_[i] !== b_[i]) {
|
||||
return a_[i] - b_[i];
|
||||
}
|
||||
}
|
||||
|
251
src/core/lib/ImageManipulation.mjs
Normal file
251
src/core/lib/ImageManipulation.mjs
Normal 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;
|
||||
}
|
@ -85,7 +85,7 @@ function getWords(length=3) {
|
||||
const words = [];
|
||||
let word;
|
||||
let previousWord;
|
||||
while (words.length < length){
|
||||
while (words.length < length) {
|
||||
do {
|
||||
word = wordList[Math.floor(Math.random() * wordList.length)];
|
||||
} while (previousWord === word);
|
||||
|
@ -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";
|
||||
|
||||
/**
|
||||
@ -97,6 +97,7 @@ class Magic {
|
||||
|
||||
if (!fileType.length) return null;
|
||||
return {
|
||||
name: fileType[0].name,
|
||||
ext: fileType[0].extension,
|
||||
mime: fileType[0].mime,
|
||||
desc: fileType[0].description
|
||||
@ -312,6 +313,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 +339,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,22 +350,22 @@ 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;
|
||||
|
||||
// If a recipe results in a file being detected, it receives a relatively good score
|
||||
if (a.fileType) aScore = 500;
|
||||
if (b.fileType) bScore = 500;
|
||||
|
||||
// If the result is valid UTF8, its score gets boosted (lower being better)
|
||||
if (a.isUTF8) aScore -= 100;
|
||||
if (b.isUTF8) bScore -= 100;
|
||||
|
||||
// If a recipe results in a file being detected, it receives a relatively good score
|
||||
if (a.fileType && aScore > 500) aScore = 500;
|
||||
if (b.fileType && bScore > 500) bScore = 500;
|
||||
|
||||
// If the option is marked useful, give it a good score
|
||||
if (a.useful) aScore = 100;
|
||||
if (b.useful) bScore = 100;
|
||||
if (a.useful && aScore > 100) aScore = 100;
|
||||
if (b.useful && bScore > 100) bScore = 100;
|
||||
|
||||
// Shorter recipes are better, so we add the length of the recipe to the score
|
||||
aScore += a.recipe.length;
|
||||
@ -390,12 +396,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 +451,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({
|
||||
@ -487,7 +498,7 @@ class Magic {
|
||||
* Taken from http://wikistats.wmflabs.org/display.php?t=wp
|
||||
*
|
||||
* @param {string} code - ISO 639 code
|
||||
* @returns {string} The full name of the languge
|
||||
* @returns {string} The full name of the language
|
||||
*/
|
||||
static codeToLanguage(code) {
|
||||
return {
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
285
src/core/lib/Protobuf.mjs
Normal file
285
src/core/lib/Protobuf.mjs
Normal file
@ -0,0 +1,285 @@
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Protobuf lib. Contains functions to decode protobuf serialised
|
||||
* data without a schema or .proto file.
|
||||
*
|
||||
* Provides utility functions to encode and decode variable length
|
||||
* integers (varint).
|
||||
*
|
||||
* @author GCHQ Contributor [3]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
class Protobuf {
|
||||
|
||||
/**
|
||||
* Protobuf constructor
|
||||
*
|
||||
* @param {byteArray|Uint8Array} data
|
||||
*/
|
||||
constructor(data) {
|
||||
// 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 or Uint8Array");
|
||||
}
|
||||
|
||||
// Set up masks
|
||||
this.TYPE = 0x07;
|
||||
this.NUMBER = 0x78;
|
||||
this.MSB = 0x80;
|
||||
this.VALUE = 0x7f;
|
||||
|
||||
// Declare offset and length
|
||||
this.offset = 0;
|
||||
this.LENGTH = data.length;
|
||||
}
|
||||
|
||||
// Public Functions
|
||||
|
||||
/**
|
||||
* Encode a varint from a number
|
||||
*
|
||||
* @param {number} number
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
static varIntEncode(number) {
|
||||
const MSB = 0x80,
|
||||
VALUE = 0x7f,
|
||||
MSBALL = ~VALUE,
|
||||
INT = Math.pow(2, 31);
|
||||
const out = [];
|
||||
let offset = 0;
|
||||
|
||||
while (number >= INT) {
|
||||
out[offset++] = (number & 0xff) | MSB;
|
||||
number /= 128;
|
||||
}
|
||||
while (number & MSBALL) {
|
||||
out[offset++] = (number & 0xff) | MSB;
|
||||
number >>>= 7;
|
||||
}
|
||||
out[offset] = number | 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a varint from the byteArray
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @returns {number}
|
||||
*/
|
||||
static varIntDecode(input) {
|
||||
const pb = new Protobuf(input);
|
||||
return pb._varInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Protobuf data
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @returns {Object}
|
||||
*/
|
||||
static decode(input) {
|
||||
const pb = new Protobuf(input);
|
||||
return pb._parse();
|
||||
}
|
||||
|
||||
// Private Class Functions
|
||||
|
||||
/**
|
||||
* Main private parsing function
|
||||
*
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_parse() {
|
||||
let object = {};
|
||||
// Continue reading whilst we still have data
|
||||
while (this.offset < this.LENGTH) {
|
||||
const field = this._parseField();
|
||||
object = this._addField(field, object);
|
||||
}
|
||||
// Throw an error if we have gone beyond the end of the data
|
||||
if (this.offset > this.LENGTH) {
|
||||
throw new Error("Exhausted Buffer");
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field read from the protobuf data into the Object. As
|
||||
* protobuf fields can appear multiple times, if the field already
|
||||
* exists we need to add the new field into an array of fields
|
||||
* for that key.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} field
|
||||
* @param {Object} object
|
||||
* @returns {Object}
|
||||
*/
|
||||
_addField(field, object) {
|
||||
// Get the field key/values
|
||||
const key = field.key;
|
||||
const value = field.value;
|
||||
object[key] = Object.prototype.hasOwnProperty.call(object, key) ?
|
||||
object[key] instanceof Array ?
|
||||
object[key].concat([value]) :
|
||||
[object[key], value] :
|
||||
value;
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a field and return the Object read from the record
|
||||
*
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_parseField() {
|
||||
// Get the field headers
|
||||
const header = this._fieldHeader();
|
||||
const type = header.type;
|
||||
const key = header.key;
|
||||
switch (type) {
|
||||
// varint
|
||||
case 0:
|
||||
return { "key": key, "value": this._varInt() };
|
||||
// fixed 64
|
||||
case 1:
|
||||
return { "key": key, "value": this._uint64() };
|
||||
// length delimited
|
||||
case 2:
|
||||
return { "key": key, "value": this._lenDelim() };
|
||||
// fixed 32
|
||||
case 5:
|
||||
return { "key": key, "value": this._uint32() };
|
||||
// unknown type
|
||||
default:
|
||||
throw new Error("Unknown type 0x" + type.toString(16));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the field header and return the type and key
|
||||
*
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_fieldHeader() {
|
||||
// Make sure we call type then number to preserve offset
|
||||
return { "type": this._fieldType(), "key": this._fieldNumber() };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the field type from the field header. Type is stored in the
|
||||
* lower 3 bits of the tag byte. This does not move the offset on as
|
||||
* we need to read the field number from the tag byte too.
|
||||
*
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
_fieldType() {
|
||||
// Field type stored in lower 3 bits of tag byte
|
||||
return this.data[this.offset] & this.TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the field number (i.e. the key) from the field header. The
|
||||
* field number is stored in the upper 5 bits of the tag byte - but
|
||||
* is also varint encoded so the follow on bytes may need to be read
|
||||
* when field numbers are > 15.
|
||||
*
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
_fieldNumber() {
|
||||
let shift = -3;
|
||||
let fieldNumber = 0;
|
||||
do {
|
||||
fieldNumber += shift < 28 ?
|
||||
shift === -3 ?
|
||||
(this.data[this.offset] & this.NUMBER) >> -shift :
|
||||
(this.data[this.offset] & this.VALUE) << shift :
|
||||
(this.data[this.offset] & this.VALUE) * Math.pow(2, shift);
|
||||
shift += 7;
|
||||
} while ((this.data[this.offset++] & this.MSB) === this.MSB);
|
||||
return fieldNumber;
|
||||
}
|
||||
|
||||
// Field Parsing Functions
|
||||
|
||||
/**
|
||||
* Read off a varint from the data
|
||||
*
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
_varInt() {
|
||||
let value = 0;
|
||||
let shift = 0;
|
||||
// Keep reading while upper bit set
|
||||
do {
|
||||
value += shift < 28 ?
|
||||
(this.data[this.offset] & this.VALUE) << shift :
|
||||
(this.data[this.offset] & this.VALUE) * Math.pow(2, shift);
|
||||
shift += 7;
|
||||
} while ((this.data[this.offset++] & this.MSB) === this.MSB);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read off a 64 bit unsigned integer from the data
|
||||
*
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
_uint64() {
|
||||
// Read off a Uint64
|
||||
let num = this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
|
||||
num = num * 0x100000000 + this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read off a length delimited field from the data
|
||||
*
|
||||
* @private
|
||||
* @returns {Object|string}
|
||||
*/
|
||||
_lenDelim() {
|
||||
// Read off the field length
|
||||
const length = this._varInt();
|
||||
const fieldBytes = this.data.slice(this.offset, this.offset + length);
|
||||
let field;
|
||||
try {
|
||||
// Attempt to parse as a new Protobuf Object
|
||||
const pbObject = new Protobuf(fieldBytes);
|
||||
field = pbObject._parse();
|
||||
} catch (err) {
|
||||
// Otherwise treat as bytes
|
||||
field = Utils.byteArrayToChars(fieldBytes);
|
||||
}
|
||||
// Move the offset and return the field
|
||||
this.offset += length;
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a 32 bit unsigned integer from the data
|
||||
*
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
_uint32() {
|
||||
// Use a dataview to read off the integer
|
||||
const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);
|
||||
const value = dataview.getUint32(0);
|
||||
this.offset += 4;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export default Protobuf;
|
@ -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
93
src/core/lib/QRCode.mjs
Normal 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 image. (${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.");
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ export default class TLVParser {
|
||||
/**
|
||||
* TLVParser constructor
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @param {byteArray|Uint8Array} input
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(input, options) {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -71,8 +71,8 @@ class AESDecrypt extends Operation {
|
||||
* @throws {OperationError} if cannot decrypt input or invalid key length
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||
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],
|
||||
@ -91,7 +91,7 @@ The following algorithms will be used based on the size of the key:
|
||||
|
||||
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
|
||||
decipher.start({
|
||||
iv: iv,
|
||||
iv: iv.length === 0 ? "" : iv,
|
||||
tag: gcmTag
|
||||
});
|
||||
decipher.update(forge.util.createBuffer(input));
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
|
@ -4,7 +4,7 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Add line numbers operation
|
||||
|
267
src/core/operations/AddTextToImage.mjs
Normal file
267
src/core/operations/AddTextToImage.mjs
Normal 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(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;
|
@ -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];
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
79
src/core/operations/BLAKE2b.mjs
Normal file
79
src/core/operations/BLAKE2b.mjs
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @author h345983745
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import blakejs from "blakejs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
|
||||
/**
|
||||
* BLAKE2b operation
|
||||
*/
|
||||
class BLAKE2b extends Operation {
|
||||
|
||||
/**
|
||||
* BLAKE2b constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "BLAKE2b";
|
||||
this.module = "Hashing";
|
||||
this.description = `Performs BLAKE2b hashing on the input.
|
||||
<br><br> BLAKE2b is a flavour of the BLAKE cryptographic hash function that is optimized for 64-bit platforms and produces digests of any size between 1 and 64 bytes.
|
||||
<br><br> Supports the use of an optional key.`;
|
||||
this.infoURL = "https://wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2b_algorithm";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Size",
|
||||
"type": "option",
|
||||
"value": ["512", "384", "256", "160", "128"]
|
||||
}, {
|
||||
"name": "Output Encoding",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Base64", "Raw"]
|
||||
}, {
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["UTF8", "Decimal", "Base64", "Hex", "Latin1"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string} The input having been hashed with BLAKE2b in the encoding format specified.
|
||||
*/
|
||||
run(input, args) {
|
||||
const [outSize, outFormat] = args;
|
||||
let key = Utils.convertToByteArray(args[2].string || "", args[2].option);
|
||||
if (key.length === 0) {
|
||||
key = null;
|
||||
} else if (key.length > 64) {
|
||||
throw new OperationError(["Key cannot be greater than 64 bytes", "It is currently " + key.length + " bytes."].join("\n"));
|
||||
}
|
||||
|
||||
input = new Uint8Array(input);
|
||||
switch (outFormat) {
|
||||
case "Hex":
|
||||
return blakejs.blake2bHex(input, key, outSize / 8);
|
||||
case "Base64":
|
||||
return toBase64(blakejs.blake2b(input, key, outSize / 8));
|
||||
case "Raw":
|
||||
return Utils.arrayBufferToStr(blakejs.blake2b(input, key, outSize / 8).buffer);
|
||||
default:
|
||||
return new OperationError("Unsupported Output Type");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BLAKE2b;
|
80
src/core/operations/BLAKE2s.mjs
Normal file
80
src/core/operations/BLAKE2s.mjs
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @author h345983745
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import blakejs from "blakejs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
|
||||
/**
|
||||
* BLAKE2s Operation
|
||||
*/
|
||||
class BLAKE2s extends Operation {
|
||||
|
||||
/**
|
||||
* BLAKE2s constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "BLAKE2s";
|
||||
this.module = "Hashing";
|
||||
this.description = `Performs BLAKE2s hashing on the input.
|
||||
<br><br>BLAKE2s is a flavour of the BLAKE cryptographic hash function that is optimized for 8- to 32-bit platforms and produces digests of any size between 1 and 32 bytes.
|
||||
<br><br>Supports the use of an optional key.`;
|
||||
this.infoURL = "https://wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Size",
|
||||
"type": "option",
|
||||
"value": ["256", "160", "128"]
|
||||
}, {
|
||||
"name": "Output Encoding",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["UTF8", "Decimal", "Base64", "Hex", "Latin1"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string} The input having been hashed with BLAKE2s in the encoding format specified.
|
||||
*/
|
||||
run(input, args) {
|
||||
const [outSize, outFormat] = args;
|
||||
let key = Utils.convertToByteArray(args[2].string || "", args[2].option);
|
||||
if (key.length === 0) {
|
||||
key = null;
|
||||
} else if (key.length > 32) {
|
||||
throw new OperationError(["Key cannot be greater than 32 bytes", "It is currently " + key.length + " bytes."].join("\n"));
|
||||
}
|
||||
|
||||
input = new Uint8Array(input);
|
||||
switch (outFormat) {
|
||||
case "Hex":
|
||||
return blakejs.blake2sHex(input, key, outSize / 8);
|
||||
case "Base64":
|
||||
return toBase64(blakejs.blake2s(input, key, outSize / 8));
|
||||
case "Raw":
|
||||
return Utils.arrayBufferToStr(blakejs.blake2s(input, key, outSize / 8).buffer);
|
||||
default:
|
||||
return new OperationError("Unsupported Output Type");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BLAKE2s;
|
@ -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
|
||||
|
@ -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
|
||||
|
107
src/core/operations/BaconCipherDecode.mjs
Normal file
107
src/core/operations/BaconCipherDecode.mjs
Normal file
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @author Karsten Silkenbäumer [github.com/kassi]
|
||||
* @copyright Karsten Silkenbäumer 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import {
|
||||
BACON_ALPHABETS,
|
||||
BACON_TRANSLATION_CASE, BACON_TRANSLATION_AMNZ, BACON_TRANSLATIONS, BACON_CLEARER_MAP, BACON_NORMALIZE_MAP,
|
||||
swapZeroAndOne
|
||||
} from "../lib/Bacon";
|
||||
|
||||
/**
|
||||
* Bacon Cipher Decode operation
|
||||
*/
|
||||
class BaconCipherDecode extends Operation {
|
||||
/**
|
||||
* BaconCipherDecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bacon Cipher Decode";
|
||||
this.module = "Default";
|
||||
this.description = "Bacon's cipher or the Baconian cipher is a method of steganography devised by Francis Bacon in 1605. A message is concealed in the presentation of text, rather than its content.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bacon%27s_cipher";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Alphabet",
|
||||
"type": "option",
|
||||
"value": Object.keys(BACON_ALPHABETS)
|
||||
},
|
||||
{
|
||||
"name": "Translation",
|
||||
"type": "option",
|
||||
"value": BACON_TRANSLATIONS
|
||||
},
|
||||
{
|
||||
"name": "Invert Translation",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [alphabet, translation, invert] = args;
|
||||
const alphabetObject = BACON_ALPHABETS[alphabet];
|
||||
|
||||
// remove invalid characters
|
||||
input = input.replace(BACON_CLEARER_MAP[translation], "");
|
||||
|
||||
// normalize to unique alphabet
|
||||
if (BACON_NORMALIZE_MAP[translation] !== undefined) {
|
||||
input = input.replace(/./g, function (c) {
|
||||
return BACON_NORMALIZE_MAP[translation][c];
|
||||
});
|
||||
} else if (translation === BACON_TRANSLATION_CASE) {
|
||||
const codeA = "A".charCodeAt(0);
|
||||
const codeZ = "Z".charCodeAt(0);
|
||||
input = input.replace(/./g, function (c) {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code >= codeA && code <= codeZ) {
|
||||
return "1";
|
||||
} else {
|
||||
return "0";
|
||||
}
|
||||
});
|
||||
} else if (translation === BACON_TRANSLATION_AMNZ) {
|
||||
const words = input.split(/\s+/);
|
||||
const letters = words.map(function (e) {
|
||||
if (e) {
|
||||
const code = e[0].toUpperCase().charCodeAt(0);
|
||||
return code >= "N".charCodeAt(0) ? "1" : "0";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
input = letters.join("");
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
input = swapZeroAndOne(input);
|
||||
}
|
||||
|
||||
// group into 5
|
||||
const inputArray = input.match(/(.{5})/g) || [];
|
||||
|
||||
let output = "";
|
||||
for (let i = 0; i < inputArray.length; i++) {
|
||||
const code = inputArray[i];
|
||||
const number = parseInt(code, 2);
|
||||
output += number < alphabetObject.alphabet.length ? alphabetObject.alphabet[number] : "?";
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
export default BaconCipherDecode;
|
101
src/core/operations/BaconCipherEncode.mjs
Normal file
101
src/core/operations/BaconCipherEncode.mjs
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @author Karsten Silkenbäumer [github.com/kassi]
|
||||
* @copyright Karsten Silkenbäumer 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import {
|
||||
BACON_ALPHABETS,
|
||||
BACON_TRANSLATIONS_FOR_ENCODING, BACON_TRANSLATION_AB,
|
||||
swapZeroAndOne
|
||||
} from "../lib/Bacon";
|
||||
|
||||
/**
|
||||
* Bacon Cipher Encode operation
|
||||
*/
|
||||
class BaconCipherEncode extends Operation {
|
||||
/**
|
||||
* BaconCipherEncode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Bacon Cipher Encode";
|
||||
this.module = "Default";
|
||||
this.description = "Bacon's cipher or the Baconian cipher is a method of steganography devised by Francis Bacon in 1605. A message is concealed in the presentation of text, rather than its content.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Bacon%27s_cipher";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Alphabet",
|
||||
"type": "option",
|
||||
"value": Object.keys(BACON_ALPHABETS)
|
||||
},
|
||||
{
|
||||
"name": "Translation",
|
||||
"type": "option",
|
||||
"value": BACON_TRANSLATIONS_FOR_ENCODING
|
||||
},
|
||||
{
|
||||
"name": "Keep extra characters",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "Invert Translation",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [alphabet, translation, keep, invert] = args;
|
||||
|
||||
const alphabetObject = BACON_ALPHABETS[alphabet];
|
||||
const charCodeA = "A".charCodeAt(0);
|
||||
const charCodeZ = "Z".charCodeAt(0);
|
||||
|
||||
let output = input.replace(/./g, function (c) {
|
||||
const charCode = c.toUpperCase().charCodeAt(0);
|
||||
if (charCode >= charCodeA && charCode <= charCodeZ) {
|
||||
let code = charCode - charCodeA;
|
||||
if (alphabetObject.codes !== undefined) {
|
||||
code = alphabetObject.codes[code];
|
||||
}
|
||||
const bacon = ("00000" + code.toString(2)).substr(-5, 5);
|
||||
return bacon;
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
});
|
||||
|
||||
if (invert) {
|
||||
output = swapZeroAndOne(output);
|
||||
}
|
||||
if (!keep) {
|
||||
output = output.replace(/[^01]/g, "");
|
||||
const outputArray = output.match(/(.{5})/g) || [];
|
||||
output = outputArray.join(" ");
|
||||
}
|
||||
if (translation === BACON_TRANSLATION_AB) {
|
||||
output = output.replace(/[01]/g, function (c) {
|
||||
return {
|
||||
"0": "A",
|
||||
"1": "B"
|
||||
}[c];
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
export default BaconCipherEncode;
|
@ -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)}%`);
|
||||
});
|
||||
|
||||
|
@ -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)}%`);
|
||||
});
|
||||
|
||||
|
@ -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";
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user