2
0
mirror of https://github.com/gchq/CyberChef synced 2025-12-05 23:53:27 +00:00

Compare commits

...

116 Commits

Author SHA1 Message Date
n1474335
2a91af152d Fixed sitemap generation 2019-09-04 11:14:45 +01:00
n1474335
0ac211ce77 9.4.1 2019-08-30 18:49:11 +01:00
n1474335
32c0d6f253 Updated dependencies 2019-08-30 18:49:05 +01:00
n1474335
de762847e9 9.4.0 2019-08-30 15:48:47 +01:00
n1474335
6248e32148 Updated CHANGELOG 2019-08-30 15:47:50 +01:00
n1474335
52f88ee32d Merge branch 'j433866-render-markdown' 2019-08-30 15:46:37 +01:00
n1474335
f8d1cf2f60 Tidied up 'Render Markdown' operation 2019-08-30 15:46:24 +01:00
n1474335
e129425d8d Merge branch 'render-markdown' of https://github.com/j433866/CyberChef into j433866-render-markdown 2019-08-30 15:33:47 +01:00
n1474335
ae20d82e1d Fix Nightwatch test 2019-08-30 15:30:30 +01:00
n1474335
463b2ce040 9.3.0 2019-08-30 11:58:37 +01:00
n1474335
412b47abba Updated CHANGELOG 2019-08-30 11:58:32 +01:00
n1474335
ed216eee73 Merge branch 'j433866-show-on-map' 2019-08-30 11:57:00 +01:00
n1474335
9dd5234962 Tidied up 'Show on map' operation 2019-08-30 11:56:48 +01:00
n1474335
018532016b Merge branch 'show-on-map' of https://github.com/j433866/CyberChef into j433866-show-on-map 2019-08-30 11:44:12 +01:00
n1474335
7dfecc38f6 Added more UI tests to ensure all modules load, categories can be viewed, and operations can be dragged 2019-08-30 11:43:30 +01:00
n1474335
572f035877 Background magic is now debounced to prevent it firing too often. 2019-08-29 14:08:56 +01:00
j433866
b94eb6adb0 Add syntax highlighting
Explicitly disable HTML rendering.
Updated description.
2019-08-29 14:08:07 +01:00
j433866
45fccb94e1 Merge remote-tracking branch 'upstream/master' into render-markdown 2019-08-29 13:23:37 +01:00
j433866
2628f17fae Change maps source to use Wikimedia maps.
Add link to Wikimedia maps ToS.
If there's no data, show the map anyway.
2019-08-29 11:43:45 +01:00
j433866
69fb6e77fc Merge remote-tracking branch 'upstream/master' into show-on-map 2019-08-29 10:42:40 +01:00
j433866
6992858e67 9.2.3 2019-08-29 10:23:41 +01:00
j433866
59917cca45 Add overflow CSS rule to fix scrolling. Fixes #626 2019-08-29 10:18:52 +01:00
n1474335
2f0b959aa4 Fixed copy:ghPages docs error 2019-08-28 18:48:31 +01:00
n1474335
4f2749e0dc 9.2.2 2019-08-28 17:14:20 +01:00
n1474335
c9deaae744 Updated supported browser versions. 2019-08-28 17:14:12 +01:00
n1474335
686ca284fe Removed JSDoc generation as it is never really used. JSDoc comments are still required but the doc files will no longer be generated. This simplifies the build process and config scripts. 2019-08-28 16:37:31 +01:00
n1474335
b21643f485 Merge branch 'master' of github.com:gchq/CyberChef 2019-08-28 16:14:49 +01:00
n1474335
4c28627459 Added pulse to Background Magic button to draw attention. 2019-08-28 16:14:13 +01:00
n1474335
6a6fe0f56d Update CONTRIBUTING.md 2019-08-28 15:47:33 +01:00
n1474335
570a4c7fca Update CHANGELOG.md 2019-08-27 18:30:33 +01:00
n1474335
094d352e5f Added eslint space-before-blocks rule 2019-08-27 18:13:33 +01:00
n1474335
44b90be7d6 Added 'fully qualified' to the description for the 'Extract Domains' operation to reduce ambiguity. #618 2019-08-27 17:59:45 +01:00
n1474335
e7980a8886 9.2.1 2019-08-27 13:02:24 +01:00
n1474335
1c9c0a48be Merge branch 'csmith-ip-format-octal' 2019-08-27 13:02:18 +01:00
n1474335
c541eebe3e Merge branch 'ip-format-octal' of https://github.com/csmith/CyberChef into csmith-ip-format-octal 2019-08-27 13:00:54 +01:00
n1474335
c51e6efe74 Merge branch 'chrishepner-scanembedded-typo' 2019-08-27 12:57:20 +01:00
Chris Smith
6c9ce15b26 Add octal support to Change IP Format.
Also add test cases covering interchanging between all four
formats.
2019-08-24 01:14:44 +01:00
Chris Hepner
8e1bd36b4c Fix typo in ScanForEmbeddedFiles
Change "suffiently" to "sufficiently"
2019-08-23 14:52:16 -07:00
n1474335
d3e3e6e6fc 9.2.0 2019-08-23 11:22:03 +01:00
n1474335
f1794a2dfe Updated CHANGELOG 2019-08-23 11:22:00 +01:00
n1474335
1efccff730 Merge branch 'h345983745-udp-header-parser' 2019-08-23 10:56:25 +01:00
n1474335
0031345383 Tidied up 'Parse UDP' operation 2019-08-23 10:56:13 +01:00
n1474335
46fa7475cf Merge branch 'udp-header-parser' of https://github.com/h345983745/CyberChef into h345983745-udp-header-parser 2019-08-23 10:40:24 +01:00
n1474335
afc7c40975 Create SECURITY.md 2019-08-22 17:38:56 +01:00
n1474335
dc99797f7b 9.1.0 2019-08-22 16:51:50 +01:00
n1474335
4624266a5c Updated CHANGELOG 2019-08-22 16:50:43 +01:00
n1474335
05bfd99318 9.0.10 2019-08-22 16:35:20 +01:00
n1474335
db3faf16b0 Merge branch 'master' of github.com:gchq/CyberChef 2019-08-22 16:34:00 +01:00
n1474335
9fc451ece8 9.0.9 2019-08-22 16:31:59 +01:00
n1474335
9e1079027b Merge branch 'j433866-carriage-returns' 2019-08-22 16:31:41 +01:00
n1474335
9774a4bd26 Tidied up CR preservation code 2019-08-22 16:31:21 +01:00
j433866
ce9e864757 9.0.9 2019-08-22 14:58:20 +01:00
j433866
737ea19c9e Fix AES decryption in GCM mode not using IVs.
Updated tests to match new results.
Included a Python script to generate AES-GCM tests
2019-08-22 14:54:58 +01:00
j433866
82b5e97a2b Merge branch 'master' into render-markdown 2019-08-22 12:31:52 +01:00
j433866
c43f829854 Slightly change wording of alerts 2019-08-22 11:56:14 +01:00
j433866
f43a868607 Add carriage return detection for pasted and switched inputs.
Fix switching the output to input not working properly.
Add nicer confirmation boxes for zipping outputs.
2019-08-22 11:53:41 +01:00
j433866
9f2d1453ed Make the wordWrap change event only fire when the
word wrap checkbox is changed
2019-08-22 11:26:43 +01:00
j433866
082d939f7d Add customisations for confirm box.
Can change the text of the accept and reject buttons.
Now returns undefined if the user clicks off it
2019-08-22 11:26:04 +01:00
j433866
8d628cf0ed Merge remote-tracking branch 'upstream/master' 2019-08-22 11:20:40 +01:00
j433866
19553dcfed Fix parse colour code operation.
The interactive part broke due to changes for multiple inputs.
Now fires the inputChange event with a fake keyup event to autobake
2019-08-22 11:00:04 +01:00
n1474335
a7938526aa 9.0.8 2019-08-21 14:28:04 +01:00
n1474335
863551ee1d AES Decrypt now handles blank IVs correctly. Fixes #613 2019-08-21 14:27:56 +01:00
n1474335
772c6bbba5 Tidied up test runner. Passing tests are no longer printed to the console. 2019-08-20 17:13:05 +01:00
n1474335
148dcbb0c5 9.0.7 2019-08-20 15:21:18 +01:00
n1474335
82abdb50b1 Fixed bug in Protobuf library causing issues with long keys 2019-08-20 15:20:43 +01:00
h345983745
b8dbb11136 Spelling 2019-08-19 21:05:38 +01:00
h345983745
b14cb99587 Removed console.log 2019-08-19 20:55:04 +01:00
h345983745
1d32a5939c Core UDP parsing functionality
Added to categorie

Description

Added Tests

Added tests
2019-08-19 20:48:05 +01:00
j433866
ae1cd8ba3e Add fade animation to modals 2019-08-15 15:03:13 +01:00
j433866
1fb6bffe1c 9.0.6 2019-08-13 16:46:17 +01:00
j433866
59864e3781 Fix Defang IP tests causing the tests to fail 2019-08-13 16:45:53 +01:00
j433866
e2c7d8c678 Increase size limit for inlined fonts / icons 2019-08-13 16:03:52 +01:00
j433866
62f82c5d12 Merge branch 'inline-fonts' 2019-08-13 15:39:33 +01:00
n1474335
ec70d8a3a2 Updated CHANGELOG 2019-08-13 14:25:36 +01:00
n1474335
1b4471a946 Merge branch 'h345983745-defang-ip' 2019-08-13 14:23:54 +01:00
n1474335
43472394c7 Tidied up 'Defang IP Addresses' operation 2019-08-13 14:23:41 +01:00
n1474335
a4e9025b8e Merge branch 'defang-ip' of https://github.com/h345983745/CyberChef into h345983745-defang-ip 2019-08-13 14:19:34 +01:00
n1474335
6b9e93e310 Updated CHANGELOG 2019-08-13 14:13:59 +01:00
n1474335
06b385563c Merge branch 'j433866-ssh-host-key' 2019-08-13 14:12:04 +01:00
n1474335
d90a23bfd5 Added 'Parse SSH Host Key' operation to the Networking category 2019-08-13 14:11:52 +01:00
j433866
91cdd50ba7 Increase size limit for inlined fonts / icons 2019-08-13 14:03:21 +01:00
j433866
4bc4db8232 Fix incorrect import of TestRegister 2019-08-13 13:39:21 +01:00
j433866
863675e636 Update nodeApi test.
'base 64' now returns 11 results as the SSH host key module mentions it
2019-08-13 13:37:21 +01:00
j433866
1cdcaebb4d Merge remote-tracking branch 'upstream/master' into ssh-host-key
Bring up to date with master
2019-08-13 13:26:40 +01:00
n1474335
cdb30d86b0 9.0.5 2019-08-12 15:27:45 +01:00
n1474335
138e3c4239 Merge branch 'use-std-esm' 2019-08-12 15:27:21 +01:00
n1474335
66b82598e3 Merge branch 'use-std-esm' of github.com:gchq/CyberChef 2019-08-12 15:26:15 +01:00
d98762625
780eecf35b update readme with Node.js compatibility 2019-08-02 14:36:40 +01:00
d98762625
8548d39318 add node consumer tests to travis 2019-08-02 11:10:15 +01:00
n1474335
6f26ff0a89 Merge branch 'master' into use-std-esm 2019-07-31 15:05:04 +01:00
n1474335
2ffd2c6b7a 9.0.4 2019-07-29 17:09:52 +01:00
n1474335
0e95ad8ed6 Updated a range of operations to use ArrayBuffers instead of byteArrays to improve performance with large files. 2019-07-29 17:09:46 +01:00
n1474335
3a8b362dfd 9.0.3 2019-07-29 15:15:34 +01:00
n1474335
82b94fad5d Fixed BigNumber type coercion issues when passed between workers 2019-07-29 15:14:29 +01:00
j433866
42cfed5fa8 Fix file inputs being overwritten with strings.
Added force option in case we really need to overwrite
2019-07-25 15:16:07 +01:00
j433866
e4452b906e Fix functions not being awaited 2019-07-25 15:14:12 +01:00
j433866
36abaeb6fb Fix tab bar shadows appearing when they shouldn't 2019-07-24 14:22:56 +01:00
j433866
507c951f28 9.0.2 2019-07-24 09:12:16 +01:00
j433866
8a67103808 Merge branch 'artemisbot-bug/dark-mode' 2019-07-24 09:10:51 +01:00
Matt
b3a10d4f9e Made some better colour choices 2019-07-23 19:17:21 +01:00
Matt
d4c4e2599d Reduced redundancy 2019-07-22 23:16:17 +01:00
Matt
67ead1c250 Fixed alternative theme tables 2019-07-22 23:12:36 +01:00
d98762625
e4d98eba6b use std/esm to make chef compatible with cjs projects. Remove webpack work for node 2019-07-19 13:14:32 +01:00
j433866
9c6ceaa58a Add tests 2019-07-15 14:12:40 +01:00
j433866
7f168d49a6 Add render markdown operation 2019-07-12 09:33:13 +01:00
j433866
897dc0fb97 9.0.1 2019-07-10 15:01:53 +01:00
j433866
a08419a4ce Remove getOutput function, use Dish object instead
Fix inputNum error in WorkerWaiter.
Replace explicit tab shadows with class lists
2019-07-10 14:58:07 +01:00
n1474335
710223d7de Updated CHANGELOG 2019-07-09 14:38:10 +01:00
j433866
ac1c93d29b Fix incorrect curve detection for ecdsa-sha2 2019-07-08 16:58:03 +01:00
j433866
944842d4eb Improve description and add to Categories 2019-07-08 16:44:36 +01:00
j433866
d56ff0825a Add extraction of actual key from public key file 2019-07-08 15:58:56 +01:00
h345983745
bac2e8c014 Removed V4 + V6 options 2019-06-29 01:12:50 +01:00
j433866
59cdd259ac Add new parse ssh host key operation 2019-05-23 11:11:37 +01:00
h345983745
219469f24f Intial Commit
Consolidated IP Regex's

Fixed Logic Error

Added Tests

Removed Changes Outside Of Operation

Added to category
2019-05-12 21:19:54 +01:00
j433866
b491b9d77d Move conversion of co-ordinates to run() instead of present() 2019-01-18 11:31:53 +00:00
j433866
237f792fb4 Add new Show on map operation 2019-01-18 11:19:06 +00:00
115 changed files with 2898 additions and 2563 deletions

View File

@@ -43,6 +43,7 @@
// stylistic conventions
"brace-style": ["error", "1tbs"],
"space-before-blocks": ["error", "always"],
"block-spacing": "error",
"array-bracket-spacing": "error",
"comma-spacing": "error",

View File

@@ -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.
@@ -25,9 +25,9 @@ Before your contributions can be accepted, you must:
## 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 principles in mind, any changes or additions to CyberChef should keep it:

3
.gitignore vendored
View File

@@ -2,9 +2,6 @@ node_modules
npm-debug.log
travis.log
build
docs/*
!docs/*.conf.json
!docs/*.ico
.vscode
.*.swp
.DS_Store

View File

@@ -3,6 +3,5 @@ npm-debug.log
travis.log
build/*
!build/node
docs
.vscode
.github

View File

@@ -11,8 +11,7 @@ before_script:
script:
- grunt lint
- grunt test
- grunt docs
- npm run node-prod
- grunt testnodeconsumer
- grunt prod --msg="$COMPILE_MSG"
- xvfb-run --server-args="-screen 0 1200x800x24" grunt testui
before_deploy:
@@ -34,7 +33,7 @@ deploy:
file_glob: true
file:
- build/prod/*.zip
- build/node/CyberChef.js
- src/node/cjs.js
on:
repo: gchq/CyberChef
tags: true

View File

@@ -2,12 +2,28 @@
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
## [9.0.0] - 2019-07-09
- [Multiple inputs](https://github.com/gchq/CyberChef/wiki/Multiple-Inputs) are now supported in the main web UI, allowing you to upload and process multiple files at once [@j433866] [#566]
- A [Node.js API](https://github.com/gchq/CyberChef/wiki/Node-API) has been implemented, meaning that CyberChef can now be used as a library, either to provide specific operations, or an entire baking environment [@d98762625] [#291]
- A [read-eval-print loop (REPL)](https://github.com/gchq/CyberChef/wiki/Node-API#repl) is also included to enable prototyping and experimentation with the API [@d98762625] [#291]
- Light and dark Solarized themes added [@j433866] [#566]
### [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]
@@ -129,6 +145,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]
@@ -158,6 +176,10 @@ All major and minor version changes will be documented in this file. Details of
[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
@@ -265,6 +287,7 @@ 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
[#506]: https://github.com/gchq/CyberChef/pull/506
@@ -275,7 +298,11 @@ All major and minor version changes will be documented in this file. Details of
[#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
[#627]: https://github.com/gchq/CyberChef/pull/627

View File

@@ -3,7 +3,6 @@
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const NodeExternals = require("webpack-node-externals");
const glob = require("glob");
const path = require("path");
@@ -15,7 +14,6 @@ const path = require("path");
* @license Apache-2.0
*/
const NODE_PROD = process.env.NODE_ENV === "production";
module.exports = function (grunt) {
grunt.file.defaultEncoding = "utf8";
@@ -36,8 +34,7 @@ module.exports = function (grunt) {
grunt.registerTask("node",
"Compiles CyberChef into a single NodeJS module.",
[
"clean:node", "clean:config", "clean:nodeConfig", "exec:generateConfig",
"exec:generateNodeIndex", "webpack:node", "webpack:nodeRepl", "chmod:build"
"clean:node", "clean:config", "clean:nodeConfig", "exec:generateConfig", "exec:generateNodeIndex"
]);
grunt.registerTask("test",
@@ -51,17 +48,14 @@ module.exports = function (grunt) {
"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("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("doc", "docs");
grunt.registerTask("tests", "test");
grunt.registerTask("lint", "eslint");
@@ -69,7 +63,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");
@@ -90,7 +83,8 @@ module.exports = function (grunt) {
COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
PKG_VERSION: JSON.stringify(pkg.version),
},
moduleEntryPoints = listEntryModules();
moduleEntryPoints = listEntryModules(),
nodeConsumerTestPath = "~/tmp-cyberchef";
/**
@@ -115,7 +109,6 @@ module.exports = function (grunt) {
node: ["build/node/*"],
config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"],
nodeConfig: ["src/node/index.mjs", "src/node/config/OperationConfig.json"],
docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
standalone: ["build/prod/CyberChef*.html"]
},
eslint: {
@@ -128,22 +121,6 @@ module.exports = function (grunt) {
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",
@@ -163,8 +140,7 @@ 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",
@@ -201,46 +177,6 @@ module.exports = function (grunt) {
]
};
},
node: {
mode: NODE_PROD ? "production" : "development",
target: "node",
entry: "./src/node/index.mjs",
externals: [NodeExternals({
whitelist: ["crypto-api/src/crypto-api"]
})],
output: {
filename: "CyberChef.js",
path: __dirname + "/build/node",
library: "CyberChef",
libraryTarget: "commonjs2"
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
],
},
nodeRepl: {
mode: NODE_PROD ? "production" : "development",
target: "node",
entry: "./src/node/repl-index.mjs",
externals: [NodeExternals({
whitelist: ["crypto-api/src/crypto-api"]
})],
output: {
filename: "CyberChef-repl.js",
path: __dirname + "/build/node",
library: "CyberChef",
libraryTarget: "commonjs2"
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
],
}
},
"webpack-dev-server": {
options: {
@@ -295,7 +231,6 @@ module.exports = function (grunt) {
"build/prod/**/*",
"!build/prod/index.html",
"!build/prod/BundleAnalyzerReport.html",
"!build/prod/sitemap.js"
],
dest: `build/prod/CyberChef_v${pkg.version}.zip`
}
@@ -333,12 +268,7 @@ module.exports = function (grunt) {
{
src: "build/prod/index.html",
dest: "build/prod/index.html"
},
{
expand: true,
src: "docs/**",
dest: "build/prod/"
},
}
]
},
standalone: {
@@ -370,12 +300,6 @@ module.exports = function (grunt) {
mode: "755",
},
src: ["build/**/*", "build/"]
},
docs: {
options: {
mode: "755",
},
src: ["docs/**/*", "docs/"]
}
},
watch: {
@@ -402,7 +326,7 @@ module.exports = function (grunt) {
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: [
@@ -428,7 +352,44 @@ module.exports = function (grunt) {
},
nodeTests: {
command: "node --experimental-modules --no-warnings --no-deprecation tests/node/index.mjs"
}
},
setupNodeConsumers: {
command: [
"echo '\n--- Testing node conumers ---'",
"npm link",
`mkdir ${nodeConsumerTestPath}`,
`cp tests/node/consumers/* ${nodeConsumerTestPath}`,
`cd ${nodeConsumerTestPath}`,
"npm link cyberchef"
].join(";"),
},
teardownNodeConsumers: {
command: [
`rm -rf ${nodeConsumerTestPath}`,
"echo '\n--- Node consumer tests complete ---'"
].join(";"),
},
testCJSNodeConsumer: {
command: [
`cd ${nodeConsumerTestPath}`,
"node --no-warnings cjs-consumer.js",
].join(";"),
stdout: false,
},
testESMNodeConsumer: {
command: [
`cd ${nodeConsumerTestPath}`,
"node --no-warnings --experimental-modules esm-consumer.mjs",
].join(";"),
stdout: false,
},
testESMDeepImportNodeConsumer: {
command: [
`cd ${nodeConsumerTestPath}`,
"node --no-warnings --experimental-modules esm-deep-import-consumer.mjs",
].join(";"),
stdout: false,
},
},
});
};

View File

@@ -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)
@@ -77,9 +76,13 @@ You can use as many operations as you like in simple or complex ways. Some examp
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
View 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,32 +0,0 @@
{
"tags": {
"allowUnknownTags": true
},
"plugins": [
"plugins/markdown",
"node_modules/jsdoc-babel"
],
"templates": {
"systemName": "CyberChef",
"footer": "",
"copyright": "&copy; 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
}
}

2691
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "9.0.0",
"version": "9.4.1",
"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,29 +27,28 @@
"type": "git",
"url": "https://github.com/gchq/CyberChef/"
},
"main": "build/node/CyberChef.js",
"main": "src/node/cjs.js",
"module": "src/node/index.mjs",
"bugs": "https://github.com/gchq/CyberChef/issues",
"browserslist": [
"Chrome >= 40",
"Firefox >= 35",
"Edge >= 14",
"node >= 6.5"
"Chrome >= 50",
"Firefox >= 38",
"node >= 10"
],
"devDependencies": {
"@babel/core": "^7.5.0",
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/preset-env": "^7.5.0",
"autoprefixer": "^9.6.0",
"babel-eslint": "^10.0.2",
"@babel/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.2.0",
"chromedriver": "^75.0.1",
"babel-plugin-dynamic-import-node": "^2.3.0",
"chromedriver": "^76.0.1",
"colors": "^1.3.3",
"css-loader": "^3.0.0",
"eslint": "^6.0.1",
"css-loader": "^3.2.0",
"eslint": "^6.2.2",
"exports-loader": "^0.7.0",
"file-loader": "^4.0.0",
"file-loader": "^4.2.0",
"grunt": "^1.0.4",
"grunt-accessibility": "~6.0.0",
"grunt-chmod": "~1.1.1",
@@ -60,34 +59,31 @@
"grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^22.0.0",
"grunt-exec": "~3.0.0",
"grunt-jsdoc": "^2.4.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.7.0",
"nightwatch": "^1.1.13",
"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": "^3.2.0",
"style-loader": "^0.23.1",
"svg-url-loader": "^3.0.0",
"url-loader": "^2.0.1",
"webpack": "^4.35.2",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-dev-server": "^3.7.2",
"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.0",
"@babel/runtime": "^7.5.5",
"arrive": "^2.4.1",
"babel-plugin-transform-builtin-extend": "1.1.2",
"bcryptjs": "^2.4.3",
@@ -99,21 +95,22 @@
"bson": "^4.0.2",
"chi-squared": "^1.1.0",
"clippyjs": "0.0.3",
"core-js": "^3.1.4",
"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",
"d3": "^5.9.7",
"d3": "^5.11.0",
"d3-hexbin": "^0.2.2",
"diff": "^4.0.1",
"es6-promisify": "^6.0.1",
"escodegen": "^1.11.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.2",
"geodesy": "^1.1.3",
"highlight.js": "^9.15.8",
"highlight.js": "^9.15.10",
"jimp": "^0.6.4",
"jquery": "3.4.1",
"js-crc": "^0.2.0",
@@ -123,14 +120,15 @@
"jsonwebtoken": "^8.5.1",
"jsqr": "^1.2.0",
"jsrsasign": "8.0.12",
"kbpgp": "2.1.2",
"kbpgp": "2.1.3",
"libbzip2-wasm": "0.0.4",
"libyara-wasm": "0.0.12",
"lodash": "^4.17.11",
"lodash": "^4.17.15",
"loglevel": "^1.6.3",
"loglevel-message-prefix": "^3.0.0",
"markdown-it": "^9.1.0",
"moment": "^2.24.0",
"moment-timezone": "^0.5.25",
"moment-timezone": "^0.5.26",
"ngeohash": "^0.6.3",
"node-forge": "^0.8.5",
"node-md6": "^0.1.0",
@@ -156,13 +154,11 @@
"scripts": {
"start": "grunt dev",
"build": "grunt prod",
"node": "NODE_ENV=development grunt node",
"node-prod": "NODE_ENV=production grunt node",
"repl": "grunt node && node build/node/CyberChef-repl.js",
"repl": "node src/node/repl.js",
"test": "grunt test",
"test-node": "grunt test-node",
"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"
}

View File

@@ -235,6 +235,10 @@ class Dish {
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();
@@ -283,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;

View File

@@ -1303,6 +1303,30 @@ export function sendStatusMessage(msg) {
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);
};
}
/*
* Polyfills

View File

@@ -122,7 +122,8 @@
"PGP Encrypt",
"PGP Decrypt",
"PGP Encrypt and Sign",
"PGP Decrypt and Verify"
"PGP Decrypt and Verify",
"Parse SSH Host Key"
]
},
{
@@ -166,6 +167,8 @@
"Parse IP range",
"Parse IPv6 address",
"Parse IPv4 header",
"Parse UDP",
"Parse SSH Host Key",
"Parse URI",
"URL Encode",
"URL Decode",
@@ -177,7 +180,8 @@
"Group IP addresses",
"Encode NetBIOS Name",
"Decode NetBIOS Name",
"Defang URL"
"Defang URL",
"Defang IP Addresses"
]
},
{
@@ -224,6 +228,7 @@
"Convert speed",
"Convert data units",
"Convert co-ordinate format",
"Show on map",
"Parse UNIX file permissions",
"Swap endianness",
"Parse colour code",
@@ -353,7 +358,8 @@
"BSON serialise",
"BSON deserialise",
"To MessagePack",
"From MessagePack"
"From MessagePack",
"Render Markdown"
]
},
{

View File

@@ -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)];

View File

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

View File

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

View File

@@ -6,9 +6,22 @@
* @license Apache-2.0
*/
import geohash from "ngeohash";
import geodesy from "geodesy";
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";
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:

View File

@@ -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];
}
}

View File

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

View File

@@ -16,14 +16,14 @@ class Protobuf {
/**
* Protobuf constructor
*
* @param {byteArray} data
* @param {byteArray|Uint8Array} data
*/
constructor(data) {
// Check we have a byteArray
if (data instanceof Array) {
// Check we have a byteArray or Uint8Array
if (data instanceof Array || data instanceof Uint8Array) {
this.data = data;
} else {
throw new Error("Protobuf input must be a byteArray");
throw new Error("Protobuf input must be a byteArray or Uint8Array");
}
// Set up masks
@@ -205,7 +205,7 @@ class Protobuf {
(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.MSD) === this.MSB);
} while ((this.data[this.offset++] & this.MSB) === this.MSB);
return fieldNumber;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,7 +64,7 @@ class BlurImage extends Operation {
throw new OperationError(`Error loading image. (${err})`);
}
try {
switch (blurType){
switch (blurType) {
case "Fast":
if (isWorkerEnvironment())
self.sendStatusMessage("Fast blurring image...");

View File

@@ -29,12 +29,12 @@ class ChangeIPFormat extends Operation {
{
"name": "Input format",
"type": "option",
"value": ["Dotted Decimal", "Decimal", "Hex"]
"value": ["Dotted Decimal", "Decimal", "Octal", "Hex"]
},
{
"name": "Output format",
"type": "option",
"value": ["Dotted Decimal", "Decimal", "Hex"]
"value": ["Dotted Decimal", "Decimal", "Octal", "Hex"]
}
];
}
@@ -54,7 +54,6 @@ class ChangeIPFormat extends Operation {
if (lines[i] === "") continue;
let baIp = [];
let octets;
let decimal;
if (inFormat === outFormat) {
output += lines[i] + "\n";
@@ -70,11 +69,10 @@ class ChangeIPFormat extends Operation {
}
break;
case "Decimal":
decimal = lines[i].toString();
baIp.push(decimal >> 24 & 255);
baIp.push(decimal >> 16 & 255);
baIp.push(decimal >> 8 & 255);
baIp.push(decimal & 255);
baIp = this.fromNumber(lines[i].toString(), 10);
break;
case "Octal":
baIp = this.fromNumber(lines[i].toString(), 8);
break;
case "Hex":
baIp = fromHex(lines[i]);
@@ -100,6 +98,10 @@ class ChangeIPFormat extends Operation {
decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0;
output += decIp.toString() + "\n";
break;
case "Octal":
decIp = ((baIp[0] << 24) | (baIp[1] << 16) | (baIp[2] << 8) | baIp[3]) >>> 0;
output += "0" + decIp.toString(8) + "\n";
break;
case "Hex":
hexIp = "";
for (j = 0; j < baIp.length; j++) {
@@ -115,6 +117,22 @@ class ChangeIPFormat extends Operation {
return output.slice(0, output.length-1);
}
/**
* Constructs an array of IP address octets from a numerical value.
* @param {string} value The value of the IP address
* @param {number} radix The numeral system to be used
* @returns {number[]}
*/
fromNumber(value, radix) {
const decimal = parseInt(value, radix);
const baIp = [];
baIp.push(decimal >> 24 & 255);
baIp.push(decimal >> 16 & 255);
baIp.push(decimal >> 8 & 255);
baIp.push(decimal & 255);
return baIp;
}
}
export default ChangeIPFormat;

View File

@@ -23,17 +23,18 @@ class CitrixCTX1Decode extends Operation {
this.module = "Encodings";
this.description = "Decodes strings in a Citrix CTX1 password format to plaintext.";
this.infoURL = "https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
if (input.length % 4 !== 0) {
throw new OperationError("Incorrect hash length");
}

View File

@@ -111,7 +111,7 @@ class DNSOverHTTPS extends Operation {
* @returns {JSON}
*/
function extractData(data) {
if (typeof(data) == "undefined"){
if (typeof(data) == "undefined") {
return [];
} else {
const dataValues = [];

View File

@@ -30,7 +30,7 @@ class DecodeText extends Operation {
"</ul>",
].join("\n");
this.infoURL = "https://wikipedia.org/wiki/Character_encoding";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -42,13 +42,13 @@ class DecodeText extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const format = IO_FORMAT[args[0]];
return cptable.utils.decode(format, input);
return cptable.utils.decode(format, new Uint8Array(input));
}
}

View File

@@ -0,0 +1,61 @@
/**
* @author h345983745
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Defang IP Addresses operation
*/
class DefangIPAddresses extends Operation {
/**
* DefangIPAddresses constructor
*/
constructor() {
super();
this.name = "Defang IP Addresses";
this.module = "Default";
this.description = "Takes a IPv4 or IPv6 address and 'Defangs' it, meaning the IP becomes invalid, removing the risk of accidentally utilising it as an IP address.";
this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = input.replace(IPV4_REGEX, x => {
return x.replace(/\./g, "[.]");
});
input = input.replace(IPV6_REGEX, x => {
return x.replace(/:/g, "[:]");
});
return input;
}
}
export default DefangIPAddresses;
/**
* IPV4 regular expression
*/
const IPV4_REGEX = new RegExp("(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", "g");
/**
* IPV6 regular expression
*/
const IPV6_REGEX = new RegExp("((?=.*::)(?!.*::.+::)(::)?([\\dA-Fa-f]{1,4}:(:|\\b)|){5}|([\\dA-Fa-f]{1,4}:){6})((([\\dA-Fa-f]{1,4}((?!\\3)::|:\\b|(?![\\dA-Fa-f])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})", "g");

View File

@@ -31,7 +31,7 @@ class EncodeText extends Operation {
].join("\n");
this.infoURL = "https://wikipedia.org/wiki/Character_encoding";
this.inputType = "string";
this.outputType = "byteArray";
this.outputType = "ArrayBuffer";
this.args = [
{
"name": "Encoding",
@@ -44,13 +44,12 @@ class EncodeText extends Operation {
/**
* @param {string} input
* @param {Object[]} args
* @returns {byteArray}
* @returns {ArrayBuffer}
*/
run(input, args) {
const format = IO_FORMAT[args[0]];
let encoded = cptable.utils.encode(format, input);
encoded = Array.from(encoded);
return encoded;
const encoded = cptable.utils.encode(format, input);
return new Uint8Array(encoded).buffer;
}
}

View File

@@ -20,7 +20,7 @@ class ExtractDomains extends Operation {
this.name = "Extract domains";
this.module = "Regex";
this.description = "Extracts domain names.<br>Note that this will not include paths. Use <strong>Extract URLs</strong> to find entire URLs.";
this.description = "Extracts fully qualified domain names.<br>Note that this will not include paths. Use <strong>Extract URLs</strong> to find entire URLs.";
this.inputType = "string";
this.outputType = "string";
this.args = [

View File

@@ -22,19 +22,20 @@ class Fletcher16Checksum extends Operation {
this.module = "Crypto";
this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.";
this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-16";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let a = 0,
b = 0;
input = new Uint8Array(input);
for (let i = 0; i < input.length; i++) {
a = (a + input[i]) % 0xff;

View File

@@ -22,19 +22,20 @@ class Fletcher32Checksum extends Operation {
this.module = "Crypto";
this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.";
this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-32";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let a = 0,
b = 0;
input = new Uint8Array(input);
for (let i = 0; i < input.length; i++) {
a = (a + input[i]) % 0xffff;

View File

@@ -22,19 +22,20 @@ class Fletcher64Checksum extends Operation {
this.module = "Crypto";
this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.";
this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-64";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let a = 0,
b = 0;
input = new Uint8Array(input);
for (let i = 0; i < input.length; i++) {
a = (a + input[i]) % 0xffffffff;

View File

@@ -22,19 +22,20 @@ class Fletcher8Checksum extends Operation {
this.module = "Crypto";
this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.";
this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let a = 0,
b = 0;
input = new Uint8Array(input);
for (let i = 0; i < input.length; i++) {
a = (a + input[i]) % 0xf;

View File

@@ -58,7 +58,7 @@ class FlipImage extends Operation {
try {
if (isWorkerEnvironment())
self.sendStatusMessage("Flipping image...");
switch (flipAxis){
switch (flipAxis) {
case "Horizontal":
image.flip(true, false);
break;

View File

@@ -106,9 +106,9 @@ class FromBase64 extends Operation {
}
/**
* @param {ArrayBuffer} input
* @param {string} input
* @param {Object[]} args
* @returns {string}
* @returns {byteArray}
*/
run(input, args) {
const [alphabet, removeNonAlphChars] = args;

View File

@@ -23,7 +23,7 @@ class GenerateHOTP extends Operation {
this.module = "Default";
this.description = "The HMAC-based One-Time Password algorithm (HOTP) is an algorithm that computes a one-time password from a shared secret key and an incrementing counter. It has been adopted as Internet Engineering Task Force standard RFC 4226, is the cornerstone of Initiative For Open Authentication (OATH), and is used in a number of two-factor authentication systems.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated.";
this.infoURL = "https://wikipedia.org/wiki/HMAC-based_One-time_Password_algorithm";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -50,7 +50,7 @@ class GenerateHOTP extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/

View File

@@ -47,7 +47,7 @@ class GenerateLoremIpsum extends Operation {
*/
run(input, args) {
const [length, lengthType] = args;
if (length < 1){
if (length < 1) {
throw new OperationError("Length must be greater than 0");
}
switch (lengthType) {

View File

@@ -23,7 +23,7 @@ class GenerateTOTP extends Operation {
this.module = "Default";
this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OATH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.";
this.infoURL = "https://wikipedia.org/wiki/Time-based_One-time_Password_algorithm";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -55,7 +55,7 @@ class GenerateTOTP extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/

View File

@@ -48,7 +48,7 @@ class ImageFilter extends Operation {
*/
async run(input, args) {
const [filterType] = args;
if (!isImage(new Uint8Array(input))){
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}

View File

@@ -34,7 +34,7 @@ class MicrosoftScriptDecoder extends Operation {
run(input, args) {
const matcher = /#@~\^.{6}==(.+).{6}==\^#~@/;
const encodedData = matcher.exec(input);
if (encodedData){
if (encodedData) {
return MicrosoftScriptDecoder._decode(encodedData[1]);
} else {
return "";

View File

@@ -22,17 +22,18 @@ class NOT extends Operation {
this.module = "Default";
this.description = "Returns the inverse of each byte.";
this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#NOT";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "byteArray";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
input = new Uint8Array(input);
return bitOp(input, null, not);
}

View File

@@ -23,7 +23,7 @@ class OR extends Operation {
this.module = "Default";
this.description = "OR the input with the given key.<br>e.g. <code>fe023da5</code>";
this.infoURL = "https://wikipedia.org/wiki/Bitwise_operation#OR";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "byteArray";
this.args = [
{
@@ -36,12 +36,13 @@ class OR extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
input = new Uint8Array(input);
return bitOp(input, key, or);
}

View File

@@ -113,7 +113,7 @@ CMYK: ${cmyk}
}).on('colorpickerChange', function(e) {
var color = e.color.string('rgba');
document.getElementById('input-text').value = color;
window.app.autoBake();
window.app.manager.input.debounceInputChange(new Event("keyup"));
});
</script>`;
}
@@ -134,7 +134,7 @@ CMYK: ${cmyk}
static _hslToRgb(h, s, l) {
let r, g, b;
if (s === 0){
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = function hue2rgb(p, q, t) {

View File

@@ -0,0 +1,150 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import { fromBase64 } from "../lib/Base64";
import { fromHex, toHexFast } from "../lib/Hex";
/**
* Parse SSH Host Key operation
*/
class ParseSSHHostKey extends Operation {
/**
* ParseSSHHostKey constructor
*/
constructor() {
super();
this.name = "Parse SSH Host Key";
this.module = "Default";
this.description = "Parses a SSH host key and extracts fields from it.<br>The key type can be:<ul><li>ssh-rsa</li><li>ssh-dss</li><li>ecdsa-sha2</li></ul>The key format can be either Hex or Base64.";
this.infoURL = "https://wikipedia.org/wiki/Secure_Shell";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input Format",
type: "option",
value: [
"Auto",
"Base64",
"Hex"
]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat] = args,
inputKey = this.convertKeyToBinary(input.trim(), inputFormat),
fields = this.parseKey(inputKey),
keyType = Utils.byteArrayToChars(fromHex(fields[0]), "");
let output = `Key type: ${keyType}`;
if (keyType === "ssh-rsa") {
output += `\nExponent: 0x${fields[1]}`;
output += `\nModulus: 0x${fields[2]}`;
} else if (keyType === "ssh-dss") {
output += `\np: 0x${fields[1]}`;
output += `\nq: 0x${fields[2]}`;
output += `\ng: 0x${fields[3]}`;
output += `\ny: 0x${fields[4]}`;
} else if (keyType.startsWith("ecdsa-sha2")) {
output += `\nCurve: ${Utils.byteArrayToChars(fromHex(fields[1]))}`;
output += `\nPoint: 0x${fields.slice(2)}`;
} else {
output += "\nUnsupported key type.";
output += `\nParameters: ${fields.slice(1)}`;
}
return output;
}
/**
* Converts the key to binary format from either hex or base64
*
* @param {string} inputKey
* @param {string} inputFormat
* @returns {byteArray}
*/
convertKeyToBinary(inputKey, inputFormat) {
const keyPattern = new RegExp(/^(?:[ssh]|[ecdsa-sha2])\S+\s+(\S*)/),
keyMatch = inputKey.match(keyPattern);
if (keyMatch) {
inputKey = keyMatch[1];
}
if (inputFormat === "Auto") {
inputFormat = this.detectKeyFormat(inputKey);
}
if (inputFormat === "Hex") {
return fromHex(inputKey);
} else if (inputFormat === "Base64") {
return fromBase64(inputKey, null, "byteArray");
} else {
throw new OperationError("Invalid input format.");
}
}
/**
* Detects if the key is base64 or hex encoded
*
* @param {string} inputKey
* @returns {string}
*/
detectKeyFormat(inputKey) {
const hexPattern = new RegExp(/^(?:[\dA-Fa-f]{2}[ ,;:]?)+$/);
const b64Pattern = new RegExp(/^\s*(?:[A-Za-z\d+/]{4})+(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?\s*$/);
if (hexPattern.test(inputKey)) {
return "Hex";
} else if (b64Pattern.test(inputKey)) {
return "Base64";
} else {
throw new OperationError("Unable to detect input key format.");
}
}
/**
* Parses fields from the key
*
* @param {byteArray} key
*/
parseKey(key) {
const fields = [];
while (key.length > 0) {
const lengthField = key.slice(0, 4);
let decodedLength = 0;
for (let i = 0; i < lengthField.length; i++) {
decodedLength += lengthField[i];
decodedLength = decodedLength << 8;
}
decodedLength = decodedLength >> 8;
// Break if length wasn't decoded correctly
if (decodedLength <= 0) break;
fields.push(toHexFast(key.slice(4, 4 + decodedLength)));
key = key.slice(4 + decodedLength);
}
return fields;
}
}
export default ParseSSHHostKey;

View File

@@ -24,7 +24,7 @@ class ParseTLV extends Operation {
this.module = "Default";
this.description = "Converts a Type-Length-Value (TLV) encoded string into a JSON object. Can optionally include a <code>Key</code> / <code>Type</code> entry. <br><br>Tags: Key-Length-Value, KLV, Length-Value, LV";
this.infoURL = "https://wikipedia.org/wiki/Type-length-value";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "JSON";
this.args = [
{
@@ -46,12 +46,13 @@ class ParseTLV extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [bytesInKey, bytesInLength, basicEncodingRules] = args;
input = new Uint8Array(input);
if (bytesInKey <= 0 && bytesInLength <= 0)
throw new OperationError("Type or Length size must be greater than 0");

View File

@@ -0,0 +1,84 @@
/**
* @author h345983745 []
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Stream from "../lib/Stream.mjs";
import {toHex} from "../lib/Hex.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Parse UDP operation
*/
class ParseUDP extends Operation {
/**
* ParseUDP constructor
*/
constructor() {
super();
this.name = "Parse UDP";
this.module = "Default";
this.description = "Parses a UDP header and payload (if present).";
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
this.inputType = "ArrayBuffer";
this.outputType = "json";
this.presentType = "html";
this.args = [];
}
/**
* @param {ArrayBuffer} input
* @returns {Object}
*/
run(input, args) {
if (input.byteLength < 8) {
throw new OperationError("Need 8 bytes for a UDP Header");
}
const s = new Stream(new Uint8Array(input));
// Parse Header
const UDPPacket = {
"Source port": s.readInt(2),
"Destination port": s.readInt(2),
"Length": s.readInt(2),
"Checksum": toHex(s.getBytes(2), "")
};
// Parse data if present
if (s.hasMore()) {
UDPPacket.Data = toHex(s.getBytes(UDPPacket.Length - 8), "");
}
return UDPPacket;
}
/**
* Displays the UDP Packet in a table style
* @param {Object} data
* @returns {html}
*/
present(data) {
const html = [];
html.push("<table class='table table-hover table-sm table-bordered table-nonfluid' style='table-layout: fixed'>");
html.push("<tr>");
html.push("<th>Field</th>");
html.push("<th>Value</th>");
html.push("</tr>");
for (const key in data) {
html.push("<tr>");
html.push("<td style=\"word-wrap:break-word\">" + key + "</td>");
html.push("<td>" + data[key] + "</td>");
html.push("</tr>");
}
html.push("</table>");
return html.join("");
}
}
export default ParseUDP;

View File

@@ -23,17 +23,18 @@ class ProtobufDecode extends Operation {
this.module = "Default";
this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.";
this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "JSON";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {JSON}
*/
run(input, args) {
input = new Uint8Array(input);
try {
return Protobuf.decode(input);
} catch (err) {

View File

@@ -27,17 +27,18 @@ class RemoveEXIF extends Operation {
"EXIF data embedded in photos usually contains information about the image file itself as well as the device used to create it.",
].join("\n");
this.infoURL = "https://wikipedia.org/wiki/Exif";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "byteArray";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
input = new Uint8Array(input);
// Do nothing if input is empty
if (input.length === 0) return input;

View File

@@ -20,17 +20,18 @@ class RemoveNullBytes extends Operation {
this.name = "Remove null bytes";
this.module = "Default";
this.description = "Removes all null bytes (<code>0x00</code>) from the input.";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "byteArray";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
input = new Uint8Array(input);
const output = [];
for (let i = 0; i < input.length; i++) {
if (input[i] !== 0) output.push(input[i]);

View File

@@ -0,0 +1,69 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import MarkdownIt from "markdown-it";
import hljs from "highlight.js";
/**
* Render Markdown operation
*/
class RenderMarkdown extends Operation {
/**
* RenderMarkdown constructor
*/
constructor() {
super();
this.name = "Render Markdown";
this.module = "Code";
this.description = "Renders input Markdown as HTML. HTML rendering is disabled to avoid XSS.";
this.infoURL = "https://wikipedia.org/wiki/Markdown";
this.inputType = "string";
this.outputType = "html";
this.args = [
{
name: "Autoconvert URLs to links",
type: "boolean",
value: false
},
{
name: "Enable syntax highlighting",
type: "boolean",
value: true
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const [convertLinks, enableHighlighting] = args,
md = new MarkdownIt({
linkify: convertLinks,
html: false, // Explicitly disable HTML rendering
highlight: function(str, lang) {
if (lang && hljs.getLanguage(lang) && enableHighlighting) {
try {
return hljs.highlight(lang, str).value;
} catch (__) {}
}
return "";
}
}),
rendered = md.render(input);
return `<div style="font-family: var(--primary-font-family)">${rendered}</div>`;
}
}
export default RenderMarkdown;

View File

@@ -41,7 +41,7 @@ class ScanForEmbeddedFiles extends Operation {
* @returns {string}
*/
run(input, args) {
let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any suffiently long file is likely to contain these magic bytes coincidentally.\n",
let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any sufficiently long file is likely to contain these magic bytes coincidentally.\n",
numFound = 0;
const categories = [],
data = new Uint8Array(input);

View File

@@ -62,7 +62,7 @@ class SharpenImage extends Operation {
async run(input, args) {
const [radius, amount, threshold] = args;
if (!isImage(new Uint8Array(input))){
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}

View File

@@ -0,0 +1,113 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Show on map operation
*/
class ShowOnMap extends Operation {
/**
* ShowOnMap constructor
*/
constructor() {
super();
this.name = "Show on map";
this.module = "Hashing";
this.description = "Displays co-ordinates on a slippy map.<br><br>Co-ordinates will be converted to decimal degrees before being shown on the map.<br><br>Supported formats:<ul><li>Degrees Minutes Seconds (DMS)</li><li>Degrees Decimal Minutes (DDM)</li><li>Decimal Degrees (DD)</li><li>Geohash</li><li>Military Grid Reference System (MGRS)</li><li>Ordnance Survey National Grid (OSNG)</li><li>Universal Transverse Mercator (UTM)</li></ul><br>This operation will not work offline.";
this.infoURL = "https://foundation.wikimedia.org/wiki/Maps_Terms_of_Use";
this.inputType = "string";
this.outputType = "string";
this.presentType = "html";
this.args = [
{
name: "Zoom Level",
type: "number",
value: 13
},
{
name: "Input Format",
type: "option",
value: ["Auto"].concat(FORMATS)
},
{
name: "Input Delimiter",
type: "option",
value: [
"Auto",
"Direction Preceding",
"Direction Following",
"\\n",
"Comma",
"Semi-colon",
"Colon"
]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (input.replace(/\s+/g, "") !== "") {
const inFormat = args[1],
inDelim = args[2];
let latLong;
try {
latLong = convertCoordinates(input, inFormat, inDelim, "Decimal Degrees", "Comma", "None", 5);
} catch (error) {
throw new OperationError(error);
}
latLong = latLong.replace(/[,]$/, "");
latLong = latLong.replace(/°/g, "");
return latLong;
}
return input;
}
/**
* @param {string} data
* @param {Object[]} args
* @returns {string}
*/
async present(data, args) {
if (data.replace(/\s+/g, "") === "") {
data = "0, 0";
}
const zoomLevel = args[0];
const tileUrl = "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png",
tileAttribution = "<a href=\"https://wikimediafoundation.org/wiki/Maps_Terms_of_Use\">Wikimedia maps</a> | &copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
leafletUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.js",
leafletCssUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.css";
return `<link rel="stylesheet" href="${leafletCssUrl}" crossorigin=""/>
<style>#output-html { white-space: normal; padding: 0; }</style>
<div id="presentedMap" style="width: 100%; height: 100%;"></div>
<script type="text/javascript">
var mapscript = document.createElement('script');
document.body.appendChild(mapscript);
mapscript.onload = function() {
var presentMap = L.map('presentedMap').setView([${data}], ${zoomLevel});
L.tileLayer('${tileUrl}', {
attribution: '${tileAttribution}'
}).addTo(presentMap);
L.marker([${data}]).addTo(presentMap)
.bindPopup('${data}')
.openPopup();
};
mapscript.src = "${leafletUrl}";
</script>`;
}
}
export default ShowOnMap;

View File

@@ -26,18 +26,19 @@ class SplitColourChannels extends Operation {
this.module = "Image";
this.description = "Splits the given image into its red, green and blue colour channels.";
this.infoURL = "https://wikipedia.org/wiki/Channel_(digital_image)";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "List<File>";
this.presentType = "html";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {List<File>}
*/
async run(input, args) {
input = new Uint8Array(input);
// Make sure that the input is an image
if (!isImage(input)) throw new OperationError("Invalid file type.");

View File

@@ -79,7 +79,7 @@ class SwapEndianness extends Operation {
const word = data.slice(i, i + wordLength);
// Pad word if too short
if (padIncompleteWords && word.length < wordLength){
if (padIncompleteWords && word.length < wordLength) {
for (j = word.length; j < wordLength; j++) {
word.push(0);
}

View File

@@ -22,17 +22,18 @@ class TCPIPChecksum extends Operation {
this.module = "Crypto";
this.description = "Calculates the checksum for a TCP (Transport Control Protocol) or IP (Internet Protocol) header from an input of raw bytes.";
this.infoURL = "https://wikipedia.org/wiki/IPv4_header_checksum";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
let csum = 0;
for (let i = 0; i < input.length; i++) {

View File

@@ -22,7 +22,7 @@ class Tar extends Operation {
this.module = "Compression";
this.description = "Packs the input into a tarball.<br><br>No support for multiple files at this time.";
this.infoURL = "https://wikipedia.org/wiki/Tar_(computing)";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "File";
this.args = [
{
@@ -34,11 +34,13 @@ class Tar extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
input = new Uint8Array(input);
const Tarball = function() {
this.bytes = new Array(512);
this.position = 0;

View File

@@ -22,7 +22,7 @@ class ToBase32 extends Operation {
this.module = "Default";
this.description = "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.";
this.infoURL = "https://wikipedia.org/wiki/Base32";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -34,12 +34,13 @@ class ToBase32 extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (!input) return "";
input = new Uint8Array(input);
const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";
let output = "",

View File

@@ -24,7 +24,7 @@ class ToBase58 extends Operation {
this.module = "Default";
this.description = "Base58 (similar to Base64) is a notation for encoding arbitrary byte data. It differs from Base64 by removing easily misread characters (i.e. l, I, 0 and O) to improve human readability.<br><br>This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>hello world</code> becomes <code>StV1DL6CwTryKyV</code><br><br>Base58 is commonly used in cryptocurrencies (Bitcoin, Ripple, etc).";
this.infoURL = "https://wikipedia.org/wiki/Base58";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -36,11 +36,12 @@ class ToBase58 extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
let alphabet = args[0] || ALPHABET_OPTIONS[0].value,
result = [0];

View File

@@ -24,7 +24,7 @@ class ToBase62 extends Operation {
this.module = "Default";
this.description = "Base62 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. The high number base results in shorter strings than with the decimal or hexadecimal system.";
this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -36,11 +36,12 @@ class ToBase62 extends Operation {
}
/**
* @param {string} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
if (input.length < 1) return "";
const ALPHABET = Utils.expandAlphRange(args[0]).join("");

View File

@@ -24,7 +24,7 @@ class ToBase85 extends Operation {
this.module = "Default";
this.description = "Base85 (also called Ascii85) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.<br><br>This operation encodes data in an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>hello world</code> becomes <code>BOu!rD]j7BEbo7</code><br><br>Base85 is commonly used in Adobe's PostScript and PDF file formats.<br><br><strong>Options</strong><br><u>Alphabet</u><ul><li>Standard - The standard alphabet, referred to as Ascii85</li><li>Z85 (ZeroMQ) - A string-safe variant of Base85, which avoids quote marks and backslash characters</li><li>IPv6 - A variant of Base85 suitable for encoding IPv6 addresses (RFC 1924)</li></ul><u>Include delimiter</u><br>Adds a '<~' and '~>' delimiter to the start and end of the data. This is standard for Adobe's implementation of Base85.";
this.infoURL = "https://wikipedia.org/wiki/Ascii85";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -41,11 +41,12 @@ class ToBase85 extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
const alphabet = Utils.expandAlphRange(args[0]).join(""),
encoding = alphabetName(alphabet),
includeDelim = args[1];

View File

@@ -24,7 +24,7 @@ class ToBinary extends Operation {
this.module = "Default";
this.description = "Displays the input data as a binary string.<br><br>e.g. <code>Hi</code> becomes <code>01001000 01101001</code>";
this.infoURL = "https://wikipedia.org/wiki/Binary_code";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -36,11 +36,12 @@ class ToBinary extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
return toBinary(input, args[0]);
}

View File

@@ -23,7 +23,7 @@ class ToDecimal extends Operation {
this.name = "To Decimal";
this.module = "Default";
this.description = "Converts the input data to an ordinal integer array.<br><br>e.g. <code>Hello</code> becomes <code>72 101 108 108 111</code>";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -40,11 +40,12 @@ class ToDecimal extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
const delim = Utils.charRep(args[0]),
signed = args[1];
if (signed) {

View File

@@ -23,7 +23,7 @@ class ToHexContent extends Operation {
this.module = "Default";
this.description = "Converts special characters in a string to hexadecimal. This format is used by SNORT for representing hex within ASCII text.<br><br>e.g. <code>foo=bar</code> becomes <code>foo|3d|bar</code>.";
this.infoURL = "http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION00451000000000000000";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -40,11 +40,12 @@ class ToHexContent extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
const convert = args[0];
const spaces = args[1];
if (convert === "All chars") {

View File

@@ -25,17 +25,18 @@ class ToQuotedPrintable extends Operation {
this.module = "Default";
this.description = "Quoted-Printable, or QP encoding, is an encoding using printable ASCII characters (alphanumeric and the equals sign '=') to transmit 8-bit data over a 7-bit data path or, generally, over a medium which is not 8-bit clean. It is defined as a MIME content transfer encoding for use in e-mail.<br><br>QP works by using the equals sign '=' as an escape character. It also limits line length to 76, as some software has limits on line length.";
this.infoURL = "https://wikipedia.org/wiki/Quoted-printable";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
let mimeEncodedStr = this.mimeEncode(input);
// fix line breaks
@@ -73,7 +74,7 @@ class ToQuotedPrintable extends Operation {
/**
* Encodes mime data.
*
* @param {byteArray} buffer
* @param {byteArray|Uint8Array} buffer
* @returns {string}
*/
mimeEncode(buffer) {

View File

@@ -51,7 +51,7 @@ class UNIXTimestampToWindowsFiletime extends Operation {
input = new BigNumber(input);
if (units === "Seconds (s)"){
if (units === "Seconds (s)") {
input = input.multipliedBy(new BigNumber("10000000"));
} else if (units === "Milliseconds (ms)") {
input = input.multipliedBy(new BigNumber("10000"));
@@ -65,7 +65,7 @@ class UNIXTimestampToWindowsFiletime extends Operation {
input = input.plus(new BigNumber("116444736000000000"));
if (format === "Hex"){
if (format === "Hex") {
return input.toString(16);
} else {
return input.toFixed();

View File

@@ -23,7 +23,7 @@ class Untar extends Operation {
this.module = "Compression";
this.description = "Unpacks a tarball and displays it per file.";
this.infoURL = "https://wikipedia.org/wiki/Tar_(computing)";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "List<File>";
this.presentType = "html";
this.args = [];
@@ -37,11 +37,12 @@ class Untar extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {List<File>}
*/
run(input, args) {
input = new Uint8Array(input);
const stream = new Stream(input),
files = [];

View File

@@ -57,7 +57,7 @@ class WindowsFiletimeToUNIXTimestamp extends Operation {
input = input.minus(new BigNumber("116444736000000000"));
if (units === "Seconds (s)"){
if (units === "Seconds (s)") {
input = input.dividedBy(new BigNumber("10000000"));
} else if (units === "Milliseconds (ms)") {
input = input.dividedBy(new BigNumber("10000"));

View File

@@ -23,7 +23,7 @@ class XOR extends Operation {
this.module = "Default";
this.description = "XOR the input with the given key.<br>e.g. <code>fe023da5</code><br><br><strong>Options</strong><br><u>Null preserving:</u> If the current byte is 0x00 or the same as the key, skip it.<br><br><u>Scheme:</u><ul><li>Standard - key is unchanged after each round</li><li>Input differential - key is set to the value of the previous unprocessed byte</li><li>Output differential - key is set to the value of the previous processed byte</li><li>Cascade - key is set to the input byte shifted by one</li></ul>";
this.infoURL = "https://wikipedia.org/wiki/XOR";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "byteArray";
this.args = [
{
@@ -46,11 +46,12 @@ class XOR extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
input = new Uint8Array(input);
const key = Utils.convertToByteArray(args[0].string || "", args[0].option),
[, scheme, nullPreserving] = args;

View File

@@ -25,7 +25,7 @@ class XORBruteForce extends Operation {
this.module = "Default";
this.description = "Enumerate all possible XOR solutions. Current maximum key length is 2 due to browser performance.<br><br>Optionally enter a string that you expect to find in the plaintext to filter results (crib).";
this.infoURL = "https://wikipedia.org/wiki/Exclusive_or";
this.inputType = "byteArray";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
@@ -72,11 +72,12 @@ class XORBruteForce extends Operation {
}
/**
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
input = new Uint8Array(input);
const [
keyLength,
sampleLength,

View File

@@ -295,7 +295,7 @@ export function help(input) {
* bake [Wrapped] - Perform an array of operations on some input.
* @returns {Function}
*/
export function bake(){
export function bake() {
/**
* bake

13
src/node/cjs.js Normal file
View File

@@ -0,0 +1,13 @@
/**
* Export the main ESM module as CommonJS
*
*
* @author d98762656 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
/*eslint no-global-assign: ["off"] */
require = require("esm")(module);
module.exports = require("./index.mjs");
module.exports.File = require("./File.mjs");

View File

@@ -7,9 +7,9 @@
* @license Apache-2.0
*/
import chef from "./index.mjs";
import repl from "repl";
import File from "./File.mjs";
const chef = require("./cjs.js");
const repl = require("repl");
/*eslint no-console: ["off"] */
@@ -26,7 +26,7 @@ const replServer = repl.start({
prompt: "chef > ",
});
global.File = File;
global.File = chef.File;
Object.keys(chef).forEach((key) => {
if (key !== "operations") {

View File

@@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import Utils from "../core/Utils";
import Utils, { debounce } from "../core/Utils";
import {fromBase64} from "../core/lib/Base64";
import Manager from "./Manager";
import HTMLCategory from "./HTMLCategory";
@@ -41,7 +41,6 @@ class App {
this.autoBakePause = false;
this.progress = 0;
this.ingId = 0;
this.timeouts = {};
}
@@ -295,7 +294,7 @@ class App {
minSize: minimise ? [0, 0, 0] : [240, 310, 450],
gutterSize: 4,
expandToMin: true,
onDrag: this.debounce(function() {
onDrag: debounce(function() {
this.manager.recipe.adjustWidth();
this.manager.input.calcMaxTabs();
this.manager.output.calcMaxTabs();
@@ -633,7 +632,7 @@ class App {
* Pops up a message to the user and writes it to the console log.
*
* @param {string} str - The message to display (HTML supported)
* @param {number} timeout - The number of milliseconds before the alert closes automatically
* @param {number} [timeout=0] - The number of milliseconds before the alert closes automatically
* 0 for never (until the user closes it)
* @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the
* console
@@ -646,14 +645,12 @@ class App {
* // Pops up a box with the message "Happy Christmas!" that will disappear after 5 seconds.
* this.alert("Happy Christmas!", 5000);
*/
alert(str, timeout, silent) {
alert(str, timeout=0, silent=false) {
const time = new Date();
log.info("[" + time.toLocaleString() + "] " + str);
if (silent) return;
timeout = timeout || 0;
this.currentSnackbar = $.snackbar({
content: str,
timeout: timeout,
@@ -670,18 +667,22 @@ class App {
*
* @param {string} title - The title of the box
* @param {string} body - The question (HTML supported)
* @param {string} accept - The text of the accept button
* @param {string} reject - The text of the reject button
* @param {function} callback - A function accepting one boolean argument which handles the
* response e.g. function(answer) {...}
* @param {Object} [scope=this] - The object to bind to the callback function
*
* @example
* // Pops up a box asking if the user would like a cookie. Prints the answer to the console.
* this.confirm("Question", "Would you like a cookie?", function(answer) {console.log(answer);});
* this.confirm("Question", "Would you like a cookie?", "Yes", "No", function(answer) {console.log(answer);});
*/
confirm(title, body, callback, scope) {
confirm(title, body, accept, reject, callback, scope) {
scope = scope || this;
document.getElementById("confirm-title").innerHTML = title;
document.getElementById("confirm-body").innerHTML = body;
document.getElementById("confirm-yes").innerText = accept;
document.getElementById("confirm-no").innerText = reject;
document.getElementById("confirm-modal").style.display = "block";
this.confirmClosed = false;
@@ -694,9 +695,14 @@ class App {
callback.bind(scope)(true);
$("#confirm-modal").modal("hide");
}.bind(this))
.one("click", "#confirm-no", function() {
this.confirmClosed = true;
callback.bind(scope)(false);
}.bind(this))
.one("hide.bs.modal", function(e) {
if (!this.confirmClosed)
callback.bind(scope)(false);
if (!this.confirmClosed) {
callback.bind(scope)(undefined);
}
this.confirmClosed = true;
}.bind(this));
}
@@ -716,6 +722,7 @@ class App {
this.updateTitle(false, null, true);
}
/**
* Update the page title to contain the new recipe
*
@@ -759,29 +766,6 @@ class App {
this.loadURIParams();
}
/**
* 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}
*/
debounce(func, wait, id, scope, args) {
return function() {
const later = function() {
func.apply(scope, args);
};
clearTimeout(this.timeouts[id]);
this.timeouts[id] = setTimeout(later, wait);
}.bind(this);
}
}
export default App;

View File

@@ -224,7 +224,7 @@ class Manager {
document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));
document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options));
this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.switchChange, this.options);
this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.setWordWrap, this.options);
this.addDynamicListener(".option-item input[type=checkbox]#wordWrap", "change", this.options.setWordWrap, this.options);
this.addDynamicListener(".option-item input[type=checkbox]#useMetaKey", "change", this.bindings.updateKeybList, this.bindings);
this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options);
this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options);

View File

@@ -123,9 +123,9 @@
document.getElementById("preloader-error").innerHTML =
"CyberChef encountered an error while loading.<br><br>" +
"The following browser versions are supported:" +
"<ul><li>Google Chrome 40+</li><li>Mozilla Firefox 35+</li><li>Microsoft Edge 14+</li></ul>" +
"<ul><li>Google Chrome 50+</li><li>Mozilla Firefox 38+</li></ul>" +
"Your user agent is:<br>" + escapeHtml(navigator.userAgent) + "<br><br>" +
"If your browser is supported, please <a href='https://github.com/gchq/CyberChef/issues/new'>" +
"If your browser is supported, please <a href='https://github.com/gchq/CyberChef/issues/new/choose'>" +
"raise an issue</a> including the following details:<br><br>" +
"<pre>" + escapeHtml(msg) + "</pre>";
};
@@ -382,7 +382,7 @@
</div>
</div>
<div class="modal" id="save-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="save-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -442,7 +442,7 @@
</div>
</div>
<div class="modal" id="load-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="load-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -469,7 +469,7 @@
</div>
</div>
<div class="modal" id="options-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="options-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -563,16 +563,23 @@
<div class="checkbox option-item">
<label for="imagePreview">
<input type="checkbox" option="imagePreview" id="imagePreview">
Render a preview of the input if it's detected to be an image.
Render a preview of the input if it's detected to be an image
</label>
</div>
<div class="checkbox option-item">
<label for="syncTabs">
<input type="checkbox" option="syncTabs" id="syncTabs">
Keep the current tab in sync between the input and output.
</label>
</div>
<label for="syncTabs">
<input type="checkbox" option="syncTabs" id="syncTabs">
Keep the current tab in sync between the input and output
</label>
</div>
<div class="checkbox option-item">
<label for="preserveCR" data-toggle="tooltip" data-placement="right" data-html="true" title="As HTML textareas don't support carriage returns, editing input must be turned off to preserve them.<br><br>When this option is enabled, editing is disabled for pasted text that contains carriage returns. Otherwise, editing will remain enabled but carriage returns will not be preserved.">
<input type="checkbox" option="preserveCR" id="preserveCR">
Preserve carriage returns when pasting an input
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="reset-options">Reset options to default</button>
@@ -582,7 +589,7 @@
</div>
</div>
<div class="modal" id="favourites-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="favourites-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -608,7 +615,7 @@
</div>
</div>
<div class="modal" id="support-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="support-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -702,7 +709,7 @@
<br>
<pre id="report-bug-info"></pre>
<br>
<a class="btn btn-primary" href="https://github.com/gchq/CyberChef/issues/new" role="button">Raise issue on GitHub</a>
<a class="btn btn-primary" href="https://github.com/gchq/CyberChef/issues/new/choose" role="button">Raise issue on GitHub</a>
</div>
<div role="tabpanel" class="tab-pane" id="about" style="padding: 20px;">
<h5><strong>What</strong></h5>
@@ -744,7 +751,7 @@
</div>
</div>
<div class="modal" id="confirm-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="confirm-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -763,7 +770,7 @@
</div>
</div>
<div class="modal" id="input-tab-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="input-tab-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -822,7 +829,7 @@
</div>
</div>
<div class="modal" id="output-tab-modal" tabindex="-1" role="dialog">
<div class="modal fade" id="output-tab-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">

View File

@@ -53,7 +53,9 @@ function main() {
logLevel: "info",
autoMagic: true,
imagePreview: true,
syncTabs: true
syncTabs: true,
preserveCR: true,
userSetCR: false
};
document.removeEventListener("DOMContentLoaded", main, false);

View File

@@ -1,4 +1,4 @@
import sm from "sitemap";
import Sitemap from "sitemap";
import OperationConfig from "../../core/config/OperationConfig.json";
@@ -10,7 +10,7 @@ import OperationConfig from "../../core/config/OperationConfig.json";
* @license Apache-2.0
*/
const sitemap = sm.createSitemap({
const sitemap = Sitemap.createSitemap({
hostname: "https://gchq.github.io/CyberChef",
});

View File

@@ -337,6 +337,29 @@
fill: var(--primary-font-colour);
}
.pulse {
box-shadow: 0 0 0 0 rgba(90, 153, 212, .3);
animation: pulse 1.5s 1;
}
.pulse:hover {
animation-play-state: paused;
}
@keyframes pulse {
0% {
transform: scale(1);
}
70% {
transform: scale(1.1);
box-shadow: 0 0 0 20px rgba(90, 153, 212, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(90, 153, 212, 0);
}
}
#input-find-options,
#output-find-options {
display: flex;

View File

@@ -39,10 +39,16 @@ div#output {
.split {
box-sizing: border-box;
overflow: auto;
/* overflow: auto;
Removed to enable Background Magic button pulse to overflow.
Replace this rule if it seems to be causing problems. */
position: relative;
}
#operations.split {
overflow: auto;
}
.split.split-horizontal, .gutter.gutter-horizontal {
height: 100%;
float: left;

View File

@@ -125,6 +125,7 @@
/* Misc. */
--drop-file-border-colour: #3a87ad;
--table-border-colour: #ccc;
--popover-background: #fff;
--popover-border-colour: #ccc;
--code-background: #f9f2f4;

View File

@@ -121,6 +121,7 @@
/* Misc. */
--drop-file-border-colour: #0e639c;
--table-border-colour: #555;
--popover-background: #444;
--popover-border-colour: #555;
--code-background: #0e639c;

View File

@@ -121,6 +121,7 @@
/* Misc. */
--drop-file-border-colour: purple;
--table-border-colour: var(--hl3);
--popover-background: turquoise;
--popover-border-colour: violet;
--code-background: black;

View File

@@ -138,6 +138,7 @@
/* Misc. */
--drop-file-border-colour: var(--base01);
--table-border-colour: var(--base01);
--popover-background: var(--base02);
--popover-border-colour: var(--base01);
--code-background: var(--base03);

View File

@@ -140,6 +140,7 @@
/* Misc. */
--drop-file-border-colour: var(--base1);
--table-border-colour: var(--base1);
--popover-background: var(--base2);
--popover-border-colour: var(--base1);
--code-background: var(--base3);

View File

@@ -148,6 +148,15 @@ optgroup {
width: auto !important;
}
.table,
.table-hover tbody tr:hover {
color: var(--primary-font-colour);
}
.table-bordered th, .table-bordered td {
border: 1px solid var(--table-border-colour);
}
.popover {
background-color: var(--popover-background);
border-color: var(--popover-border-colour);

View File

@@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import ChefWorker from "worker-loader?inline&fallback=false!../../core/ChefWorker";
import ChefWorker from "worker-loader?inline&fallback=false!../../core/ChefWorker.js";
/**
* Waiter to handle conversations with a ChefWorker in the background.

View File

@@ -5,9 +5,9 @@
* @license Apache-2.0
*/
import LoaderWorker from "worker-loader?inline&fallback=false!../workers/LoaderWorker";
import InputWorker from "worker-loader?inline&fallback=false!../workers/InputWorker";
import Utils from "../../core/Utils.mjs";
import LoaderWorker from "worker-loader?inline&fallback=false!../workers/LoaderWorker.js";
import InputWorker from "worker-loader?inline&fallback=false!../workers/InputWorker.mjs";
import Utils, { debounce } from "../../core/Utils.mjs";
import { toBase64 } from "../../core/lib/Base64.mjs";
import { isImage } from "../../core/lib/FileType.mjs";
@@ -222,8 +222,6 @@ class InputWaiter {
if (Object.prototype.hasOwnProperty.call(r, "progress") &&
Object.prototype.hasOwnProperty.call(r, "inputNum")) {
this.manager.tabs.updateInputTabProgress(r.inputNum, r.progress, 100);
} else if (Object.prototype.hasOwnProperty.call(r, "fileBuffer")) {
this.manager.tabs.updateInputTabProgress(r.inputNum, 100, 100);
}
const transferable = Object.prototype.hasOwnProperty.call(r, "fileBuffer") ? [r.fileBuffer] : undefined;
@@ -272,7 +270,7 @@ class InputWaiter {
this.showLoadingInfo(r.data, true);
break;
case "setInput":
this.app.debounce(this.set, 50, "setInput", this, [r.data.inputObj, r.data.silent])();
debounce(this.set, 50, "setInput", this, [r.data.inputObj, r.data.silent])();
break;
case "inputAdded":
this.inputAdded(r.data.changeTab, r.data.inputNum);
@@ -305,6 +303,9 @@ class InputWaiter {
case "removeChefWorker":
this.removeChefWorker();
break;
case "fileLoaded":
this.fileLoaded(r.data.inputNum);
break;
default:
log.error(`Unknown action ${r.action}.`);
}
@@ -315,7 +316,7 @@ class InputWaiter {
*/
bakeAll() {
this.app.progress = 0;
this.app.debounce(this.manager.controls.toggleBakeButtonFunction, 20, "toggleBakeButton", this, ["loading"]);
debounce(this.manager.controls.toggleBakeButtonFunction, 20, "toggleBakeButton", this, ["loading"]);
this.inputWorker.postMessage({
action: "bakeAll"
});
@@ -331,7 +332,7 @@ class InputWaiter {
* @param {number} inputData.size - The size in bytes of the input file
* @param {string} inputData.type - The MIME type of the input file
* @param {number} inputData.progress - The load progress of the input file
* @param {boolean} [silent=false] - If true, fires the manager statechange event
* @param {boolean} [silent=false] - If false, fires the manager statechange event
*/
async set(inputData, silent=false) {
return new Promise(function(resolve, reject) {
@@ -373,7 +374,7 @@ class InputWaiter {
if (!silent) window.dispatchEvent(this.manager.statechange);
} else {
this.setFile(inputData);
this.setFile(inputData, silent);
}
}.bind(this));
@@ -389,8 +390,9 @@ class InputWaiter {
* @param {number} inputData.size - The size in bytes of the input file
* @param {string} inputData.type - The MIME type of the input file
* @param {number} inputData.progress - The load progress of the input file
* @param {boolean} [silent=true] - If false, fires the manager statechange event
*/
setFile(inputData) {
setFile(inputData, silent=true) {
const activeTab = this.manager.tabs.getActiveInputTab();
if (inputData.inputNum !== activeTab) return;
@@ -414,6 +416,30 @@ class InputWaiter {
this.setInputInfo(inputData.size, null);
this.displayFilePreview(inputData);
if (!silent) window.dispatchEvent(this.manager.statechange);
}
/**
* Update file details when a file completes loading
*
* @param {number} inputNum - The inputNum of the input which has finished loading
*/
fileLoaded(inputNum) {
this.manager.tabs.updateInputTabProgress(inputNum, 100, 100);
const activeTab = this.manager.tabs.getActiveInputTab();
if (activeTab !== inputNum) return;
this.inputWorker.postMessage({
action: "setInput",
data: {
inputNum: inputNum,
silent: false
}
});
this.updateFileProgress(inputNum, 100);
}
/**
@@ -495,19 +521,6 @@ class InputWaiter {
fileLoaded.textContent = progress + "%";
fileLoaded.style.color = "";
}
if (progress === 100 && progress !== oldProgress) {
// Don't set the input if the progress hasn't changed
this.inputWorker.postMessage({
action: "setInput",
data: {
inputNum: inputNum,
silent: false
}
});
window.dispatchEvent(this.manager.statechange);
}
}
/**
@@ -515,8 +528,9 @@ class InputWaiter {
*
* @param {number} inputNum
* @param {string | ArrayBuffer} value
* @param {boolean} [force=false] - If true, forces the value to be updated even if the type is different to the currently stored type
*/
updateInputValue(inputNum, value) {
updateInputValue(inputNum, value, force=false) {
let includeInput = false;
const recipeStr = toBase64(value, "A-Za-z0-9+/"); // B64 alphabet with no padding
if (recipeStr.length > 0 && recipeStr.length <= 68267) {
@@ -534,7 +548,8 @@ class InputWaiter {
action: "updateInputValue",
data: {
inputNum: inputNum,
value: value
value: value,
force: force
}
}, transferable);
}
@@ -666,7 +681,7 @@ class InputWaiter {
* @param {event} e
*/
debounceInputChange(e) {
this.app.debounce(this.inputChange, 50, "inputChange", this, [e])();
debounce(this.inputChange, 50, "inputChange", this, [e])();
}
/**
@@ -709,33 +724,50 @@ class InputWaiter {
*
* @param {event} e
*/
inputPaste(e) {
const pastedData = e.clipboardData.getData("Text");
if (pastedData.length < (this.app.options.ioDisplayThreshold * 1024)) {
// Pasting normally fires the inputChange() event before
// changing the value, so instead change it here ourselves
// and manually fire inputChange()
e.preventDefault();
const inputText = document.getElementById("input-text");
const selStart = inputText.selectionStart;
const selEnd = inputText.selectionEnd;
const startVal = inputText.value.slice(0, selStart);
const endVal = inputText.value.slice(selEnd);
inputText.value = startVal + pastedData + endVal;
inputText.setSelectionRange(selStart + pastedData.length, selStart + pastedData.length);
this.debounceInputChange(e);
} else {
e.preventDefault();
e.stopPropagation();
async inputPaste(e) {
e.preventDefault();
e.stopPropagation();
const self = this;
/**
* Triggers the input file/binary data overlay
*
* @param {string} pastedData
*/
function triggerOverlay(pastedData) {
const file = new File([pastedData], "PastedData", {
type: "text/plain",
lastModified: Date.now()
});
this.loadUIFiles([file]);
self.loadUIFiles([file]);
}
const pastedData = e.clipboardData.getData("Text");
const inputText = document.getElementById("input-text");
const selStart = inputText.selectionStart;
const selEnd = inputText.selectionEnd;
const startVal = inputText.value.slice(0, selStart);
const endVal = inputText.value.slice(selEnd);
const val = startVal + pastedData + endVal;
if (val.length >= (this.app.options.ioDisplayThreshold * 1024)) {
// Data too large to display, use overlay
triggerOverlay(val);
return false;
} else if (await this.preserveCarriageReturns(val)) {
// Data contains a carriage return and the user doesn't wish to edit it, use overlay
// We check this in a separate condition to make sure it is not run unless absolutely
// necessary.
triggerOverlay(val);
return false;
} else {
// Pasting normally fires the inputChange() event before
// changing the value, so instead change it here ourselves
// and manually fire inputChange()
inputText.value = val;
inputText.setSelectionRange(selStart + pastedData.length, selStart + pastedData.length);
this.debounceInputChange(e);
}
}
@@ -813,6 +845,46 @@ class InputWaiter {
}
}
/**
* Checks if an input contains carriage returns.
* If a CR is detected, checks if the preserve CR option has been set,
* and if not, asks the user for their preference.
*
* @param {string} input - The input to be checked
* @returns {boolean} - If true, the input contains a CR which should be
* preserved, so display an overlay so it can't be edited
*/
async preserveCarriageReturns(input) {
if (input.indexOf("\r") < 0) return false;
const optionsStr = "This behaviour can be changed in the <a href='#' onclick='document.getElementById(\"options\").click()'>Options pane</a>";
if (!this.app.options.userSetCR) {
// User has not set a CR preference yet
let preserve = await new Promise(function(resolve, reject) {
this.app.confirm(
"Carriage Return Detected",
"A <a href='https://wikipedia.org/wiki/Carriage_return'>carriage return</a> (<code>\\r</code>, <code>0x0d</code>) was detected in your input. As HTML textareas <a href='https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element'>can't display carriage returns</a>, editing must be turned off to preserve them. <br>Alternatively, you can enable editing but your carriage returns will not be preserved.<br><br>This preference will be saved but can be toggled in the options pane.",
"Preserve Carriage Returns",
"Enable Editing", resolve, this);
}.bind(this));
if (preserve === undefined) {
// The confirm pane was closed without picking a specific choice
this.app.alert(`Not preserving carriage returns.\n${optionsStr}`, 5000);
preserve = false;
}
this.manager.options.updateOption("preserveCR", preserve);
this.manager.options.updateOption("userSetCR", true);
} else {
if (this.app.options.preserveCR) {
this.app.alert(`A carriage return (\\r, 0x0d) was detected in your input, so editing has been disabled to preserve it.<br>${optionsStr}`, 10000);
} else {
this.app.alert(`A carriage return (\\r, 0x0d) was detected in your input. Editing is remaining enabled, but carriage returns will not be preserved.<br>${optionsStr}`, 10000);
}
}
return this.app.options.preserveCR;
}
/**
* Load files from the UI into the inputWorker
*
@@ -988,9 +1060,13 @@ class InputWaiter {
this.manager.highlighter.removeHighlights();
getSelection().removeAllRanges();
const tabsList = document.getElementById("input-tabs").children;
for (let i = tabsList.length - 1; i >= 0; i--) {
tabsList.item(i).remove();
const tabsList = document.getElementById("input-tabs");
const tabsListChildren = tabsList.children;
tabsList.classList.remove("tabs-left");
tabsList.classList.remove("tabs-right");
for (let i = tabsListChildren.length - 1; i >= 0; i--) {
tabsListChildren.item(i).remove();
}
this.showLoadingInfo({
@@ -1021,7 +1097,7 @@ class InputWaiter {
this.manager.highlighter.removeHighlights();
getSelection().removeAllRanges();
this.updateInputValue(inputNum, "");
this.updateInputValue(inputNum, "", true);
this.set({
inputNum: inputNum,
@@ -1111,7 +1187,7 @@ class InputWaiter {
});
} else if (numTabs === this.maxTabs) {
// Can't create a new tab
document.getElementById("input-tabs").lastElementChild.style.boxShadow = "-15px 0px 15px -15px var(--primary-border-colour) inset";
document.getElementById("input-tabs").lastElementChild.classList.add("tabs-right");
}
if (changeTab) this.changeTab(inputNum, false);

View File

@@ -1,174 +1,180 @@
/**
* Waiter to handle events related to the CyberChef options.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*
* @constructor
* @param {App} app - The main view object for CyberChef.
*/
const OptionsWaiter = function(app, manager) {
this.app = app;
this.manager = manager;
};
/**
* Loads options and sets values of switches and inputs to match them.
*
* @param {Object} options
* Waiter to handle events related to the CyberChef options.
*/
OptionsWaiter.prototype.load = function(options) {
for (const option in options) {
this.app.options[option] = options[option];
class OptionsWaiter {
/**
* OptionsWaiter constructor.
*
* @param {App} app - The main view object for CyberChef.
* @param {Manager} manager - The CyberChef event manager.
*/
constructor(app, manager) {
this.app = app;
this.manager = manager;
}
// Set options to match object
const cboxes = document.querySelectorAll("#options-body input[type=checkbox]");
let i;
for (i = 0; i < cboxes.length; i++) {
cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")];
}
/**
* Loads options and sets values of switches and inputs to match them.
*
* @param {Object} options
*/
load(options) {
for (const option in options) {
this.app.options[option] = options[option];
}
const nboxes = document.querySelectorAll("#options-body input[type=number]");
for (i = 0; i < nboxes.length; i++) {
nboxes[i].value = this.app.options[nboxes[i].getAttribute("option")];
nboxes[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
}
// Set options to match object
const cboxes = document.querySelectorAll("#options-body input[type=checkbox]");
let i;
for (i = 0; i < cboxes.length; i++) {
cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")];
}
const selects = document.querySelectorAll("#options-body select");
for (i = 0; i < selects.length; i++) {
const val = this.app.options[selects[i].getAttribute("option")];
if (val) {
selects[i].value = val;
selects[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
} else {
selects[i].selectedIndex = 0;
const nboxes = document.querySelectorAll("#options-body input[type=number]");
for (i = 0; i < nboxes.length; i++) {
nboxes[i].value = this.app.options[nboxes[i].getAttribute("option")];
nboxes[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
}
const selects = document.querySelectorAll("#options-body select");
for (i = 0; i < selects.length; i++) {
const val = this.app.options[selects[i].getAttribute("option")];
if (val) {
selects[i].value = val;
selects[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
} else {
selects[i].selectedIndex = 0;
}
}
}
};
/**
* Handler for options click events.
* Dispays the options pane.
*
* @param {event} e
*/
OptionsWaiter.prototype.optionsClick = function(e) {
e.preventDefault();
$("#options-modal").modal();
};
/**
* Handler for reset options click events.
* Resets options back to their default values.
*/
OptionsWaiter.prototype.resetOptionsClick = function() {
this.load(this.app.doptions);
};
/**
* Handler for switch change events.
* Modifies the option state and saves it to local storage.
*
* @param {event} e
*/
OptionsWaiter.prototype.switchChange = function(e) {
const el = e.target;
const option = el.getAttribute("option");
const state = el.checked;
log.debug(`Setting ${option} to ${state}`);
this.app.options[option] = state;
if (this.app.isLocalStorageAvailable())
localStorage.setItem("options", JSON.stringify(this.app.options));
};
/**
* Handler for number change events.
* Modifies the option value and saves it to local storage.
*
* @param {event} e
*/
OptionsWaiter.prototype.numberChange = function(e) {
const el = e.target;
const option = el.getAttribute("option");
const val = parseInt(el.value, 10);
log.debug(`Setting ${option} to ${val}`);
this.app.options[option] = val;
if (this.app.isLocalStorageAvailable())
localStorage.setItem("options", JSON.stringify(this.app.options));
};
/**
* Handler for select change events.
* Modifies the option value and saves it to local storage.
*
* @param {event} e
*/
OptionsWaiter.prototype.selectChange = function(e) {
const el = e.target;
const option = el.getAttribute("option");
log.debug(`Setting ${option} to ${el.value}`);
this.app.options[option] = el.value;
if (this.app.isLocalStorageAvailable())
localStorage.setItem("options", JSON.stringify(this.app.options));
};
/**
* Sets or unsets word wrap on the input and output depending on the wordWrap option value.
*/
OptionsWaiter.prototype.setWordWrap = function() {
document.getElementById("input-text").classList.remove("word-wrap");
document.getElementById("output-text").classList.remove("word-wrap");
document.getElementById("output-html").classList.remove("word-wrap");
document.getElementById("input-highlighter").classList.remove("word-wrap");
document.getElementById("output-highlighter").classList.remove("word-wrap");
if (!this.app.options.wordWrap) {
document.getElementById("input-text").classList.add("word-wrap");
document.getElementById("output-text").classList.add("word-wrap");
document.getElementById("output-html").classList.add("word-wrap");
document.getElementById("input-highlighter").classList.add("word-wrap");
document.getElementById("output-highlighter").classList.add("word-wrap");
/**
* Handler for options click events.
* Dispays the options pane.
*
* @param {event} e
*/
optionsClick(e) {
e.preventDefault();
$("#options-modal").modal();
}
};
/**
* Changes the theme by setting the class of the <html> element.
*
* @param {Event} e
*/
OptionsWaiter.prototype.themeChange = function (e) {
const themeClass = e.target.value;
document.querySelector(":root").className = themeClass;
};
/**
* Handler for reset options click events.
* Resets options back to their default values.
*/
resetOptionsClick() {
this.load(this.app.doptions);
}
/**
* Changes the console logging level.
*
* @param {Event} e
*/
OptionsWaiter.prototype.logLevelChange = function (e) {
const level = e.target.value;
log.setLevel(level, false);
this.manager.worker.setLogLevel();
this.manager.input.setLogLevel();
};
/**
* Handler for switch change events.
*
* @param {event} e
*/
switchChange(e) {
const el = e.target;
const option = el.getAttribute("option");
const state = el.checked;
this.updateOption(option, state);
}
/**
* Handler for number change events.
*
* @param {event} e
*/
numberChange(e) {
const el = e.target;
const option = el.getAttribute("option");
const val = parseInt(el.value, 10);
this.updateOption(option, val);
}
/**
* Handler for select change events.
*
* @param {event} e
*/
selectChange(e) {
const el = e.target;
const option = el.getAttribute("option");
this.updateOption(option, el.value);
}
/**
* Modifies an option value and saves it to local storage.
*
* @param {string} option - The option to be updated
* @param {string|number|boolean} value - The new value of the option
*/
updateOption(option, value) {
log.debug(`Setting ${option} to ${value}`);
this.app.options[option] = value;
if (this.app.isLocalStorageAvailable())
localStorage.setItem("options", JSON.stringify(this.app.options));
}
/**
* Sets or unsets word wrap on the input and output depending on the wordWrap option value.
*/
setWordWrap() {
document.getElementById("input-text").classList.remove("word-wrap");
document.getElementById("output-text").classList.remove("word-wrap");
document.getElementById("output-html").classList.remove("word-wrap");
document.getElementById("input-highlighter").classList.remove("word-wrap");
document.getElementById("output-highlighter").classList.remove("word-wrap");
if (!this.app.options.wordWrap) {
document.getElementById("input-text").classList.add("word-wrap");
document.getElementById("output-text").classList.add("word-wrap");
document.getElementById("output-html").classList.add("word-wrap");
document.getElementById("input-highlighter").classList.add("word-wrap");
document.getElementById("output-highlighter").classList.add("word-wrap");
}
}
/**
* Changes the theme by setting the class of the <html> element.
*
* @param {Event} e
*/
themeChange(e) {
const themeClass = e.target.value;
document.querySelector(":root").className = themeClass;
}
/**
* Changes the console logging level.
*
* @param {Event} e
*/
logLevelChange(e) {
const level = e.target.value;
log.setLevel(level, false);
this.manager.worker.setLogLevel();
this.manager.input.setLogLevel();
}
}
export default OptionsWaiter;

View File

@@ -5,9 +5,10 @@
* @license Apache-2.0
*/
import Utils from "../../core/Utils.mjs";
import Utils, { debounce } from "../../core/Utils.mjs";
import Dish from "../../core/Dish.mjs";
import FileSaver from "file-saver";
import ZipWorker from "worker-loader?inline&fallback=false!../workers/ZipWorker";
import ZipWorker from "worker-loader?inline&fallback=false!../workers/ZipWorker.mjs";
/**
* Waiter to handle events related to the output
@@ -41,39 +42,6 @@ class OutputWaiter {
}
}
/**
* Gets the output for the specified input number
*
* @param {number} inputNum - The input to get the output for
* @param {boolean} [raw=true] - If true, returns the raw data instead of the presented result.
* @returns {string | ArrayBuffer}
*/
getOutput(inputNum, raw=true) {
if (!this.outputExists(inputNum)) return -1;
if (this.outputs[inputNum].data === null) return "";
if (raw) {
let data = this.outputs[inputNum].data.dish.value;
if (Array.isArray(data)) {
data = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
data[i] = this.outputs[inputNum].data.dish.value[i];
}
data = data.buffer;
} else if (typeof data !== "object" && typeof data !== "string") {
data = String(data);
}
return data;
} else if (typeof this.outputs[inputNum].data.result === "string") {
return this.outputs[inputNum].data.result;
} else {
return this.outputs[inputNum].data.result || "";
}
}
/**
* Gets the dish object for an output.
*
@@ -103,16 +71,6 @@ class OutputWaiter {
return true;
}
/**
* Gets the output string or FileBuffer for the active input
*
* @param {boolean} [raw=true] - If true, returns the raw data instead of the presented result.
* @returns {string | ArrayBuffer}
*/
getActive(raw=true) {
return this.getOutput(this.manager.tabs.getActiveOutputTab(), raw);
}
/**
* Adds a new output to the output array.
* Creates a new tab if we have less than maxtabs tabs open
@@ -152,6 +110,10 @@ class OutputWaiter {
this.addOutput(inputNum);
}
if (Object.prototype.hasOwnProperty.call(data, "dish")) {
data.dish = new Dish(data.dish);
}
this.outputs[inputNum].data = data;
const tabItem = this.manager.tabs.getOutputTabItem(inputNum);
@@ -255,9 +217,16 @@ class OutputWaiter {
*/
removeAllOutputs() {
this.outputs = {};
const tabs = document.getElementById("output-tabs").children;
for (let i = tabs.length - 1; i >= 0; i--) {
tabs.item(i).remove();
this.resetSwitch();
const tabsList = document.getElementById("output-tabs");
const tabsListChildren = tabsList.children;
tabsList.classList.remove("tabs-left");
tabsList.classList.remove("tabs-right");
for (let i = tabsListChildren.length - 1; i >= 0; i--) {
tabsListChildren.item(i).remove();
}
}
@@ -267,13 +236,13 @@ class OutputWaiter {
* @param {number} inputNum
*/
async set(inputNum) {
if (inputNum !== this.manager.tabs.getActiveOutputTab()) return;
if (inputNum !== this.manager.tabs.getActiveOutputTab() ||
!this.outputExists(inputNum)) return;
this.toggleLoader(true);
return new Promise(async function(resolve, reject) {
const output = this.outputs[inputNum],
activeTab = this.manager.tabs.getActiveOutputTab();
if (output === undefined || output === null) return;
if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10);
const outputText = document.getElementById("output-text");
@@ -281,6 +250,7 @@ class OutputWaiter {
const outputFile = document.getElementById("output-file");
const outputHighlighter = document.getElementById("output-highlighter");
const inputHighlighter = document.getElementById("input-highlighter");
// If pending or baking, show loader and status message
// If error, style the tab and handle the error
// If done, display the output if it's the active tab
@@ -399,7 +369,7 @@ class OutputWaiter {
}
this.setOutputInfo(length, lines, output.data.duration);
this.backgroundMagic();
debounce(this.backgroundMagic, 50, "backgroundMagic", this, [])();
}
}.bind(this));
}
@@ -544,11 +514,18 @@ class OutputWaiter {
* Handler for file download events.
*/
async downloadFile() {
let fileName = window.prompt("Please enter a filename: ", "download.dat");
const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab());
if (dish === null) {
this.app.alert("Could not find any output data to download. Has this output been baked?", 3000);
return;
}
const fileName = window.prompt("Please enter a filename: ", "download.dat");
if (fileName === null) fileName = "download.dat";
// Assume if the user clicks cancel they don't want to download
if (fileName === null) return;
const file = new File([this.getActive(true)], fileName);
const data = await dish.get(Dish.ARRAY_BUFFER),
file = new File([data], fileName);
FileSaver.saveAs(file, fileName, false);
}
@@ -556,12 +533,22 @@ class OutputWaiter {
* Handler for save all click event
* Saves all outputs to a single archvie file
*/
saveAllClick() {
async saveAllClick() {
const downloadButton = document.getElementById("save-all-to-file");
if (downloadButton.firstElementChild.innerHTML === "archive") {
this.downloadAllFiles();
} else if (window.confirm("Cancel zipping of outputs?")) {
this.terminateZipWorker();
} else {
const cancel = await new Promise(function(resolve, reject) {
this.app.confirm(
"Cancel zipping?",
"The outputs are currently being zipped for download.<br>Cancel zipping?",
"Continue zipping",
"Cancel zipping",
resolve, this);
}.bind(this));
if (!cancel) {
this.terminateZipWorker();
}
}
}
@@ -571,57 +558,61 @@ class OutputWaiter {
* be zipped for download
*/
async downloadAllFiles() {
return new Promise(resolve => {
const inputNums = Object.keys(this.outputs);
for (let i = 0; i < inputNums.length; i++) {
const iNum = inputNums[i];
if (this.outputs[iNum].status !== "baked" ||
this.outputs[iNum].bakeId !== this.manager.worker.bakeId) {
if (window.confirm("Not all outputs have been baked yet. Continue downloading outputs?")) {
break;
} else {
return;
}
const inputNums = Object.keys(this.outputs);
for (let i = 0; i < inputNums.length; i++) {
const iNum = inputNums[i];
if (this.outputs[iNum].status !== "baked" ||
this.outputs[iNum].bakeId !== this.manager.worker.bakeId) {
const continueDownloading = await new Promise(function(resolve, reject) {
this.app.confirm(
"Incomplete outputs",
"Not all outputs have been baked yet. Continue downloading outputs?",
"Download", "Cancel", resolve, this);
}.bind(this));
if (continueDownloading) {
break;
} else {
return;
}
}
}
let fileName = window.prompt("Please enter a filename: ", "download.zip");
let fileName = window.prompt("Please enter a filename: ", "download.zip");
if (fileName === null || fileName === "") {
// Don't zip the files if there isn't a filename
this.app.alert("No filename was specified.", 3000);
return;
}
if (fileName === null || fileName === "") {
// Don't zip the files if there isn't a filename
this.app.alert("No filename was specified.", 3000);
return;
}
if (!fileName.match(/.zip$/)) {
fileName += ".zip";
}
if (!fileName.match(/.zip$/)) {
fileName += ".zip";
}
let fileExt = window.prompt("Please enter a file extension for the files, or leave blank to detect automatically.", "");
let fileExt = window.prompt("Please enter a file extension for the files, or leave blank to detect automatically.", "");
if (fileExt === null) fileExt = "";
if (fileExt === null) fileExt = "";
if (this.zipWorker !== null) {
this.terminateZipWorker();
}
if (this.zipWorker !== null) {
this.terminateZipWorker();
}
const downloadButton = document.getElementById("save-all-to-file");
const downloadButton = document.getElementById("save-all-to-file");
downloadButton.classList.add("spin");
downloadButton.title = `Zipping ${inputNums.length} files...`;
downloadButton.setAttribute("data-original-title", `Zipping ${inputNums.length} files...`);
downloadButton.classList.add("spin");
downloadButton.title = `Zipping ${inputNums.length} files...`;
downloadButton.setAttribute("data-original-title", `Zipping ${inputNums.length} files...`);
downloadButton.firstElementChild.innerHTML = "autorenew";
downloadButton.firstElementChild.innerHTML = "autorenew";
log.debug("Creating ZipWorker");
this.zipWorker = new ZipWorker();
this.zipWorker.postMessage({
outputs: this.outputs,
filename: fileName,
fileExtension: fileExt
});
this.zipWorker.addEventListener("message", this.handleZipWorkerMessage.bind(this));
log.debug("Creating ZipWorker");
this.zipWorker = new ZipWorker();
this.zipWorker.postMessage({
outputs: this.outputs,
filename: fileName,
fileExtension: fileExt
});
this.zipWorker.addEventListener("message", this.handleZipWorkerMessage.bind(this));
}
/**
@@ -684,7 +675,7 @@ class OutputWaiter {
tabsWrapper.appendChild(newTab);
} else if (numTabs === this.maxTabs) {
// Can't create a new tab
document.getElementById("output-tabs").lastElementChild.style.boxShadow = "-15px 0px 15px -15px var(--primary-border-colour) inset";
document.getElementById("output-tabs").lastElementChild.classList.add("tabs-right");
}
this.displayTabInfo(inputNum);
@@ -726,7 +717,7 @@ class OutputWaiter {
}
}
this.app.debounce(this.set, 50, "setOutput", this, [inputNum])();
debounce(this.set, 50, "setOutput", this, [inputNum])();
document.getElementById("output-html").scroll(0, 0);
document.getElementById("output-text").scroll(0, 0);
@@ -966,8 +957,8 @@ class OutputWaiter {
*/
refreshTabs(activeTab, direction) {
const newNums = this.getNearbyNums(activeTab, direction),
tabsLeft = (newNums[0] !== this.getSmallestInputNum()),
tabsRight = (newNums[newNums.length - 1] !== this.getLargestInputNum());
tabsLeft = (newNums[0] !== this.getSmallestInputNum() && newNums.length > 0),
tabsRight = (newNums[newNums.length - 1] !== this.getLargestInputNum() && newNums.length > 0);
this.manager.tabs.refreshOutputTabs(newNums, activeTab, tabsLeft, tabsRight);
@@ -1039,8 +1030,8 @@ class OutputWaiter {
*/
async backgroundMagic() {
this.hideMagicButton();
if (!this.app.options.autoMagic || !this.getActive(true)) return;
const dish = this.outputs[this.manager.tabs.getActiveOutputTab()].data.dish;
const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab());
if (!this.app.options.autoMagic || dish === null) return;
const buffer = await this.getDishBuffer(dish);
const sample = buffer.slice(0, 1000) || "";
@@ -1092,6 +1083,7 @@ class OutputWaiter {
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.escapeHtml(Utils.truncate(result), 30)}"</span>`);
magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, "");
magicButton.classList.remove("hidden");
magicButton.classList.add("pulse");
}
@@ -1101,6 +1093,7 @@ class OutputWaiter {
hideMagicButton() {
const magicButton = document.getElementById("magic");
magicButton.classList.add("hidden");
magicButton.classList.remove("pulse");
magicButton.setAttribute("data-recipe", "");
magicButton.setAttribute("data-original-title", "Magic!");
}
@@ -1180,13 +1173,15 @@ class OutputWaiter {
* Handler for copy click events.
* Copies the output to the clipboard
*/
copyClick() {
let output = this.getActive(true);
if (typeof output !== "string") {
output = Utils.arrayBufferToStr(output);
async copyClick() {
const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab());
if (dish === null) {
this.app.alert("Could not find data to copy. Has this output been baked yet?", 3000);
return;
}
const output = await dish.get(Dish.STRING);
// Create invisible textarea to populate with the raw dish string (not the printable version that
// contains dots instead of the actual bytes)
const textarea = document.createElement("textarea");
@@ -1223,8 +1218,14 @@ class OutputWaiter {
*
* @returns {boolean}
*/
containsCR() {
return this.getActive(false).indexOf("\r") >= 0;
async containsCR() {
const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab());
if (dish === null) return;
if (dish.type === Dish.STRING) {
const data = await dish.get(Dish.STRING);
return data.indexOf("\r") >= 0;
}
}
/**
@@ -1232,14 +1233,39 @@ class OutputWaiter {
* Moves the current output into the input textarea.
*/
async switchClick() {
const active = await this.getDishBuffer(this.getOutputDish(this.manager.tabs.getActiveOutputTab()));
const activeTab = this.manager.tabs.getActiveOutputTab();
const transferable = [];
const switchButton = document.getElementById("switch");
switchButton.classList.add("spin");
switchButton.disabled = true;
switchButton.firstElementChild.innerHTML = "autorenew";
$(switchButton).tooltip("hide");
let active = await this.getDishBuffer(this.getOutputDish(activeTab));
if (!this.outputExists(activeTab)) {
this.resetSwitchButton();
return;
}
if (this.outputs[activeTab].data.type === "string" &&
active.byteLength <= this.app.options.ioDisplayThreshold * 1024) {
const dishString = await this.getDishStr(this.getOutputDish(activeTab));
if (!await this.manager.input.preserveCarriageReturns(dishString)) {
active = dishString;
}
} else {
transferable.push(active);
}
this.manager.input.inputWorker.postMessage({
action: "inputSwitch",
data: {
inputNum: this.manager.tabs.getActiveInputTab(),
inputNum: activeTab,
outputData: active
}
}, [active]);
}, transferable);
}
/**
@@ -1257,6 +1283,9 @@ class OutputWaiter {
inputSwitch(switchData) {
this.switchOrigData = switchData;
document.getElementById("undo-switch").disabled = false;
this.resetSwitchButton();
}
/**
@@ -1265,17 +1294,35 @@ class OutputWaiter {
*/
undoSwitchClick() {
this.manager.input.updateInputObj(this.switchOrigData.inputNum, this.switchOrigData.data);
this.manager.input.fileLoaded(this.switchOrigData.inputNum);
this.resetSwitch();
}
/**
* Removes the switch data and resets the switch buttons
*/
resetSwitch() {
if (this.switchOrigData !== undefined) {
delete this.switchOrigData;
}
const undoSwitch = document.getElementById("undo-switch");
undoSwitch.disabled = true;
$(undoSwitch).tooltip("hide");
this.manager.input.inputWorker.postMessage({
action: "setInput",
data: {
inputNum: this.switchOrigData.inputNum,
silent: false
}
});
this.resetSwitchButton();
}
/**
* Resets the switch button to its usual state
*/
resetSwitchButton() {
const switchButton = document.getElementById("switch");
switchButton.classList.remove("spin");
switchButton.disabled = false;
switchButton.firstElementChild.innerHTML = "open_in_browser";
}
/**
@@ -1312,7 +1359,7 @@ class OutputWaiter {
/**
* Searches the outputs using the filter settings and displays the results
*/
filterTabSearch() {
async filterTabSearch() {
const showPending = document.getElementById("output-show-pending").checked,
showBaking = document.getElementById("output-show-baking").checked,
showBaked = document.getElementById("output-show-baked").checked,
@@ -1349,15 +1396,26 @@ class OutputWaiter {
"stale": "Stale (output is out of date)",
"inactive": "Not baked yet"
};
results.push({
inputNum: iNum,
textDisplay: outDisplay[output.status]
});
} else if (output.status === "baked" && showBaked && output.progress === false) {
let data = this.getOutput(iNum, false).slice(0, 4096);
if (typeof data !== "string") {
data = Utils.arrayBufferToStr(data);
// If the output has a dish object, check it against the filter
if (Object.prototype.hasOwnProperty.call(output, "data") &&
output.data &&
Object.prototype.hasOwnProperty.call(output.data, "dish")) {
const data = await output.data.dish.get(Dish.STRING);
if (contentFilterExp.test(data)) {
results.push({
inputNum: iNum,
textDisplay: data.slice(0, 100)
});
}
} else {
results.push({
inputNum: iNum,
textDisplay: outDisplay[output.status]
});
}
} else if (output.status === "baked" && showBaked && output.progress === false) {
let data = await output.data.dish.get(Dish.STRING);
data = data.replace(/[\r\n]/g, "");
if (contentFilterExp.test(data)) {
results.push({
@@ -1366,10 +1424,7 @@ class OutputWaiter {
});
}
} else if (output.progress !== false && showErrored) {
let data = this.getOutput(iNum, false).slice(0, 4096);
if (typeof data !== "string") {
data = Utils.arrayBufferToStr(data);
}
let data = await output.data.dish.get(Dish.STRING);
data = data.replace(/[\r\n]/g, "");
if (contentFilterExp.test(data)) {
results.push({

View File

@@ -4,6 +4,8 @@
* @license Apache-2.0
*/
import { debounce } from "../../core/Utils.mjs";
/**
* Waiter to handle events related to the window object.
*/
@@ -25,7 +27,7 @@ class WindowWaiter {
* continuous resetting).
*/
windowResize() {
this.app.debounce(this.app.resetLayout, 200, "windowResize", this.app, [])();
debounce(this.app.resetLayout, 200, "windowResize", this.app, [])();
}

View File

@@ -5,8 +5,9 @@
* @license Apache-2.0
*/
import ChefWorker from "worker-loader?inline&fallback=false!../../core/ChefWorker";
import DishWorker from "worker-loader?inline&fallback=false!../workers/DishWorker";
import ChefWorker from "worker-loader?inline&fallback=false!../../core/ChefWorker.js";
import DishWorker from "worker-loader?inline&fallback=false!../workers/DishWorker.mjs";
import { debounce } from "../../core/Utils.mjs";
/**
* Waiter to handle conversations with the ChefWorker
@@ -178,7 +179,7 @@ class WorkerWaiter {
let inputNum = 0;
log.debug(`Receiving ${r.action} from ChefWorker.`);
if ("inputNum" in r.data) {
if (Object.prototype.hasOwnProperty.call(r.data, "inputNum")) {
inputNum = r.data.inputNum;
}
@@ -281,7 +282,7 @@ class WorkerWaiter {
*/
setBakingStatus(bakingStatus) {
this.app.baking = bakingStatus;
this.app.debounce(this.manager.controls.toggleBakeButtonFunction, 20, "toggleBakeButton", this, [bakingStatus ? "cancel" : "bake"])();
debounce(this.manager.controls.toggleBakeButtonFunction, 20, "toggleBakeButton", this, [bakingStatus ? "cancel" : "bake"])();
if (bakingStatus) this.manager.output.hideMagicButton();
}

View File

@@ -202,6 +202,7 @@ self.bakeInput = function(inputNum, bakeId) {
if (inputObj === null ||
inputObj === undefined ||
inputObj.status !== "loaded") {
self.postMessage({
action: "queueInputError",
data: {
@@ -441,7 +442,7 @@ self.updateTabHeader = function(inputNum) {
*
* @param {object} inputData
* @param {number} inputData.inputNum - The input to get the data for
* @param {boolean} inputData.silent - If false, the manager statechange event won't be fired
* @param {boolean} inputData.silent - If false, the manager statechange event will be fired
*/
self.setInput = function(inputData) {
const inputNum = inputData.inputNum;
@@ -493,8 +494,8 @@ self.setInput = function(inputData) {
self.refreshTabs = function(inputNum, direction) {
const nums = self.getNearbyNums(inputNum, direction),
inputNums = Object.keys(self.inputs),
tabsLeft = (self.getSmallestInputNum(inputNums) !== nums[0]),
tabsRight = (self.getLargestInputNum(inputNums) !== nums[nums.length - 1]);
tabsLeft = (self.getSmallestInputNum(inputNums) !== nums[0] && nums.length > 0),
tabsRight = (self.getLargestInputNum(inputNums) !== nums[nums.length - 1] && nums.length > 0);
self.postMessage({
action: "refreshTabs",
@@ -546,10 +547,13 @@ self.updateInputProgress = function(inputData) {
* @param {object} inputData
* @param {number} inputData.inputNum - The input that's having its value updated
* @param {string | ArrayBuffer} inputData.value - The new value of the input
* @param {boolean} inputData.force - If true, still updates the input value if the input type is different to the stored value
*/
self.updateInputValue = function(inputData) {
const inputNum = inputData.inputNum;
if (inputNum < 1) return;
if (Object.prototype.hasOwnProperty.call(self.inputs[inputNum].data, "fileBuffer") &&
typeof inputData.value === "string" && !inputData.force) return;
const value = inputData.value;
if (self.inputs[inputNum] !== undefined) {
if (typeof value === "string") {
@@ -587,7 +591,7 @@ self.updateInputObj = function(inputData) {
const inputNum = inputData.inputNum;
const data = inputData.data;
if (self.getInputObj(inputNum) === -1) return;
if (self.getInputObj(inputNum) === undefined) return;
self.inputs[inputNum].data = data;
};
@@ -660,11 +664,19 @@ self.handleLoaderMessage = function(r) {
if ("fileBuffer" in r) {
log.debug(`Input file ${inputNum} loaded.`);
self.loadingInputs--;
self.updateInputValue({
inputNum: inputNum,
value: r.fileBuffer
});
self.postMessage({
action: "fileLoaded",
data: {
inputNum: inputNum
}
});
const idx = self.getLoaderWorkerIdx(r.id);
self.loadNextFile(idx);
} else if ("progress" in r) {
@@ -779,7 +791,7 @@ self.loadFiles = function(filesData) {
}
self.getLoadProgress();
self.setInput({inputNum: activeTab, silent: false});
self.setInput({inputNum: activeTab, silent: true});
};
/**
@@ -1022,7 +1034,7 @@ self.inputSwitch = function(switchData) {
const currentData = currentInput.data;
if (currentInput === undefined || currentInput === null) return;
if (typeof switchData.outputData === "object") {
if (typeof switchData.outputData !== "string") {
const output = new Uint8Array(switchData.outputData),
types = detectFileType(output);
let type = "unknown",
@@ -1033,15 +1045,22 @@ self.inputSwitch = function(switchData) {
}
// ArrayBuffer
currentInput.data = {
fileBuffer: switchData.outputData,
name: `output.${ext}`,
size: switchData.outputData.byteLength.toLocaleString(),
type: type
};
self.updateInputObj({
inputNum: switchData.inputNum,
data: {
fileBuffer: switchData.outputData,
name: `output.${ext}`,
size: switchData.outputData.byteLength.toLocaleString(),
type: type
}
});
} else {
// String
currentInput.data = switchData.outputData;
self.updateInputValue({
inputNum: switchData.inputNum,
value: switchData.outputData,
force: true
});
}
self.postMessage({
@@ -1052,6 +1071,11 @@ self.inputSwitch = function(switchData) {
}
});
self.setInput({inputNum: switchData.inputNum, silent: false});
self.postMessage({
action: "fileLoaded",
data: {
inputNum: switchData.inputNum
}
});
};

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